Skip to content
Open
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
4 changes: 2 additions & 2 deletions src/certs/snp/mod.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0

/// ECDSA signatures.
pub mod ecdsa;
/// Attestation report signature parsing and verification helpers.
pub mod signature;

#[cfg(any(feature = "openssl", feature = "crypto_nossl"))]
/// Certificate Authority (CA) certificates.
Expand Down
95 changes: 48 additions & 47 deletions src/certs/snp/ecdsa/mod.rs → src/certs/snp/signature/ecdsa.rs
Original file line number Diff line number Diff line change
@@ -1,26 +1,19 @@
// SPDX-License-Identifier: Apache-2.0

#[cfg(any(feature = "openssl", feature = "crypto_nossl"))]
use super::*;

use crate::{
parser::{ByteParser, Decoder, Encoder},
util::{
hexline::HexLine,
parser_helper::{ReadExt, WriteExt},
},
};
use crate::{parser::ByteParser, util::hexline::HexLine};

use std::io::{Read, Result, Write};

#[cfg(feature = "openssl")]
use crate::certs::snp::{AsLeBytes, FromLe};
use crate::certs::snp::{AsLeBytes, Certificate, FromLe};

#[cfg(any(feature = "openssl", feature = "crypto_nossl"))]
use std::convert::TryFrom;

#[cfg(feature = "openssl")]
use openssl::{bn, ecdsa};
use openssl::{bn, ecdsa, ecdsa::EcdsaSig, sha::Sha384};

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -68,6 +61,8 @@ impl Decoder<()> for Signature {
fn decode(reader: &mut impl Read, _: ()) -> Result<Self> {
let r = reader.read_bytes()?;
let s = reader.read_bytes()?;
// Firmware signature field is 0x200 bytes; after r and s, the remaining bytes are reserved/padding.
reader.skip_bytes::<368>()?;
Comment thread
DGonzalezVillal marked this conversation as resolved.
Ok(Self { r, s })
}
}
Expand Down Expand Up @@ -129,16 +124,6 @@ impl From<ecdsa::EcdsaSig> for Signature {
}
}

#[cfg(feature = "openssl")]
impl TryFrom<&[u8]> for Signature {
type Error = Error;

#[inline]
fn try_from(value: &[u8]) -> Result<Self> {
Ok(ecdsa::EcdsaSig::from_der(value)?.into())
}
}

#[cfg(feature = "openssl")]
impl TryFrom<&Signature> for ecdsa::EcdsaSig {
type Error = Error;
Expand Down Expand Up @@ -166,23 +151,57 @@ impl TryFrom<&Signature> for p384::ecdsa::Signature {
GenericArray::clone_from_slice(&s_big_endian),
)
.map_err(|e| {
Error::new(
ErrorKind::Other,
format!("failed to deserialize signature from scalars: {e:?}"),
)
Error::other(format!(
"failed to deserialize signature from scalars: {e:?}"
))
})
}
}

