use ic_core::traits::SignatureScheme as _;
use rustls::pki_types::{
alg_id, AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm,
};
pub(crate) static ECDSA_P256_SHA256: Ecdsa = Ecdsa {
curve: Curve::P256,
public_key_alg_id: alg_id::ECDSA_P256,
signature_alg_id: alg_id::ECDSA_SHA256,
scalar_len: 32,
};
pub(crate) static ECDSA_P384_SHA384: Ecdsa = Ecdsa {
curve: Curve::P384,
public_key_alg_id: alg_id::ECDSA_P384,
signature_alg_id: alg_id::ECDSA_SHA384,
scalar_len: 48,
};
#[derive(Debug, Clone, Copy)]
enum Curve {
P256,
P384,
}
#[derive(Debug)]
pub(crate) struct Ecdsa {
curve: Curve,
public_key_alg_id: AlgorithmIdentifier,
signature_alg_id: AlgorithmIdentifier,
scalar_len: usize,
}
impl SignatureVerificationAlgorithm for Ecdsa {
fn verify_signature(
&self,
public_key: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<(), InvalidSignature> {
let mut buf = [0u8; 96];
let fixed = &mut buf[..self.scalar_len * 2];
ic_pkix::ecdsa_signature::from_der(signature, fixed).map_err(|_| InvalidSignature)?;
let verified = match self.curve {
Curve::P256 => ic_ec::p256::EcdsaP256Sha256::verify(public_key, message, fixed),
Curve::P384 => ic_ec::p384::EcdsaP384Sha384::verify(public_key, message, fixed),
};
verified.map_err(|_| InvalidSignature)
}
fn public_key_alg_id(&self) -> AlgorithmIdentifier {
self.public_key_alg_id
}
fn signature_alg_id(&self) -> AlgorithmIdentifier {
self.signature_alg_id
}
fn fips(&self) -> bool {
false
}
}
pub(crate) static ED25519: Ed25519 = Ed25519;
#[derive(Debug)]
pub(crate) struct Ed25519;
impl SignatureVerificationAlgorithm for Ed25519 {
fn verify_signature(
&self,
public_key: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<(), InvalidSignature> {
ic_ec::Ed25519::verify(public_key, message, signature).map_err(|_| InvalidSignature)
}
fn public_key_alg_id(&self) -> AlgorithmIdentifier {
alg_id::ED25519
}
fn signature_alg_id(&self) -> AlgorithmIdentifier {
alg_id::ED25519
}
fn fips(&self) -> bool {
false
}
}
pub(crate) static RSA_PKCS1_SHA256: Rsa = Rsa {
scheme: RsaScheme::Pkcs1Sha256,
signature_alg_id: alg_id::RSA_PKCS1_SHA256,
};
pub(crate) static RSA_PKCS1_SHA384: Rsa = Rsa {
scheme: RsaScheme::Pkcs1Sha384,
signature_alg_id: alg_id::RSA_PKCS1_SHA384,
};
pub(crate) static RSA_PKCS1_SHA512: Rsa = Rsa {
scheme: RsaScheme::Pkcs1Sha512,
signature_alg_id: alg_id::RSA_PKCS1_SHA512,
};
pub(crate) static RSA_PSS_SHA256: Rsa = Rsa {
scheme: RsaScheme::PssSha256,
signature_alg_id: alg_id::RSA_PSS_SHA256,
};
pub(crate) static RSA_PSS_SHA384: Rsa = Rsa {
scheme: RsaScheme::PssSha384,
signature_alg_id: alg_id::RSA_PSS_SHA384,
};
pub(crate) static RSA_PSS_SHA512: Rsa = Rsa {
scheme: RsaScheme::PssSha512,
signature_alg_id: alg_id::RSA_PSS_SHA512,
};
#[derive(Debug, Clone, Copy, PartialEq)]
enum RsaScheme {
Pkcs1Sha256,
Pkcs1Sha384,
Pkcs1Sha512,
PssSha256,
PssSha384,
PssSha512,
}
#[derive(Debug)]
pub(crate) struct Rsa {
scheme: RsaScheme,
signature_alg_id: AlgorithmIdentifier,
}
impl SignatureVerificationAlgorithm for Rsa {
fn verify_signature(
&self,
public_key: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<(), InvalidSignature> {
let (modulus, exponent) =
ic_pkix::parse_rsa_public_key(public_key).map_err(|_| InvalidSignature)?;
let key = ic_rsa::RsaPublicKey::from_components(modulus, exponent)
.map_err(|_| InvalidSignature)?;
let verified = match self.scheme {
RsaScheme::Pkcs1Sha256 => ic_rsa::Pkcs1Sha256::verify(&key, message, signature),
RsaScheme::Pkcs1Sha384 => ic_rsa::Pkcs1Sha384::verify(&key, message, signature),
RsaScheme::Pkcs1Sha512 => ic_rsa::Pkcs1Sha512::verify(&key, message, signature),
RsaScheme::PssSha256 => ic_rsa::PssSha256::verify(&key, message, signature),
RsaScheme::PssSha384 => ic_rsa::PssSha384::verify(&key, message, signature),
RsaScheme::PssSha512 => ic_rsa::PssSha512::verify(&key, message, signature),
};
verified.map_err(|_| InvalidSignature)
}
fn public_key_alg_id(&self) -> AlgorithmIdentifier {
alg_id::RSA_ENCRYPTION
}
fn signature_alg_id(&self) -> AlgorithmIdentifier {
self.signature_alg_id
}
fn fips(&self) -> bool {
false
}
}
#[cfg(test)]
mod tests {
use super::*;
fn rsa_key() -> &'static ic_rsa::RsaPrivateKey {
use std::sync::OnceLock;
static KEY: OnceLock<ic_rsa::RsaPrivateKey> = OnceLock::new();
KEY.get_or_init(|| {
let mut rng = ic_drbg::Rng::from_os().expect("os randomness");
ic_rsa::generate(2048, &mut rng).expect("rsa key generation")
})
}
fn rsa_spki_body(key: &ic_rsa::RsaPrivateKey) -> alloc::vec::Vec<u8> {
let public = key.public_key();
let mut modulus = alloc::vec![0u8; public.size()];
public.modulus_bytes(&mut modulus).unwrap();
let mut der = alloc::vec![0u8; 1024];
let n = ic_pkix::write_rsa_public_key(&modulus, public.exponent(), &mut der).unwrap();
der.truncate(n);
der
}
#[test]
fn rsa_signatures_verify_through_the_adapter() {
let key = rsa_key();
let spki = rsa_spki_body(key);
let message = b"the transcript a CertificateVerify covers";
let mut rng = ic_drbg::Rng::from_os().unwrap();
let mut sig = alloc::vec![0u8; key.public_key().size()];
let mut checked = 0;
for (name, alg) in [
("rsa-pkcs1-sha256", &RSA_PKCS1_SHA256),
("rsa-pkcs1-sha384", &RSA_PKCS1_SHA384),
("rsa-pkcs1-sha512", &RSA_PKCS1_SHA512),
("rsa-pss-sha256", &RSA_PSS_SHA256),
("rsa-pss-sha384", &RSA_PSS_SHA384),
("rsa-pss-sha512", &RSA_PSS_SHA512),
] {
match alg.scheme {
RsaScheme::Pkcs1Sha256 => ic_rsa::Pkcs1Sha256::sign(key, message, &mut sig),
RsaScheme::Pkcs1Sha384 => ic_rsa::Pkcs1Sha384::sign(key, message, &mut sig),
RsaScheme::Pkcs1Sha512 => ic_rsa::Pkcs1Sha512::sign(key, message, &mut sig),
RsaScheme::PssSha256 => ic_rsa::PssSha256::sign(key, message, &mut rng, &mut sig),
RsaScheme::PssSha384 => ic_rsa::PssSha384::sign(key, message, &mut rng, &mut sig),
RsaScheme::PssSha512 => ic_rsa::PssSha512::sign(key, message, &mut rng, &mut sig),
}
.unwrap_or_else(|e| panic!("{name}: signing failed: {e}"));
alg.verify_signature(&spki, message, &sig)
.unwrap_or_else(|_| panic!("{name}: a signature ic_rsa made did not verify"));
assert!(
alg.verify_signature(&spki, b"a different transcript", &sig)
.is_err(),
"{name}: a signature verified against the wrong message"
);
for (other_name, other) in [
("rsa-pkcs1-sha256", &RSA_PKCS1_SHA256),
("rsa-pkcs1-sha384", &RSA_PKCS1_SHA384),
("rsa-pkcs1-sha512", &RSA_PKCS1_SHA512),
("rsa-pss-sha256", &RSA_PSS_SHA256),
("rsa-pss-sha384", &RSA_PSS_SHA384),
("rsa-pss-sha512", &RSA_PSS_SHA512),
] {
if other.scheme == alg.scheme {
continue;
}
assert!(
other.verify_signature(&spki, message, &sig).is_err(),
"a {name} signature was accepted as {other_name}"
);
}
checked += 1;
}
assert_eq!(checked, 6, "not every RSA scheme was exercised");
}
#[test]
fn the_rsa_algorithm_identifiers_are_distinct_and_rsae() {
let all = [
&RSA_PKCS1_SHA256,
&RSA_PKCS1_SHA384,
&RSA_PKCS1_SHA512,
&RSA_PSS_SHA256,
&RSA_PSS_SHA384,
&RSA_PSS_SHA512,
];
for (i, a) in all.iter().enumerate() {
assert_eq!(
a.public_key_alg_id(),
alg_id::RSA_ENCRYPTION,
"TLS's rsa_pss_rsae_* and rsa_pkcs1_* both use rsaEncryption keys"
);
for b in &all[i + 1..] {
assert_ne!(
a.signature_alg_id(),
b.signature_alg_id(),
"two RSA schemes share a signature algorithm identifier"
);
}
assert!(!a.fips());
}
}
#[test]
fn a_short_modulus_is_refused_rather_than_verified() {
assert_eq!(ic_rsa::MIN_MODULUS_BITS, 2048);
let short = alloc::vec![0xc7u8; 128];
assert!(
ic_rsa::RsaPublicKey::from_components(&short, 65537).is_err(),
"a 1024-bit modulus was accepted"
);
let long = alloc::vec![0xc7u8; 256];
assert!(
ic_rsa::RsaPublicKey::from_components(&long, 65537).is_ok(),
"the control key was rejected for some other reason, so the \
comparison says nothing about the modulus size"
);
let mut der = alloc::vec![0u8; 512];
let n = ic_pkix::write_rsa_public_key(&short, 65537, &mut der).unwrap();
assert!(RSA_PKCS1_SHA256
.verify_signature(&der[..n], b"anything", &[0u8; 128])
.is_err());
}
#[test]
fn hostile_rsa_inputs_are_refused_rather_than_fatal() {
let key = rsa_key();
let spki = rsa_spki_body(key);
let mut tried = 0;
for bad_key in [
alloc::vec![],
alloc::vec![0x30],
alloc::vec![0x30, 0x82, 0xff, 0xff],
alloc::vec![0xffu8; 300],
spki[..spki.len() / 2].to_vec(),
] {
for bad_sig in [
alloc::vec![],
alloc::vec![0u8; 256],
alloc::vec![0xffu8; 1000],
] {
assert!(RSA_PKCS1_SHA256
.verify_signature(&bad_key, b"message", &bad_sig)
.is_err());
assert!(RSA_PSS_SHA256
.verify_signature(&bad_key, b"message", &bad_sig)
.is_err());
tried += 2;
}
}
for bad_sig in [
alloc::vec![],
alloc::vec![0u8; 255],
alloc::vec![0u8; 256],
alloc::vec![0xffu8; 256],
alloc::vec![0xffu8; 257],
] {
assert!(RSA_PKCS1_SHA256
.verify_signature(&spki, b"message", &bad_sig)
.is_err());
assert!(RSA_PSS_SHA256
.verify_signature(&spki, b"message", &bad_sig)
.is_err());
tried += 2;
}
assert!(tried >= 40, "only {tried} hostile inputs tried");
}
#[test]
fn a_real_signature_verifies_through_the_der_path() {
let sk = [7u8; 32];
let mut pk = [0u8; 65];
ic_ec::p256::EcdsaP256Sha256::public_key(&sk, &mut pk).unwrap();
let message = b"a message that was genuinely signed";
let mut fixed = [0u8; 64];
ic_ec::p256::EcdsaP256Sha256::sign(&sk, message, &mut fixed).unwrap();
let mut der = [0u8; 80];
let n = ic_pkix::ecdsa_signature::to_der(&fixed, &mut der).unwrap();
ECDSA_P256_SHA256
.verify_signature(&pk, message, &der[..n])
.expect("a signature this library made must verify");
assert!(ECDSA_P256_SHA256
.verify_signature(&pk, b"a different message", &der[..n])
.is_err());
}
#[test]
fn p384_verifies_through_the_der_path() {
let sk = [9u8; 48];
let mut pk = [0u8; 97];
ic_ec::p384::EcdsaP384Sha384::public_key(&sk, &mut pk).unwrap();
let message = b"a message that was genuinely signed";
let mut fixed = [0u8; 96];
ic_ec::p384::EcdsaP384Sha384::sign(&sk, message, &mut fixed).unwrap();
let mut der = [0u8; 112];
let n = ic_pkix::ecdsa_signature::to_der(&fixed, &mut der).unwrap();
ECDSA_P384_SHA384
.verify_signature(&pk, message, &der[..n])
.expect("a signature this library made must verify");
assert!(ECDSA_P384_SHA384
.verify_signature(&pk, b"something else", &der[..n])
.is_err());
}
#[test]
fn hostile_signatures_are_refused_rather_than_fatal() {
let sk = [7u8; 32];
let mut pk = [0u8; 65];
ic_ec::p256::EcdsaP256Sha256::public_key(&sk, &mut pk).unwrap();
let mut refused = 0;
for len in [0usize, 1, 8, 63, 64, 70, 71, 72, 200] {
for fill in [0x00u8, 0xff, 0x30, 0x02, 0x80] {
let sig = alloc::vec![fill; len];
assert!(
ECDSA_P256_SHA256
.verify_signature(&pk, b"message", &sig)
.is_err(),
"a signature of {len} bytes of {fill:#04x} was accepted"
);
refused += 1;
let bad_key = alloc::vec![fill; 65];
assert!(ECDSA_P256_SHA256
.verify_signature(&bad_key, b"message", &sig)
.is_err());
refused += 1;
}
}
assert!(refused > 80, "only {refused} hostile inputs tried");
}
#[test]
fn the_algorithm_identifiers_are_the_expected_ones() {
assert_eq!(ECDSA_P256_SHA256.public_key_alg_id(), alg_id::ECDSA_P256);
assert_eq!(ECDSA_P256_SHA256.signature_alg_id(), alg_id::ECDSA_SHA256);
assert_eq!(ECDSA_P384_SHA384.public_key_alg_id(), alg_id::ECDSA_P384);
assert_eq!(ECDSA_P384_SHA384.signature_alg_id(), alg_id::ECDSA_SHA384);
}
#[test]
fn neither_claims_fips_validation() {
assert!(!ECDSA_P256_SHA256.fips());
assert!(!ECDSA_P384_SHA384.fips());
}
}