diff --git a/src/main.rs b/src/main.rs index 0301e095..5ae4319a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,12 +21,10 @@ use std::time::{Duration, SystemTime}; use anyhow::Context; use axum::body::Bytes; -use axum::extract::{Extension, TypedHeader}; -use axum::headers::ContentType; +use axum::extract::Extension; use axum::routing::{get, post}; use axum::Router; use hyper::StatusCode; -use mime::Mime; use tower_http::{ trace::{ DefaultOnBodyChunk, DefaultOnEos, DefaultOnFailure, DefaultOnRequest, DefaultOnResponse, @@ -42,7 +40,7 @@ use const_oid::db::rfc5280::{ }; use const_oid::db::rfc5912::ID_EXTENSION_REQ; use der::asn1::{GeneralizedTime, Ia5StringRef, UIntRef}; -use der::{Decode, Encode}; +use der::{Decode, Encode, Sequence}; use pkcs8::PrivateKeyInfo; use x509::ext::pkix::{BasicConstraints, ExtendedKeyUsage, KeyUsage, KeyUsages, SubjectAltName}; use x509::name::RdnSequence; @@ -54,8 +52,6 @@ use clap::Parser; use confargs::{prefix_char_filter, Toml}; use zeroize::Zeroizing; -const PKCS10: &str = "application/pkcs10"; - /// Attestation server for use with Enarx. /// /// Any command-line options listed here may be specified by one or @@ -93,6 +89,20 @@ struct State { san: Option, } +/// ASN.1 +/// Output ::= SEQUENCE { +/// chain SEQUENCE OF Certificate, +/// issued SEQUENCE OF Certificate, +/// } +#[derive(Clone, Debug, Default, Sequence)] +struct Output<'a> { + /// The signing certificate chain back to the root. + pub chain: Vec>, + + /// All issued certificates. + pub issued: Vec>, +} + impl State { pub fn load( san: Option, @@ -260,8 +270,11 @@ async fn health() -> StatusCode { StatusCode::OK } +/// Receives: +/// ASN.1 SEQUENCE OF CertRequest. +/// Returns: +/// ASN.1 SEQUENCE OF Output. async fn attest( - TypedHeader(ct): TypedHeader, body: Bytes, Extension(state): Extension>, ) -> Result, StatusCode> { @@ -271,52 +284,6 @@ async fn attest( let issuer = Certificate::from_der(&state.crt).or(Err(ISE))?; let isskey = PrivateKeyInfo::from_der(&state.key).or(Err(ISE))?; - // Ensure the correct mime type. - let mime: Mime = PKCS10.parse().unwrap(); - if ct != ContentType::from(mime) { - return Err(StatusCode::BAD_REQUEST); - } - - // Decode and verify the certification request. - let cr = CertReq::from_der(body.as_ref()).or(Err(StatusCode::BAD_REQUEST))?; - let cri = cr.verify().or(Err(StatusCode::BAD_REQUEST))?; - - // Validate requested extensions. - let mut attested = false; - let mut extensions = Vec::new(); - for attr in cri.attributes.iter() { - if attr.oid != ID_EXTENSION_REQ { - return Err(StatusCode::BAD_REQUEST); - } - - for any in attr.values.iter() { - let ereq: ExtensionReq<'_> = any.decode_into().or(Err(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(&cri, &ext, dbg), Kvm::ATT), - Sgx::OID => (Sgx::default().verify(&cri, &ext, dbg), Sgx::ATT), - Snp::OID => (Snp::default().verify(&cri, &ext, dbg), Snp::ATT), - _ => return Err(StatusCode::BAD_REQUEST), // unsupported extension - }; - - // Save results. - attested |= att; - if copy.or(Err(StatusCode::BAD_REQUEST))? { - extensions.push(ext); - } - } - } - } - if !attested { - return Err(StatusCode::UNAUTHORIZED); - } - // Get the current time and the expiration of the cert. let now = SystemTime::now(); let end = now + Duration::from_secs(60 * 60 * 24 * 28); @@ -325,57 +292,113 @@ async fn attest( not_after: Time::try_from(end).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))?, - )]; + let crs = Vec::>::from_der(body.as_ref()).or(Err(StatusCode::BAD_REQUEST))?; - // 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))?)); - } + // Decode and verify the certification request. + crs.into_iter() + .map(|cr| { + let issuer = issuer.clone(); + let cri = cr.verify().or(Err(StatusCode::BAD_REQUEST))?; + + // Validate requested extensions. + let mut attested = false; + let mut extensions = Vec::new(); + for attr in cri.attributes.iter() { + if attr.oid != ID_EXTENSION_REQ { + return Err(StatusCode::BAD_REQUEST); + } - // Encode the subject alt name. - let sans: Vec = SubjectAltName(sans).to_vec().or(Err(ISE))?; - 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.tbs_certificate.subject.clone(), - validity, - subject: RdnSequence(Vec::new()), - subject_public_key_info: cri.public_key, - issuer_unique_id: issuer.tbs_certificate.subject_unique_id, - subject_unique_id: None, - extensions: Some(extensions), - }; + for any in attr.values.iter() { + let ereq: ExtensionReq<'_> = + any.decode_into().or(Err(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(&cri, &ext, dbg), Kvm::ATT), + Sgx::OID => (Sgx::default().verify(&cri, &ext, dbg), Sgx::ATT), + Snp::OID => (Snp::default().verify(&cri, &ext, dbg), Snp::ATT), + _ => return Err(StatusCode::BAD_REQUEST), // unsupported extension + }; + + // Save results. + attested |= att; + if copy.or(Err(StatusCode::BAD_REQUEST))? { + extensions.push(ext); + } + } + } + } + if !attested { + return Err(StatusCode::UNAUTHORIZED); + } + + // 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))?)); + } - // Sign the certificate. - let crt = tbs.sign(&isskey).or(Err(ISE))?; - let crt = Certificate::from_der(&crt).or(Err(ISE))?; + // Encode the subject alt name. + let sans: Vec = SubjectAltName(sans).to_vec().or(Err(ISE))?; + 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), + }; - // Create and return the PkiPath. - vec![issuer, crt].to_vec().or(Err(ISE)) + // Sign the certificate. + tbs.sign(&isskey).or(Err(ISE)) + }) + .collect::, _>>() + .and_then(|crts| { + let mut issued = Vec::with_capacity(crts.len()); + for crt in crts.iter() { + let crt = Certificate::from_der(crt).or(Err(ISE))?; + issued.push(crt) + } + Output { + chain: vec![issuer.clone()], + issued, + } + .to_vec() + .or(Err(ISE)) + }) } #[cfg(test)] @@ -392,7 +415,7 @@ mod tests { use x509::{ext::Extension, name::RdnSequence}; use axum::response::Response; - use http::{header::CONTENT_TYPE, Request}; + use http::Request; use hyper::Body; use tower::ServiceExt; // for `app.oneshot()` @@ -425,12 +448,18 @@ mod tests { }; // Sign the request. - cri.sign(&pki).unwrap() + vec![CertReq::from_der(&cri.sign(&pki).unwrap()).unwrap()] + .to_vec() + .unwrap() } async fn attest_response(state: State, response: Response) { let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); - let path = PkiPath::from_der(&body).unwrap(); + + let response = Output::from_der(body.as_ref()).unwrap(); + + let mut path = PkiPath::from(response.chain); + path.push(response.issued[0].clone()); let issr = Certificate::from_der(&state.crt).unwrap(); assert_eq!(2, path.len()); assert_eq!(issr, path[0]); @@ -440,14 +469,12 @@ mod tests { #[test] fn reencode() { let encoded = cr(SECP_256_R_1, vec![]); + let crs = Vec::>::from_der(&encoded).unwrap(); + assert_eq!(crs.len(), 1); - for byte in &encoded { - eprint!("{:02X}", byte); - } - eprintln!(); - + let encoded: Vec = crs[0].to_vec().unwrap(); let decoded = CertReq::from_der(&encoded).unwrap(); - let reencoded = decoded.to_vec().unwrap(); + let reencoded: Vec = decoded.to_vec().unwrap(); assert_eq!(encoded, reencoded); } @@ -462,7 +489,6 @@ mod tests { let request = Request::builder() .method("POST") .uri("/") - .header(CONTENT_TYPE, PKCS10) .body(Body::from(cr(SECP_256_R_1, vec![ext]))) .unwrap(); @@ -482,7 +508,6 @@ mod tests { let request = Request::builder() .method("POST") .uri("/") - .header(CONTENT_TYPE, PKCS10) .body(Body::from(cr(SECP_256_R_1, vec![ext]))) .unwrap(); @@ -492,6 +517,41 @@ mod tests { attest_response(state, response).await; } + #[tokio::test] + async fn kvm_hostname_many_certs() { + let ext = Extension { + extn_id: Kvm::OID, + critical: false, + extn_value: &[], + }; + + let one_cr_bytes = cr(SECP_256_R_1, vec![ext]); + let crs = Vec::>::from_der(&one_cr_bytes).unwrap(); + assert_eq!(crs.len(), 1); + + let mut five_crs = Vec::new(); + five_crs.push(crs[0].clone()); + five_crs.push(crs[0].clone()); + five_crs.push(crs[0].clone()); + five_crs.push(crs[0].clone()); + five_crs.push(crs[0].clone()); + + let request = Request::builder() + .method("POST") + .uri("/") + .body(Body::from(five_crs.to_vec().unwrap())) + .unwrap(); + + let state = hostname_state(); + let response = app(state.clone()).oneshot(request).await.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + + let body = hyper::body::to_bytes(response.into_body()).await.unwrap(); + let output = Output::from_der(body.as_ref()).unwrap(); + + assert_eq!(output.issued.len(), five_crs.len()); + } + #[tokio::test] async fn sgx_certs() { for quote in [ @@ -507,7 +567,6 @@ mod tests { let request = Request::builder() .method("POST") .uri("/") - .header(CONTENT_TYPE, PKCS10) .body(Body::from(cr(SECP_256_R_1, vec![ext]))) .unwrap(); @@ -532,7 +591,6 @@ mod tests { let request = Request::builder() .method("POST") .uri("/") - .header(CONTENT_TYPE, PKCS10) .body(Body::from(cr(SECP_256_R_1, vec![ext]))) .unwrap(); @@ -561,7 +619,6 @@ mod tests { let request = Request::builder() .method("POST") .uri("/") - .header(CONTENT_TYPE, PKCS10) .body(Body::from(cr(SECP_384_R_1, vec![ext]))) .unwrap(); @@ -588,7 +645,6 @@ mod tests { let request = Request::builder() .method("POST") .uri("/") - .header(CONTENT_TYPE, PKCS10) .body(Body::from(cr(SECP_384_R_1, vec![ext]))) .unwrap(); @@ -603,7 +659,6 @@ mod tests { let request = Request::builder() .method("POST") .uri("/") - .header(CONTENT_TYPE, PKCS10) .body(Body::from(cr(SECP_256_R_1, vec![]))) .unwrap(); @@ -616,7 +671,6 @@ mod tests { let request = Request::builder() .method("POST") .uri("/") - .header(CONTENT_TYPE, PKCS10) .body(Body::from(cr(SECP_256_R_1, vec![]))) .unwrap(); @@ -624,36 +678,10 @@ mod tests { assert_eq!(response.status(), StatusCode::UNAUTHORIZED); } - #[tokio::test] - async fn err_no_content_type() { - let request = Request::builder() - .method("POST") - .uri("/") - .body(Body::from(cr(SECP_256_R_1, vec![]))) - .unwrap(); - - let response = app(certificates_state()).oneshot(request).await.unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - } - - #[tokio::test] - async fn err_bad_content_type() { - let request = Request::builder() - .method("POST") - .header(CONTENT_TYPE, "text/plain") - .uri("/") - .body(Body::from(cr(SECP_256_R_1, vec![]))) - .unwrap(); - - let response = app(certificates_state()).oneshot(request).await.unwrap(); - assert_eq!(response.status(), StatusCode::BAD_REQUEST); - } - #[tokio::test] async fn err_empty_body() { let request = Request::builder() .method("POST") - .header(CONTENT_TYPE, PKCS10) .uri("/") .body(Body::empty()) .unwrap(); @@ -666,7 +694,6 @@ mod tests { async fn err_bad_body() { let request = Request::builder() .method("POST") - .header(CONTENT_TYPE, PKCS10) .uri("/") .body(Body::from(vec![0x01, 0x02, 0x03, 0x04])) .unwrap(); @@ -683,7 +710,6 @@ mod tests { let request = Request::builder() .method("POST") - .header(CONTENT_TYPE, PKCS10) .uri("/") .body(Body::from(cr)) .unwrap();