#[cfg(feature = "openssl")]
impl TryFrom<&Signature> for Vec<u8> {
type Error = Error;
/// Verify ECDSA signature on attestation report using VEK certificate
pub fn verify_ecdsa_signature(body: &[u8], signature: &[u8], vek: &Certificate) -> Result<()> {
let sev_sig = Signature::from_bytes(signature)?;
Comment thread
larrydewey marked this conversation as resolved.

#[inline]
fn try_from(value: &Signature) -> Result<Self> {
Ok(ecdsa::EcdsaSig::try_from(value)?.to_der()?)
let sig: EcdsaSig = EcdsaSig::try_from(&sev_sig)?;

let mut hasher = Sha384::new();
hasher.update(body);
let base_digest = hasher.finish();

let ec = vek.public_key()?.ec_key()?;
let signed = sig.verify(&base_digest, &ec)?;
match signed {
true => Ok(()),
false => Err(Error::other("VEK does not sign the attestation report")),
}
}

#[cfg(feature = "crypto_nossl")]
/// Verify ECDSA signature on attestation report using VEK certificate
pub fn verify_ecdsa_signature(body: &[u8], signature: &[u8], vek: &Certificate) -> Result<()> {
let sev_sig = Signature::from_bytes(signature)?;

let sig = p384::ecdsa::Signature::try_from(&sev_sig).map_err(|e| {
std::io::Error::other(format!(
"failed to generate signature from raw bytes: {e:?}"
))
})?;

use sha2::Digest;
let base_digest = sha2::Sha384::new_with_prefix(body);
let verifying_key =
p384::ecdsa::VerifyingKey::from_sec1_bytes(vek.public_key_sec1()).map_err(|e| {
std::io::Error::other(format!(
"failed to deserialize public key from sec1 bytes: {e:?}"
))
})?;
use p384::ecdsa::signature::DigestVerifier;
verifying_key.verify_digest(base_digest, &sig).map_err(|e| {
std::io::Error::other(format!("VEK does not sign the attestation report: {e:?}"))
})
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -261,31 +280,13 @@ mod tests {
assert_ne!(sig.s(), &[0u8; 72]);
}

#[test]
fn test_try_from_bytes() {
let r = BigNum::from_dec_str("123").unwrap();
let s = BigNum::from_dec_str("456").unwrap();
let ecdsa_sig = ecdsa::EcdsaSig::from_private_components(r, s).unwrap();
let der = ecdsa_sig.to_der().unwrap();
let sig = Signature::try_from(der.as_slice()).unwrap();
assert_ne!(sig.r(), &[0u8; 72]);
assert_ne!(sig.s(), &[0u8; 72]);
}

#[test]
fn test_try_into_ecdsa_sig() {
let sig: Signature = Default::default();
let ecdsa_sig: ecdsa::EcdsaSig = (&sig).try_into().unwrap();
assert_eq!(ecdsa_sig.r().to_vec(), vec![]);
assert_eq!(ecdsa_sig.s().to_vec(), vec![]);
}

#[test]
fn test_try_into_vec() {
let sig: Signature = Default::default();
let der: Vec<u8> = (&sig).try_into().unwrap();
assert!(!der.is_empty());
}
}

#[cfg(feature = "crypto_nossl")]
Expand Down
99 changes: 99 additions & 0 deletions src/certs/snp/signature/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: Apache-2.0

use std::{
convert::TryFrom,
fmt::Display,
io::{Read, Write},
};

#[cfg(any(feature = "openssl", feature = "crypto_nossl"))]
use std::io::Error;

use crate::{
parser::{Decoder, Encoder},
util::parser_helper::{ReadExt, WriteExt},
};

#[cfg(any(feature = "openssl", feature = "crypto_nossl"))]
use crate::certs::snp::Certificate;

/// ECDSA algorithm signature
pub mod ecdsa;

/// Signature algorithms that firmware may use to sign the SEV-SNP attestation report.
///
/// The algorithm identifier is encoded in the report body and is therefore derived
/// from **untrusted bytes**. It MUST NOT be treated as a trust signal on its own.
/// Authenticity is only established by successfully verifying the report signature.
///
/// This enum is intentionally explicit: unknown values are rejected during decoding.
#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum SignatureAlgorithm {
/// ECDSA with SECP384R1 curve
EcdsaSecp384r1 = 1,
}

/// Creates a [`SignatureAlgorithm`] from the numeric algorithm identifier encoded
/// in the report body.
///
/// # Errors
///
/// Returns `InvalidData` if `v` does not correspond to a supported algorithm.
impl TryFrom<u32> for SignatureAlgorithm {
type Error = std::io::Error;

fn try_from(v: u32) -> Result<Self, Self::Error> {
match v {
1 => Ok(SignatureAlgorithm::EcdsaSecp384r1),
v => Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Unsupported signature algorithm: {}", v),
)),
}
}
}

impl SignatureAlgorithm {
/// Verify the report signature over `body` using `vek` and this algorithm.
///
/// This validates the signature bytes against the signed body bytes; it does
/// not parse or validate any fields inside the body.
#[cfg(any(feature = "openssl", feature = "crypto_nossl"))]
pub fn verify(
Comment thread
DGonzalezVillal marked this conversation as resolved.
&self,
body: &[u8],
signature: &[u8],
vek: &Certificate,
) -> Result<(), std::io::Error> {
match self {
SignatureAlgorithm::EcdsaSecp384r1 => {
ecdsa::verify_ecdsa_signature(body, signature, vek)
}
}
}
}

impl Encoder<()> for SignatureAlgorithm {
fn encode(&self, writer: &mut impl Write, _: ()) -> Result<(), std::io::Error> {
match self {
SignatureAlgorithm::EcdsaSecp384r1 => writer.write_bytes(1u32, ())?,
};
Ok(())
}
}

impl Decoder<()> for SignatureAlgorithm {
fn decode(reader: &mut impl Read, _: ()) -> Result<Self, std::io::Error> {
let algo: u32 = reader.read_bytes()?;
Self::try_from(algo)
}
}

impl Display for SignatureAlgorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SignatureAlgorithm::EcdsaSecp384r1 => write!(f, "ECDSA with SECP384R1"),
}
}
}
Loading
Loading