Skip to content

Commit 7d662c1

Browse files
committed
Add tool for poking around in debug cred certs
This tool currently does a two things: 1) extract the both the issuers public key (the RoTK) as well as the public part of the debug key (DK) from the debug cert (DC) 2) verify the signature over the DC This is the functionality required to link the DC to both the issuer / trust anchor (RoTK) and the key being authorized to sign debug challenges (the DK).
1 parent 2040dc2 commit 7d662c1

File tree

3 files changed

+236
-0
lines changed

3 files changed

+236
-0
lines changed

Cargo.lock

Lines changed: 21 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ zeroize_derive = "1.4.2"
4040
glob = "0.3.2"
4141
rsa = "0.9.3"
4242
sha2 = "0.10.8"
43+
zerocopy = { version = "0.8.17", features = ["derive", "std", "zerocopy-derive"] }

src/bin/dac-pubkey.rs

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
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

Comments
 (0)