|
| 1 | +// Copyright (c) Microsoft Corporation. |
| 2 | +// Licensed under the MIT License. |
| 3 | + |
| 4 | +//! Certificate-based trust validation — create an ephemeral certificate chain, |
| 5 | +//! construct a COSE_Sign1 message with an embedded x5chain header, then validate |
| 6 | +//! using the X.509 certificate trust pack. |
| 7 | +//! |
| 8 | +//! Run with: |
| 9 | +//! cargo run --example certificate_trust_validation -p cose_sign1_certificates |
| 10 | +
|
| 11 | +use std::sync::Arc; |
| 12 | + |
| 13 | +use cbor_primitives::{CborEncoder, CborProvider}; |
| 14 | +use cbor_primitives_everparse::EverParseCborProvider; |
| 15 | +use cose_sign1_certificates::validation::pack::{ |
| 16 | + CertificateTrustOptions, X509CertificateTrustPack, |
| 17 | +}; |
| 18 | +use cose_sign1_validation::fluent::*; |
| 19 | +use cose_sign1_validation_primitives::CoseHeaderLocation; |
| 20 | + |
| 21 | +fn main() { |
| 22 | + // ── 1. Generate an ephemeral self-signed certificate ───────────── |
| 23 | + println!("=== Step 1: Generate ephemeral certificate ===\n"); |
| 24 | + |
| 25 | + let rcgen::CertifiedKey { cert, .. } = |
| 26 | + rcgen::generate_simple_self_signed(vec!["example-leaf".to_string()]) |
| 27 | + .expect("rcgen failed"); |
| 28 | + let leaf_der = cert.der().to_vec(); |
| 29 | + println!(" Leaf cert DER size: {} bytes", leaf_der.len()); |
| 30 | + |
| 31 | + // ── 2. Build a minimal COSE_Sign1 with x5chain header ─────────── |
| 32 | + println!("\n=== Step 2: Build COSE_Sign1 with x5chain ===\n"); |
| 33 | + |
| 34 | + let payload = b"Hello, COSE world!"; |
| 35 | + let cose_bytes = build_cose_sign1_with_x5chain(&leaf_der, payload); |
| 36 | + println!(" COSE message size: {} bytes", cose_bytes.len()); |
| 37 | + println!(" Payload: {:?}", std::str::from_utf8(payload).unwrap()); |
| 38 | + |
| 39 | + // ── 3. Set up the certificate trust pack ───────────────────────── |
| 40 | + println!("\n=== Step 3: Configure certificate trust pack ===\n"); |
| 41 | + |
| 42 | + // For this example, treat the embedded x5chain as trusted. |
| 43 | + // In production, configure actual trust roots and revocation checks. |
| 44 | + let cert_pack = Arc::new(X509CertificateTrustPack::new(CertificateTrustOptions { |
| 45 | + trust_embedded_chain_as_trusted: true, |
| 46 | + ..Default::default() |
| 47 | + })); |
| 48 | + println!(" Trust pack: embedded x5chain treated as trusted"); |
| 49 | + |
| 50 | + let trust_packs: Vec<Arc<dyn CoseSign1TrustPack>> = vec![cert_pack]; |
| 51 | + |
| 52 | + // ── 4. Build a validator with bypass trust + signature bypass ───── |
| 53 | + // (We bypass the actual crypto check because the COSE message's |
| 54 | + // signature is a dummy — in a real scenario the signing service |
| 55 | + // would produce a valid signature.) |
| 56 | + println!("\n=== Step 4: Validate with trust bypass ===\n"); |
| 57 | + |
| 58 | + let validator = CoseSign1Validator::new(trust_packs.clone()).with_options(|o| { |
| 59 | + o.certificate_header_location = CoseHeaderLocation::Any; |
| 60 | + o.trust_evaluation_options.bypass_trust = true; |
| 61 | + }); |
| 62 | + |
| 63 | + let result = validator |
| 64 | + .validate_bytes( |
| 65 | + EverParseCborProvider, |
| 66 | + Arc::from(cose_bytes.clone().into_boxed_slice()), |
| 67 | + ) |
| 68 | + .expect("validation pipeline error"); |
| 69 | + |
| 70 | + println!(" resolution: {:?}", result.resolution.kind); |
| 71 | + println!(" trust: {:?}", result.trust.kind); |
| 72 | + println!(" signature: {:?}", result.signature.kind); |
| 73 | + println!(" overall: {:?}", result.overall.kind); |
| 74 | + |
| 75 | + // ── 5. Demonstrate custom trust plan ───────────────────────────── |
| 76 | + println!("\n=== Step 5: Custom trust plan (advanced) ===\n"); |
| 77 | + |
| 78 | + use cose_sign1_certificates::validation::fluent_ext::PrimarySigningKeyScopeRulesExt; |
| 79 | + |
| 80 | + let cert_pack2 = Arc::new(X509CertificateTrustPack::new(CertificateTrustOptions { |
| 81 | + trust_embedded_chain_as_trusted: true, |
| 82 | + ..Default::default() |
| 83 | + })); |
| 84 | + let packs: Vec<Arc<dyn CoseSign1TrustPack>> = vec![cert_pack2]; |
| 85 | + |
| 86 | + let plan = TrustPlanBuilder::new(packs) |
| 87 | + .for_primary_signing_key(|key| { |
| 88 | + key.require_x509_chain_trusted() |
| 89 | + .and() |
| 90 | + .require_signing_certificate_present() |
| 91 | + }) |
| 92 | + .compile() |
| 93 | + .expect("plan compile"); |
| 94 | + |
| 95 | + let validator2 = CoseSign1Validator::new(plan).with_options(|o| { |
| 96 | + o.certificate_header_location = CoseHeaderLocation::Any; |
| 97 | + }); |
| 98 | + |
| 99 | + let result2 = validator2 |
| 100 | + .validate_bytes( |
| 101 | + EverParseCborProvider, |
| 102 | + Arc::from(cose_bytes.into_boxed_slice()), |
| 103 | + ) |
| 104 | + .expect("validation pipeline error"); |
| 105 | + |
| 106 | + println!(" resolution: {:?}", result2.resolution.kind); |
| 107 | + println!(" trust: {:?}", result2.trust.kind); |
| 108 | + println!(" signature: {:?}", result2.signature.kind); |
| 109 | + println!(" overall: {:?}", result2.overall.kind); |
| 110 | + |
| 111 | + // Print failures if any |
| 112 | + let stages = [ |
| 113 | + ("resolution", &result2.resolution), |
| 114 | + ("trust", &result2.trust), |
| 115 | + ("signature", &result2.signature), |
| 116 | + ("overall", &result2.overall), |
| 117 | + ]; |
| 118 | + for (name, stage) in stages { |
| 119 | + if !stage.failures.is_empty() { |
| 120 | + println!("\n {} failures:", name); |
| 121 | + for f in &stage.failures { |
| 122 | + println!(" - {}", f.message); |
| 123 | + } |
| 124 | + } |
| 125 | + } |
| 126 | + |
| 127 | + println!("\n=== Example completed! ==="); |
| 128 | +} |
| 129 | + |
| 130 | +/// Build a minimal COSE_Sign1 byte sequence with an embedded x5chain header. |
| 131 | +/// |
| 132 | +/// The message structure is: |
| 133 | +/// [protected_headers_bstr, unprotected_headers_map, payload_bstr, signature_bstr] |
| 134 | +/// |
| 135 | +/// Protected headers contain: |
| 136 | +/// { 1 (alg): -7 (ES256), 33 (x5chain): bstr(cert_der) } |
| 137 | +fn build_cose_sign1_with_x5chain(leaf_der: &[u8], payload: &[u8]) -> Vec<u8> { |
| 138 | + let p = EverParseCborProvider; |
| 139 | + let mut enc = p.encoder(); |
| 140 | + |
| 141 | + // COSE_Sign1 is a 4-element CBOR array |
| 142 | + enc.encode_array(4).unwrap(); |
| 143 | + |
| 144 | + // Protected headers: CBOR bstr wrapping a CBOR map |
| 145 | + let mut hdr_enc = p.encoder(); |
| 146 | + hdr_enc.encode_map(2).unwrap(); |
| 147 | + hdr_enc.encode_i64(1).unwrap(); // label: alg |
| 148 | + hdr_enc.encode_i64(-7).unwrap(); // value: ES256 |
| 149 | + hdr_enc.encode_i64(33).unwrap(); // label: x5chain |
| 150 | + hdr_enc.encode_bstr(leaf_der).unwrap(); |
| 151 | + let protected_bytes = hdr_enc.into_bytes(); |
| 152 | + enc.encode_bstr(&protected_bytes).unwrap(); |
| 153 | + |
| 154 | + // Unprotected headers: empty map |
| 155 | + enc.encode_map(0).unwrap(); |
| 156 | + |
| 157 | + // Payload: embedded byte string |
| 158 | + enc.encode_bstr(payload).unwrap(); |
| 159 | + |
| 160 | + // Signature: dummy (not cryptographically valid) |
| 161 | + enc.encode_bstr(b"example-signature-placeholder").unwrap(); |
| 162 | + |
| 163 | + enc.into_bytes() |
| 164 | +} |
0 commit comments