From 14a58fcdd196b988550f5f8ee877dfad3f8cd0f0 Mon Sep 17 00:00:00 2001 From: Vadim Bendebury Date: Thu, 23 Jan 2025 16:32:19 -0800 Subject: [PATCH] opentitanlib: accept ECDSA signatures as ASN.1 blobs When processing ECDSA signatures consider the input file to be either a raw signature, with 32 bytes R and S values in little endian format concatenated into a single 64 bytes blob, or an ASN.1 encoded ECDSA signature, which is a sequence of two big endian big number values, which could be anywhere from one to 33 bytes long. In case input is in ASN .1 format Convert the values into little endian and truncate/expand as necessary to generated the internal representation of R and S of exactly 32 bytes each. Tested by attaching the Crypto4a HSM produced ECDSA signature to an owner binary image and verifying the signature, both using opentitantool. Signed-off-by: Vadim Bendebury --- sw/host/opentitanlib/src/crypto/ecdsa.rs | 61 +++++++++++++++++++++++- 1 file changed, 59 insertions(+), 2 deletions(-) diff --git a/sw/host/opentitanlib/src/crypto/ecdsa.rs b/sw/host/opentitanlib/src/crypto/ecdsa.rs index 358c9e66dbbbd..88ab62448fccd 100644 --- a/sw/host/opentitanlib/src/crypto/ecdsa.rs +++ b/sw/host/opentitanlib/src/crypto/ecdsa.rs @@ -6,7 +6,9 @@ use anyhow::{anyhow, ensure, Context, Result}; use ecdsa::elliptic_curve::pkcs8::{DecodePrivateKey, EncodePrivateKey}; use ecdsa::elliptic_curve::pkcs8::{DecodePublicKey, EncodePublicKey}; use ecdsa::signature::hazmat::PrehashVerifier; -use p256::ecdsa::{Signature, SigningKey, VerifyingKey}; +use ecdsa::Signature; +use p256::ecdsa::{SigningKey, VerifyingKey}; +use p256::NistP256; use rand::rngs::OsRng; use serde::{Deserialize, Serialize}; use serde_annotate::Annotate; @@ -120,7 +122,23 @@ impl EcdsaRawSignature { pub fn read_from_file(path: &Path) -> Result { let mut file = File::open(path).with_context(|| format!("Failed to read file: {path:?}"))?; - EcdsaRawSignature::read(&mut file) + let file_size = std::fs::metadata(path) + .with_context(|| format!("Failed to get metadata for {path:?}"))? + .len(); + + let raw_size = EcdsaRawSignature::size() as u64; + if file_size == raw_size { + // This must be a raw signature, just read it as is. + EcdsaRawSignature::read(&mut file) + } else { + // Let's try interpreting the file as ASN.1 DER. + let mut data = Vec::::new(); + + file.read_to_end(&mut data) + .with_context(|| "Failed to read {path:?}")?; + + EcdsaRawSignature::from_der(&data).with_context(|| format!("Failed parsing {path:?}")) + } } pub fn write(&self, dest: &mut impl Write) -> Result<()> { @@ -146,6 +164,45 @@ impl EcdsaRawSignature { pub fn is_empty(&self) -> bool { self.r.iter().all(|&v| v == 0) && self.s.iter().all(|&v| v == 0) } + + fn size() -> usize { + let sig: EcdsaRawSignature = EcdsaRawSignature::default(); + + sig.r.len() + sig.s.len() + } + + fn from_der(data: &[u8]) -> Result { + let sig = Signature::::from_der(data).with_context(|| "Failed to parse DER")?; + + // R and S are integers in big endian format. The size of the numbers is + // not fixed, it could be 33 bytes in case the value has the top byte + // top bit set, and a leading zero is added to keep the value positive + // (50% chance), or it could be shorter than 32 bytes (1/256 chance). + // Need to get around these issues and convert into little endian + // expected format by the firmware. + let mut raw_r: Vec = sig.r().to_bytes().to_vec(); + let mut raw_s: Vec = sig.s().to_bytes().to_vec(); + + // Convert to little endian format. + raw_r.reverse(); + raw_s.reverse(); + + let mut r = [0u8; 32]; + let mut s = [0u8; 32]; + + // Copy only what's necessary and ignore the leading zeros in case of + // 33 byte values. + let copy_size = r.len().min(raw_r.len()); + r[0..copy_size].copy_from_slice(&raw_r[0..copy_size]); + + let copy_size = s.len().min(raw_s.len()); + s[0..copy_size].copy_from_slice(&raw_s[0..copy_size]); + + Ok(EcdsaRawSignature { + r: r.to_vec(), + s: s.to_vec(), + }) + } } impl EcdsaPublicKey {