From 53f21fa6685fd4422a08cf582a2f7bafd5b7342e Mon Sep 17 00:00:00 2001 From: shimun Date: Wed, 22 Feb 2023 13:02:05 +0100 Subject: [PATCH] added: renew command --- server/src/api.rs | 78 ++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 15 deletions(-) diff --git a/server/src/api.rs b/server/src/api.rs index 2eb089d..2b1e99d 100644 --- a/server/src/api.rs +++ b/server/src/api.rs @@ -18,7 +18,7 @@ use jwt_compact::alg::{Hs256, Hs256Key}; use jwt_compact::{AlgorithmExt, Token, UntrustedToken}; use rand::{thread_rng, Rng}; use serde::{Deserialize, Serialize}; -use ssh_key::{Certificate, PublicKey}; +use ssh_key::{Certificate, Fingerprint, PublicKey}; use tokio::sync::Mutex; use tower::ServiceBuilder; use tower_http::{trace::TraceLayer, ServiceBuilderExt}; @@ -135,6 +135,7 @@ pub async fn run( let app = Router::new() .typed_get(get_certs_identifier) + .typed_get(get_certs_pubkey) .typed_put(put_cert_update) .typed_get(get_cert_info) .typed_post(post_certs_identifier); @@ -219,6 +220,22 @@ struct AuthClaims { identifier: String, } +async fn request_client_auth(enabled: bool, identifier: &str, jwt_key: &Hs256Key) -> ApiResult<()> { + use jwt_compact::{Claims, Header, TimeOptions}; + if enabled { + let claims = Claims::new(AuthClaims { + identifier: identifier.into(), + }) + .set_duration(&TimeOptions::default(), chrono::Duration::seconds(120)); + let challenge = Hs256 + .compact_token(Header::default(), &claims, &jwt_key) + .context("jwt sign")?; + return Err(ApiError::AuthenticationRequired(challenge)); + } else { + Ok(()) + } +} + /// Retrieve an certificate for identifier /// TODO: add option to require auth /// return Unauthorized with an challenge @@ -233,16 +250,7 @@ async fn get_certs_identifier( .. }): State, ) -> ApiResult { - use jwt_compact::{Claims, Header, TimeOptions}; - - if client_auth { - let claims = Claims::new(AuthClaims { identifier }) - .set_duration(&TimeOptions::default(), chrono::Duration::seconds(120)); - let challenge = Hs256 - .compact_token(Header::default(), &claims, &jwt_key) - .context("jwt sign")?; - return Err(ApiError::AuthenticationRequired(challenge)); - } + request_client_auth(client_auth, &identifier, &jwt_key).await?; let certs = certs.lock().await; let cert = certs .get(&identifier) @@ -250,24 +258,55 @@ async fn get_certs_identifier( Ok(cert.to_openssh().context("to openssh")?) } +async fn get_certs_pubkey( + GetCertsPubkey { pubkey_hash }: GetCertsPubkey, + State(ApiState { + certs, + jwt_key: _, + client_auth: _, + .. + }): State, +) -> ApiResult> { + let certs = certs.lock().await; + let ids = certs + .values() + .filter(|cert| &cert.public_key().fingerprint(pubkey_hash.algorithm()) == &pubkey_hash) + .map(|cert| cert.key_id().to_string()) + .collect::>(); + Ok(Json(CertIds { ids })) +} + #[cfg(feature = "info")] #[derive(Debug, Serialize)] struct CertInfo { principals: Vec, ca: PublicKey, + ca_hash: Fingerprint, identity: PublicKey, + identity_hash: Fingerprint, key_id: String, expiry: SystemTime, + renew_command: String, } impl From<&Certificate> for CertInfo { fn from(cert: &Certificate) -> Self { + let validity = cert.valid_after_time().duration_since(cert.valid_before_time()).unwrap(); + let validity_days = validity.as_secs() / ((60*60) * 24); + let host_key = if cert.cert_type().is_host() { + " -h" + } else { "" }; + let opts = cert.critical_options().iter().map(|(opt, val)| if val.is_empty() { opt.clone() } else { format!("{opt}={val}") }).map(|arg| format!("-O {arg}")).join(" "); + let renew_command = format!("ssh-keygen -s ./ca_key {host_key} -I {} -n {} -V {validity_days}d {opts}", cert.key_id(), cert.valid_principals().join(",")); CertInfo { principals: cert.valid_principals().to_vec(), ca: cert.signature_key().clone().into(), + ca_hash: cert.signature_key().fingerprint(ssh_key::HashAlg::Sha256), identity: cert.public_key().clone().into(), + identity_hash: cert.public_key().fingerprint(ssh_key::HashAlg::Sha256), key_id: cert.key_id().to_string(), expiry: cert.valid_before_time(), + renew_command } } } @@ -395,7 +434,16 @@ mod tests { } fn ca_pub() -> PublicKey { - PublicKey::new(ca_key().public.into(), "TEST CA") + PublicKey::new( + ca_key().public.into(), + format!( + "TEST CA {}", + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() + ), + ) } fn user_key() -> Ed25519Keypair { @@ -421,7 +469,7 @@ mod tests { .unwrap() .key_id("test_cert") .unwrap() - .comment("A TEST CERT") + .comment(&format!("A TEST CERT, VALID FOR {}s", validity.as_secs())) .unwrap(); builder.sign(&ca_private).unwrap() @@ -455,9 +503,9 @@ mod tests { let user: PublicKey = user_key().public.into(); let (cert_first, cert_newer, cert_outdated) = { ( + user_cert(ca.clone(), user.clone(), Duration::from_secs(300)), + user_cert(ca.clone(), user.clone(), Duration::from_secs(600)), user_cert(ca.clone(), user.clone(), Duration::from_secs(30)), - user_cert(ca.clone(), user.clone(), Duration::from_secs(60)), - user_cert(ca.clone(), user.clone(), Duration::from_secs(3)), ) }; let res = put_cert_update(PutCert, State(state.clone()), CertificateBody(cert_first)).await;