Compare commits

..

1 Commits

Author SHA1 Message Date
e3b920fcd5
added: renew command 2023-02-22 14:56:01 +01:00

View File

@ -135,7 +135,6 @@ pub async fn run(
let app = Router::new() let app = Router::new()
.typed_get(get_certs_identifier) .typed_get(get_certs_identifier)
.typed_get(get_certs_pubkey)
.typed_put(put_cert_update) .typed_put(put_cert_update)
.typed_get(get_cert_info) .typed_get(get_cert_info)
.typed_post(post_certs_identifier); .typed_post(post_certs_identifier);
@ -220,22 +219,6 @@ struct AuthClaims {
identifier: String, 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 /// Retrieve an certificate for identifier
/// TODO: add option to require auth /// TODO: add option to require auth
/// return Unauthorized with an challenge /// return Unauthorized with an challenge
@ -250,7 +233,16 @@ async fn get_certs_identifier(
.. ..
}): State<ApiState>, }): State<ApiState>,
) -> ApiResult<String> { ) -> ApiResult<String> {
request_client_auth(client_auth, &identifier, &jwt_key).await?; 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));
}
let certs = certs.lock().await; let certs = certs.lock().await;
let cert = certs let cert = certs
.get(&identifier) .get(&identifier)
@ -258,24 +250,6 @@ async fn get_certs_identifier(
Ok(cert.to_openssh().context("to openssh")?) Ok(cert.to_openssh().context("to openssh")?)
} }
async fn get_certs_pubkey(
GetCertsPubkey { pubkey_hash }: GetCertsPubkey,
State(ApiState {
certs,
jwt_key: _,
client_auth: _,
..
}): State<ApiState>,
) -> ApiResult<Json<CertIds>> {
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::<Vec<_>>();
Ok(Json(CertIds { ids }))
}
#[cfg(feature = "info")] #[cfg(feature = "info")]
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
struct CertInfo { struct CertInfo {
@ -291,13 +265,34 @@ struct CertInfo {
impl From<&Certificate> for CertInfo { impl From<&Certificate> for CertInfo {
fn from(cert: &Certificate) -> Self { fn from(cert: &Certificate) -> Self {
let validity = cert.valid_after_time().duration_since(cert.valid_before_time()).unwrap(); let validity = cert
.valid_after_time()
.duration_since(cert.valid_before_time())
.unwrap();
let validity_days = validity.as_secs() / ((60 * 60) * 24); let validity_days = validity.as_secs() / ((60 * 60) * 24);
let host_key = if cert.cert_type().is_host() { let host_key = if cert.cert_type().is_host() {
" -h" " -h"
} else { "" }; } 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(",")); };
let opts = cert
.critical_options()
.iter()
.map(|(opt, val)| {
if val.is_empty() {
opt.clone()
} else {
format!("{opt}={val}")
}
})
.map(|arg| format!("-O {arg}"))
.collect::<Vec<_>>()
.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 { CertInfo {
principals: cert.valid_principals().to_vec(), principals: cert.valid_principals().to_vec(),
ca: cert.signature_key().clone().into(), ca: cert.signature_key().clone().into(),
@ -306,7 +301,7 @@ impl From<&Certificate> for CertInfo {
identity_hash: cert.public_key().fingerprint(ssh_key::HashAlg::Sha256), identity_hash: cert.public_key().fingerprint(ssh_key::HashAlg::Sha256),
key_id: cert.key_id().to_string(), key_id: cert.key_id().to_string(),
expiry: cert.valid_before_time(), expiry: cert.valid_before_time(),
renew_command renew_command,
} }
} }
} }
@ -434,16 +429,7 @@ mod tests {
} }
fn ca_pub() -> PublicKey { fn ca_pub() -> PublicKey {
PublicKey::new( PublicKey::new(ca_key().public.into(), "TEST CA")
ca_key().public.into(),
format!(
"TEST CA {}",
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs()
),
)
} }
fn user_key() -> Ed25519Keypair { fn user_key() -> Ed25519Keypair {
@ -469,7 +455,7 @@ mod tests {
.unwrap() .unwrap()
.key_id("test_cert") .key_id("test_cert")
.unwrap() .unwrap()
.comment(&format!("A TEST CERT, VALID FOR {}s", validity.as_secs())) .comment("A TEST CERT")
.unwrap(); .unwrap();
builder.sign(&ca_private).unwrap() builder.sign(&ca_private).unwrap()
@ -503,9 +489,9 @@ mod tests {
let user: PublicKey = user_key().public.into(); let user: PublicKey = user_key().public.into();
let (cert_first, cert_newer, cert_outdated) = { 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(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; let res = put_cert_update(PutCert, State(state.clone()), CertificateBody(cert_first)).await;