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

opentitanlib: accept ECDSA signatures as ASN.1 blobs #26010

Merged
merged 1 commit into from
Jan 30, 2025
Merged
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
48 changes: 45 additions & 3 deletions sw/host/opentitanlib/src/crypto/ecdsa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -97,7 +99,7 @@ impl Default for EcdsaRawSignature {
impl TryFrom<&[u8]> for EcdsaRawSignature {
type Error = Error;
fn try_from(value: &[u8]) -> std::result::Result<Self, Self::Error> {
if value.len() != 64 {
if value.len() != Self::SIZE {
return Err(Error::InvalidSignature(anyhow!(
"bad length: {}",
value.len()
Expand All @@ -109,6 +111,8 @@ impl TryFrom<&[u8]> for EcdsaRawSignature {
}

impl EcdsaRawSignature {
const SIZE: usize = 64;

pub fn read(src: &mut impl Read) -> Result<Self> {
let mut sig = Self::default();
src.read_exact(&mut sig.r)?;
Expand All @@ -119,7 +123,23 @@ impl EcdsaRawSignature {
pub fn read_from_file(path: &Path) -> Result<EcdsaRawSignature> {
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 = Self::SIZE as u64;
if file_size == raw_size {
cfrantz marked this conversation as resolved.
Show resolved Hide resolved
// 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::<u8>::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<()> {
Expand All @@ -145,6 +165,28 @@ impl EcdsaRawSignature {
pub fn is_empty(&self) -> bool {
self.r.iter().all(|&v| v == 0) && self.s.iter().all(|&v| v == 0)
}

fn from_der(data: &[u8]) -> Result<EcdsaRawSignature> {
let sig = Signature::<NistP256>::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 has to be 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 of exactly 32 bytes format expected by the firmware.
let mut r: Vec<u8> = sig.r().to_bytes().to_vec();
let mut s: Vec<u8> = sig.s().to_bytes().to_vec();

// Convert to little endian format.
r.reverse();
s.reverse();

r.resize(32, 0u8);
s.resize(32, 0u8);

Ok(EcdsaRawSignature { r, s })
}
}

impl EcdsaPublicKey {
Expand Down
Loading