|
| 1 | +// This Source Code Form is subject to the terms of the Mozilla Public |
| 2 | +// License, v. 2.0. If a copy of the MPL was not distributed with this |
| 3 | +// file, You can obtain one at https://mozilla.org/MPL/2.0/. |
| 4 | + |
| 5 | +use anyhow::{anyhow, Context, Result}; |
| 6 | +use clap::{Parser, Subcommand, ValueEnum}; |
| 7 | +use rsa::{ |
| 8 | + pkcs1::{EncodeRsaPublicKey, LineEnding}, |
| 9 | + pkcs1v15::{Signature, VerifyingKey}, |
| 10 | + pkcs8::EncodePublicKey, |
| 11 | + sha2::{Digest, Sha256}, |
| 12 | + signature::DigestVerifier, |
| 13 | + BigUint, RsaPublicKey, |
| 14 | +}; |
| 15 | +use std::{ |
| 16 | + fs, |
| 17 | + io::{self, Write}, |
| 18 | + path::PathBuf, |
| 19 | +}; |
| 20 | +use zerocopy::{FromBytes, LittleEndian, U16, U32}; |
| 21 | + |
| 22 | +#[derive(Debug, FromBytes)] |
| 23 | +#[repr(C)] |
| 24 | +// This structure is described in UM11126 §51.7 and is specific to RSA 4k |
| 25 | +// keys. |
| 26 | +struct DebugCredentialCertificate { |
| 27 | + // first 2 bytes of VERSION |
| 28 | + pub version_major: U16<LittleEndian>, |
| 29 | + // second 2 bytes of VERSION |
| 30 | + pub version_minor: U16<LittleEndian>, |
| 31 | + // SOC class specifier |
| 32 | + pub soc_class_id: U32<LittleEndian>, |
| 33 | + // SOC UUID (uniquely identifies an SOC intance) |
| 34 | + pub uuid: [u8; 16], |
| 35 | + // RoT metadata uniquely identifying the CAs that authorize debug and |
| 36 | + // verified boot credentials |
| 37 | + pub rotmeta: [[u8; 32]; 4], |
| 38 | + // public part of RSA 4k key authorized by this debug credential cert to |
| 39 | + // sign debug auth challenges |
| 40 | + // NOTE: these are big endian byte streams |
| 41 | + pub debug_modulus: [u8; 512], |
| 42 | + pub debug_exponent: [u8; 4], |
| 43 | + // SoC specific Credential Constraint |
| 44 | + pub credential_constraint: U32<LittleEndian>, |
| 45 | + pub vendor_usage: U32<LittleEndian>, |
| 46 | + pub credential_beacon: U32<LittleEndian>, |
| 47 | + // public part of RSA 4k key acting as a trust anchor on the LPC55 platform |
| 48 | + // NOTE: these are big endian byte streams |
| 49 | + pub rotk_modulus: [u8; 512], |
| 50 | + pub rotk_exponent: [u8; 4], |
| 51 | + // RSA-SSA PKCS#1 v1.5 signature |
| 52 | + pub signature: [u8; 512], |
| 53 | +} |
| 54 | + |
| 55 | +#[derive(ValueEnum, Copy, Clone, Debug, Default)] |
| 56 | +enum Format { |
| 57 | + Pkcs1, |
| 58 | + #[default] |
| 59 | + Spki, |
| 60 | +} |
| 61 | + |
| 62 | +#[derive(ValueEnum, Copy, Clone, Debug, Default)] |
| 63 | +enum Encoding { |
| 64 | + Der, |
| 65 | + #[default] |
| 66 | + Pem, |
| 67 | +} |
| 68 | + |
| 69 | +#[derive(ValueEnum, Copy, Clone, Debug, Default)] |
| 70 | +enum PublicKey { |
| 71 | + #[default] |
| 72 | + Dck, |
| 73 | + Rotk, |
| 74 | +} |
| 75 | + |
| 76 | +#[derive(Subcommand, Debug)] |
| 77 | +enum Command { |
| 78 | + /// extract and transcode a public key from the DC |
| 79 | + Pubkey { |
| 80 | + /// The public key to extract |
| 81 | + #[clap(default_value_t, long, value_enum)] |
| 82 | + kind: PublicKey, |
| 83 | + |
| 84 | + /// The encoding used to serialize the public key. |
| 85 | + #[clap(default_value_t, long, value_enum)] |
| 86 | + encoding: Encoding, |
| 87 | + |
| 88 | + /// The format use to represent the public key. |
| 89 | + #[clap(default_value_t, long, value_enum)] |
| 90 | + format: Format, |
| 91 | + }, |
| 92 | + Verify, |
| 93 | +} |
| 94 | + |
| 95 | +/// Extract and transcode the RSA public keys from an Lpc55 debug |
| 96 | +/// credential certificate (DC). |
| 97 | +#[derive(Parser, Debug)] |
| 98 | +struct Args { |
| 99 | + #[command(subcommand)] |
| 100 | + command: Command, |
| 101 | + |
| 102 | + /// Path to signed debug auth credential file |
| 103 | + dc_file: PathBuf, |
| 104 | +} |
| 105 | + |
| 106 | +fn pubkey_out( |
| 107 | + pub_key: &RsaPublicKey, |
| 108 | + encoding: Encoding, |
| 109 | + format: Format, |
| 110 | +) -> Result<()> { |
| 111 | + match encoding { |
| 112 | + Encoding::Der => { |
| 113 | + let der = match format { |
| 114 | + Format::Pkcs1 => pub_key.to_pkcs1_der().context( |
| 115 | + "Get DER encoded, PKCS#1 formatted RSA public key", |
| 116 | + )?, |
| 117 | + Format::Spki => pub_key.to_public_key_der().context( |
| 118 | + "Get DER encoded, SPKI formatted RSA public key", |
| 119 | + )?, |
| 120 | + }; |
| 121 | + io::stdout() |
| 122 | + .write_all(der.as_bytes()) |
| 123 | + .context("write encoded public key to stdout") |
| 124 | + } |
| 125 | + Encoding::Pem => { |
| 126 | + let pem = match format { |
| 127 | + Format::Pkcs1 => pub_key |
| 128 | + .to_pkcs1_pem(LineEnding::default()) |
| 129 | + .context("Get PEM encoded PKCS#1 from RSA public key")?, |
| 130 | + Format::Spki => pub_key |
| 131 | + .to_public_key_pem(LineEnding::default()) |
| 132 | + .context("Get SPKI PEM from RSA public key")?, |
| 133 | + }; |
| 134 | + io::stdout() |
| 135 | + .write_all(pem.as_bytes()) |
| 136 | + .context("write encoded public key to stdout") |
| 137 | + } |
| 138 | + } |
| 139 | +} |
| 140 | + |
| 141 | +fn main() -> Result<()> { |
| 142 | + let args = Args::parse(); |
| 143 | + |
| 144 | + let dc_bytes = fs::read(&args.dc_file).with_context(|| { |
| 145 | + format!("Reading debug cert file: {}", args.dc_file.display()) |
| 146 | + })?; |
| 147 | + |
| 148 | + let (dc, remain) = |
| 149 | + match DebugCredentialCertificate::read_from_prefix(dc_bytes.as_slice()) |
| 150 | + { |
| 151 | + Ok((dc, remain)) => (dc, remain), |
| 152 | + Err(_) => { |
| 153 | + return Err(anyhow!("Failed to parse debug auth credential")) |
| 154 | + } |
| 155 | + }; |
| 156 | + |
| 157 | + if !remain.is_empty() { |
| 158 | + return Err(anyhow!( |
| 159 | + "Failed to parse debug cert: {} bytes left over", |
| 160 | + remain.len() |
| 161 | + )); |
| 162 | + } |
| 163 | + |
| 164 | + if !(dc.version_major == 1 && dc.version_minor == 1) { |
| 165 | + return Err(anyhow!( |
| 166 | + "Unsupported debug cert version: {}.{}", |
| 167 | + dc.version_major, |
| 168 | + dc.version_minor |
| 169 | + )); |
| 170 | + } |
| 171 | + |
| 172 | + match args.command { |
| 173 | + Command::Pubkey { |
| 174 | + kind, |
| 175 | + encoding, |
| 176 | + format, |
| 177 | + } => { |
| 178 | + let (n, e) = match kind { |
| 179 | + PublicKey::Dck => (&dc.debug_modulus, &dc.debug_exponent), |
| 180 | + PublicKey::Rotk => (&dc.rotk_modulus, &dc.rotk_exponent), |
| 181 | + }; |
| 182 | + |
| 183 | + let pub_key = RsaPublicKey::new( |
| 184 | + BigUint::from_bytes_be(n), |
| 185 | + BigUint::from_bytes_be(e), |
| 186 | + ) |
| 187 | + .context("Extracting RSA public key from debug cert")?; |
| 188 | + |
| 189 | + pubkey_out(&pub_key, encoding, format) |
| 190 | + } |
| 191 | + Command::Verify => { |
| 192 | + let sig = Signature::try_from(&dc.signature[..]).context( |
| 193 | + "Extracting RSASSA-PKCS1-v1_5 signature from debug cert", |
| 194 | + )?; |
| 195 | + |
| 196 | + // reconstruct the sha256 digest of the message that was signed |
| 197 | + let tbs_offset = dc_bytes.len() - dc.signature.len(); |
| 198 | + let mut digest = Sha256::new(); |
| 199 | + digest.update(&dc_bytes[..tbs_offset]); |
| 200 | + let digest = digest; |
| 201 | + |
| 202 | + let pub_key = RsaPublicKey::new( |
| 203 | + BigUint::from_bytes_be(&dc.rotk_modulus), |
| 204 | + BigUint::from_bytes_be(&dc.rotk_exponent), |
| 205 | + ) |
| 206 | + .context("Extracting RSA public key for RoTK from debug cert")?; |
| 207 | + |
| 208 | + let verifier = VerifyingKey::<Sha256>::new(pub_key); |
| 209 | + verifier |
| 210 | + .verify_digest(digest, &sig) |
| 211 | + .context("Verifying signature over debug cert against RoTK") |
| 212 | + } |
| 213 | + } |
| 214 | +} |
0 commit comments