Browse Source

Fix some warnings, begin key rotation support.

Kestrel 11 tháng trước cách đây
mục cha
commit
e5dd2c1b71
12 tập tin đã thay đổi với 91 bổ sung150 xóa
  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> {