Skip to content

Commit

Permalink
refactor: clean-up attestation handler
Browse files Browse the repository at this point in the history
- Remove redundant clones
- Clean-up error handling and setup logging to debug level as part of it
- Reorganize `use` statements according to Enarx guidelines

Signed-off-by: Roman Volosatovs <roman@profian.com>
  • Loading branch information
rvolosatovs authored and rjzak committed Sep 22, 2022
1 parent e7840a4 commit f9fcc2b
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 138 deletions.
54 changes: 4 additions & 50 deletions src/crypto/certreq.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
// SPDX-FileCopyrightText: 2022 Profian Inc. <opensource@profian.com>
// 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.
Expand All @@ -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<Vec<u8>>;

/// Check that the `CertReqInfo`
fn attest(&self, issuer: &Certificate<'_>) -> Result<Vec<Extension<'a>>>;
}

impl<'a> CertReqInfoExt<'a> for CertReqInfo<'a> {
impl<'a> CertReqInfoExt for CertReqInfo<'a> {
fn sign(self, pki: &PrivateKeyInfo<'_>) -> Result<Vec<u8>> {
let algo = pki.signs_with()?;
let body = self.to_vec()?;
Expand All @@ -60,42 +52,4 @@ impl<'a> CertReqInfoExt<'a> for CertReqInfo<'a> {

Ok(rval.to_vec()?)
}

fn attest(&self, issuer: &Certificate<'_>) -> Result<Vec<Extension<'a>>> {
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)
}
}
234 changes: 146 additions & 88 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand All @@ -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.
Expand Down Expand Up @@ -268,100 +271,155 @@ async fn health() -> StatusCode {
StatusCode::OK
}

fn attest_request(
issuer: &Certificate<'_>,
pki: &PrivateKeyInfo<'_>,
sans: SubjectAltName<'_>,
cr: CertReq<'_>,
validity: &Validity,
) -> Result<Vec<u8>, StatusCode> {
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<u8> = 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))?;

// Create and sign the new certificate.
TbsCertificate {
version: x509::Version::V3,
serial_number,
signature,
issuer: issuer.tbs_certificate.subject.clone(),
validity: *validity,
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:
/// ASN.1 SEQUENCE OF Output.
async fn attest(
body: Bytes,
Extension(state): Extension<Arc<State>>,
) -> Result<Vec<u8>, StatusCode> {
const ISE: StatusCode = StatusCode::INTERNAL_SERVER_ERROR;

) -> Result<Vec<u8>, 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<u8> = SubjectAltName(sans).to_vec().or(Err(ISE))?;
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))?;

// 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))?,
not_before: Time::try_from(now).or(Err(StatusCode::INTERNAL_SERVER_ERROR))?,
not_after: Time::try_from(end).or(Err(StatusCode::INTERNAL_SERVER_ERROR))?,
};

let crs = Vec::<CertReq<'_>>::from_der(body.as_ref()).or(Err(StatusCode::BAD_REQUEST))?;

// Decode and verify the certification request.
crs.into_iter()
// Decode and verify the certification requests.
Vec::<CertReq<'_>>::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, &validity)
})
.collect::<Result<Vec<_>, _>>()
.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::<Result<_, _>>()?;
Output {
chain: vec![issuer.clone()],
chain: vec![issuer],
issued,
}
.to_vec()
.or(Err(ISE))
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))
})
}

Expand Down

0 comments on commit f9fcc2b

Please sign in to comment.