diff --git a/biscuit-auth/Cargo.toml b/biscuit-auth/Cargo.toml index a7756e39..2d308bd4 100644 --- a/biscuit-auth/Cargo.toml +++ b/biscuit-auth/Cargo.toml @@ -11,7 +11,10 @@ homepage = "https://github.com/biscuit-auth/biscuit" repository = "https://github.com/biscuit-auth/biscuit-rust" [features] -default = ["regex-full", "datalog-macro", "pem"] +default = ["regex-full", "datalog-macro", "default-backend-pem"] +default-backend = ["dep:ed25519-dalek", "dep:p256"] +default-backend-pem = ["default-backend", "pem", "ed25519-dalek/pem", "ed25519-dalek/pkcs8"] +awslc-backend = ["dep:aws-lc-rs", "dep:aws-lc-sys"] regex-full = ["regex/perf", "regex/unicode"] wasm = ["wasm-bindgen"] # used by biscuit-wasm to serialize errors to JSON @@ -23,13 +26,11 @@ bwk = ["chrono", "serde"] docsrs = [] uuid = ["dep:uuid"] # used to expose pem/der loaders for keypairs -pem = ["ed25519-dalek/pem", "ed25519-dalek/pkcs8"] +pem = [] [dependencies] rand_core = "^0.6" -sha2 = "^0.9" prost = "0.10" -prost-types = "0.10" regex = { version = "1.5", default-features = false, features = ["std"] } nom = { version = "7", default-features = false, features = ["std"] } hex = "0.4" @@ -38,21 +39,20 @@ thiserror = "1" rand = { version = "0.8" } wasm-bindgen = { version = "0.2", optional = true } base64 = "0.13.0" -ed25519-dalek = { version = "2.0.0", features = ["rand_core", "zeroize"] } +ed25519-dalek = { version = "2.0.0", features = ["rand_core", "zeroize"], optional = true } serde = { version = "1.0.132", optional = true, features = ["derive"] } -getrandom = { version = "0.2.15" } time = { version = "0.3.7", features = ["formatting", "parsing"] } uuid = { version = "1", optional = true } biscuit-parser = { version = "0.2.0", path = "../biscuit-parser" } biscuit-quote = { version = "0.3.0", optional = true, path = "../biscuit-quote" } chrono = { version = "0.4.26", optional = true, default-features = false, features = [ - "serde", + "serde", ] } serde_json = "1.0.117" -ecdsa = { version = "0.16.9", features = ["signing", "verifying", "pem", "alloc", "pkcs8", "serde"] } -p256 = "0.13.2" -pkcs8 = "0.9.0" -elliptic-curve = { version = "0.13.8", features = ["pkcs8"] } +p256 = { version = "0.13.2", optional = true } +pkcs8 = { version = "0.10.2", features = ["pem"] } +aws-lc-rs = { version = "1.16.0", optional = true } +aws-lc-sys = { version = "0.37.1", optional = true } [dev-dependencies] bencher = "0.1.5" diff --git a/biscuit-auth/src/crypto/awslc/ed25519.rs b/biscuit-auth/src/crypto/awslc/ed25519.rs new file mode 100644 index 00000000..a406eba7 --- /dev/null +++ b/biscuit-auth/src/crypto/awslc/ed25519.rs @@ -0,0 +1,428 @@ +use crate::crypto::{KeyPairImpl, KeySerialization, PrivateKeyImpl, PublicKeyImpl, Signature}; +use crate::error; +use crate::error::Format; +use crate::format::schema::public_key::Algorithm; +use aws_lc_rs::encoding::AsBigEndian; +use aws_lc_rs::signature::{Ed25519KeyPair, KeyPair as AwsKeyPair, UnparsedPublicKey, ED25519}; +use rand_core::{CryptoRng, RngCore}; +use std::convert::TryInto; +use std::fmt::{Display, Formatter}; +use std::hash::{Hash, Hasher}; +use zeroize::Zeroizing; + +#[derive(Debug, Clone, Copy)] +pub struct PublicKey(UnparsedPublicKey<[u8; 32]>); + +impl PartialEq for PublicKey { + fn eq(&self, other: &Self) -> bool { + self.0.as_ref() == other.0.as_ref() + } +} + +impl Eq for PublicKey {} + +impl Hash for PublicKey { + fn hash(&self, state: &mut H) { + (self.algorithm() as i32).hash(state); + self.0.as_ref().hash(state); + } +} + +impl Display for PublicKey { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "ed25519/{}", self.to_bytes_hex()) + } +} + +impl KeySerialization for PublicKey { + type Bytes = Vec; + type String = String; + + fn to_bytes(&self) -> Self::Bytes { + self.0.as_ref().to_vec() + } + + fn from_bytes(bytes: &[u8]) -> Result { + let bytes: [u8; 32] = bytes + .try_into() + .map_err(|_| Format::InvalidKeySize(bytes.len()))?; + Ok(Self(UnparsedPublicKey::new(&ED25519, bytes))) + } +} + +#[cfg(feature = "pem")] +impl super::AwsLcPublicKey for PublicKey { + fn parsed(&self) -> Result { + self.0 + .parse() + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8) + } +} + +impl PublicKeyImpl for PublicKey { + fn verify_signature(&self, data: &[u8], signature: &Signature) -> Result<(), Format> { + self.0 + .verify(data, signature.0.as_slice()) + .map_err(|e| e.to_string()) + .map_err(error::Signature::InvalidSignature) + .map_err(Format::Signature) + } + + fn algorithm(&self) -> Algorithm { + Algorithm::Ed25519 + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PrivateKey(Zeroizing<[u8; 32]>); + +impl KeySerialization for PrivateKey { + type Bytes = Zeroizing>; + type String = Zeroizing; + + fn to_bytes(&self) -> Self::Bytes { + self.0.to_vec().into() + } + + fn from_bytes(bytes: &[u8]) -> Result { + let bytes: [u8; 32] = bytes + .try_into() + .map_err(|_| Format::InvalidKeySize(bytes.len()))?; + let _ = Ed25519KeyPair::from_seed_unchecked(&bytes) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + Ok(Self(Zeroizing::new(bytes))) + } +} + +impl PrivateKeyImpl for PrivateKey { + type PublicKey = PublicKey; + + fn public(&self) -> Self::PublicKey { + let kp = + Ed25519KeyPair::from_seed_unchecked(self.0.as_ref()).expect("invalid ed25519 key seed"); + PublicKey::from_bytes(kp.public_key().as_ref()).expect("invalid ed25519 public key") + } + + fn algorithm(&self) -> Algorithm { + Algorithm::Ed25519 + } +} + +#[cfg(feature = "pem")] +impl crate::crypto::KeyPemSerialization for PrivateKey { + fn from_der(bytes: &[u8]) -> Result { + let kp = Ed25519KeyPair::from_pkcs8(bytes) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + let seed_bin: aws_lc_rs::encoding::Curve25519SeedBin = kp + .seed() + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)? + .as_be_bytes() + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + let mut seed = Zeroizing::new([0u8; 32]); + seed.copy_from_slice(seed_bin.as_ref()); + Ok(Self(seed)) + } + + fn from_pem(str: &str) -> Result { + use pkcs8::spki::Document; + let (label, doc) = Document::from_pem(str) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + if label != "PRIVATE KEY" { + return Err(Format::InvalidKey(format!( + "unexpected PEM label: expected \"PRIVATE KEY\", got \"{label}\"" + ))); + } + Self::from_der(doc.as_ref()) + } + + fn to_der(&self) -> Result { + use aws_lc_rs::encoding::AsDer; + let kp = Ed25519KeyPair::from_seed_unchecked(self.0.as_ref()) + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8)?; + let pkcs8: aws_lc_rs::encoding::Pkcs8V1Der = kp + .as_der() + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8)?; + Ok(Zeroizing::new(pkcs8.as_ref().to_vec())) + } + + fn to_pem(&self) -> Result { + use pkcs8::der::pem::LineEnding; + let der = self.to_der()?; + let pem = pkcs8::der::pem::encode_string("PRIVATE KEY", LineEnding::LF, &der) + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8)?; + Ok(Zeroizing::new(pem)) + } +} + +#[derive(Debug)] +pub struct KeyPair(Ed25519KeyPair); + +impl PartialEq for KeyPair { + fn eq(&self, other: &Self) -> bool { + self.private() == other.private() + } +} + +impl KeyPairImpl for KeyPair { + type PublicKey = PublicKey; + type PrivateKey = PrivateKey; + + fn sign(&self, data: &[u8]) -> Result { + let signature = self.0.sign(data); + + Ok(Signature(signature.as_ref().to_vec())) + } + + fn from_private(key: &Self::PrivateKey) -> Self { + let kp = Ed25519KeyPair::from_seed_unchecked(key.0.as_ref()).expect("invalid key seed"); + Self(kp) + } + + fn public(&self) -> Self::PublicKey { + PublicKey::from_bytes(self.0.public_key().as_ref()).expect("invalid public key") + } + + fn algorithm(&self) -> Algorithm { + Algorithm::Ed25519 + } + + fn private(&self) -> Self::PrivateKey { + let seed = self.0.seed().expect("could not get seed"); + PrivateKey::from_bytes(seed.as_be_bytes().expect("get seed bytes").as_ref()) + .expect("invalid seed") + } + + fn generate() -> Self { + let kp = Ed25519KeyPair::generate().expect("could not generate keys"); + Self(kp) + } + + fn generate_with_rng(rng: &mut R) -> Self { + let mut scalar = Zeroizing::new([0u8; 32]); + rng.fill_bytes(&mut *scalar); + let priv_key = PrivateKey(scalar); + Self::from_private(&priv_key) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn sign_and_verify() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + let msg = b"hello world"; + let sig = kp.sign(msg).unwrap(); + pub_key.verify_signature(msg, &sig).unwrap(); + } + + #[test] + fn verify_wrong_message_fails() { + let kp = KeyPair::generate(); + let sig = kp.sign(b"correct message").unwrap(); + assert!(kp + .public() + .verify_signature(b"wrong message", &sig) + .is_err()); + } + + #[test] + fn verify_wrong_key_fails() { + let kp1 = KeyPair::generate(); + let kp2 = KeyPair::generate(); + let msg = b"test"; + let sig = kp1.sign(msg).unwrap(); + assert!(kp2.public().verify_signature(msg, &sig).is_err()); + } + + #[test] + fn private_key_bytes_roundtrip() { + let kp = KeyPair::generate(); + let priv_key = kp.private(); + let bytes = priv_key.to_bytes(); + let restored = PrivateKey::from_bytes(&bytes).unwrap(); + assert_eq!(priv_key, restored); + assert_eq!(priv_key.public(), restored.public()); + } + + #[test] + fn private_key_hex_roundtrip() { + let kp = KeyPair::generate(); + let priv_key = kp.private(); + let hex = priv_key.to_bytes_hex(); + let restored = PrivateKey::from_bytes_hex(&hex).unwrap(); + assert_eq!(priv_key, restored); + } + + #[test] + fn public_key_bytes_roundtrip() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + let bytes = pub_key.to_bytes(); + assert_eq!(bytes.len(), 32); + let restored = PublicKey::from_bytes(&bytes).unwrap(); + assert_eq!(pub_key, restored); + } + + #[test] + fn public_key_hex_roundtrip() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + let hex = pub_key.to_bytes_hex(); + let restored = PublicKey::from_bytes_hex(&hex).unwrap(); + assert_eq!(pub_key, restored); + } + + #[test] + fn keypair_from_private_key() { + let kp = KeyPair::generate(); + let priv_key = kp.private(); + let kp2 = KeyPair::from_private(&priv_key); + assert_eq!(kp.public(), kp2.public()); + + let msg = b"test message"; + let sig = kp2.sign(msg).unwrap(); + kp.public().verify_signature(msg, &sig).unwrap(); + } + + #[test] + fn keypair_equality() { + let kp = KeyPair::generate(); + let kp2 = KeyPair::from_private(&kp.private()); + assert_eq!(kp, kp2); + } + + #[test] + fn invalid_private_key_size() { + assert!(matches!( + PrivateKey::from_bytes(&[0xaa]), + Err(Format::InvalidKeySize(1)) + )); + assert!(matches!( + PrivateKey::from_bytes(&[0u8; 31]), + Err(Format::InvalidKeySize(31)) + )); + assert!(matches!( + PrivateKey::from_bytes(&[0u8; 33]), + Err(Format::InvalidKeySize(33)) + )); + } + + #[test] + fn invalid_public_key_size() { + assert!(matches!( + PublicKey::from_bytes(&[0xaa]), + Err(Format::InvalidKeySize(1)) + )); + assert!(matches!( + PublicKey::from_bytes(&[0u8; 31]), + Err(Format::InvalidKeySize(31)) + )); + assert!(matches!( + PublicKey::from_bytes(&[0u8; 33]), + Err(Format::InvalidKeySize(33)) + )); + } + + #[test] + fn algorithm_identifiers() { + let kp = KeyPair::generate(); + assert_eq!(kp.algorithm(), Algorithm::Ed25519); + assert_eq!(kp.public().algorithm(), Algorithm::Ed25519); + assert_eq!(kp.private().algorithm(), Algorithm::Ed25519); + } + + #[test] + fn public_key_display() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + let display = format!("{pub_key}"); + assert!(display.starts_with("ed25519/")); + // 32 bytes = 64 hex chars + let hex_part = display.strip_prefix("ed25519/").unwrap(); + assert_eq!(hex_part.len(), 64); + } + + #[test] + fn public_key_hash_consistency() { + use std::collections::HashSet; + let kp = KeyPair::generate(); + let pub1 = kp.public(); + let pub2 = PublicKey::from_bytes(&pub1.to_bytes()).unwrap(); + + let mut set = HashSet::new(); + set.insert(pub1); + assert!(set.contains(&pub2)); + } + + #[cfg(feature = "pem")] + mod pem_tests { + use super::*; + use crate::crypto::KeyPemSerialization; + + #[test] + fn private_key_pem_roundtrip() { + let kp = KeyPair::generate(); + let priv_key = kp.private(); + let pem = priv_key.to_pem().unwrap(); + assert!(pem.contains("PRIVATE KEY")); + let restored = PrivateKey::from_pem(&pem).unwrap(); + assert_eq!(priv_key, restored); + } + + #[test] + fn private_key_der_roundtrip() { + let kp = KeyPair::generate(); + let priv_key = kp.private(); + let der = priv_key.to_der().unwrap(); + let restored = PrivateKey::from_der(&der).unwrap(); + assert_eq!(priv_key, restored); + } + + #[test] + fn public_key_pem_roundtrip() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + let pem = pub_key.to_pem().unwrap(); + assert!(pem.contains("PUBLIC KEY")); + let restored = PublicKey::from_pem(&pem).unwrap(); + assert_eq!(pub_key, restored); + } + + #[test] + fn public_key_der_roundtrip() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + let der = pub_key.to_der().unwrap(); + let restored = PublicKey::from_der(&der).unwrap(); + assert_eq!(pub_key, restored); + } + + #[test] + fn private_key_wrong_pem_label() { + let kp = KeyPair::generate(); + let pem = kp.private().to_pem().unwrap(); + let bad_pem = pem.replace("PRIVATE KEY", "PUBLIC KEY"); + assert!(PrivateKey::from_pem(&bad_pem).is_err()); + } + + #[test] + fn public_key_wrong_pem_label() { + let kp = KeyPair::generate(); + let pem = kp.public().to_pem().unwrap(); + let bad_pem = pem.replace("PUBLIC KEY", "PRIVATE KEY"); + assert!(PublicKey::from_pem(&bad_pem).is_err()); + } + } +} diff --git a/biscuit-auth/src/crypto/awslc/mod.rs b/biscuit-auth/src/crypto/awslc/mod.rs new file mode 100644 index 00000000..6008f737 --- /dev/null +++ b/biscuit-auth/src/crypto/awslc/mod.rs @@ -0,0 +1,62 @@ +pub mod ed25519; +pub mod secp256r1; + +#[cfg(feature = "pem")] +use crate::error::Format; +#[cfg(feature = "pem")] +use aws_lc_rs::signature::ParsedPublicKey; + +/// Trait for aws-lc-rs public key types that can produce a ParsedPublicKey for DER export. +#[cfg(feature = "pem")] +pub(crate) trait AwsLcPublicKey: crate::crypto::KeySerialization> { + fn parsed(&self) -> Result; +} + +#[cfg(feature = "pem")] +impl crate::crypto::KeyPemSerialization for T +where + T: AwsLcPublicKey + crate::crypto::KeySerialization, String = String>, +{ + fn from_der(bytes: &[u8]) -> Result { + use pkcs8::der::Decode; + use pkcs8::spki::SubjectPublicKeyInfoRef; + let spki = SubjectPublicKeyInfoRef::from_der(bytes) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + Self::from_bytes(spki.subject_public_key.raw_bytes()) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey) + } + + fn from_pem(str: &str) -> Result { + use pkcs8::spki::Document; + let (label, doc) = Document::from_pem(str) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + if label != "PUBLIC KEY" { + return Err(Format::InvalidKey(format!( + "unexpected PEM label: expected \"PUBLIC KEY\", got \"{label}\"" + ))); + } + Self::from_der(doc.as_ref()) + } + + fn to_der(&self) -> Result { + use aws_lc_rs::encoding::AsDer; + let parsed = self.parsed()?; + let der = parsed + .as_der() + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8)?; + Ok(der.as_ref().to_vec()) + } + + fn to_pem(&self) -> Result { + use pkcs8::der::pem::encode_string; + use pkcs8::der::pem::LineEnding; + let der = self.to_der()?; + encode_string("PUBLIC KEY", LineEnding::LF, &der) + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8) + } +} diff --git a/biscuit-auth/src/crypto/awslc/secp256r1.rs b/biscuit-auth/src/crypto/awslc/secp256r1.rs new file mode 100644 index 00000000..a81ef084 --- /dev/null +++ b/biscuit-auth/src/crypto/awslc/secp256r1.rs @@ -0,0 +1,628 @@ +use crate::crypto::{KeyPairImpl, KeySerialization, PrivateKeyImpl, PublicKeyImpl, Signature}; +use crate::error; +use crate::error::Format; +use crate::error::Signature::InvalidSignatureGeneration; +use crate::format::schema::public_key::Algorithm; +use aws_lc_rs::encoding::{AsBigEndian, EcPrivateKeyBin}; +#[cfg(feature = "pem")] +use aws_lc_rs::encoding::{AsDer, EcPrivateKeyRfc5915Der}; +use aws_lc_rs::rand::SystemRandom; +use aws_lc_rs::signature::{ + EcdsaKeyPair, KeyPair as AwsLcKeyPair, UnparsedPublicKey, ECDSA_P256_SHA256_ASN1, + ECDSA_P256_SHA256_ASN1_SIGNING, +}; +use aws_lc_sys::{ + point_conversion_form_t, EC_GROUP_free, EC_GROUP_new_by_curve_name, EC_POINT_free, + EC_POINT_new, EC_POINT_oct2point, EC_POINT_point2oct, NID_X9_62_prime256v1, EC_GROUP, EC_POINT, +}; +use rand_core::{CryptoRng, RngCore}; +use std::fmt::{Display, Formatter}; +use std::hash::{Hash, Hasher}; +use zeroize::Zeroizing; + +#[derive(Debug, Clone, Copy)] +pub struct PublicKey(UnparsedPublicKey<[u8; 65]>); + +impl PartialEq for PublicKey { + fn eq(&self, other: &Self) -> bool { + self.0.as_ref() == other.0.as_ref() + } +} + +impl Eq for PublicKey {} + +impl Hash for PublicKey { + fn hash(&self, state: &mut H) { + (self.algorithm() as i32).hash(state); + self.0.as_ref().hash(state); + } +} + +impl Display for PublicKey { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "secp256r1/{}", self.to_bytes_hex()) + } +} + +impl KeySerialization for PublicKey { + type Bytes = Vec; + type String = String; + + fn to_bytes(&self) -> Self::Bytes { + compress_p256(self.0.as_ref()).to_vec() + } + + fn from_bytes(bytes: &[u8]) -> Result { + match bytes.len() { + 33 => { + let uncompressed = decompress_p256(bytes)?; + Ok(Self(UnparsedPublicKey::new( + &ECDSA_P256_SHA256_ASN1, + uncompressed, + ))) + } + 65 => { + let mut arr = [0u8; 65]; + arr.copy_from_slice(bytes); + Ok(Self(UnparsedPublicKey::new(&ECDSA_P256_SHA256_ASN1, arr))) + } + _ => Err(Format::InvalidKeySize(bytes.len())), + } + } +} + +#[cfg(feature = "pem")] +impl super::AwsLcPublicKey for PublicKey { + fn parsed(&self) -> Result { + self.0 + .parse() + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8) + } +} + +impl PublicKeyImpl for PublicKey { + fn verify_signature(&self, data: &[u8], signature: &Signature) -> Result<(), Format> { + self.0 + .verify(data, signature.0.as_slice()) + .map_err(|e| e.to_string()) + .map_err(error::Signature::InvalidSignature) + .map_err(Format::Signature) + } + + fn algorithm(&self) -> Algorithm { + Algorithm::Secp256r1 + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PrivateKey(Zeroizing<[u8; 32]>); + +impl KeySerialization for PrivateKey { + type Bytes = Zeroizing>; + type String = Zeroizing; + + fn to_bytes(&self) -> Self::Bytes { + self.0.to_vec().into() + } + + fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != 32 { + return Err(Format::InvalidKeySize(bytes.len())); + } + let _ = aws_lc_rs::agreement::PrivateKey::from_private_key( + &aws_lc_rs::agreement::ECDH_P256, + bytes, + ) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + let mut scalar = Zeroizing::new([0u8; 32]); + scalar.copy_from_slice(bytes); + Ok(Self(scalar)) + } +} + +#[cfg(feature = "pem")] +impl crate::crypto::KeyPemSerialization for PrivateKey { + fn from_der(bytes: &[u8]) -> Result { + // Parse via aws-lc to validate, then extract the scalar + let priv_key = aws_lc_rs::agreement::PrivateKey::from_private_key_der( + &aws_lc_rs::agreement::ECDH_P256, + bytes, + ) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + let scalar_bin: EcPrivateKeyBin = priv_key + .as_be_bytes() + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + let mut scalar = Zeroizing::new([0u8; 32]); + scalar.copy_from_slice(scalar_bin.as_ref()); + Ok(Self(scalar)) + } + + fn from_pem(str: &str) -> Result { + use pkcs8::spki::Document; + let (label, doc) = Document::from_pem(str) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + if label != "PRIVATE KEY" && label != "EC PRIVATE KEY" { + return Err(Format::InvalidKey(format!( + "unexpected PEM label: expected \"PRIVATE KEY\" or \"EC PRIVATE KEY\", got \"{label}\"" + ))); + } + Self::from_der(doc.as_ref()) + } + + fn to_der(&self) -> Result { + let priv_key = aws_lc_rs::agreement::PrivateKey::from_private_key( + &aws_lc_rs::agreement::ECDH_P256, + self.0.as_ref(), + ) + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8)?; + let rfc5915: EcPrivateKeyRfc5915Der = priv_key + .as_der() + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8)?; + Ok(Zeroizing::new(rfc5915.as_ref().to_vec())) + } + + fn to_pem(&self) -> Result { + use pkcs8::der::pem::LineEnding; + let der = self.to_der()?; + let pem = pkcs8::der::pem::encode_string("EC PRIVATE KEY", LineEnding::LF, &der) + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8)?; + Ok(Zeroizing::new(pem)) + } +} + +impl PrivateKeyImpl for PrivateKey { + type PublicKey = PublicKey; + + fn public(&self) -> Self::PublicKey { + let priv_key = aws_lc_rs::agreement::PrivateKey::from_private_key( + &aws_lc_rs::agreement::ECDH_P256, + self.0.as_ref(), + ) + .expect("stored scalar should produce a valid private key"); + let pub_key = priv_key + .compute_public_key() + .expect("should be able to compute public key"); + // compute_public_key returns uncompressed SEC1 + let mut uncompressed = [0u8; 65]; + uncompressed.copy_from_slice(pub_key.as_ref()); + PublicKey(UnparsedPublicKey::new( + &ECDSA_P256_SHA256_ASN1, + uncompressed, + )) + } + + fn algorithm(&self) -> Algorithm { + Algorithm::Secp256r1 + } +} + +#[derive(Debug)] +pub struct KeyPair(EcdsaKeyPair); + +impl PartialEq for KeyPair { + fn eq(&self, other: &Self) -> bool { + self.private() == other.private() + } +} + +impl KeyPairImpl for KeyPair { + type PublicKey = PublicKey; + type PrivateKey = PrivateKey; + + fn sign(&self, data: &[u8]) -> Result { + let rng = SystemRandom::new(); + let signature = self + .0 + .sign(&rng, data) + .map_err(|e| e.to_string()) + .map_err(InvalidSignatureGeneration) + .map_err(Format::Signature)?; + + Ok(Signature(signature.as_ref().to_vec())) + } + + fn from_private(key: &Self::PrivateKey) -> Self { + let kp = + keypair_from_scalar(&*key.0).expect("stored scalar should produce a valid keypair"); + Self(kp) + } + + fn public(&self) -> Self::PublicKey { + let pub_key = self.0.public_key(); + let mut uncompressed = [0u8; 65]; + uncompressed.copy_from_slice(pub_key.as_ref()); + PublicKey(UnparsedPublicKey::new( + &ECDSA_P256_SHA256_ASN1, + uncompressed, + )) + } + + fn algorithm(&self) -> Algorithm { + Algorithm::Secp256r1 + } + + fn private(&self) -> Self::PrivateKey { + let scalar_bin: EcPrivateKeyBin = self + .0 + .private_key() + .as_be_bytes() + .expect("scalar export failed"); + let mut scalar = Zeroizing::new([0u8; 32]); + scalar.copy_from_slice(scalar_bin.as_ref()); + PrivateKey(scalar) + } + + fn generate() -> Self { + let kp = EcdsaKeyPair::generate(&ECDSA_P256_SHA256_ASN1_SIGNING) + .expect("P256 key generation failed"); + Self(kp) + } + + fn generate_with_rng(rng: &mut R) -> Self { + let mut scalar = Zeroizing::new([0u8; 32]); + rng.fill_bytes(&mut *scalar); + let priv_key = PrivateKey(scalar); + Self::from_private(&priv_key) + } +} + +/// Build an EcdsaKeyPair from a raw 32-byte scalar +fn keypair_from_scalar(scalar: &[u8]) -> Result { + let priv_key = aws_lc_rs::agreement::PrivateKey::from_private_key( + &aws_lc_rs::agreement::ECDH_P256, + scalar, + ) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + let pub_key = priv_key + .compute_public_key() + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + EcdsaKeyPair::from_private_key_and_public_key( + &ECDSA_P256_SHA256_ASN1_SIGNING, + scalar, + pub_key.as_ref(), + ) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey) +} + +struct EcGroup(*mut EC_GROUP); + +impl EcGroup { + pub fn init(nid: i32) -> Result { + let group = unsafe { EC_GROUP_new_by_curve_name(nid) }; + if group.is_null() { + Err(Format::InvalidKey("failed to create EC group".to_string())) + } else { + Ok(Self(group)) + } + } +} + +impl Drop for EcGroup { + fn drop(&mut self) { + unsafe { EC_GROUP_free(self.0) }; + } +} + +struct EcPoint<'a> { + point: *mut EC_POINT, + group: &'a EcGroup, +} + +impl<'a> EcPoint<'a> { + pub fn init(group: &'a EcGroup) -> Result { + let point = unsafe { EC_POINT_new(group.0) }; + if point.is_null() { + Err(Format::InvalidKey("failed to create EC point".to_string())) + } else { + Ok(Self { group, point }) + } + } + + pub fn decompress(&self, compressed: &[u8]) -> Result<[u8; 65], Format> { + let rc = unsafe { + EC_POINT_oct2point( + self.group.0, + self.point, + compressed.as_ptr(), + compressed.len(), + std::ptr::null_mut(), + ) + }; + if rc != 1 { + return Err(Format::InvalidKey( + "failed to decompress EC point".to_string(), + )); + } + + let mut uncompressed = [0u8; 65]; + + let len = unsafe { + EC_POINT_point2oct( + self.group.0, + self.point, + point_conversion_form_t::POINT_CONVERSION_UNCOMPRESSED, + uncompressed.as_mut_ptr(), + uncompressed.len(), + std::ptr::null_mut(), + ) + }; + + if len != 65 { + Err(Format::InvalidKey( + "unexpected uncompressed point length".to_string(), + )) + } else { + Ok(uncompressed) + } + } +} + +impl<'a> Drop for EcPoint<'a> { + fn drop(&mut self) { + unsafe { EC_POINT_free(self.point) }; + } +} + +/// Decompress a SEC1 compressed P-256 point (33 bytes) to uncompressed (65 bytes). +fn decompress_p256(compressed: &[u8]) -> Result<[u8; 65], Format> { + let group = EcGroup::init(NID_X9_62_prime256v1)?; + let point = EcPoint::init(&group)?; + + point.decompress(compressed) +} + +/// Compress an uncompressed SEC1 P-256 point (65 bytes) to compressed (33 bytes). +fn compress_p256(uncompressed: &[u8]) -> [u8; 33] { + let mut compressed = [0u8; 33]; + // 0x02 if y is even, 0x03 if y is odd + compressed[0] = if uncompressed[64] & 1 == 0 { + 0x02 + } else { + 0x03 + }; + compressed[1..33].copy_from_slice(&uncompressed[1..33]); + compressed +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn sign_and_verify() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + let msg = b"hello world"; + let sig = kp.sign(msg).unwrap(); + pub_key.verify_signature(msg, &sig).unwrap(); + } + + #[test] + fn verify_wrong_message_fails() { + let kp = KeyPair::generate(); + let sig = kp.sign(b"correct message").unwrap(); + assert!(kp + .public() + .verify_signature(b"wrong message", &sig) + .is_err()); + } + + #[test] + fn verify_wrong_key_fails() { + let kp1 = KeyPair::generate(); + let kp2 = KeyPair::generate(); + let msg = b"test"; + let sig = kp1.sign(msg).unwrap(); + assert!(kp2.public().verify_signature(msg, &sig).is_err()); + } + + #[test] + fn private_key_bytes_roundtrip() { + let kp = KeyPair::generate(); + let priv_key = kp.private(); + let bytes = priv_key.to_bytes(); + let restored = PrivateKey::from_bytes(&bytes).unwrap(); + assert_eq!(priv_key, restored); + assert_eq!(priv_key.public(), restored.public()); + } + + #[test] + fn private_key_hex_roundtrip() { + let kp = KeyPair::generate(); + let priv_key = kp.private(); + let hex = priv_key.to_bytes_hex(); + let restored = PrivateKey::from_bytes_hex(&hex).unwrap(); + assert_eq!(priv_key, restored); + } + + #[test] + fn public_key_compressed_roundtrip() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + // to_bytes produces compressed (33 bytes) + let compressed = pub_key.to_bytes(); + assert_eq!(compressed.len(), 33); + let restored = PublicKey::from_bytes(&compressed).unwrap(); + assert_eq!(pub_key, restored); + } + + #[test] + fn public_key_uncompressed_roundtrip() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + // from_bytes also accepts uncompressed (65 bytes) + let uncompressed: &[u8] = pub_key.0.as_ref(); + assert_eq!(uncompressed.len(), 65); + let restored = PublicKey::from_bytes(uncompressed).unwrap(); + assert_eq!(pub_key, restored); + } + + #[test] + fn public_key_hex_roundtrip() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + let hex = pub_key.to_bytes_hex(); + let restored = PublicKey::from_bytes_hex(&hex).unwrap(); + assert_eq!(pub_key, restored); + } + + #[test] + fn keypair_from_private_key() { + let kp = KeyPair::generate(); + let priv_key = kp.private(); + let kp2 = KeyPair::from_private(&priv_key); + assert_eq!(kp.public(), kp2.public()); + + // Signature from reconstructed keypair verifies with original public key + let msg = b"test message"; + let sig = kp2.sign(msg).unwrap(); + kp.public().verify_signature(msg, &sig).unwrap(); + } + + #[test] + fn keypair_equality() { + let kp = KeyPair::generate(); + let kp2 = KeyPair::from_private(&kp.private()); + assert_eq!(kp, kp2); + } + + #[test] + fn invalid_private_key_size() { + assert!(matches!( + PrivateKey::from_bytes(&[0xaa]), + Err(Format::InvalidKeySize(1)) + )); + assert!(matches!( + PrivateKey::from_bytes(&[0u8; 31]), + Err(Format::InvalidKeySize(31)) + )); + assert!(matches!( + PrivateKey::from_bytes(&[0u8; 33]), + Err(Format::InvalidKeySize(33)) + )); + } + + #[test] + fn invalid_public_key_size() { + assert!(matches!( + PublicKey::from_bytes(&[0xaa]), + Err(Format::InvalidKeySize(1)) + )); + assert!(matches!( + PublicKey::from_bytes(&[0u8; 32]), + Err(Format::InvalidKeySize(32)) + )); + } + + #[test] + fn algorithm_identifiers() { + let kp = KeyPair::generate(); + assert_eq!(kp.algorithm(), Algorithm::Secp256r1); + assert_eq!(kp.public().algorithm(), Algorithm::Secp256r1); + assert_eq!(kp.private().algorithm(), Algorithm::Secp256r1); + } + + #[test] + fn public_key_display() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + let display = format!("{pub_key}"); + assert!(display.starts_with("secp256r1/")); + // The hex portion should be 66 chars (33 compressed bytes) + let hex_part = display.strip_prefix("secp256r1/").unwrap(); + assert_eq!(hex_part.len(), 66); + } + + #[test] + fn public_key_hash_consistency() { + use std::collections::HashSet; + let kp = KeyPair::generate(); + let pub1 = kp.public(); + let pub2 = PublicKey::from_bytes(&pub1.to_bytes()).unwrap(); + + let mut set = HashSet::new(); + set.insert(pub1); + assert!(set.contains(&pub2)); + } + + #[test] + fn compress_decompress_roundtrip() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + let uncompressed: &[u8] = pub_key.0.as_ref(); + let compressed = compress_p256(uncompressed); + let decompressed = decompress_p256(&compressed).unwrap(); + assert_eq!(uncompressed, &decompressed); + } + + #[cfg(feature = "pem")] + mod pem_tests { + use super::*; + use crate::crypto::KeyPemSerialization; + + #[test] + fn private_key_pem_roundtrip() { + let kp = KeyPair::generate(); + let priv_key = kp.private(); + let pem = priv_key.to_pem().unwrap(); + assert!(pem.contains("EC PRIVATE KEY")); + let restored = PrivateKey::from_pem(&pem).unwrap(); + assert_eq!(priv_key, restored); + } + + #[test] + fn private_key_der_roundtrip() { + let kp = KeyPair::generate(); + let priv_key = kp.private(); + let der = priv_key.to_der().unwrap(); + let restored = PrivateKey::from_der(&der).unwrap(); + assert_eq!(priv_key, restored); + } + + #[test] + fn public_key_pem_roundtrip() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + let pem = pub_key.to_pem().unwrap(); + assert!(pem.contains("PUBLIC KEY")); + let restored = PublicKey::from_pem(&pem).unwrap(); + assert_eq!(pub_key, restored); + } + + #[test] + fn public_key_der_roundtrip() { + let kp = KeyPair::generate(); + let pub_key = kp.public(); + let der = pub_key.to_der().unwrap(); + let restored = PublicKey::from_der(&der).unwrap(); + assert_eq!(pub_key, restored); + } + + #[test] + fn private_key_wrong_pem_label() { + let kp = KeyPair::generate(); + let pem = kp.private().to_pem().unwrap(); + // Tamper with the label + let bad_pem = pem.replace("EC PRIVATE KEY", "PUBLIC KEY"); + assert!(PrivateKey::from_pem(&bad_pem).is_err()); + } + + #[test] + fn public_key_wrong_pem_label() { + let kp = KeyPair::generate(); + let pem = kp.public().to_pem().unwrap(); + let bad_pem = pem.replace("PUBLIC KEY", "PRIVATE KEY"); + assert!(PublicKey::from_pem(&bad_pem).is_err()); + } + } +} diff --git a/biscuit-auth/src/crypto/default/ed25519.rs b/biscuit-auth/src/crypto/default/ed25519.rs new file mode 100644 index 00000000..bbf2855b --- /dev/null +++ b/biscuit-auth/src/crypto/default/ed25519.rs @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2019 Geoffroy Couprie and Contributors to the Eclipse Foundation. + * SPDX-License-Identifier: Apache-2.0 + */ +//! cryptographic operations +//! +//! Biscuit tokens are based on a chain of Ed25519 signatures. +//! This provides the fundamental operation for offline delegation: from a message +//! and a valid signature, it is possible to add a new message and produce a valid +//! signature for the whole. +//! +//! The implementation is based on [ed25519_dalek](https://github.com/dalek-cryptography/ed25519-dalek). +#![allow(non_snake_case)] + +use crate::{error, error::Format}; +use std::convert::TryInto; +use std::fmt::Display; +use std::hash::{Hash, Hasher}; + +use crate::crypto::{KeyPairImpl, KeySerialization, PrivateKeyImpl, PublicKeyImpl, Signature}; +use crate::format::schema::public_key::Algorithm; +use ed25519_dalek::*; +use rand_core::{CryptoRng, RngCore}; +use zeroize::Zeroizing; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PublicKey(VerifyingKey); + +#[cfg(feature = "pem")] +impl crate::crypto::KeyPemSerialization for PublicKey { + fn from_der(bytes: &[u8]) -> Result { + use ed25519_dalek::pkcs8::DecodePublicKey; + let pubkey = VerifyingKey::from_public_key_der(bytes) + .map_err(|e| Format::InvalidKey(e.to_string()))?; + Ok(Self(pubkey)) + } + + fn from_pem(str: &str) -> Result { + use ed25519_dalek::pkcs8::DecodePublicKey; + let pubkey = VerifyingKey::from_public_key_pem(str) + .map_err(|e| Format::InvalidKey(e.to_string()))?; + Ok(Self(pubkey)) + } + + fn to_der(&self) -> Result { + use ed25519_dalek::pkcs8::EncodePublicKey; + self.0 + .to_public_key_der() + .map(|b| b.to_vec()) + .map_err(|e| Format::PKCS8(e.to_string())) + } + + fn to_pem(&self) -> Result { + use ed25519_dalek::pkcs8::spki::der::pem::LineEnding; + use ed25519_dalek::pkcs8::EncodePublicKey; + self.0 + .to_public_key_pem(LineEnding::LF) + .map_err(|e| Format::PKCS8(e.to_string())) + } +} + +impl Hash for PublicKey { + fn hash(&self, state: &mut H) { + (self.algorithm() as i32).hash(state); + self.0.as_bytes().hash(state); + } +} + +impl Display for PublicKey { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "ed25519/{}", self.to_bytes_hex()) + } +} + +impl KeySerialization for PublicKey { + type Bytes = Vec; + type String = String; + + fn to_bytes(&self) -> Self::Bytes { + self.0.as_bytes().to_vec() + } + + fn from_bytes(bytes: &[u8]) -> Result { + let bytes: [u8; 32] = bytes + .try_into() + .map_err(|_| Format::InvalidKeySize(bytes.len()))?; + VerifyingKey::from_bytes(&bytes) + .map(Self) + .map_err(|s| s.to_string()) + .map_err(Format::InvalidKey) + } +} + +impl PublicKeyImpl for PublicKey { + fn verify_signature(&self, data: &[u8], signature: &Signature) -> Result<(), Format> { + let signature_bytes: [u8; 64] = signature.0.as_slice().try_into().map_err(|e| { + Format::BlockDeserializationError(format!( + "block signature deserialization error: {:?}", + e + )) + })?; + let sig = ed25519_dalek::Signature::from_bytes(&signature_bytes); + self.0 + .verify_strict(data, &sig) + .map_err(|s| s.to_string()) + .map_err(error::Signature::InvalidSignature) + .map_err(Format::Signature) + } + + fn algorithm(&self) -> Algorithm { + Algorithm::Ed25519 + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PrivateKey(SecretKey); + +#[cfg(feature = "pem")] +impl crate::crypto::KeyPemSerialization for PrivateKey { + fn from_der(bytes: &[u8]) -> Result { + use ed25519_dalek::pkcs8::DecodePrivateKey; + SigningKey::from_pkcs8_der(bytes) + .map(|kp| Self(kp.to_bytes())) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey) + } + + fn from_pem(str: &str) -> Result { + use ed25519_dalek::pkcs8::DecodePrivateKey; + SigningKey::from_pkcs8_pem(str) + .map(|kp| Self(kp.to_bytes())) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey) + } + + fn to_der(&self) -> Result { + use ed25519_dalek::pkcs8::EncodePrivateKey; + let kp = SigningKey::from_bytes(&self.0); + kp.to_pkcs8_der() + .map(|d| d.to_bytes()) + .map_err(|e| Format::PKCS8(e.to_string())) + } + + fn to_pem(&self) -> Result { + use ed25519_dalek::pkcs8::spki::der::pem::LineEnding; + use ed25519_dalek::pkcs8::EncodePrivateKey; + let kp = SigningKey::from_bytes(&self.0); + kp.to_pkcs8_pem(LineEnding::LF) + .map_err(|e| Format::PKCS8(e.to_string())) + } +} + +impl KeySerialization for PrivateKey { + type Bytes = Zeroizing>; + type String = Zeroizing; + + fn to_bytes(&self) -> Self::Bytes { + self.0.to_vec().into() + } + + fn from_bytes(bytes: &[u8]) -> Result { + let bytes: [u8; 32] = bytes + .try_into() + .map_err(|_| Format::InvalidKeySize(bytes.len()))?; + Ok(Self(bytes)) + } +} + +impl PrivateKeyImpl for PrivateKey { + type PublicKey = PublicKey; + + fn public(&self) -> Self::PublicKey { + let kp = SigningKey::from_bytes(&self.0); + PublicKey(kp.verifying_key()) + } + + fn algorithm(&self) -> Algorithm { + Algorithm::Ed25519 + } +} + +#[derive(Debug, PartialEq)] +pub struct KeyPair(SigningKey); + +impl KeyPairImpl for KeyPair { + type PublicKey = PublicKey; + type PrivateKey = PrivateKey; + + fn sign(&self, data: &[u8]) -> Result { + Ok(Signature( + self.0 + .try_sign(data) + .map_err(|s| s.to_string()) + .map_err(error::Signature::InvalidSignatureGeneration) + .map_err(Format::Signature)? + .to_bytes() + .to_vec(), + )) + } + + fn from_private(key: &Self::PrivateKey) -> Self { + Self(SigningKey::from(key.0)) + } + + fn public(&self) -> Self::PublicKey { + PublicKey(self.0.verifying_key()) + } + + fn algorithm(&self) -> Algorithm { + Algorithm::Ed25519 + } + + fn private(&self) -> Self::PrivateKey { + PrivateKey(self.0.to_bytes()) + } + + fn generate() -> Self { + Self::generate_with_rng(&mut rand::rngs::OsRng) + } + + fn generate_with_rng(rng: &mut R) -> Self { + let kp = SigningKey::generate(rng); + Self(kp) + } +} diff --git a/biscuit-auth/src/crypto/default/mod.rs b/biscuit-auth/src/crypto/default/mod.rs new file mode 100644 index 00000000..41b30f58 --- /dev/null +++ b/biscuit-auth/src/crypto/default/mod.rs @@ -0,0 +1,2 @@ +pub mod ed25519; +pub mod secp256r1; diff --git a/biscuit-auth/src/crypto/default/secp256r1.rs b/biscuit-auth/src/crypto/default/secp256r1.rs new file mode 100644 index 00000000..4551e077 --- /dev/null +++ b/biscuit-auth/src/crypto/default/secp256r1.rs @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2019 Geoffroy Couprie and Contributors to the Eclipse Foundation. + * SPDX-License-Identifier: Apache-2.0 + */ +#![allow(non_snake_case)] + +use crate::error::Format; +use std::fmt::{Display, Formatter}; + +use crate::crypto::{ + error, KeyPairImpl, KeySerialization, PrivateKeyImpl, PublicKeyImpl, Signature, +}; + +use crate::format::schema::public_key::Algorithm; +use p256::ecdsa::{signature::Signer, signature::Verifier, SigningKey, VerifyingKey}; +use p256::elliptic_curve::rand_core::{CryptoRng, OsRng, RngCore}; +use p256::SecretKey; +use std::hash::{Hash, Hasher}; +use zeroize::Zeroizing; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct PublicKey(VerifyingKey); + +#[cfg(feature = "pem")] +impl crate::crypto::KeyPemSerialization for PublicKey { + fn from_der(bytes: &[u8]) -> Result { + use p256::pkcs8::DecodePublicKey; + let pubkey = VerifyingKey::from_public_key_der(bytes) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + Ok(Self(pubkey)) + } + + fn from_pem(str: &str) -> Result { + use p256::pkcs8::DecodePublicKey; + + let pubkey = VerifyingKey::from_public_key_pem(str) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey)?; + Ok(Self(pubkey)) + } + + fn to_der(&self) -> Result { + use p256::pkcs8::EncodePublicKey; + self.0 + .to_public_key_der() + .map(|b| b.to_vec()) + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8) + } + + fn to_pem(&self) -> Result { + use p256::pkcs8::EncodePublicKey; + use p256::pkcs8::LineEnding; + self.0 + .to_public_key_pem(LineEnding::LF) + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8) + } +} + +impl KeySerialization for PublicKey { + type Bytes = Vec; + type String = String; + + fn to_bytes(&self) -> Self::Bytes { + self.0.to_encoded_point(true).as_bytes().to_vec() + } + + fn from_bytes(bytes: &[u8]) -> Result { + let key = VerifyingKey::from_sec1_bytes(bytes) + .map_err(|s| s.to_string()) + .map_err(Format::InvalidKey)?; + Ok(Self(key)) + } +} + +impl Display for PublicKey { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "secp256r1/{}", self.to_bytes_hex()) + } +} + +impl PublicKeyImpl for PublicKey { + fn verify_signature(&self, data: &[u8], signature: &Signature) -> Result<(), Format> { + let sig = p256::ecdsa::Signature::from_der(&signature.0).map_err(|e| { + Format::BlockSignatureDeserializationError(format!( + "block signature deserialization error: {:?}", + e + )) + })?; + + self.0 + .verify(data, &sig) + .map_err(|s| s.to_string()) + .map_err(error::Signature::InvalidSignature) + .map_err(Format::Signature) + } + + fn algorithm(&self) -> Algorithm { + Algorithm::Secp256r1 + } +} + +impl Hash for PublicKey { + fn hash(&self, state: &mut H) { + (self.algorithm() as i32).hash(state); + self.0.to_encoded_point(true).as_bytes().hash(state); + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct PrivateKey(SecretKey); + +impl KeySerialization for PrivateKey { + type Bytes = Zeroizing>; + type String = Zeroizing; + + fn to_bytes(&self) -> Self::Bytes { + self.0.to_bytes().to_vec().into() + } + + fn from_bytes(bytes: &[u8]) -> Result { + if bytes.len() != 32 { + return Err(Format::InvalidKeySize(bytes.len())); + } + SecretKey::from_bytes(bytes.into()) + .map(Self) + .map_err(|s| s.to_string()) + .map_err(Format::InvalidKey) + } +} + +#[cfg(feature = "pem")] +impl crate::crypto::KeyPemSerialization for PrivateKey { + fn from_der(bytes: &[u8]) -> Result { + use p256::pkcs8::DecodePrivateKey; + SecretKey::from_pkcs8_der(bytes) + .map(Self) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey) + } + + fn from_pem(str: &str) -> Result { + use p256::pkcs8::DecodePrivateKey; + SecretKey::from_pkcs8_pem(str) + .map(Self) + .map_err(|e| e.to_string()) + .map_err(Format::InvalidKey) + } + + fn to_der(&self) -> Result { + use p256::pkcs8::EncodePrivateKey; + self.0 + .to_pkcs8_der() + .map(|d| d.to_bytes()) + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8) + } + + fn to_pem(&self) -> Result { + use p256::pkcs8::EncodePrivateKey; + use p256::pkcs8::LineEnding; + self.0 + .to_pkcs8_pem(LineEnding::LF) + .map_err(|e| e.to_string()) + .map_err(Format::PKCS8) + } +} + +impl PrivateKeyImpl for PrivateKey { + type PublicKey = PublicKey; + + fn public(&self) -> Self::PublicKey { + let kp: SigningKey = self.0.clone().into(); + PublicKey(*kp.verifying_key()) + } + + fn algorithm(&self) -> Algorithm { + Algorithm::Secp256r1 + } +} + +#[derive(Debug, PartialEq)] +pub struct KeyPair(SigningKey); + +impl KeyPairImpl for KeyPair { + type PublicKey = PublicKey; + type PrivateKey = PrivateKey; + + fn sign(&self, data: &[u8]) -> Result { + let signature: p256::ecdsa::Signature = self + .0 + .try_sign(data) + .map_err(|s| s.to_string()) + .map_err(error::Signature::InvalidSignatureGeneration) + .map_err(Format::Signature)?; + Ok(Signature(signature.to_der().as_bytes().to_vec())) + } + + fn from_private(key: &Self::PrivateKey) -> Self { + let kp: SigningKey = key.0.clone().into(); + Self(kp) + } + + fn public(&self) -> Self::PublicKey { + PublicKey(*self.0.verifying_key()) + } + + fn algorithm(&self) -> Algorithm { + Algorithm::Secp256r1 + } + + fn private(&self) -> Self::PrivateKey { + PrivateKey(SecretKey::new(self.0.as_nonzero_scalar().into())) + } + + fn generate() -> Self { + Self::generate_with_rng(&mut OsRng) + } + + fn generate_with_rng(rng: &mut R) -> Self { + Self(SigningKey::random(rng)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn serialization() { + let keypair = KeyPair::generate(); + let public = keypair.public(); + let private = keypair.private(); + let private_hex = private.to_bytes_hex(); + let public_hex = public.to_bytes_hex(); + + println!("private: {}", private_hex.as_str()); + println!("public: {public_hex}"); + + let message = "hello world"; + let signature = keypair.sign(message.as_bytes()).unwrap(); + println!("signature: {}", hex::encode(&signature.0)); + + let deserialized_priv = PrivateKey::from_bytes_hex(&private_hex).unwrap(); + let deserialized_pub = PublicKey::from_bytes_hex(&public_hex).unwrap(); + + assert_eq!(private.0.to_bytes(), deserialized_priv.0.to_bytes()); + assert_eq!(public, deserialized_pub); + + deserialized_pub + .verify_signature(message.as_bytes(), &signature) + .unwrap(); + //panic!(); + } + + #[test] + fn invalid_sizes() { + assert_eq!( + PrivateKey::from_bytes(&[0xaa]).unwrap_err(), + Format::InvalidKeySize(1) + ); + assert_eq!( + PrivateKey::from_bytes(&[0xaa]).unwrap_err(), + Format::InvalidKeySize(1) + ); + PublicKey::from_bytes(&[0xaa]).unwrap_err(); + } +} diff --git a/biscuit-auth/src/crypto/ed25519.rs b/biscuit-auth/src/crypto/ed25519.rs deleted file mode 100644 index 9782fc22..00000000 --- a/biscuit-auth/src/crypto/ed25519.rs +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Copyright (c) 2019 Geoffroy Couprie and Contributors to the Eclipse Foundation. - * SPDX-License-Identifier: Apache-2.0 - */ -//! cryptographic operations -//! -//! Biscuit tokens are based on a chain of Ed25519 signatures. -//! This provides the fundamental operation for offline delegation: from a message -//! and a valid signature, it is possible to add a new message and produce a valid -//! signature for the whole. -//! -//! The implementation is based on [ed25519_dalek](https://github.com/dalek-cryptography/ed25519-dalek). -#![allow(non_snake_case)] -use crate::{error::Format, format::schema}; - -use super::error; -use super::Signature; -#[cfg(feature = "pem")] -use ed25519_dalek::pkcs8::DecodePrivateKey; -use ed25519_dalek::Signer; -use ed25519_dalek::*; -use rand_core::{CryptoRng, RngCore}; -use std::{convert::TryInto, hash::Hash, ops::Drop}; -use zeroize::Zeroize; - -/// pair of cryptographic keys used to sign a token's block -#[derive(Debug, PartialEq)] -pub struct KeyPair { - pub(super) kp: ed25519_dalek::SigningKey, -} - -impl KeyPair { - pub fn new() -> Self { - Self::new_with_rng(&mut rand::rngs::OsRng) - } - - pub fn new_with_rng(rng: &mut T) -> Self { - let kp = ed25519_dalek::SigningKey::generate(rng); - KeyPair { kp } - } - - pub fn from(key: &PrivateKey) -> Self { - KeyPair { - kp: ed25519_dalek::SigningKey::from_bytes(&key.0), - } - } - - /// deserializes from a byte array - pub fn from_bytes(bytes: &[u8]) -> Result { - let bytes: [u8; 32] = bytes - .try_into() - .map_err(|_| Format::InvalidKeySize(bytes.len()))?; - - Ok(KeyPair { - kp: ed25519_dalek::SigningKey::from_bytes(&bytes), - }) - } - - pub fn sign(&self, data: &[u8]) -> Result { - Ok(Signature( - self.kp - .try_sign(&data) - .map_err(|s| s.to_string()) - .map_err(error::Signature::InvalidSignatureGeneration) - .map_err(error::Format::Signature)? - .to_bytes() - .to_vec(), - )) - } - - pub fn private(&self) -> PrivateKey { - PrivateKey(self.kp.to_bytes()) - } - - pub fn public(&self) -> PublicKey { - PublicKey(self.kp.verifying_key()) - } - - pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { - crate::format::schema::public_key::Algorithm::Ed25519 - } - - #[cfg(feature = "pem")] - pub fn from_private_key_der(bytes: &[u8]) -> Result { - let kp = SigningKey::from_pkcs8_der(bytes) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(KeyPair { kp }) - } - - #[cfg(feature = "pem")] - pub fn from_private_key_pem(str: &str) -> Result { - let kp = SigningKey::from_pkcs8_pem(str) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(KeyPair { kp }) - } - - #[cfg(feature = "pem")] - pub fn to_private_key_der(&self) -> Result>, error::Format> { - use ed25519_dalek::pkcs8::EncodePrivateKey; - let kp = self - .kp - .to_pkcs8_der() - .map_err(|e| error::Format::PKCS8(e.to_string()))?; - Ok(kp.to_bytes()) - } - - #[cfg(feature = "pem")] - pub fn to_private_key_pem(&self) -> Result, error::Format> { - use ed25519_dalek::pkcs8::EncodePrivateKey; - use p256::pkcs8::LineEnding; - let kp = self - .kp - .to_pkcs8_pem(LineEnding::LF) - .map_err(|e| error::Format::PKCS8(e.to_string()))?; - Ok(kp) - } -} - -impl std::default::Default for KeyPair { - fn default() -> Self { - Self::new() - } -} - -/// the private part of a [KeyPair] -#[derive(Debug, PartialEq)] -pub struct PrivateKey(pub(crate) ed25519_dalek::SecretKey); - -impl PrivateKey { - /// serializes to a byte array - pub fn to_bytes(&self) -> Vec { - self.0.to_vec() - } - - /// serializes to an hex-encoded string - pub fn to_bytes_hex(&self) -> String { - hex::encode(self.0) - } - - /// deserializes from a byte array - pub fn from_bytes(bytes: &[u8]) -> Result { - let bytes: [u8; 32] = bytes - .try_into() - .map_err(|_| Format::InvalidKeySize(bytes.len()))?; - Ok(PrivateKey(bytes)) - } - - /// deserializes from an hex-encoded string - pub fn from_bytes_hex(str: &str) -> Result { - let bytes = hex::decode(str).map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Self::from_bytes(&bytes) - } - - #[cfg(feature = "pem")] - pub fn from_der(bytes: &[u8]) -> Result { - let kp = SigningKey::from_pkcs8_der(bytes) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(PrivateKey(kp.to_bytes())) - } - - #[cfg(feature = "pem")] - pub fn from_pem(str: &str) -> Result { - let kp = SigningKey::from_pkcs8_pem(str) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(PrivateKey(kp.to_bytes())) - } - - #[cfg(feature = "pem")] - pub fn to_der(&self) -> Result>, error::Format> { - use ed25519_dalek::pkcs8::EncodePrivateKey; - let kp = ed25519_dalek::SigningKey::from_bytes(&self.0) - .to_pkcs8_der() - .map_err(|e| error::Format::PKCS8(e.to_string()))?; - Ok(kp.to_bytes()) - } - - #[cfg(feature = "pem")] - pub fn to_pem(&self) -> Result, error::Format> { - use ed25519_dalek::pkcs8::EncodePrivateKey; - use p256::pkcs8::LineEnding; - let kp = ed25519_dalek::SigningKey::from_bytes(&self.0) - .to_pkcs8_pem(LineEnding::LF) - .map_err(|e| error::Format::PKCS8(e.to_string()))?; - Ok(kp) - } - - /// returns the matching public key - pub fn public(&self) -> PublicKey { - PublicKey(SigningKey::from_bytes(&self.0).verifying_key()) - } - - pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { - crate::format::schema::public_key::Algorithm::Ed25519 - } -} - -impl std::clone::Clone for PrivateKey { - fn clone(&self) -> Self { - PrivateKey::from_bytes(&self.to_bytes()).unwrap() - } -} - -impl Drop for PrivateKey { - fn drop(&mut self) { - self.0.zeroize(); - } -} - -/// the public part of a [KeyPair] -#[derive(Debug, Clone, Copy, Eq)] -pub struct PublicKey(ed25519_dalek::VerifyingKey); - -impl PublicKey { - /// serializes to a byte array - pub fn to_bytes(&self) -> [u8; 32] { - self.0.to_bytes() - } - - /// serializes to an hex-encoded string - pub fn to_bytes_hex(&self) -> String { - hex::encode(self.to_bytes()) - } - - /// deserializes from a byte array - pub fn from_bytes(bytes: &[u8]) -> Result { - let bytes: [u8; 32] = bytes - .try_into() - .map_err(|_| Format::InvalidKeySize(bytes.len()))?; - - ed25519_dalek::VerifyingKey::from_bytes(&bytes) - .map(PublicKey) - .map_err(|s| s.to_string()) - .map_err(Format::InvalidKey) - } - - /// deserializes from an hex-encoded string - pub fn from_bytes_hex(str: &str) -> Result { - let bytes = hex::decode(str).map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Self::from_bytes(&bytes) - } - - pub fn from_proto(key: &schema::PublicKey) -> Result { - if key.algorithm != schema::public_key::Algorithm::Ed25519 as i32 { - return Err(error::Format::DeserializationError(format!( - "deserialization error: unexpected key algorithm {}", - key.algorithm - ))); - } - - PublicKey::from_bytes(&key.key) - } - - pub fn to_proto(&self) -> schema::PublicKey { - schema::PublicKey { - algorithm: schema::public_key::Algorithm::Ed25519 as i32, - key: self.to_bytes().to_vec(), - } - } - - pub fn verify_signature( - &self, - data: &[u8], - signature: &Signature, - ) -> Result<(), error::Format> { - let signature_bytes: [u8; 64] = signature.0.clone().try_into().map_err(|e| { - error::Format::BlockSignatureDeserializationError(format!( - "block signature deserialization error: {:?}", - e - )) - })?; - let sig = ed25519_dalek::Signature::from_bytes(&signature_bytes); - - self.0 - .verify_strict(&data, &sig) - .map_err(|s| s.to_string()) - .map_err(error::Signature::InvalidSignature) - .map_err(error::Format::Signature) - } - - pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { - crate::format::schema::public_key::Algorithm::Ed25519 - } - - #[cfg(feature = "pem")] - pub fn from_der(bytes: &[u8]) -> Result { - use ed25519_dalek::pkcs8::DecodePublicKey; - - let pubkey = ed25519_dalek::VerifyingKey::from_public_key_der(bytes) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(PublicKey(pubkey)) - } - - #[cfg(feature = "pem")] - pub fn from_pem(str: &str) -> Result { - use ed25519_dalek::pkcs8::DecodePublicKey; - - let pubkey = ed25519_dalek::VerifyingKey::from_public_key_pem(str) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(PublicKey(pubkey)) - } - - #[cfg(feature = "pem")] - pub fn to_der(&self) -> Result, error::Format> { - use ed25519_dalek::pkcs8::EncodePublicKey; - let kp = self - .0 - .to_public_key_der() - .map_err(|e| error::Format::PKCS8(e.to_string()))?; - Ok(kp.to_vec()) - } - - #[cfg(feature = "pem")] - pub fn to_pem(&self) -> Result { - use ed25519_dalek::pkcs8::EncodePublicKey; - use p256::pkcs8::LineEnding; - let kp = self - .0 - .to_public_key_pem(LineEnding::LF) - .map_err(|e| error::Format::PKCS8(e.to_string()))?; - Ok(kp) - } - - pub(crate) fn write(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ed25519/{}", hex::encode(&self.to_bytes())) - } - - pub fn print(&self) -> String { - format!("ed25519/{}", hex::encode(&self.to_bytes())) - } -} - -impl PartialEq for PublicKey { - fn eq(&self, other: &Self) -> bool { - self.0.to_bytes() == other.0.to_bytes() - } -} - -impl Hash for PublicKey { - fn hash(&self, state: &mut H) { - (crate::format::schema::public_key::Algorithm::Ed25519 as i32).hash(state); - self.0.to_bytes().hash(state); - } -} diff --git a/biscuit-auth/src/crypto/mod.rs b/biscuit-auth/src/crypto/mod.rs index 1c1054ce..bf2efe1f 100644 --- a/biscuit-auth/src/crypto/mod.rs +++ b/biscuit-auth/src/crypto/mod.rs @@ -16,44 +16,75 @@ use crate::format::schema; use crate::format::ThirdPartyVerificationMode; use super::error; -mod ed25519; -mod p256; +#[cfg(all(feature = "awslc-backend", not(feature = "default-backend")))] +mod awslc; +#[cfg(feature = "default-backend")] +mod default; +mod traits; use nom::Finish; use rand_core::{CryptoRng, RngCore}; use std::fmt; -use std::hash::Hash; use std::str::FromStr; +pub use traits::*; +use zeroize::Zeroizing; + +/// The default cryptographic backend using ed25519-dalek and p256 crates. +pub struct DefaultBackend; + +#[cfg(feature = "default-backend")] +impl Backend for DefaultBackend { + type Ed25519 = default::ed25519::KeyPair; + type P256 = default::secp256r1::KeyPair; +} + +#[cfg(all(feature = "awslc-backend", not(feature = "default-backend")))] +impl Backend for DefaultBackend { + type Ed25519 = awslc::ed25519::KeyPair; + type P256 = awslc::secp256r1::KeyPair; +} + +type Ed25519KeyPair = ::Ed25519; +type Ed25519PrivateKey = ::PrivateKey; +type Ed25519PublicKey = ::PublicKey; +type P256KeyPair = ::P256; +type P256PrivateKey = ::PrivateKey; +type P256PublicKey = ::PublicKey; /// pair of cryptographic keys used to sign a token's block #[derive(Debug, PartialEq)] pub enum KeyPair { - Ed25519(ed25519::KeyPair), - P256(p256::KeyPair), + Ed25519(Ed25519KeyPair), + P256(P256KeyPair), } impl KeyPair { /// Create a new ed25519 keypair with the default OS RNG pub fn new() -> Self { - Self::new_with_rng(Algorithm::Ed25519, &mut rand::rngs::OsRng) + Self::new_with_algorithm(Algorithm::Ed25519) } /// Create a new keypair with a chosen algorithm and the default OS RNG pub fn new_with_algorithm(algorithm: Algorithm) -> Self { - Self::new_with_rng(algorithm, &mut rand::rngs::OsRng) + // don't use new_with_rng here, some crypto libraries don't support + // custom rng + match algorithm { + Algorithm::Ed25519 => KeyPair::Ed25519(Ed25519KeyPair::generate()), + Algorithm::Secp256r1 => KeyPair::P256(P256KeyPair::generate()), + } } pub fn new_with_rng(algorithm: Algorithm, rng: &mut T) -> Self { match algorithm { - Algorithm::Ed25519 => KeyPair::Ed25519(ed25519::KeyPair::new_with_rng(rng)), - Algorithm::Secp256r1 => KeyPair::P256(p256::KeyPair::new_with_rng(rng)), + Algorithm::Ed25519 => KeyPair::Ed25519(Ed25519KeyPair::generate_with_rng(rng)), + Algorithm::Secp256r1 => KeyPair::P256(P256KeyPair::generate_with_rng(rng)), } } pub fn from(key: &PrivateKey) -> Self { match key { - PrivateKey::Ed25519(key) => KeyPair::Ed25519(ed25519::KeyPair::from(key)), - PrivateKey::P256(key) => KeyPair::P256(p256::KeyPair::from(key)), + PrivateKey::Ed25519(key) => KeyPair::Ed25519(Ed25519KeyPair::from_private(key)), + PrivateKey::P256(key) => KeyPair::P256(P256KeyPair::from_private(key)), } } @@ -62,14 +93,15 @@ impl KeyPair { bytes: &[u8], algorithm: schema::public_key::Algorithm, ) -> Result { - match algorithm { + let private_key = match algorithm { schema::public_key::Algorithm::Ed25519 => { - Ok(KeyPair::Ed25519(ed25519::KeyPair::from_bytes(bytes)?)) + PrivateKey::from_bytes(bytes, Algorithm::Ed25519) } schema::public_key::Algorithm::Secp256r1 => { - Ok(KeyPair::P256(p256::KeyPair::from_bytes(bytes)?)) + PrivateKey::from_bytes(bytes, Algorithm::Secp256r1) } - } + }?; + Ok(Self::from(&private_key)) } pub fn sign(&self, data: &[u8]) -> Result { @@ -84,12 +116,8 @@ impl KeyPair { bytes: &[u8], algorithm: Algorithm, ) -> Result { - match algorithm { - Algorithm::Ed25519 => Ok(KeyPair::Ed25519(ed25519::KeyPair::from_private_key_der( - bytes, - )?)), - Algorithm::Secp256r1 => Ok(KeyPair::P256(p256::KeyPair::from_private_key_der(bytes)?)), - } + let private_key = PrivateKey::from_der_with_algorithm(bytes, algorithm)?; + Ok(Self::from(&private_key)) } #[cfg(feature = "pem")] @@ -102,12 +130,8 @@ impl KeyPair { str: &str, algorithm: Algorithm, ) -> Result { - match algorithm { - Algorithm::Ed25519 => Ok(KeyPair::Ed25519(ed25519::KeyPair::from_private_key_pem( - str, - )?)), - Algorithm::Secp256r1 => Ok(KeyPair::P256(p256::KeyPair::from_private_key_pem(str)?)), - } + let private_key = PrivateKey::from_pem_with_algorithm(str, algorithm)?; + Ok(Self::from(&private_key)) } #[cfg(feature = "pem")] @@ -116,18 +140,18 @@ impl KeyPair { } #[cfg(feature = "pem")] - pub fn to_private_key_der(&self) -> Result>, error::Format> { + pub fn to_private_key_der(&self) -> Result>, error::Format> { match self { - KeyPair::Ed25519(key) => key.to_private_key_der(), - KeyPair::P256(key) => key.to_private_key_der(), + KeyPair::Ed25519(key) => key.private().to_der(), + KeyPair::P256(key) => key.private().to_der(), } } #[cfg(feature = "pem")] - pub fn to_private_key_pem(&self) -> Result, error::Format> { + pub fn to_private_key_pem(&self) -> Result, error::Format> { match self { - KeyPair::Ed25519(key) => key.to_private_key_pem(), - KeyPair::P256(key) => key.to_private_key_pem(), + KeyPair::Ed25519(key) => key.private().to_pem(), + KeyPair::P256(key) => key.private().to_pem(), } } @@ -145,33 +169,37 @@ impl KeyPair { } } - pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + pub fn algorithm(&self) -> schema::public_key::Algorithm { match self { - KeyPair::Ed25519(_) => crate::format::schema::public_key::Algorithm::Ed25519, - KeyPair::P256(_) => crate::format::schema::public_key::Algorithm::Secp256r1, + KeyPair::Ed25519(_) => schema::public_key::Algorithm::Ed25519, + KeyPair::P256(_) => schema::public_key::Algorithm::Secp256r1, } } } -impl std::default::Default for KeyPair { +impl Default for KeyPair { fn default() -> Self { Self::new() } } /// the private part of a [KeyPair] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum PrivateKey { - Ed25519(ed25519::PrivateKey), - P256(p256::PrivateKey), + Ed25519(Ed25519PrivateKey), + P256(P256PrivateKey), } impl FromStr for PrivateKey { type Err = error::Format; fn from_str(s: &str) -> Result { match s.split_once('/') { - Some(("ed25519-private", bytes)) => Self::from_bytes_hex(bytes, Algorithm::Ed25519), - Some(("secp256r1-private", bytes)) => Self::from_bytes_hex(bytes, Algorithm::Secp256r1), + Some(("ed25519-private", bytes)) => Ok(PrivateKey::Ed25519( + Ed25519PrivateKey::from_bytes_hex(bytes)?, + )), + Some(("secp256r1-private", bytes)) => { + Ok(PrivateKey::P256(P256PrivateKey::from_bytes_hex(bytes)?)) + } Some((alg, _)) => Err(error::Format::InvalidKey(format!( "Unsupported key algorithm {alg}" ))), @@ -184,9 +212,9 @@ impl FromStr for PrivateKey { impl PrivateKey { /// serializes to a byte array - pub fn to_bytes(&self) -> zeroize::Zeroizing> { + pub fn to_bytes(&self) -> Zeroizing> { match self { - PrivateKey::Ed25519(key) => zeroize::Zeroizing::new(key.to_bytes()), + PrivateKey::Ed25519(key) => key.to_bytes(), PrivateKey::P256(key) => key.to_bytes(), } } @@ -208,8 +236,8 @@ impl PrivateKey { /// deserializes from a byte array pub fn from_bytes(bytes: &[u8], algorithm: Algorithm) -> Result { match algorithm { - Algorithm::Ed25519 => Ok(PrivateKey::Ed25519(ed25519::PrivateKey::from_bytes(bytes)?)), - Algorithm::Secp256r1 => Ok(PrivateKey::P256(p256::PrivateKey::from_bytes(bytes)?)), + Algorithm::Ed25519 => Ok(PrivateKey::Ed25519(Ed25519PrivateKey::from_bytes(bytes)?)), + Algorithm::Secp256r1 => Ok(PrivateKey::P256(P256PrivateKey::from_bytes(bytes)?)), } } @@ -225,8 +253,8 @@ impl PrivateKey { algorithm: Algorithm, ) -> Result { match algorithm { - Algorithm::Ed25519 => Ok(PrivateKey::Ed25519(ed25519::PrivateKey::from_der(bytes)?)), - Algorithm::Secp256r1 => Ok(PrivateKey::P256(p256::PrivateKey::from_der(bytes)?)), + Algorithm::Ed25519 => Ok(PrivateKey::Ed25519(Ed25519PrivateKey::from_der(bytes)?)), + Algorithm::Secp256r1 => Ok(PrivateKey::P256(P256PrivateKey::from_der(bytes)?)), } } @@ -238,8 +266,8 @@ impl PrivateKey { #[cfg(feature = "pem")] pub fn from_pem_with_algorithm(str: &str, algorithm: Algorithm) -> Result { match algorithm { - Algorithm::Ed25519 => Ok(PrivateKey::Ed25519(ed25519::PrivateKey::from_pem(str)?)), - Algorithm::Secp256r1 => Ok(PrivateKey::P256(p256::PrivateKey::from_pem(str)?)), + Algorithm::Ed25519 => Ok(PrivateKey::Ed25519(Ed25519PrivateKey::from_pem(str)?)), + Algorithm::Secp256r1 => Ok(PrivateKey::P256(P256PrivateKey::from_pem(str)?)), } } @@ -249,7 +277,7 @@ impl PrivateKey { } #[cfg(feature = "pem")] - pub fn to_der(&self) -> Result>, error::Format> { + pub fn to_der(&self) -> Result>, error::Format> { match self { PrivateKey::Ed25519(key) => key.to_der(), PrivateKey::P256(key) => key.to_der(), @@ -257,7 +285,7 @@ impl PrivateKey { } #[cfg(feature = "pem")] - pub fn to_pem(&self) -> Result, error::Format> { + pub fn to_pem(&self) -> Result, error::Format> { match self { PrivateKey::Ed25519(key) => key.to_pem(), PrivateKey::P256(key) => key.to_pem(), @@ -272,26 +300,26 @@ impl PrivateKey { } } - pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + pub fn algorithm(&self) -> schema::public_key::Algorithm { match self { - PrivateKey::Ed25519(_) => crate::format::schema::public_key::Algorithm::Ed25519, - PrivateKey::P256(_) => crate::format::schema::public_key::Algorithm::Secp256r1, + PrivateKey::Ed25519(key) => key.algorithm(), + PrivateKey::P256(key) => key.algorithm(), } } } /// the public part of a [KeyPair] -#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq)] +#[derive(Debug, PartialEq, Clone, Hash, Copy, Eq)] pub enum PublicKey { - Ed25519(ed25519::PublicKey), - P256(p256::PublicKey), + Ed25519(Ed25519PublicKey), + P256(P256PublicKey), } impl PublicKey { /// serializes to a byte array pub fn to_bytes(&self) -> Vec { match self { - PublicKey::Ed25519(key) => key.to_bytes().into(), + PublicKey::Ed25519(key) => key.to_bytes(), PublicKey::P256(key) => key.to_bytes(), } } @@ -304,8 +332,8 @@ impl PublicKey { /// deserializes from a byte array pub fn from_bytes(bytes: &[u8], algorithm: Algorithm) -> Result { match algorithm { - Algorithm::Ed25519 => Ok(PublicKey::Ed25519(ed25519::PublicKey::from_bytes(bytes)?)), - Algorithm::Secp256r1 => Ok(PublicKey::P256(p256::PublicKey::from_bytes(bytes)?)), + Algorithm::Ed25519 => Ok(PublicKey::Ed25519(Ed25519PublicKey::from_bytes(bytes)?)), + Algorithm::Secp256r1 => Ok(PublicKey::P256(P256PublicKey::from_bytes(bytes)?)), } } @@ -317,11 +345,9 @@ impl PublicKey { pub fn from_proto(key: &schema::PublicKey) -> Result { if key.algorithm == schema::public_key::Algorithm::Ed25519 as i32 { - Ok(PublicKey::Ed25519(ed25519::PublicKey::from_bytes( - &key.key, - )?)) + Self::from_bytes(&key.key, Algorithm::Ed25519) } else if key.algorithm == schema::public_key::Algorithm::Secp256r1 as i32 { - Ok(PublicKey::P256(p256::PublicKey::from_bytes(&key.key)?)) + Self::from_bytes(&key.key, Algorithm::Secp256r1) } else { Err(error::Format::DeserializationError(format!( "deserialization error: unexpected key algorithm {}", @@ -343,8 +369,8 @@ impl PublicKey { algorithm: Algorithm, ) -> Result { match algorithm { - Algorithm::Ed25519 => Ok(PublicKey::Ed25519(ed25519::PublicKey::from_der(bytes)?)), - Algorithm::Secp256r1 => Ok(PublicKey::P256(p256::PublicKey::from_der(bytes)?)), + Algorithm::Ed25519 => Ok(PublicKey::Ed25519(Ed25519PublicKey::from_der(bytes)?)), + Algorithm::Secp256r1 => Ok(PublicKey::P256(P256PublicKey::from_der(bytes)?)), } } @@ -356,8 +382,8 @@ impl PublicKey { #[cfg(feature = "pem")] pub fn from_pem_with_algorithm(str: &str, algorithm: Algorithm) -> Result { match algorithm { - Algorithm::Ed25519 => Ok(PublicKey::Ed25519(ed25519::PublicKey::from_pem(str)?)), - Algorithm::Secp256r1 => Ok(PublicKey::P256(p256::PublicKey::from_pem(str)?)), + Algorithm::Ed25519 => Ok(PublicKey::Ed25519(Ed25519PublicKey::from_pem(str)?)), + Algorithm::Secp256r1 => Ok(PublicKey::P256(P256PublicKey::from_pem(str)?)), } } @@ -393,10 +419,10 @@ impl PublicKey { } } - pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + pub fn algorithm(&self) -> schema::public_key::Algorithm { match self { - PublicKey::Ed25519(_) => crate::format::schema::public_key::Algorithm::Ed25519, - PublicKey::P256(_) => crate::format::schema::public_key::Algorithm::Secp256r1, + PublicKey::Ed25519(_) => schema::public_key::Algorithm::Ed25519, + PublicKey::P256(_) => schema::public_key::Algorithm::Secp256r1, } } @@ -407,24 +433,20 @@ impl PublicKey { } } - pub(crate) fn write(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - PublicKey::Ed25519(key) => key.write(f), - PublicKey::P256(key) => key.write(f), - } - } - pub fn print(&self) -> String { match self { - PublicKey::Ed25519(key) => key.print(), - PublicKey::P256(key) => key.print(), + PublicKey::Ed25519(key) => key.to_string(), + PublicKey::P256(key) => key.to_string(), } } } impl fmt::Display for PublicKey { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.write(f) + match self { + PublicKey::Ed25519(key) => fmt::Display::fmt(key, f), + PublicKey::P256(key) => fmt::Display::fmt(key, f), + } } } @@ -490,8 +512,8 @@ pub fn sign_authority_block( version: u32, ) -> Result { let to_sign = match version { - 0 => generate_authority_block_signature_payload_v0(&message, &next_key.public()), - 1 => generate_authority_block_signature_payload_v1(&message, &next_key.public(), version), + 0 => generate_authority_block_signature_payload_v0(message, &next_key.public()), + 1 => generate_authority_block_signature_payload_v1(message, &next_key.public(), version), _ => { return Err(error::Format::DeserializationError(format!( "unsupported block version: {}", @@ -515,9 +537,9 @@ pub fn sign_block( version: u32, ) -> Result { let to_sign = match version { - 0 => generate_block_signature_payload_v0(&message, &next_key.public(), external_signature), + 0 => generate_block_signature_payload_v0(message, &next_key.public(), external_signature), 1 => generate_block_signature_payload_v1( - &message, + message, &next_key.public(), external_signature, previous_signature, @@ -645,7 +667,7 @@ pub(crate) fn generate_block_signature_payload_v0( let mut to_verify = payload.to_vec(); if let Some(signature) = external_signature.as_ref() { - to_verify.extend_from_slice(&signature.signature.to_bytes()); + to_verify.extend_from_slice(signature.signature.to_bytes()); } to_verify.extend(&(next_key.algorithm() as i32).to_le_bytes()); to_verify.extend(next_key.to_bytes()); @@ -696,7 +718,7 @@ pub(crate) fn generate_block_signature_payload_v1( if let Some(signature) = external_signature.as_ref() { to_verify.extend(b"\0EXTERNALSIG\0".to_vec()); - to_verify.extend_from_slice(&signature.signature.to_bytes()); + to_verify.extend_from_slice(signature.signature.to_bytes()); } to_verify diff --git a/biscuit-auth/src/crypto/p256.rs b/biscuit-auth/src/crypto/p256.rs deleted file mode 100644 index a76e4ce4..00000000 --- a/biscuit-auth/src/crypto/p256.rs +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright (c) 2019 Geoffroy Couprie and Contributors to the Eclipse Foundation. - * SPDX-License-Identifier: Apache-2.0 - */ -#![allow(non_snake_case)] -use crate::{error::Format, format::schema}; - -use super::error; -use super::Signature; - -use p256::ecdsa::{signature::Signer, signature::Verifier, SigningKey, VerifyingKey}; -use p256::elliptic_curve::rand_core::{CryptoRng, OsRng, RngCore}; -use p256::NistP256; -use std::hash::Hash; - -/// pair of cryptographic keys used to sign a token's block -#[derive(Debug, PartialEq)] -pub struct KeyPair { - kp: SigningKey, -} - -impl KeyPair { - pub fn new() -> Self { - Self::new_with_rng(&mut OsRng) - } - - pub fn new_with_rng(rng: &mut T) -> Self { - let kp = SigningKey::random(rng); - - KeyPair { kp } - } - - pub fn from(key: &PrivateKey) -> Self { - KeyPair { kp: key.0.clone() } - } - - /// deserializes from a big endian byte array - pub fn from_bytes(bytes: &[u8]) -> Result { - // the version of generic-array used by p256 panics if the input length - // is incorrect (including when using `.try_into()`) - if bytes.len() != 32 { - return Err(Format::InvalidKeySize(bytes.len())); - } - let kp = SigningKey::from_bytes(bytes.into()) - .map_err(|s| s.to_string()) - .map_err(Format::InvalidKey)?; - - Ok(KeyPair { kp }) - } - - pub fn sign(&self, data: &[u8]) -> Result { - let signature: ecdsa::Signature = self - .kp - .try_sign(&data) - .map_err(|s| s.to_string()) - .map_err(error::Signature::InvalidSignatureGeneration) - .map_err(error::Format::Signature)?; - Ok(Signature(signature.to_der().as_bytes().to_owned())) - } - - pub fn private(&self) -> PrivateKey { - PrivateKey(self.kp.clone()) - } - - pub fn public(&self) -> PublicKey { - PublicKey(*self.kp.verifying_key()) - } - - pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { - crate::format::schema::public_key::Algorithm::Secp256r1 - } - - #[cfg(feature = "pem")] - pub fn from_private_key_der(bytes: &[u8]) -> Result { - use p256::pkcs8::DecodePrivateKey; - - let kp = SigningKey::from_pkcs8_der(bytes) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(KeyPair { kp }) - } - - #[cfg(feature = "pem")] - pub fn from_private_key_pem(str: &str) -> Result { - use p256::pkcs8::DecodePrivateKey; - - let kp = SigningKey::from_pkcs8_pem(str) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(KeyPair { kp }) - } - - #[cfg(feature = "pem")] - pub fn to_private_key_der(&self) -> Result>, error::Format> { - use p256::pkcs8::EncodePrivateKey; - let kp = self - .kp - .to_pkcs8_der() - .map_err(|e| error::Format::PKCS8(e.to_string()))?; - Ok(kp.to_bytes()) - } - - #[cfg(feature = "pem")] - pub fn to_private_key_pem(&self) -> Result, error::Format> { - use p256::pkcs8::EncodePrivateKey; - use p256::pkcs8::LineEnding; - let kp = self - .kp - .to_pkcs8_pem(LineEnding::LF) - .map_err(|e| error::Format::PKCS8(e.to_string()))?; - Ok(kp) - } -} - -impl std::default::Default for KeyPair { - fn default() -> Self { - Self::new() - } -} - -/// the private part of a [KeyPair] -#[derive(Debug, PartialEq)] -pub struct PrivateKey(SigningKey); - -impl PrivateKey { - /// serializes to a big endian byte array - pub fn to_bytes(&self) -> zeroize::Zeroizing> { - let field_bytes = self.0.to_bytes(); - zeroize::Zeroizing::new(field_bytes.to_vec()) - } - - /// serializes to an hex-encoded string - pub fn to_bytes_hex(&self) -> String { - hex::encode(self.to_bytes()) - } - - /// deserializes from a big endian byte array - pub fn from_bytes(bytes: &[u8]) -> Result { - // the version of generic-array used by p256 panics if the input length - // is incorrect (including when using `.try_into()`) - if bytes.len() != 32 { - return Err(Format::InvalidKeySize(bytes.len())); - } - SigningKey::from_bytes(bytes.into()) - .map(PrivateKey) - .map_err(|s| s.to_string()) - .map_err(Format::InvalidKey) - } - - /// deserializes from an hex-encoded string - pub fn from_bytes_hex(str: &str) -> Result { - let bytes = hex::decode(str).map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Self::from_bytes(&bytes) - } - - #[cfg(feature = "pem")] - pub fn from_der(bytes: &[u8]) -> Result { - use p256::pkcs8::DecodePrivateKey; - - let kp = SigningKey::from_pkcs8_der(bytes) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(PrivateKey(kp)) - } - - #[cfg(feature = "pem")] - pub fn from_pem(str: &str) -> Result { - use p256::pkcs8::DecodePrivateKey; - - let kp = SigningKey::from_pkcs8_pem(str) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(PrivateKey(kp)) - } - - #[cfg(feature = "pem")] - pub fn to_der(&self) -> Result>, error::Format> { - use p256::pkcs8::EncodePrivateKey; - let kp = self - .0 - .to_pkcs8_der() - .map_err(|e| error::Format::PKCS8(e.to_string()))?; - Ok(kp.to_bytes()) - } - - #[cfg(feature = "pem")] - pub fn to_pem(&self) -> Result, error::Format> { - use p256::pkcs8::EncodePrivateKey; - use p256::pkcs8::LineEnding; - let kp = self - .0 - .to_pkcs8_pem(LineEnding::LF) - .map_err(|e| error::Format::PKCS8(e.to_string()))?; - Ok(kp) - } - - /// returns the matching public key - pub fn public(&self) -> PublicKey { - PublicKey(*(&self.0).verifying_key()) - } - - pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { - crate::format::schema::public_key::Algorithm::Ed25519 - } -} - -impl std::clone::Clone for PrivateKey { - fn clone(&self) -> Self { - PrivateKey::from_bytes(&self.to_bytes()).unwrap() - } -} - -/// the public part of a [KeyPair] -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct PublicKey(VerifyingKey); - -impl PublicKey { - /// serializes to a byte array - pub fn to_bytes(&self) -> Vec { - self.0.to_encoded_point(true).to_bytes().into() - } - - /// serializes to an hex-encoded string - pub fn to_bytes_hex(&self) -> String { - hex::encode(self.to_bytes()) - } - - /// deserializes from a byte array - pub fn from_bytes(bytes: &[u8]) -> Result { - let k = VerifyingKey::from_sec1_bytes(bytes) - .map_err(|s| s.to_string()) - .map_err(Format::InvalidKey)?; - - Ok(Self(k.into())) - } - - /// deserializes from an hex-encoded string - pub fn from_bytes_hex(str: &str) -> Result { - let bytes = hex::decode(str).map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Self::from_bytes(&bytes) - } - - #[cfg(feature = "pem")] - pub fn from_der(bytes: &[u8]) -> Result { - use p256::pkcs8::DecodePublicKey; - - let pubkey = VerifyingKey::from_public_key_der(bytes) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(PublicKey(pubkey)) - } - - #[cfg(feature = "pem")] - pub fn from_pem(str: &str) -> Result { - use p256::pkcs8::DecodePublicKey; - - let pubkey = VerifyingKey::from_public_key_pem(str) - .map_err(|e| error::Format::InvalidKey(e.to_string()))?; - Ok(PublicKey(pubkey)) - } - - #[cfg(feature = "pem")] - pub fn to_der(&self) -> Result, error::Format> { - use p256::pkcs8::EncodePublicKey; - let kp = self - .0 - .to_public_key_der() - .map_err(|e| error::Format::PKCS8(e.to_string()))?; - Ok(kp.to_vec()) - } - - #[cfg(feature = "pem")] - pub fn to_pem(&self) -> Result { - use p256::pkcs8::EncodePublicKey; - use p256::pkcs8::LineEnding; - let kp = self - .0 - .to_public_key_pem(LineEnding::LF) - .map_err(|e| error::Format::PKCS8(e.to_string()))?; - Ok(kp) - } - - pub fn from_proto(key: &schema::PublicKey) -> Result { - if key.algorithm != schema::public_key::Algorithm::Ed25519 as i32 { - return Err(error::Format::DeserializationError(format!( - "deserialization error: unexpected key algorithm {}", - key.algorithm - ))); - } - - PublicKey::from_bytes(&key.key) - } - - pub fn to_proto(&self) -> schema::PublicKey { - schema::PublicKey { - algorithm: schema::public_key::Algorithm::Ed25519 as i32, - key: self.to_bytes().to_vec(), - } - } - - pub fn verify_signature( - &self, - data: &[u8], - signature: &Signature, - ) -> Result<(), error::Format> { - let sig = p256::ecdsa::Signature::from_der(&signature.0).map_err(|e| { - error::Format::BlockSignatureDeserializationError(format!( - "block signature deserialization error: {:?}", - e - )) - })?; - - self.0 - .verify(&data, &sig) - .map_err(|s| s.to_string()) - .map_err(error::Signature::InvalidSignature) - .map_err(error::Format::Signature) - } - - pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { - crate::format::schema::public_key::Algorithm::Ed25519 - } - - pub(crate) fn write(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "secp256r1/{}", hex::encode(&self.to_bytes())) - } - pub fn print(&self) -> String { - format!("secp256r1/{}", hex::encode(&self.to_bytes())) - } -} - -impl Hash for PublicKey { - fn hash(&self, state: &mut H) { - (crate::format::schema::public_key::Algorithm::Ed25519 as i32).hash(state); - self.to_bytes().hash(state); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serialization() { - let kp = KeyPair::new(); - let private = kp.private(); - let public = kp.public(); - let private_hex = private.to_bytes_hex(); - let public_hex = public.to_bytes_hex(); - - println!("private: {private_hex}"); - println!("public: {public_hex}"); - - let message = "hello world"; - let signature = kp.sign(message.as_bytes()).unwrap(); - println!("signature: {}", hex::encode(&signature.0)); - - let deserialized_priv = PrivateKey::from_bytes_hex(&private_hex).unwrap(); - let deserialized_pub = PublicKey::from_bytes_hex(&public_hex).unwrap(); - - assert_eq!(private.0.to_bytes(), deserialized_priv.0.to_bytes()); - assert_eq!(public, deserialized_pub); - - deserialized_pub - .verify_signature(message.as_bytes(), &signature) - .unwrap(); - //panic!(); - } - - #[test] - fn invalid_sizes() { - assert_eq!( - PrivateKey::from_bytes(&[0xaa]).unwrap_err(), - error::Format::InvalidKeySize(1) - ); - assert_eq!( - KeyPair::from_bytes(&[0xaa]).unwrap_err(), - error::Format::InvalidKeySize(1) - ); - PublicKey::from_bytes(&[0xaa]).unwrap_err(); - } -} diff --git a/biscuit-auth/src/crypto/traits.rs b/biscuit-auth/src/crypto/traits.rs new file mode 100644 index 00000000..c165a42d --- /dev/null +++ b/biscuit-auth/src/crypto/traits.rs @@ -0,0 +1,141 @@ +//! Cryptographic key traits for backend abstraction. +//! +//! This module defines traits that allow different cryptographic backends +//! (e.g., ed25519-dalek, ring) to be used interchangeably. +//! +//! # Trait Hierarchy +//! +//! - [`Backend`] - Groups Ed25519 and P256 implementations for a crypto library +//! - [`PrivateKeyImpl`] - Private key implementation (generation, signing) +//! - [`PublicKeyImpl`] - Public key implementation (verification) +//! - [`KeySerialization`] - Base trait for byte/hex serialization +//! - [`KeyPemSerialization`] - Extension trait for PEM/DER formats (requires `pem` feature) +//! + +use super::Signature; +use crate::error; +use rand_core::{CryptoRng, RngCore}; +use zeroize::Zeroizing; + +/// Trait for grouping cryptographic backend implementations. +/// +/// A backend provides implementations for both Ed25519 and P256 (secp256r1) +/// key types. This allows swapping out the underlying crypto library +/// (e.g., ed25519-dalek/p256 vs ring) at compile time. +/// +/// # Example +/// +/// ```ignore +/// use biscuit_auth::crypto::{Backend, DefaultBackend}; +/// +/// // Use the default backend +/// type MyKey = ::Ed25519; +/// let key = MyKey::generate(); +/// ``` +pub trait Backend { + /// The Ed25519 private key implementation. + type Ed25519: KeyPairImpl; + /// The P256 (secp256r1) private key implementation. + type P256: KeyPairImpl; +} + +/// Extension trait for PEM/DER key serialization. +/// +/// This trait is only available when the `pem` feature is enabled. +/// Types implementing [`PublicKeyImpl`] or [`PrivateKeyImpl`] should also +/// implement this trait to support standard key formats. +#[cfg(feature = "pem")] +pub trait KeyPemSerialization: KeySerialization + Sized { + /// Deserializes a key from DER-encoded bytes. + fn from_der(bytes: &[u8]) -> Result; + + /// Deserializes a key from a PEM-encoded string. + fn from_pem(str: &str) -> Result; + + /// Serializes the key to DER-encoded bytes. + fn to_der(&self) -> Result; + + /// Serializes the key to a PEM-encoded string. + fn to_pem(&self) -> Result; +} + +/// Trait for key byte serialization and deserialization. +/// +/// Provides methods for converting keys to and from raw bytes and hex strings. +/// All key types should implement this trait for basic serialization support. +pub trait KeySerialization: Sized { + type Bytes: AsRef<[u8]> + From>; + type String: AsRef + From; + /// Returns an owned copy of the key's raw bytes. + fn to_bytes(&self) -> Self::Bytes; + + /// Serializes the key to a hex-encoded string. + fn to_bytes_hex(&self) -> Self::String { + hex::encode(self.to_bytes()).into() + } + + /// Deserializes a key from raw bytes. + fn from_bytes(bytes: &[u8]) -> Result; + + /// Deserializes a key from a hex-encoded string. + fn from_bytes_hex(str: &str) -> Result { + let bytes = hex::decode(str).map_err(|e| error::Format::InvalidKey(e.to_string()))?; + Self::from_bytes(&bytes) + } +} + +/// Trait for public key implementation. +/// +/// Public keys are used to verify signatures created by the corresponding +/// private key. They are safe to share and are typically embedded in tokens. +pub trait PublicKeyImpl: KeySerialization, String = String> { + /// Verifies a signature against this public key. + /// + /// Returns `Ok(())` if the signature is valid for the given data, + /// or an error if verification fails. + fn verify_signature(&self, data: &[u8], signature: &Signature) -> Result<(), error::Format>; + + /// Returns the algorithm identifier for this key. + fn algorithm(&self) -> crate::format::schema::public_key::Algorithm; +} + +/// Trait for private key implementation. +/// +/// Private keys are used to sign data and must be kept secret. +/// Each private key has a corresponding public key that can verify +/// its signatures. +pub trait PrivateKeyImpl: + KeySerialization>, String = Zeroizing> +{ + type PublicKey: PublicKeyImpl; + + /// Returns the public key corresponding to this private key. + fn public(&self) -> Self::PublicKey; + + /// Returns the algorithm identifier for this key. + fn algorithm(&self) -> crate::format::schema::public_key::Algorithm; +} + +pub trait KeyPairImpl { + type PublicKey: PublicKeyImpl; + type PrivateKey: PrivateKeyImpl; + + /// Signs data with this private key. + /// + /// Returns the signature on success, or an error if signing fails. + fn sign(&self, data: &[u8]) -> Result; + + fn from_private(key: &Self::PrivateKey) -> Self; + + fn public(&self) -> Self::PublicKey; + + fn algorithm(&self) -> crate::format::schema::public_key::Algorithm; + + fn private(&self) -> Self::PrivateKey; + + /// Generates a new private key using the default OS random number generator. + fn generate() -> Self; + + /// Generates a new private key using the provided random number generator. + fn generate_with_rng(rng: &mut R) -> Self; +} diff --git a/biscuit-auth/src/token/builder/scope.rs b/biscuit-auth/src/token/builder/scope.rs index 150bd345..7ce3656b 100644 --- a/biscuit-auth/src/token/builder/scope.rs +++ b/biscuit-auth/src/token/builder/scope.rs @@ -57,7 +57,7 @@ impl fmt::Display for Scope { match self { Scope::Authority => write!(f, "authority"), Scope::Previous => write!(f, "previous"), - Scope::PublicKey(pk) => pk.write(f), + Scope::PublicKey(pk) => write!(f, "{}", pk), Scope::Parameter(s) => { write!(f, "{{{}}}", s) }