Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new tool for poking around in debug cred certs #293

Merged
merged 1 commit into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ zeroize_derive = "1.4.2"
glob = "0.3.2"
rsa = "0.9.3"
sha2 = "0.10.8"
zerocopy = { version = "0.8.17", features = ["derive", "std", "zerocopy-derive"] }
214 changes: 214 additions & 0 deletions src/bin/dac-pubkey.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

use anyhow::{anyhow, Context, Result};
use clap::{Parser, Subcommand, ValueEnum};
use rsa::{
pkcs1::{EncodeRsaPublicKey, LineEnding},
pkcs1v15::{Signature, VerifyingKey},
pkcs8::EncodePublicKey,
sha2::{Digest, Sha256},
signature::DigestVerifier,
BigUint, RsaPublicKey,
};
use std::{
fs,
io::{self, Write},
path::PathBuf,
};
use zerocopy::{FromBytes, LittleEndian, U16, U32};

#[derive(Debug, FromBytes)]
#[repr(C)]
// This structure is described in UM11126 §51.7 and is specific to RSA 4k
// keys.
struct DebugCredentialCertificate {
// first 2 bytes of VERSION
pub version_major: U16<LittleEndian>,
// second 2 bytes of VERSION
pub version_minor: U16<LittleEndian>,
// SOC class specifier
pub soc_class_id: U32<LittleEndian>,
// SOC UUID (uniquely identifies an SOC intance)
pub uuid: [u8; 16],
// RoT metadata uniquely identifying the CAs that authorize debug and
// verified boot credentials
pub rotmeta: [[u8; 32]; 4],
// public part of RSA 4k key authorized by this debug credential cert to
// sign debug auth challenges
// NOTE: these are big endian byte streams
pub debug_modulus: [u8; 512],
pub debug_exponent: [u8; 4],
// SoC specific Credential Constraint
pub credential_constraint: U32<LittleEndian>,
pub vendor_usage: U32<LittleEndian>,
pub credential_beacon: U32<LittleEndian>,
// public part of RSA 4k key acting as a trust anchor on the LPC55 platform
// NOTE: these are big endian byte streams
pub rotk_modulus: [u8; 512],
pub rotk_exponent: [u8; 4],
// RSA-SSA PKCS#1 v1.5 signature
pub signature: [u8; 512],
}

#[derive(ValueEnum, Copy, Clone, Debug, Default)]
enum Format {
Pkcs1,
#[default]
Spki,
}

#[derive(ValueEnum, Copy, Clone, Debug, Default)]
enum Encoding {
Der,
#[default]
Pem,
}

#[derive(ValueEnum, Copy, Clone, Debug, Default)]
enum PublicKey {
#[default]
Dck,
Rotk,
}

#[derive(Subcommand, Debug)]
enum Command {
/// extract and transcode a public key from the DC
Pubkey {
/// The public key to extract
#[clap(default_value_t, long, value_enum)]
kind: PublicKey,

/// The encoding used to serialize the public key.
#[clap(default_value_t, long, value_enum)]
encoding: Encoding,

/// The format use to represent the public key.
#[clap(default_value_t, long, value_enum)]
format: Format,
},
Verify,
}

/// Extract and transcode the RSA public keys from an Lpc55 debug
/// credential certificate (DC).
#[derive(Parser, Debug)]
struct Args {
#[command(subcommand)]
command: Command,

/// Path to signed debug auth credential file
dc_file: PathBuf,
}

fn pubkey_out(
pub_key: &RsaPublicKey,
encoding: Encoding,
format: Format,
) -> Result<()> {
match encoding {
Encoding::Der => {
let der = match format {
Format::Pkcs1 => pub_key.to_pkcs1_der().context(
"Get DER encoded, PKCS#1 formatted RSA public key",
)?,
Format::Spki => pub_key.to_public_key_der().context(
"Get DER encoded, SPKI formatted RSA public key",
)?,
};
io::stdout()
.write_all(der.as_bytes())
.context("write encoded public key to stdout")
}
Encoding::Pem => {
let pem = match format {
Format::Pkcs1 => pub_key
.to_pkcs1_pem(LineEnding::default())
.context("Get PEM encoded PKCS#1 from RSA public key")?,
Format::Spki => pub_key
.to_public_key_pem(LineEnding::default())
.context("Get SPKI PEM from RSA public key")?,
};
io::stdout()
.write_all(pem.as_bytes())
.context("write encoded public key to stdout")
}
}
}

fn main() -> Result<()> {
let args = Args::parse();

let dc_bytes = fs::read(&args.dc_file).with_context(|| {
format!("Reading debug cert file: {}", args.dc_file.display())
})?;

let (dc, remain) =
match DebugCredentialCertificate::read_from_prefix(dc_bytes.as_slice())
{
Ok((dc, remain)) => (dc, remain),
Err(_) => {
return Err(anyhow!("Failed to parse debug auth credential"))
}
};

if !remain.is_empty() {
return Err(anyhow!(
"Failed to parse debug cert: {} bytes left over",
remain.len()
));
}

if !(dc.version_major == 1 && dc.version_minor == 1) {
return Err(anyhow!(
"Unsupported debug cert version: {}.{}",
dc.version_major,
dc.version_minor
));
}

match args.command {
Command::Pubkey {
kind,
encoding,
format,
} => {
let (n, e) = match kind {
PublicKey::Dck => (&dc.debug_modulus, &dc.debug_exponent),
PublicKey::Rotk => (&dc.rotk_modulus, &dc.rotk_exponent),
};

let pub_key = RsaPublicKey::new(
BigUint::from_bytes_be(n),
BigUint::from_bytes_be(e),
)
.context("Extracting RSA public key from debug cert")?;

pubkey_out(&pub_key, encoding, format)
}
Command::Verify => {
let sig = Signature::try_from(&dc.signature[..]).context(
"Extracting RSASSA-PKCS1-v1_5 signature from debug cert",
)?;

// reconstruct the sha256 digest of the message that was signed
let tbs_offset = dc_bytes.len() - dc.signature.len();
let mut digest = Sha256::new();
digest.update(&dc_bytes[..tbs_offset]);
let digest = digest;

let pub_key = RsaPublicKey::new(
BigUint::from_bytes_be(&dc.rotk_modulus),
BigUint::from_bytes_be(&dc.rotk_exponent),
)
.context("Extracting RSA public key for RoTK from debug cert")?;

let verifier = VerifyingKey::<Sha256>::new(pub_key);
verifier
.verify_digest(digest, &sig)
.context("Verifying signature over debug cert against RoTK")
}
}
}
Loading