Skip to content

Commit

Permalink
fix: fix ic_tee_nitro_attestation
Browse files Browse the repository at this point in the history
  • Loading branch information
zensh committed Nov 5, 2024
1 parent 3ccc7fd commit db3e112
Show file tree
Hide file tree
Showing 13 changed files with 198 additions and 73 deletions.
58 changes: 23 additions & 35 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ strip = true
opt-level = 's'

[workspace.package]
version = "0.1.0"
version = "0.1.1"
edition = "2021"
repository = "https://github.com/ldclabs/ic-tee"
keywords = ["tee", "canister", "icp", "nitro"]
Expand Down Expand Up @@ -44,7 +44,6 @@ lazy_static = "1.5"
serde = "1"
serde_json = "1"
serde_bytes = "0.11"
p384 = { version = "0.13" }
sha2 = "0.10"
sha3 = "0.10"
ic-cdk = "0.16"
Expand Down
12 changes: 12 additions & 0 deletions nitro_enclave/host_iptables.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# route local incoming packets on port 8080 to the transparent proxy
iptables -t nat -A OUTPUT -p tcp --dport 8080 -o lo -j REDIRECT --to-port 1200
iptables -t nat -A OUTPUT -p tcp --dport 8080 -d 127.0.0.1 -j REDIRECT --to-port 1200

# route incoming packets on port 443 to the transparent proxy
iptables -A PREROUTING -t nat -p tcp --dport 443 -i ens5 -j REDIRECT --to-port 1200
# route incoming packets on port 1025:65535 to the transparent proxy
# iptables -A PREROUTING -t nat -p tcp --dport 1025:65535 -i ens5 -j REDIRECT --to-port 1200

iptables -L -t nat -v -n --line-number
# delete a rule by line number 7
# sudo iptables -t nat -D PREROUTING 7
2 changes: 1 addition & 1 deletion nitro_enclave/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ iptables -t nat -A POSTROUTING -o lo -s 0.0.0.0 -j SNAT --to-source 127.0.0.1
iptables -L -t nat -v -n

# your custom setup goes here
export LOG_LEVEL=Debug
# export LOG_LEVEL=Debug

# starting supervisord
cat /etc/supervisord.conf
Expand Down
2 changes: 2 additions & 0 deletions nitro_enclave/supervisord.conf
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ stdout_logfile_maxbytes=0
stderr_logfile=/dev/stdout
stderr_logfile_maxbytes=0

# It is used to develop and test purpose only
# It should be removed in production
[program:ic_tee_nitro_gateway-local]
command=/app/vsock-to-ip --vsock-addr 88:8080 --ip-addr 127.0.0.1:8080
autorestart=true
Expand Down
4 changes: 2 additions & 2 deletions src/ic_tee_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,8 +200,8 @@ async fn main() -> Result<()> {
};
pretty_println(&doc)?;
match error {
Some(err) => println!("{} attestation verify failed: {}", kind, err),
None => println!("{} attestation verify success", kind),
Some(err) => println!("{} attestation verification failed: {}", kind, err),
None => println!("{} attestation verification success", kind),
}
}

Expand Down
25 changes: 22 additions & 3 deletions src/ic_tee_identity/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,38 @@ fn sign_in(kind: String, attestation: ByteBuf) -> Result<SignInResponse, String>
.public_key
.ok_or_else(|| "missing public key".to_string())?;

