#![cfg(feature = "algo-p256")]
use p256::ecdsa::signature::Signer as _;
use p256::ecdsa::signature::Verifier as _;
use p256::ecdsa::{Signature, SigningKey, VerifyingKey};
use p256::pkcs8::DecodePrivateKey;
use crate::{Algorithm, Error};
pub const KEYID_PREFIX: &str = "p256:";
#[derive(Debug, Clone)]
pub struct P256Signer {
sk: SigningKey,
}
impl P256Signer {
pub fn new(mut secret: [u8; 32]) -> Result<Self, Error> {
use zeroize::Zeroize;
let result = SigningKey::from_bytes(&secret.into()).map_err(|_| Error::P256KeyInvalid);
secret.zeroize();
let sk = result?;
Ok(Self { sk })
}
pub fn from_seed_zeroizing(secret: &zeroize::Zeroizing<[u8; 32]>) -> Result<Self, Error> {
use zeroize::Zeroize;
let mut tmp = [0u8; 32];
tmp.copy_from_slice(secret.as_slice());
let result = SigningKey::from_bytes(&tmp.into()).map_err(|_| Error::P256KeyInvalid);
tmp.zeroize();
let sk = result?;
Ok(Self { sk })
}
pub fn from_der_pkcs8(bytes: &[u8]) -> Result<Self, Error> {
let sk = SigningKey::from_pkcs8_der(bytes).map_err(|_| Error::P256KeyInvalid)?;
Ok(Self { sk })
}
#[must_use]
pub fn public_key_sec1(&self) -> Vec<u8> {
self.sk
.verifying_key()
.to_sec1_point(true)
.as_bytes()
.to_vec()
}
#[must_use]
pub fn public_key_sec1_uncompressed(&self) -> Vec<u8> {
self.sk
.verifying_key()
.to_sec1_point(false)
.as_bytes()
.to_vec()
}
#[must_use]
pub fn algorithm(&self) -> Algorithm {
Algorithm::P256
}
#[must_use]
pub fn keyid(&self) -> String {
let hex = to_hex(&self.public_key_sec1());
format!("{KEYID_PREFIX}{hex}")
}
pub fn sign_dsse(&self, pae: &[u8]) -> Result<Vec<u8>, Error> {
let sig: Signature = self.sk.sign(pae);
let sig = sig.normalize_s();
Ok(sig.to_bytes().to_vec())
}
}
impl crate::signer::Signer for P256Signer {
fn algorithm(&self) -> Algorithm {
Algorithm::P256
}
fn keyid(&self) -> Result<String, Error> {
Ok(Self::keyid(self))
}
fn sign(&mut self, pae: &[u8]) -> Result<Vec<u8>, Error> {
Self::sign_dsse(self, pae)
}
}
pub fn verify_p256(pubkey_sec1: &[u8], msg: &[u8], sig_compact: &[u8]) -> Result<(), Error> {
let vk = VerifyingKey::from_sec1_bytes(pubkey_sec1).map_err(|_| Error::P256KeyInvalid)?;
if sig_compact.len() != 64 {
return Err(Error::P256SignatureInvalid);
}
let mut arr = [0u8; 64];
arr.copy_from_slice(sig_compact);
let sig = Signature::from_bytes(&arr.into()).map_err(|_| Error::P256SignatureInvalid)?;
if sig.normalize_s() != sig {
return Err(Error::P256SignatureInvalid);
}
vk.verify(msg, &sig).map_err(|_| Error::P256VerifyFailed)
}
fn to_hex(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut out = String::with_capacity(bytes.len() * 2);
for b in bytes {
out.push(HEX[(b >> 4) as usize] as char);
out.push(HEX[(b & 0x0F) as usize] as char);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
const TEST_SECRET: [u8; 32] = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e,
0x1f, 0x20,
];
#[test]
fn sign_verify_roundtrip_p256() {
let signer = P256Signer::new(TEST_SECRET).unwrap();
let pae = b"DSSEv1 28 application/vnd.in-toto+json 2 {}";
let sig = signer.sign_dsse(pae).unwrap();
assert_eq!(sig.len(), 64, "compact ECDSA sig is 64 bytes");
verify_p256(&signer.public_key_sec1(), pae, &sig).expect("roundtrip verify");
}
#[test]
fn verify_rejects_tampered_signature() {
let signer = P256Signer::new(TEST_SECRET).unwrap();
let pae = b"DSSEv1 4 test 2 hi";
let mut sig = signer.sign_dsse(pae).unwrap();
sig[40] ^= 0x01;
assert!(verify_p256(&signer.public_key_sec1(), pae, &sig).is_err());
}
#[test]
fn verify_rejects_wrong_pubkey() {
let signer = P256Signer::new(TEST_SECRET).unwrap();
let other = P256Signer::new([0x77; 32]).unwrap();
let pae = b"DSSEv1 4 test 2 hi";
let sig = signer.sign_dsse(pae).unwrap();
assert!(verify_p256(&other.public_key_sec1(), pae, &sig).is_err());
}
#[test]
fn keyid_has_p256_prefix() {
let signer = P256Signer::new(TEST_SECRET).unwrap();
let kid = signer.keyid();
assert!(
kid.starts_with(KEYID_PREFIX),
"keyid {kid} lacks p256: prefix"
);
assert_eq!(kid.len(), KEYID_PREFIX.len() + 66);
let hex = &kid[KEYID_PREFIX.len()..];
assert!(
hex.bytes()
.all(|c| c.is_ascii_digit() || (b'a'..=b'f').contains(&c)),
"keyid hex is lowercase-only"
);
}
#[test]
fn pubkey_round_trip_compressed_and_uncompressed() {
let signer = P256Signer::new(TEST_SECRET).unwrap();
let pae = b"DSSEv1 4 test 2 hi";
let sig = signer.sign_dsse(pae).unwrap();
let compressed = signer.public_key_sec1();
assert_eq!(compressed.len(), 33, "SEC1 compressed is 33 bytes");
assert!(
compressed[0] == 0x02 || compressed[0] == 0x03,
"compressed prefix is 0x02/0x03"
);
verify_p256(&compressed, pae, &sig).expect("verify with compressed pubkey");
let uncompressed = signer.public_key_sec1_uncompressed();
assert_eq!(uncompressed.len(), 65, "SEC1 uncompressed is 65 bytes");
assert_eq!(uncompressed[0], 0x04, "uncompressed prefix is 0x04");
verify_p256(&uncompressed, pae, &sig).expect("verify with uncompressed pubkey");
}
#[test]
fn determinism_rfc6979() {
let signer = P256Signer::new(TEST_SECRET).unwrap();
let pae = b"the quick brown fox";
let a = signer.sign_dsse(pae).unwrap();
let b = signer.sign_dsse(pae).unwrap();
assert_eq!(a, b, "RFC 6979: same key + same msg ⇒ same signature");
}
#[test]
fn verify_rejects_high_s_signature() {
let n: [u8; 32] = [
0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xBC, 0xE6, 0xFA, 0xAD, 0xA7, 0x17, 0x9E, 0x84, 0xF3, 0xB9, 0xCA, 0xC2,
0xFC, 0x63, 0x25, 0x51,
];
let signer = P256Signer::new(TEST_SECRET).unwrap();
let pae = b"low-s guard";
let sig = signer.sign_dsse(pae).unwrap();
let s_bytes: [u8; 32] = sig[32..].try_into().unwrap();
assert!(s_bytes[0] <= 0x7F, "signer emitted non-low-S");
let mut s_prime = [0u8; 32];
let mut borrow: u16 = 0;
for i in (0..32).rev() {
let lhs = u16::from(n[i]);
let rhs = u16::from(s_bytes[i]) + borrow;
if lhs >= rhs {
s_prime[i] = u8::try_from(lhs - rhs).expect("difference fits in u8");
borrow = 0;
} else {
s_prime[i] = u8::try_from((lhs + 256) - rhs).expect("difference fits in u8");
borrow = 1;
}
}
assert!(s_prime[0] > 0x7F, "s' should be high-S");
let mut high = sig.clone();
high[32..].copy_from_slice(&s_prime);
let err = verify_p256(&signer.public_key_sec1(), pae, &high).unwrap_err();
assert!(
matches!(err, Error::P256SignatureInvalid),
"got {err:?}, want P256SignatureInvalid"
);
verify_p256(&signer.public_key_sec1(), pae, &sig).unwrap();
}
#[test]
fn pkcs8_round_trip() {
use p256::SecretKey;
use p256::pkcs8::EncodePrivateKey;
let sk = SecretKey::from_bytes(&TEST_SECRET.into()).unwrap();
let der = sk.to_pkcs8_der().unwrap();
let reloaded = P256Signer::from_der_pkcs8(der.as_bytes()).unwrap();
let direct = P256Signer::new(TEST_SECRET).unwrap();
assert_eq!(direct.public_key_sec1(), reloaded.public_key_sec1());
}
#[test]
fn rejects_zero_scalar() {
let err = P256Signer::new([0u8; 32]).unwrap_err();
assert!(matches!(err, Error::P256KeyInvalid));
}
#[test]
fn verify_rejects_malformed_pubkey() {
let pae = b"hi";
let sig = vec![0u8; 64];
assert!(matches!(
verify_p256(&[0xFF], pae, &sig),
Err(Error::P256KeyInvalid)
));
}
#[test]
fn verify_rejects_wrong_signature_length() {
let signer = P256Signer::new(TEST_SECRET).unwrap();
assert!(matches!(
verify_p256(&signer.public_key_sec1(), b"hi", &[0u8; 63]),
Err(Error::P256SignatureInvalid)
));
assert!(matches!(
verify_p256(&signer.public_key_sec1(), b"hi", &[0u8; 65]),
Err(Error::P256SignatureInvalid)
));
}
}