use matter_cert::PublicKey;
use p256::ecdsa::{signature::Signer as EcdsaSigner, Signature, SigningKey};
use p256::pkcs8::DecodePrivateKey;
use crate::error::{Error, Result};
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum SignerError {
#[error("signer hardware unavailable")]
Unavailable,
#[error("signer rejected the operation: {0}")]
Rejected(&'static str),
#[error("internal signer error")]
Internal,
}
pub trait CaseSigner: Send + Sync + std::fmt::Debug {
fn sign_p256_sha256(&self, message: &[u8]) -> std::result::Result<[u8; 64], SignerError>;
fn public_key(&self) -> &PublicKey;
}
use ring::rand::SystemRandom;
use ring::signature::{EcdsaKeyPair, ECDSA_P256_SHA256_FIXED_SIGNING};
pub struct RingSigner {
signing_key: SigningKey,
public_key: PublicKey,
}
impl std::fmt::Debug for RingSigner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RingSigner")
.field("public_key", &self.public_key)
.field("signing_key", &"<p256::ecdsa::SigningKey>")
.finish_non_exhaustive()
}
}
impl RingSigner {
pub fn from_pkcs8(pkcs8_bytes: &[u8]) -> Result<Self> {
let signing_key = SigningKey::from_pkcs8_der(pkcs8_bytes)
.map_err(|_| Error::SigningFailed(SignerError::Internal))?;
let verifying_key = signing_key.verifying_key();
let encoded = verifying_key.to_encoded_point(false); let encoded_bytes = encoded.as_bytes();
if encoded_bytes.len() != 65 {
return Err(Error::SigningFailed(SignerError::Internal));
}
let mut pk_bytes = [0u8; 65];
pk_bytes.copy_from_slice(encoded_bytes);
let public_key =
PublicKey::new(pk_bytes).map_err(|_| Error::SigningFailed(SignerError::Internal))?;
Ok(Self {
signing_key,
public_key,
})
}
pub fn generate() -> Result<(Self, Vec<u8>)> {
let rng = SystemRandom::new();
let pkcs8 = EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_FIXED_SIGNING, &rng)
.map_err(|_| Error::SigningFailed(SignerError::Internal))?;
let pkcs8_vec = pkcs8.as_ref().to_vec();
let signer = Self::from_pkcs8(&pkcs8_vec)?;
Ok((signer, pkcs8_vec))
}
}
impl CaseSigner for RingSigner {
fn sign_p256_sha256(&self, message: &[u8]) -> std::result::Result<[u8; 64], SignerError> {
let sig: Signature = self.signing_key.sign(message);
let sig = sig.normalize_s().unwrap_or(sig);
Ok(sig.to_bytes().into())
}
fn public_key(&self) -> &PublicKey {
&self.public_key
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
#[test]
fn generate_and_round_trip_through_pkcs8() {
let (signer1, pkcs8) = RingSigner::generate().unwrap();
let signer2 = RingSigner::from_pkcs8(&pkcs8).unwrap();
assert_eq!(
signer1.public_key().as_bytes(),
signer2.public_key().as_bytes()
);
}
#[test]
fn signing_produces_64_byte_signature() {
let (signer, _) = RingSigner::generate().unwrap();
let sig = signer.sign_p256_sha256(b"hello world").unwrap();
assert_eq!(sig.len(), 64);
}
#[test]
fn signing_same_message_twice_is_deterministic() {
let (signer, _) = RingSigner::generate().unwrap();
let a = signer.sign_p256_sha256(b"same").unwrap();
let b = signer.sign_p256_sha256(b"same").unwrap();
assert_eq!(a, b, "RFC 6979 signing must be deterministic");
}
#[test]
fn signing_produces_verifiable_signature() {
use ring::signature::{UnparsedPublicKey, ECDSA_P256_SHA256_FIXED};
let (signer, _) = RingSigner::generate().unwrap();
let msg = b"verify me";
let sig = signer.sign_p256_sha256(msg).unwrap();
let pk = UnparsedPublicKey::new(&ECDSA_P256_SHA256_FIXED, signer.public_key().as_bytes());
pk.verify(msg, &sig).expect("signature must verify");
}
#[test]
fn pkcs8_from_invalid_bytes_returns_error() {
let err = RingSigner::from_pkcs8(b"garbage").unwrap_err();
assert!(matches!(err, Error::SigningFailed(SignerError::Internal)));
}
#[test]
fn signing_produces_low_s_signature() {
let n_hi = 0xFFFF_FFFF_0000_0000_FFFF_FFFF_FFFF_FFFFu128;
let n_lo = 0xBCE6_FAAD_A717_9E84_F3B9_CAC2_FC63_2551u128;
let (signer, _) = RingSigner::generate().unwrap();
for msg in &[b"alpha" as &[u8], b"beta", b"gamma", b"delta", b"epsilon"] {
let sig = signer.sign_p256_sha256(msg).unwrap();
let s_hi = u128::from_be_bytes(sig[32..48].try_into().unwrap());
let s_lo = u128::from_be_bytes(sig[48..64].try_into().unwrap());
let half_hi = (n_hi >> 1) | ((n_lo >> 127) << 127); let half_lo = n_lo >> 1;
let is_low_s = s_hi < half_hi || (s_hi == half_hi && s_lo <= half_lo);
assert!(
is_low_s,
"signature s must be ≤ n/2 (low-s) for message {msg:?}: s = {s_hi:016x}{s_lo:016x}",
);
}
}
}