Bläddra i källkod

Fix some warnings, begin key rotation support.

Kestrel 11 månader sedan
förälder
incheckning
e5dd2c1b71
12 ändrade filer med 91 tillägg och 150 borttagningar
  1. 6 42
      src/cli.rs
  2. 1 1
      src/config/helper.rs
  3. 5 7
      src/jwt.rs
  4. 18 3
      src/key.rs
  5. 0 1
      src/main.rs
  6. 13 0
      src/schema.rs
  7. 0 75
      src/scope_management.rs
  8. 14 5
      src/server.rs
  9. 22 3
      src/server/oidc.rs
  10. 4 4
      src/server/oidc/api.rs
  11. 7 8
      src/token.rs
  12. 1 1
      src/token_management.rs

+ 6 - 42
src/cli.rs

@@ -120,19 +120,20 @@ impl RootArgs {
 
         log::info!("Initializing!");
 
-        let primary_realm = "primary".to_string();
-
-        if db.realms.keyed(&primary_realm).get()?.is_some() {
+        if db.realms.keyed("primary").get()?.is_some() {
             log::warn!("Already initialized with primary realm!");
             return Ok(());
         }
 
         // create primary realm
-        db.realms.insert(schema::Realm {
-            shortname: primary_realm,
+        let primary = db.realms.insert_and_return(schema::Realm {
+            shortname: "primary".into(),
             ..Default::default()
         })?;
 
+        // add a HMAC key for refresh tokens
+        key::generate_in(&primary, KeyType::HMac(key::HMacType::Sha256))?;
+
         Ok(())
     }
 }
@@ -174,43 +175,6 @@ impl KeyArgs {
     }
 }
 
-#[derive(Debug, Subcommand)]
-enum ClientCommand {
-    Create {
-        /// Name for the new client
-        name: String,
-        /// Signing key type to use for this client. Default is ed25519.
-        key_type: Option<KeyType>,
-    },
-    List,
-    Inspect {
-        name: String,
-    },
-}
-
-#[derive(Debug, Parser)]
-struct ClientArgs {
-    #[clap(subcommand)]
-    command: ClientCommand,
-}
-
-impl ClientArgs {
-    async fn run(&self, args: RunArgs) -> Result<(), UIDCError> {
-        todo!()
-        /*
-        match self.command {
-            ClientCommand::Create { name, key_type } => {
-                client_management::create(&args.realm, name, key_type.unwrap_or(KeyType::Ed25519))
-            }
-            ClientCommand::List => {
-                todo!()
-            }
-            ClientCommand::Inspect { name } => client_management::inspect(&args.realm, name),
-        }
-        */
-    }
-}
-
 #[derive(Debug, Subcommand)]
 enum ConfigCommand {
     Dump,

+ 1 - 1
src/config/helper.rs

@@ -489,7 +489,7 @@ impl<'de> serde::Deserializer<'de> for &'de mut ConfigDeserializer<'de> {
         todo!("deserialize_seq")
     }
 
-    fn deserialize_map<V>(mut self, visitor: V) -> Result<V::Value, Self::Error>
+    fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
     where
         V: serde::de::Visitor<'de>,
     {

+ 5 - 7
src/jwt.rs

@@ -2,14 +2,12 @@ use crate::key::{HMacType, ParsedKey};
 
 fn jwt_algorithm(pk: &ParsedKey) -> &'static str {
     match pk {
-        ParsedKey::HMAC { hmty, .. } => {
-            match hmty {
-                HMacType::Sha256 => "HS256",
-                HMacType::Sha512 => "HS512",
-            }
+        ParsedKey::HMAC { hmty, .. } => match hmty {
+            HMacType::Sha256 => "HS256",
+            HMacType::Sha512 => "HS512",
         },
-        ParsedKey::Ed25519 { ..  } => "EdDSA",
-        ParsedKey::RSA { ..  } => "RS256",
+        ParsedKey::Ed25519 { .. } => "EdDSA",
+        ParsedKey::RSA { .. } => "RS256",
     }
 }
 

+ 18 - 3
src/key.rs

@@ -276,6 +276,7 @@ fn generate_rsa(realm: &schema::Realm, kty: KeyType, bits: usize) -> Result<Pars
     realm.keys.insert(schema::Key {
         key_id: key_id.clone(),
         key_type: kty.into(),
+        key_state: schema::KeyState::Active.into(),
         public_data: public.into(),
         secret_data: secret.into(),
         expiry,
@@ -291,14 +292,27 @@ pub fn generate_in(realm: &schema::Realm, kty: KeyType) -> Result<ParsedKey, UID
             let rng = ring::rand::SystemRandom::new();
             let mut key_id = [0u8; 16];
             rng.fill(&mut key_id)
-                .map_err(|_| UIDCError::Abort("couldn't generate random values"));
+                .map_err(|_| UIDCError::Abort("couldn't generate random values"))?;
+            let key_id = pubkey_id(&key_id);
 
             let mut keydata = vec![];
             keydata.resize(hmty.digest_width(), 0u8);
             rng.fill(keydata.as_mut_slice())
-                .map_err(|_| UIDCError::Abort("couldn't generate random values"));
+                .map_err(|_| UIDCError::Abort("couldn't generate random values"))?;
+
+            let expiry = time::OffsetDateTime::now_utc() + time::Duration::days(730);
+            realm.keys.insert(schema::Key {
+                key_id: key_id.clone(),
+                key_type: kty.into(),
+                key_state: schema::KeyState::Active.into(),
+                // no separate public data for EdDSA keys
+                public_data: vec![],
+                secret_data: keydata.clone(),
+                expiry,
+            })?;
+
             Ok(ParsedKey::HMAC {
-                key_id: base64::encode(key_id),
+                key_id,
                 keydata,
                 hmty,
             })
@@ -320,6 +334,7 @@ pub fn generate_in(realm: &schema::Realm, kty: KeyType) -> Result<ParsedKey, UID
             realm.keys.insert(schema::Key {
                 key_id: key_id.clone(),
                 key_type: kty.into(),
+                key_state: schema::KeyState::Active.into(),
                 // no separate public data for EdDSA keys
                 public_data: vec![],
                 secret_data: keydata,

+ 0 - 1
src/main.rs

@@ -8,7 +8,6 @@ mod jwt;
 mod key;
 mod role_management;
 mod schema;
-mod scope_management;
 mod server;
 mod token;
 mod token_management;

+ 13 - 0
src/schema.rs

@@ -91,12 +91,25 @@ pub struct Realm {
     pub auth_codes: microrm::RelationMap<AuthCode>,
 }
 
+#[derive(serde::Serialize, serde::Deserialize, Clone, Copy, PartialEq, Debug)]
+pub enum KeyState {
+    /// Key can be used without restrictions for signing and verification.
+    Active,
+    /// Key will be used for signing only if no other key of the same type is found, but is still
+    /// good for verification of existing tokens.
+    Retiring,
+    /// Key is now fully retired and will not be used for signing or for verification.
+    Retired
+}
+
 #[derive(Entity)]
 pub struct Key {
     #[key]
     pub key_id: String,
     pub key_type: microrm::Serialized<KeyType>,
+    pub key_state: microrm::Serialized<KeyState>,
     pub public_data: Vec<u8>,
+    #[elide]
     pub secret_data: Vec<u8>,
     pub expiry: time::OffsetDateTime,
 }

+ 0 - 75
src/scope_management.rs

@@ -1,75 +0,0 @@
-use crate::{schema, UIDCError};
-use microrm::prelude::*;
-
-pub fn create_scope(realm: &microrm::Stored<schema::Realm>, name: &str) -> Result<(), UIDCError> {
-    realm.scopes.insert(schema::Scope {
-        realm: realm.id(),
-        shortname: name.into(),
-        roles: Default::default(),
-    })?;
-    Ok(())
-}
-
-pub fn list_scopes(realm: &schema::Realm) -> Result<(), UIDCError> {
-    for scope in realm.scopes.get()? {
-        println!("{}", scope.shortname);
-    }
-    Ok(())
-}
-
-pub fn inspect_scope(
-    realm: &microrm::Stored<schema::Realm>,
-    scope_name: &str,
-) -> Result<(), UIDCError> {
-    let scope = realm
-        .scopes
-        .keyed((realm.id(), scope_name))
-        .get()?
-        .ok_or(UIDCError::Abort("no such scope"))?;
-
-    println!("scope name: {}", scope.shortname);
-
-    println!("attached roles:");
-    for role in scope.roles.get()? {
-        println!(" - {}", role.shortname);
-    }
-
-    Ok(())
-}
-
-pub fn attach_role(
-    realm: &microrm::Stored<schema::Realm>,
-    scope_name: &str,
-    role_name: &str,
-) -> Result<(), UIDCError> {
-    let scope = realm.scopes.keyed((realm.id(), scope_name)).get()?;
-    let role = realm.roles.keyed((realm.id(), role_name)).get()?;
-
-    match (scope, role) {
-        (None, _) => Err(UIDCError::Abort("no such scope")),
-        (_, None) => Err(UIDCError::Abort("no such role")),
-        (Some(scope), Some(role)) => {
-            scope.roles.connect_to(role.id())?;
-            Ok(())
-        }
-    }
-}
-
-pub fn detach_role(
-    realm: &microrm::Stored<schema::Realm>,
-    scope_name: &str,
-    role_name: &str,
-) -> Result<(), UIDCError> {
-    let scope = realm.scopes.keyed((realm.id(), scope_name)).get()?;
-    let role = realm.roles.keyed((realm.id(), role_name)).get()?;
-
-    if let Some((scope, role)) = scope.as_ref().zip(role) {
-        scope.roles.disconnect_from(role.id())?;
-    } else if scope.is_none() {
-        println!("No such scope!");
-    } else {
-        println!("No such role!");
-    }
-
-    Ok(())
-}

+ 14 - 5
src/server.rs

@@ -25,13 +25,22 @@ async fn index(req: tide::Request<ServerStateWrapper>) -> tide::Result<tide::Res
         .and_then(|session| shelper.get_auth_for_session(realm.id(), &session));
 
     let response = tide::Response::builder(200)
-        .content_type(tide::http::mime::PLAIN)
+        .content_type(tide::http::mime::HTML)
         .body(format!(
             r#"
-            realm: {realm:?}
-            session: {session:?}
-            auth: {auth:?}
-            um link: {link}
+            <!DOCTYPE html>
+            <html>
+            <head>
+            <title>UIDC realm root</title>
+            </head>
+            <body>
+            <table>
+            <tr><td style="width: 10em">realm</td><td><pre>{realm:?}</pre></td></tr>
+            <tr><td>session</td><td><pre>{session:?}</pre></td></tr>
+            <tr><td>auth</td><td><pre>{auth:?}</pre></td></tr>
+            <tr><td>um link</td><td><a href="{link}">user management</a></td></tr>
+            </table>
+            </body></html>
             "#,
             link = req.url().join("um/").unwrap()
         ))

+ 22 - 3
src/server/oidc.rs

@@ -20,9 +20,9 @@ pub enum OIDCErrorType {
     UnauthorizedClient,
     AccessDenied,
     UnsupportedResponseType,
-    InvalidScope,
+    // InvalidScope,
     ServerError,
-    TemporarilyUnavailable,
+    // TemporarilyUnavailable,
 }
 
 /// error type,
@@ -347,7 +347,26 @@ async fn do_token<'l>(mut request: Request) -> Result<tide::Response, OIDCError<
             )
             .build())
     } else if treq.grant_type == "refresh_token" {
-        todo!()
+        let Some(rtoken) = treq.refresh_token else {
+            return Err(OIDCError(OIDCErrorType::InvalidRequest, "no refresh_token given".into(), None))
+        };
+        let Ok((access, refresh)) =
+            token::trade_refresh_token(&request.state().core.config, rtoken.as_str())
+        else {
+            todo!();
+        };
+        Ok(tide::Response::builder(200)
+            .content_type(tide::http::mime::JSON)
+            .body(
+                serde_json::to_value(api::TokenResponse {
+                    access_token: access.as_str(),
+                    token_type: "bearer",
+                    refresh_token: Some(refresh.as_str()),
+                    scope: None,
+                })
+                .unwrap(),
+            )
+            .build())
     } else {
         Err(OIDCError(
             OIDCErrorType::InvalidRequest,

+ 4 - 4
src/server/oidc/api.rs

@@ -16,7 +16,7 @@ pub struct AuthorizationResponse<'l> {
     pub code: Option<&'l str>,
 }
 
-#[derive(Serialize)]
+/*#[derive(Serialize)]
 pub enum AuthorizationResponseErrorType {
     InvalidRequest,
     UnauthorizedClient,
@@ -25,13 +25,13 @@ pub enum AuthorizationResponseErrorType {
     InvalidScope,
     ServerError,
     TemporarilyUnavailable,
-}
+}*/
 
-#[derive(Serialize)]
+/*#[derive(Serialize)]
 pub struct AuthorizationResponseError {
     state: Option<String>,
     error: AuthorizationResponseErrorType,
-}
+}*/
 
 #[derive(Deserialize)]
 pub struct TokenRequestBody {

+ 7 - 8
src/token.rs

@@ -5,7 +5,6 @@ use microrm::prelude::*;
 pub enum TokenError {
     DBError(microrm::Error),
     InternalError(&'static str),
-    RequestError(&'static str),
 }
 
 impl From<microrm::Error> for TokenError {
@@ -106,18 +105,18 @@ pub fn generate_access_token<'a>(
 }
 
 pub fn generate_refresh_token<'a>(
-    config: &config::Config,
-    realm: &schema::Realm,
-    client: &schema::Client,
-    user: &schema::User,
-    scopes: impl Iterator<Item = &'a str>,
+    _config: &config::Config,
+    _realm: &schema::Realm,
+    _client: &schema::Client,
+    _user: &schema::User,
+    _scopes: impl Iterator<Item = &'a str>,
 ) -> Result<String, UIDCError> {
     todo!()
 }
 
 pub fn trade_refresh_token<'a>(
-    config: &config::Config,
-    token: &'a str,
+    _config: &config::Config,
+    _token: &'a str,
 ) -> Result<(String, String), UIDCError> {
     todo!()
 }

+ 1 - 1
src/token_management.rs

@@ -58,7 +58,7 @@ pub fn create_refresh_token(
 }
 
 pub fn inspect_token(
-    config: &Config,
+    _config: &Config,
     realm: &schema::Realm,
     token: Option<&String>,
 ) -> Result<(), UIDCError> {