use alloc::vec::Vec;
use p256::ecdsa::signature::{Signer as _, Verifier as _};
use sha2::Digest as _;
use super::envelope::{Aes128Cbc, IV_LEN, KeyAgreement};
use super::pki::VerifyWith;
use super::{Hash, PncError, Sign, Suite, Verify};
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Sha2;
impl Hash for Sha2 {
fn digest(&self, suite: Suite, data: &[u8]) -> Vec<u8> {
match suite {
Suite::EcdsaSha256 => sha2::Sha256::digest(data).to_vec(),
Suite::EcdsaSha512 => sha2::Sha512::digest(data).to_vec(),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum SigningKey {
P256(p256::ecdsa::SigningKey),
P521(p521::ecdsa::SigningKey),
}
impl SigningKey {
pub fn p256(scalar: &[u8]) -> Result<Self, PncError> {
p256::ecdsa::SigningKey::from_slice(scalar)
.map(Self::P256)
.map_err(|_| PncError::Backend("not a valid P-256 private scalar"))
}
pub fn p521(scalar: &[u8]) -> Result<Self, PncError> {
p521::ecdsa::SigningKey::from_slice(scalar)
.map(Self::P521)
.map_err(|_| PncError::Backend("not a valid P-521 private scalar"))
}
#[must_use]
pub const fn suite(&self) -> Suite {
match self {
Self::P256(_) => Suite::EcdsaSha256,
Self::P521(_) => Suite::EcdsaSha512,
}
}
#[must_use]
pub fn verifying_key(&self) -> VerifyingKey {
match self {
Self::P256(k) => VerifyingKey::P256(*k.verifying_key()),
Self::P521(k) => VerifyingKey::P521(*k.verifying_key()),
}
}
}
impl Sign for SigningKey {
fn sign(&self, suite: Suite, data: &[u8]) -> Result<Vec<u8>, PncError> {
if suite != self.suite() {
return Err(PncError::Backend("the signing key is not on this suite's curve"));
}
Ok(match self {
Self::P256(k) => {
let sig: p256::ecdsa::Signature = k.sign(data);
sig.to_bytes().to_vec()
}
Self::P521(k) => {
let sig: p521::ecdsa::Signature = k.sign(data);
sig.to_bytes().to_vec()
}
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum VerifyingKey {
P256(p256::ecdsa::VerifyingKey),
P521(p521::ecdsa::VerifyingKey),
}
impl VerifyingKey {
pub fn p256_sec1(point: &[u8]) -> Result<Self, PncError> {
p256::ecdsa::VerifyingKey::from_sec1_bytes(point)
.map(Self::P256)
.map_err(|_| PncError::Backend("not a valid P-256 public point"))
}
pub fn p521_sec1(point: &[u8]) -> Result<Self, PncError> {
p521::ecdsa::VerifyingKey::from_sec1_bytes(point)
.map(Self::P521)
.map_err(|_| PncError::Backend("not a valid P-521 public point"))
}
#[must_use]
pub const fn suite(&self) -> Suite {
match self {
Self::P256(_) => Suite::EcdsaSha256,
Self::P521(_) => Suite::EcdsaSha512,
}
}
}
impl Verify for VerifyingKey {
fn verify(&self, suite: Suite, data: &[u8], signature: &[u8]) -> Result<(), PncError> {
if suite != self.suite() {
return Err(PncError::Backend("the verifying key is not on this suite's curve"));
}
match self {
Self::P256(k) => {
let sig = p256::ecdsa::Signature::from_slice(signature)
.map_err(|_| PncError::BadSignature)?;
k.verify(data, &sig).map_err(|_| PncError::BadSignature)
}
Self::P521(k) => {
let sig = p521::ecdsa::Signature::from_slice(signature)
.map_err(|_| PncError::BadSignature)?;
k.verify(data, &sig).map_err(|_| PncError::BadSignature)
}
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Backend;
impl VerifyWith for Backend {
fn verify_with(
&self,
suite: Suite,
public_key: &[u8],
data: &[u8],
signature: &[u8],
) -> Result<(), PncError> {
let key = match suite {
Suite::EcdsaSha256 => VerifyingKey::p256_sec1(public_key)?,
Suite::EcdsaSha512 => VerifyingKey::p521_sec1(public_key)?,
};
key.verify(suite, data, signature)
}
}
#[derive(Debug, Clone)]
pub struct AgreementKey(p256::SecretKey);
impl AgreementKey {
pub fn new(scalar: &[u8]) -> Result<Self, PncError> {
p256::SecretKey::from_slice(scalar)
.map(Self)
.map_err(|_| PncError::Backend("not a valid P-256 private scalar"))
}
#[must_use]
pub fn public_key(&self) -> Vec<u8> {
use p256::elliptic_curve::sec1::ToSec1Point as _;
self.0.public_key().to_sec1_point(false).as_bytes().to_vec()
}
}
impl KeyAgreement for AgreementKey {
fn agree(&self, peer_public_key: &[u8]) -> Result<Vec<u8>, PncError> {
use p256::elliptic_curve::sec1::FromSec1Point as _;
let point = p256::Sec1Point::from_bytes(peer_public_key)
.map_err(|_| PncError::Backend("not a valid P-256 public point"))?;
let affine = Option::<p256::AffinePoint>::from(p256::AffinePoint::from_sec1_point(&point))
.ok_or(PncError::Backend("the public point is not on the curve"))?;
let shared = p256::ecdh::diffie_hellman(self.0.to_nonzero_scalar(), affine);
Ok(shared.raw_secret_bytes().to_vec())
}
fn public_key_of(&self, scalar: &[u8]) -> Result<Vec<u8>, PncError> {
Ok(Self::new(scalar)?.public_key())
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Aes128;
impl Aes128Cbc for Aes128 {
fn decrypt(&self, key: &[u8; 16], iv: &[u8; IV_LEN], data: &[u8]) -> Result<Vec<u8>, PncError> {
use cbc::cipher::{BlockModeDecrypt as _, KeyIvInit as _};
if data.is_empty() || !data.len().is_multiple_of(16) {
return Err(PncError::Backend("the ciphertext is not a whole number of AES blocks"));
}
let mut out = data.to_vec();
cbc::Decryptor::<aes::Aes128>::new(key.into(), iv.into())
.decrypt_padded::<cbc::cipher::block_padding::NoPadding>(&mut out)
.map_err(|_| PncError::Backend("AES-CBC decryption failed"))?;
Ok(out)
}
fn encrypt(&self, key: &[u8; 16], iv: &[u8; IV_LEN], data: &[u8]) -> Result<Vec<u8>, PncError> {
use cbc::cipher::{BlockModeEncrypt as _, KeyIvInit as _};
if data.is_empty() || !data.len().is_multiple_of(16) {
return Err(PncError::Backend("the plaintext is not a whole number of AES blocks"));
}
let mut out = data.to_vec();
let len = out.len();
cbc::Encryptor::<aes::Aes128>::new(key.into(), iv.into())
.encrypt_padded::<cbc::cipher::block_padding::NoPadding>(&mut out, len)
.map_err(|_| PncError::Backend("AES-CBC encryption failed"))?;
Ok(out)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pnc::Signed;
fn keys() -> (SigningKey, SigningKey) {
let mut p521_scalar = [0x17u8; 66];
p521_scalar[0] = 0x00;
(SigningKey::p256(&[0x42; 32]).unwrap(), SigningKey::p521(&p521_scalar).unwrap())
}
#[test]
fn digest_lengths_match_the_suites() {
assert_eq!(Sha2.digest(Suite::EcdsaSha256, b"x").len(), Suite::EcdsaSha256.digest_len());
assert_eq!(Sha2.digest(Suite::EcdsaSha512, b"x").len(), Suite::EcdsaSha512.digest_len());
}
#[test]
fn signatures_are_the_raw_r_s_pair() {
let (p256_key, p521_key) = keys();
assert_eq!(p256_key.sign(Suite::EcdsaSha256, b"data").unwrap().len(), 64);
assert_eq!(p521_key.sign(Suite::EcdsaSha512, b"data").unwrap().len(), 132);
}
#[test]
fn a_signature_verifies_against_its_own_key() {
for key in [keys().0, keys().1] {
let suite = key.suite();
let sig = key.sign(suite, b"data").unwrap();
key.verifying_key().verify(suite, b"data", &sig).unwrap();
assert_eq!(
key.verifying_key().verify(suite, b"other", &sig),
Err(PncError::BadSignature)
);
}
}
#[test]
fn a_public_point_round_trips_through_sec1() {
let (p256_key, p521_key) = keys();
let VerifyingKey::P256(vk) = p256_key.verifying_key() else { panic!("wrong curve") };
let point = vk.to_sec1_point(false);
assert_eq!(VerifyingKey::p256_sec1(point.as_bytes()).unwrap(), p256_key.verifying_key());
let VerifyingKey::P521(vk) = p521_key.verifying_key() else { panic!("wrong curve") };
let point = vk.to_sec1_point(false);
assert_eq!(VerifyingKey::p521_sec1(point.as_bytes()).unwrap(), p521_key.verifying_key());
}
#[test]
fn a_key_will_not_stand_in_for_the_other_curve() {
let (p256_key, p521_key) = keys();
assert!(p256_key.sign(Suite::EcdsaSha512, b"data").is_err());
assert!(p521_key.sign(Suite::EcdsaSha256, b"data").is_err());
let sig = p256_key.sign(Suite::EcdsaSha256, b"data").unwrap();
assert!(p521_key.verifying_key().verify(Suite::EcdsaSha256, b"data", &sig).is_err());
}
#[cfg(feature = "iso2")]
#[test]
fn a_real_iso2_signature_verifies() {
let key = SigningKey::p256(&[0x42; 32]).unwrap();
let fragment = [0x80, 0x11, 0x22, 0x33];
let elements = [Signed::new("ID1", &fragment)];
let signature = crate::pnc::iso2::sign(&elements, &Sha2, &key).unwrap();
crate::pnc::iso2::verify(&signature, &elements, &Sha2, &key.verifying_key()).unwrap();
let other = [0x80, 0x44];
assert!(
crate::pnc::iso2::verify(
&signature,
&[Signed::new("ID1", &other)],
&Sha2,
&key.verifying_key(),
)
.is_err()
);
}
}