// TODO: check request method and params
let _req: AttestationUserRequest<SignInParams> = attestation.user_data.map_or_else(
let req: AttestationUserRequest<SignInParams> = attestation.user_data.map_or_else(
|| Err("missing user data".to_string()),
|data| from_reader(data.as_slice()).map_err(|err| format!("invalid user data: {:?}", err)),
)?;
if req.method != "sign_in" {
return Err("invalid attestation user request method".to_string());
}

let user_key = match req.params.as_ref() {
Some(SignInParams { id_scope }) => {
if id_scope == "image" {
canister_user_key(ic_cdk::id(), &kind, pcr0.as_slice(), None)
} else if id_scope == "enclave" {
canister_user_key(
ic_cdk::id(),
&kind,
pcr0.as_slice(),
Some(attestation.module_id.as_bytes()),
)
} else {
return Err(format!("unsupport id_scope: {}", id_scope));
}
}
_ => return Err("invalid attestation user request params".to_string()),
};

let session_expires_in_ms = store::state::with_mut(|state| {
state.sign_in_count = state.sign_in_count.saturating_add(1);
state.session_expires_in_ms
});
let expiration = (now_ms + session_expires_in_ms) * MILLISECONDS;

let user_key = canister_user_key(ic_cdk::id(), &kind, pcr0.as_slice(), None);
let principal = Principal::self_authenticating(&user_key);
let delegation_hash = delegation_signature_msg(pubkey.as_slice(), expiration, None);
store::state::add_signature(principal.as_slice(), delegation_hash.as_slice());
Expand Down
6 changes: 5 additions & 1 deletion src/ic_tee_nitro_attestation/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ lazy_static = { workspace = true }
serde = { workspace = true }
serde_bytes = { workspace = true }
ciborium = { workspace = true }
p384 = { workspace = true }
sha2 = { workspace = true }
coset = { workspace = true }
x509-parser = { workspace = true, features = ["verify"] }
const-hex = { workspace = true }
ring = "0.17"

[dev-dependencies]
const-hex = { workspace = true }
50 changes: 38 additions & 12 deletions src/ic_tee_nitro_attestation/src/attestation.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use ciborium::from_reader;
use coset::{iana, Algorithm, CborSerializable, CoseSign1};
use lazy_static::lazy_static;
use p384::ecdsa::{signature::Verifier, Signature, VerifyingKey};
use ring::signature::{VerificationAlgorithm, ECDSA_P384_SHA384_FIXED};
use sha2::Sha384;
use x509_parser::prelude::*;
use x509_parser::{pem::Pem, prelude::*};

// https://github.com/aws/aws-nitro-enclaves-nsm-api/blob/main/docs/attestation_process.md

Expand All @@ -12,10 +12,19 @@ use crate::Attestation;
const SIGN1_TAG_PREFIX: &[u8] = &[0xd2]; // COSE_Sign1 Tag 18
const ALG_ES384: Algorithm = Algorithm::Assigned(iana::Algorithm::ES384);

static ROOT_CERT_PEM: &[u8] = include_bytes!("./AWS_NitroEnclaves_Root-G1.pem");
const ROOT_CERT_PEM: &[u8] = include_bytes!("./AWS_NitroEnclaves_Root-G1.pem");

lazy_static! {
static ref ROOT_CERT: X509Certificate<'static> = x509_cert(ROOT_CERT_PEM).unwrap();
static ref ROOT_CERT_PUBKEY: Vec<u8> = {
let mut pems = Pem::iter_from_buffer(ROOT_CERT_PEM)
.collect::<Result<Vec<_>, _>>()
.expect("Failed to parse PEM buffer");
let pem = pems.pop().expect("No PEM blocks found");
let cert = pem.parse_x509().expect("Failed to parse X.509 certificate");
let pk = cert.public_key();
let pk = pk.subject_public_key.as_ref().to_vec();
pk
};
}

pub fn parse(attestation_doc: &[u8]) -> Result<(CoseSign1, Attestation), String> {
Expand Down Expand Up @@ -45,16 +54,18 @@ pub fn parse_and_verify(attestation_doc: &[u8]) -> Result<Attestation, String> {
cs1.protected.header.alg
));
}
let sig = Signature::from_der(&cs1.signature)
.map_err(|err| format!("invalid signature: {:?}", err))?;

let cert = x509_cert(&doc.certificate)?;
let pub_key = cert.public_key();
let verifying_key = VerifyingKey::from_sec1_bytes(pub_key.raw)
.map_err(|err| format!("invalid public key in X.509 certificate: {:?}", err))?;
let msg = cs1.tbs_data(&[]);
verifying_key
.verify(&sha384(&msg), &sig)
.map_err(|err| format!("signature verification failed: {:?}", err))?;

ECDSA_P384_SHA384_FIXED
.verify(
pub_key.subject_public_key.as_ref().into(),
msg.as_slice().into(),
cs1.signature.as_slice().into(),
)
.map_err(|_| "signature verification failed".to_string())?;

let mut certs: Vec<X509Certificate> = Vec::with_capacity(doc.cabundle.len());
for pem in &doc.cabundle {
Expand All @@ -63,6 +74,7 @@ pub fn parse_and_verify(attestation_doc: &[u8]) -> Result<Attestation, String> {
}
certs.push(cert);
certs.reverse();

verify_cert_chain(&certs)?;

Ok(doc)
Expand Down Expand Up @@ -102,7 +114,7 @@ pub fn verify_cert_chain(certs: &[X509Certificate]) -> Result<(), String> {
err
)
})?;
} else if cert.public_key() != ROOT_CERT.public_key() {
} else if cert.public_key().subject_public_key.as_ref() != *ROOT_CERT_PUBKEY {
return Err(format!(
"certificate chain is broken: last certificate {:?} is not a root certificate",
cert.subject()
Expand All @@ -111,3 +123,17 @@ pub fn verify_cert_chain(certs: &[X509Certificate]) -> Result<(), String> {
}
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
// https://github.com/briansmith/ring/issues/1942
// export C_INCLUDE_PATH="/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/:$C_INCLUDE_PATH"
#[test]
fn test_parse_and_verify() {
let doc: &[u8] = include_bytes!("./test/attestation2.hex");
let doc = const_hex::decode(doc).unwrap();
let attestation = parse_and_verify(&doc).unwrap();
println!("{:?}", attestation);
}
}
Loading

0 comments on commit db3e112

Please sign in to comment.