diff --git a/src/crypto/certreq.rs b/src/crypto/certreq.rs index ec76c0c1..e79d7b9b 100644 --- a/src/crypto/certreq.rs +++ b/src/crypto/certreq.rs @@ -1,17 +1,12 @@ // SPDX-FileCopyrightText: 2022 Profian Inc. // SPDX-License-Identifier: AGPL-3.0-only -use anyhow::{anyhow, bail, Result}; +use anyhow::{anyhow, Result}; use der::{asn1::BitStringRef, Encode}; use pkcs8::PrivateKeyInfo; -use x509::ext::Extension; -use x509::request::{CertReq, CertReqInfo, ExtensionReq}; +use x509::request::{CertReq, CertReqInfo}; use super::{PrivateKeyInfoExt, SubjectPublicKeyInfoExt}; -use crate::ext::{kvm::Kvm, sgx::Sgx, snp::Snp, ExtVerifier}; - -use const_oid::db::rfc5912::ID_EXTENSION_REQ; -use x509::Certificate; pub trait CertReqExt<'a> { /// Verifies that a certification request is sane. @@ -38,15 +33,12 @@ impl<'a> CertReqExt<'a> for CertReq<'a> { } } -pub trait CertReqInfoExt<'a> { +pub trait CertReqInfoExt { /// Signs the `CertReqInfo` with the specified `PrivateKeyInfo` fn sign(self, pki: &PrivateKeyInfo<'_>) -> Result>; - - /// Check that the `CertReqInfo` - fn attest(&self, issuer: &Certificate<'_>) -> Result>>; } -impl<'a> CertReqInfoExt<'a> for CertReqInfo<'a> { +impl<'a> CertReqInfoExt for CertReqInfo<'a> { fn sign(self, pki: &PrivateKeyInfo<'_>) -> Result> { let algo = pki.signs_with()?; let body = self.to_vec()?; @@ -60,42 +52,4 @@ impl<'a> CertReqInfoExt<'a> for CertReqInfo<'a> { Ok(rval.to_vec()?) } - - fn attest(&self, issuer: &Certificate<'_>) -> Result>> { - let mut extensions = Vec::new(); - let mut attested = false; - for attr in self.attributes.iter() { - if attr.oid != ID_EXTENSION_REQ { - bail!("invalid extension"); - } - - for any in attr.values.iter() { - let ereq: ExtensionReq<'_> = any.decode_into().or_else(|e| bail!(e))?; - for ext in Vec::from(ereq) { - // If the issuer is self-signed, we are in debug mode. - let iss = &issuer.tbs_certificate; - let dbg = iss.issuer_unique_id == iss.subject_unique_id; - let dbg = dbg && iss.issuer == iss.subject; - - // Validate the extension. - let (copy, att) = match ext.extn_id { - Kvm::OID => (Kvm::default().verify(self, &ext, dbg), Kvm::ATT), - Sgx::OID => (Sgx::default().verify(self, &ext, dbg), Sgx::ATT), - Snp::OID => (Snp::default().verify(self, &ext, dbg), Snp::ATT), - _ => bail!("unsupported extension"), - }; - - // Save results. - attested |= att; - if copy.or_else(|e| bail!(e))? { - extensions.push(ext); - } - } - } - } - if !attested { - bail!("attestation failed"); - } - Ok(extensions) - } } diff --git a/src/main.rs b/src/main.rs index 13cf2d6a..caa902b3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,8 +10,6 @@ mod crypto; mod ext; use crypto::*; -use rustls_pemfile::Item; -use x509::ext::pkix::name::GeneralName; use std::net::{IpAddr, SocketAddr}; use std::path::{Path, PathBuf}; @@ -21,33 +19,38 @@ use std::time::{Duration, SystemTime}; use anyhow::Context; use axum::body::Bytes; use axum::extract::Extension; +use axum::response::IntoResponse; use axum::routing::{get, post}; use axum::Router; -use hyper::StatusCode; -use tower_http::{ - trace::{ - DefaultOnBodyChunk, DefaultOnEos, DefaultOnFailure, DefaultOnRequest, DefaultOnResponse, - TraceLayer, - }, - LatencyUnit, -}; -use tracing::Level; - +use clap::Parser; +use confargs::{prefix_char_filter, Toml}; use const_oid::db::rfc5280::{ ID_CE_BASIC_CONSTRAINTS, ID_CE_EXT_KEY_USAGE, ID_CE_KEY_USAGE, ID_CE_SUBJECT_ALT_NAME, ID_KP_CLIENT_AUTH, ID_KP_SERVER_AUTH, }; +use const_oid::db::rfc5912::ID_EXTENSION_REQ; use der::asn1::{GeneralizedTime, Ia5StringRef, UIntRef}; use der::{Decode, Encode, Sequence}; +use ext::kvm::Kvm; +use ext::sgx::Sgx; +use ext::snp::Snp; +use ext::ExtVerifier; +use hyper::StatusCode; use pkcs8::PrivateKeyInfo; +use rustls_pemfile::Item; +use tower_http::trace::{ + DefaultOnBodyChunk, DefaultOnEos, DefaultOnFailure, DefaultOnRequest, DefaultOnResponse, + TraceLayer, +}; +use tower_http::LatencyUnit; +use tracing::{debug, Level}; +use x509::attr::Attribute; +use x509::ext::pkix::name::GeneralName; use x509::ext::pkix::{BasicConstraints, ExtendedKeyUsage, KeyUsage, KeyUsages, SubjectAltName}; use x509::name::RdnSequence; -use x509::request::CertReq; +use x509::request::{CertReq, ExtensionReq}; use x509::time::{Time, Validity}; use x509::{Certificate, TbsCertificate}; - -use clap::Parser; -use confargs::{prefix_char_filter, Toml}; use zeroize::Zeroizing; /// Attestation server for use with Enarx. @@ -268,6 +271,119 @@ async fn health() -> StatusCode { StatusCode::OK } +fn attest_request( + issuer: &Certificate<'_>, + pki: &PrivateKeyInfo<'_>, + sans: SubjectAltName<'_>, + cr: CertReq<'_>, +) -> Result, StatusCode> { + // Time-to-live for the issued certificate + const TTL: Duration = Duration::from_secs(60 * 60 * 24 * 28); + + let info = cr.verify().map_err(|e| { + debug!("failed to verify certificate info: {e}"); + StatusCode::BAD_REQUEST + })?; + + let mut extensions = Vec::new(); + let mut attested = false; + for Attribute { oid, values } in info.attributes.iter() { + if *oid != ID_EXTENSION_REQ { + debug!("invalid extension {oid}"); + return Err(StatusCode::BAD_REQUEST); + } + for any in values.iter() { + let ereq: ExtensionReq<'_> = any.decode_into().map_err(|e| { + debug!("failed to decode extension request: {e}"); + StatusCode::BAD_REQUEST + })?; + for ext in Vec::from(ereq) { + // If the issuer is self-signed, we are in debug mode. + let iss = &issuer.tbs_certificate; + let dbg = iss.issuer_unique_id == iss.subject_unique_id; + let dbg = dbg && iss.issuer == iss.subject; + + // Validate the extension. + let (copy, att) = match ext.extn_id { + Kvm::OID => (Kvm::default().verify(&info, &ext, dbg), Kvm::ATT), + Sgx::OID => (Sgx::default().verify(&info, &ext, dbg), Sgx::ATT), + Snp::OID => (Snp::default().verify(&info, &ext, dbg), Snp::ATT), + oid => { + debug!("extension `{oid}` is unsupported"); + return Err(StatusCode::BAD_REQUEST); + } + }; + let copy = copy.map_err(|e| { + debug!("extension validation failed: {e}"); + StatusCode::BAD_REQUEST + })?; + + // Save results. + attested |= att; + if copy { + extensions.push(ext); + } + } + } + } + if !attested { + debug!("attestation failed"); + return Err(StatusCode::UNAUTHORIZED); + } + + // Add Subject Alternative Name + let sans: Vec = sans.to_vec().or(Err(StatusCode::INTERNAL_SERVER_ERROR))?; + extensions.push(x509::ext::Extension { + extn_id: ID_CE_SUBJECT_ALT_NAME, + critical: false, + extn_value: &sans, + }); + + // Add extended key usage. + let eku = ExtendedKeyUsage(vec![ID_KP_SERVER_AUTH, ID_KP_CLIENT_AUTH]) + .to_vec() + .or(Err(StatusCode::INTERNAL_SERVER_ERROR))?; + extensions.push(x509::ext::Extension { + extn_id: ID_CE_EXT_KEY_USAGE, + critical: false, + extn_value: &eku, + }); + + // Generate the instance id. + let uuid = uuid::Uuid::new_v4(); + let serial_number = UIntRef::new(uuid.as_bytes()).or(Err(StatusCode::INTERNAL_SERVER_ERROR))?; + + let signature = pki + .signs_with() + .or(Err(StatusCode::INTERNAL_SERVER_ERROR))?; + + // Get the current time and the expiration of the cert. + let now = SystemTime::now(); + let not_before = now.try_into().or(Err(StatusCode::INTERNAL_SERVER_ERROR))?; + let not_after = (now + TTL) + .try_into() + .or(Err(StatusCode::INTERNAL_SERVER_ERROR))?; + + // Create and sign the new certificate. + TbsCertificate { + version: x509::Version::V3, + serial_number, + signature, + issuer: issuer.tbs_certificate.subject.clone(), + validity: Validity { + not_before, + not_after, + }, + subject: RdnSequence(Vec::new()), + subject_public_key_info: info.public_key, + issuer_unique_id: issuer.tbs_certificate.subject_unique_id, + subject_unique_id: None, + extensions: Some(extensions), + } + .sign(pki) + .or(Err(StatusCode::INTERNAL_SERVER_ERROR)) +} + /// Receives: /// ASN.1 SEQUENCE OF CertRequest. /// Returns: @@ -275,93 +391,40 @@ async fn health() -> StatusCode { async fn attest( body: Bytes, Extension(state): Extension>, -) -> Result, StatusCode> { - const ISE: StatusCode = StatusCode::INTERNAL_SERVER_ERROR; - +) -> Result, impl IntoResponse> { // Decode the signing certificate and key. - let issuer = Certificate::from_der(&state.crt).or(Err(ISE))?; - let isskey = PrivateKeyInfo::from_der(&state.key).or(Err(ISE))?; - - // Create the basic subject alt name. - let mut sans = vec![GeneralName::DnsName( - Ia5StringRef::new("foo.bar.hub.profian.com").or(Err(ISE))?, - )]; - - // Optionally, add the configured subject alt name. - if let Some(name) = state.san.as_ref() { - sans.push(GeneralName::DnsName(Ia5StringRef::new(name).or(Err(ISE))?)); - } - - // Encode the subject alt name. - let sans: Vec = SubjectAltName(sans).to_vec().or(Err(ISE))?; - - // Get the current time and the expiration of the cert. - let now = SystemTime::now(); - let end = now + Duration::from_secs(60 * 60 * 24 * 28); - let validity = Validity { - not_before: Time::try_from(now).or(Err(ISE))?, - not_after: Time::try_from(end).or(Err(ISE))?, - }; - - let crs = Vec::>::from_der(body.as_ref()).or(Err(StatusCode::BAD_REQUEST))?; + let issuer = Certificate::from_der(&state.crt).or(Err(StatusCode::INTERNAL_SERVER_ERROR))?; + let isskey = PrivateKeyInfo::from_der(&state.key).or(Err(StatusCode::INTERNAL_SERVER_ERROR))?; - // Decode and verify the certification request. - crs.into_iter() + // Decode and verify the certification requests. + Vec::>::from_der(body.as_ref()) + .or(Err(StatusCode::BAD_REQUEST))? + .into_iter() .map(|cr| { - let issuer = issuer.clone(); - let cri = cr.verify().or(Err(StatusCode::BAD_REQUEST))?; - - let mut extensions = cri.attest(&issuer).or(Err(StatusCode::UNAUTHORIZED))?; - - // Add Subject Alternative Name - extensions.push(x509::ext::Extension { - extn_id: ID_CE_SUBJECT_ALT_NAME, - critical: false, - extn_value: &sans, - }); - - // Add extended key usage. - let eku = ExtendedKeyUsage(vec![ID_KP_SERVER_AUTH, ID_KP_CLIENT_AUTH]) - .to_vec() - .or(Err(ISE))?; - extensions.push(x509::ext::Extension { - extn_id: ID_CE_EXT_KEY_USAGE, - critical: false, - extn_value: &eku, - }); - - // Generate the instance id. - let uuid = uuid::Uuid::new_v4(); - - // Create the new certificate. - let tbs = TbsCertificate { - version: x509::Version::V3, - serial_number: UIntRef::new(uuid.as_bytes()).or(Err(ISE))?, - signature: isskey.signs_with().or(Err(ISE))?, - issuer: issuer.clone().tbs_certificate.subject.clone(), - validity, - subject: RdnSequence(Vec::new()), - subject_public_key_info: cri.public_key, - issuer_unique_id: issuer.clone().tbs_certificate.subject_unique_id, - subject_unique_id: None, - extensions: Some(extensions), - }; - - // Sign the certificate. - tbs.sign(&isskey).or(Err(ISE)) + // Create the basic subject alt name. + let name = Ia5StringRef::new("foo.bar.hub.profian.com") + .or(Err(StatusCode::INTERNAL_SERVER_ERROR))?; + let mut sans = vec![GeneralName::DnsName(name)]; + + // Optionally, add the configured subject alt name. + if let Some(name) = &state.san { + let name = Ia5StringRef::new(name).or(Err(StatusCode::INTERNAL_SERVER_ERROR))?; + sans.push(GeneralName::DnsName(name)); + } + attest_request(&issuer, &isskey, SubjectAltName(sans), cr) }) .collect::, _>>() - .and_then(|crts| { - let issued = crts + .and_then(|issued| { + let issued = issued .iter() - .map(|c| Certificate::from_der(c).unwrap()) - .collect(); + .map(|c| Certificate::from_der(c).or(Err(StatusCode::INTERNAL_SERVER_ERROR))) + .collect::>()?; Output { - chain: vec![issuer.clone()], + chain: vec![issuer], issued, } .to_vec() - .or(Err(ISE)) + .or(Err(StatusCode::INTERNAL_SERVER_ERROR)) }) }