diff --git a/biscuit-auth/src/crypto/ed25519.rs b/biscuit-auth/src/crypto/ed25519.rs index 36fb86b5..f4baba25 100644 --- a/biscuit-auth/src/crypto/ed25519.rs +++ b/biscuit-auth/src/crypto/ed25519.rs @@ -53,15 +53,13 @@ impl KeyPair { } 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(), - )) + Ok(self + .kp + .try_sign(data) + .map_err(|s| s.to_string()) + .map_err(error::Signature::InvalidSignatureGeneration) + .map_err(error::Format::Signature)? + .into()) } pub fn private(&self) -> PrivateKey { diff --git a/biscuit-auth/src/crypto/mod.rs b/biscuit-auth/src/crypto/mod.rs index e0128032..88d4ba9c 100644 --- a/biscuit-auth/src/crypto/mod.rs +++ b/biscuit-auth/src/crypto/mod.rs @@ -12,7 +12,7 @@ //! The implementation is based on [ed25519_dalek](https://github.com/dalek-cryptography/ed25519-dalek). #![allow(non_snake_case)] use crate::builder::Algorithm; -use crate::format::schema; +use crate::format::schema::{self, public_key::Algorithm as SchemaAlgorithm}; use crate::format::ThirdPartyVerificationMode; use super::error; @@ -25,6 +25,9 @@ use std::fmt; use std::hash::Hash; use std::str::FromStr; +mod traits; +pub use traits::*; + /// pair of cryptographic keys used to sign a token's block #[derive(Debug, PartialEq)] pub enum KeyPair { @@ -32,6 +35,22 @@ pub enum KeyPair { P256(p256::KeyPair), } +impl Signer for KeyPair { + fn sign(&self, data: &[u8]) -> Result { + match self { + Self::Ed25519(key) => key.sign(data), + Self::P256(key) => key.sign(data), + } + } + + fn algorithm(&self) -> Algorithm { + match self { + Self::Ed25519(_) => Algorithm::Ed25519, + Self::P256(_) => Algorithm::Secp256r1, + } + } +} + impl KeyPair { /// Create a new ed25519 keypair with the default OS RNG pub fn new() -> Self { @@ -74,8 +93,8 @@ impl KeyPair { pub fn sign(&self, data: &[u8]) -> Result { match self { - KeyPair::Ed25519(key) => key.sign(data), - KeyPair::P256(key) => key.sign(data), + Self::Ed25519(key) => key.sign(data), + Self::P256(key) => key.sign(data), } } @@ -145,10 +164,10 @@ impl KeyPair { } } - pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + pub fn algorithm(&self) -> SchemaAlgorithm { match self { - KeyPair::Ed25519(_) => crate::format::schema::public_key::Algorithm::Ed25519, - KeyPair::P256(_) => crate::format::schema::public_key::Algorithm::Secp256r1, + Self::Ed25519(_) => SchemaAlgorithm::Ed25519, + Self::P256(_) => SchemaAlgorithm::Secp256r1, } } } @@ -272,10 +291,10 @@ impl PrivateKey { } } - pub fn algorithm(&self) -> crate::format::schema::public_key::Algorithm { + pub fn algorithm(&self) -> SchemaAlgorithm { match self { - PrivateKey::Ed25519(_) => crate::format::schema::public_key::Algorithm::Ed25519, - PrivateKey::P256(_) => crate::format::schema::public_key::Algorithm::Secp256r1, + Self::Ed25519(_) => SchemaAlgorithm::Ed25519, + Self::P256(_) => SchemaAlgorithm::Secp256r1, } } } @@ -429,13 +448,13 @@ impl fmt::Display for PublicKey { } #[derive(Clone, Debug)] +/// A signature of a [Biscuit](crate::Biscuit) block. +/// +/// May be constructed via [Into] from [ed25519_dalek::Signature] or +/// from [ecdsa::Signature] with the [NistP256](::p256::NistP256) curve. pub struct Signature(pub(crate) Vec); impl Signature { - pub fn from_bytes(data: &[u8]) -> Result { - Ok(Signature(data.to_owned())) - } - pub(crate) fn from_vec(data: Vec) -> Self { Signature(data) } @@ -445,6 +464,18 @@ impl Signature { } } +impl From for Signature { + fn from(value: ed25519_dalek::Signature) -> Self { + Self(value.to_vec()) + } +} + +impl From> for Signature { + fn from(value: ecdsa::Signature<::p256::NistP256>) -> Self { + Self(value.to_der().as_bytes().to_vec()) + } +} + impl FromStr for PublicKey { type Err = error::Format; @@ -484,7 +515,7 @@ pub enum TokenNext { } pub fn sign_authority_block( - keypair: &KeyPair, + signer: &impl Signer, next_key: &KeyPair, message: &[u8], version: u32, @@ -500,7 +531,7 @@ pub fn sign_authority_block( } }; - let signature = keypair.sign(&to_sign)?; + let signature = signer.sign(&to_sign)?; Ok(Signature(signature.to_bytes().to_vec())) } diff --git a/biscuit-auth/src/crypto/p256.rs b/biscuit-auth/src/crypto/p256.rs index 153ede7b..ed1904ff 100644 --- a/biscuit-auth/src/crypto/p256.rs +++ b/biscuit-auth/src/crypto/p256.rs @@ -51,7 +51,7 @@ impl KeyPair { .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())) + Ok(signature.into()) } pub fn private(&self) -> PrivateKey { diff --git a/biscuit-auth/src/crypto/traits.rs b/biscuit-auth/src/crypto/traits.rs new file mode 100644 index 00000000..4b34bb56 --- /dev/null +++ b/biscuit-auth/src/crypto/traits.rs @@ -0,0 +1,18 @@ +use super::Signature; +use crate::{error, Algorithm}; +// so we can link to this in cargo docs. +#[cfg(doc)] +use crate::BiscuitBuilder; + +/// A trait for signing arbitrary byte inputs with biscuit-compatible +/// [algorithms](Algorithm). +/// +/// Instances of `Signer` may be used with [BiscuitBuilder] as root keys. +pub trait Signer { + /// The algorithm used. Must match the signature returned via [sign](Self::sign). + fn algorithm(&self) -> Algorithm; + /// Sign a series of bytes, returning a signature. This signature must match + /// what [self.algorithm()](Self::algorithm) returns. Any incorrect values + /// will likely result in invalid tokens. + fn sign(&self, data: &[u8]) -> Result; +} diff --git a/biscuit-auth/src/format/mod.rs b/biscuit-auth/src/format/mod.rs index 4ff3c70a..090da74d 100644 --- a/biscuit-auth/src/format/mod.rs +++ b/biscuit-auth/src/format/mod.rs @@ -8,7 +8,7 @@ //! //! - serialization of Biscuit blocks to Protobuf then `Vec` //! - serialization of a wrapper structure containing serialized blocks and the signature -use super::crypto::{self, KeyPair, PrivateKey, PublicKey, TokenNext}; +use super::crypto::{self, KeyPair, PrivateKey, PublicKey, Signer, TokenNext}; use prost::Message; @@ -17,8 +17,10 @@ use super::token::Block; use crate::crypto::ExternalSignature; use crate::crypto::Signature; use crate::datalog::SymbolTable; +use crate::format::schema::public_key::Algorithm as SchemaAlgorithm; use crate::token::RootKeyProvider; use crate::token::DATALOG_3_3; +use crate::Algorithm; /// Structures generated from the Protobuf schema pub mod schema; /*{ @@ -292,7 +294,7 @@ impl SerializedBiscuit { /// creates a new token pub fn new( root_key_id: Option, - root_keypair: &KeyPair, + root_keypair: &impl Signer, next_keypair: &KeyPair, authority: &Block, ) -> Result { @@ -315,7 +317,7 @@ impl SerializedBiscuit { /// creates a new token pub(crate) fn new_inner( root_key_id: Option, - root_keypair: &KeyPair, + root_signer: &impl Signer, next_keypair: &KeyPair, authority: &Block, authority_signature_version: u32, @@ -328,7 +330,7 @@ impl SerializedBiscuit { })?; let signature = crypto::sign_authority_block( - root_keypair, + root_signer, next_keypair, &v, authority_signature_version, @@ -548,7 +550,7 @@ pub(crate) enum ThirdPartyVerificationMode { } fn block_signature_version( - block_keypair: &KeyPair, + block_keypair: &impl Signer, next_keypair: &KeyPair, external_signature: &Option, block_version: &Option, @@ -568,8 +570,8 @@ where _ => {} } - match (block_keypair, next_keypair) { - (KeyPair::Ed25519(_), KeyPair::Ed25519(_)) => {} + match (block_keypair.algorithm(), next_keypair.algorithm()) { + (Algorithm::Ed25519, SchemaAlgorithm::Ed25519) => {} _ => { return NON_ED25519_SIGNATURE_VERSION; } diff --git a/biscuit-auth/src/lib.rs b/biscuit-auth/src/lib.rs index 3648912f..3f59e5df 100644 --- a/biscuit-auth/src/lib.rs +++ b/biscuit-auth/src/lib.rs @@ -251,7 +251,7 @@ pub mod format; pub mod parser; mod token; -pub use crypto::{KeyPair, PrivateKey, PublicKey}; +pub use crypto::{KeyPair, PrivateKey, PublicKey, Signature, Signer}; pub use token::authorizer::{Authorizer, AuthorizerLimits}; pub use token::builder; pub use token::builder::{Algorithm, AuthorizerBuilder, BiscuitBuilder, BlockBuilder}; diff --git a/biscuit-auth/src/token/builder/biscuit.rs b/biscuit-auth/src/token/builder/biscuit.rs index a02bfb1a..55b9a913 100644 --- a/biscuit-auth/src/token/builder/biscuit.rs +++ b/biscuit-auth/src/token/builder/biscuit.rs @@ -4,7 +4,7 @@ */ use super::{BlockBuilder, Check, Fact, Rule, Scope, Term}; use crate::builder_ext::BuilderExt; -use crate::crypto::PublicKey; +use crate::crypto::{PublicKey, Signer}; use crate::datalog::SymbolTable; use crate::token::default_symbol_table; use crate::{error, Biscuit, KeyPair}; @@ -124,21 +124,21 @@ impl BiscuitBuilder { f } - pub fn build(self, root_key: &KeyPair) -> Result { - self.build_with_symbols(root_key, default_symbol_table()) + pub fn build(self, root: &impl Signer) -> Result { + self.build_with_symbols(root, default_symbol_table()) } pub fn build_with_symbols( self, - root_key: &KeyPair, + root: &impl Signer, symbols: SymbolTable, ) -> Result { - self.build_with_rng(root_key, symbols, &mut rand::rngs::OsRng) + self.build_with_rng(root, symbols, &mut rand::rngs::OsRng) } pub fn build_with_rng( self, - root: &KeyPair, + root: &impl Signer, symbols: SymbolTable, rng: &mut R, ) -> Result { @@ -148,7 +148,7 @@ impl BiscuitBuilder { pub fn build_with_key_pair( self, - root: &KeyPair, + root: &impl Signer, symbols: SymbolTable, next: &KeyPair, ) -> Result { diff --git a/biscuit-auth/src/token/mod.rs b/biscuit-auth/src/token/mod.rs index 218804c9..74d4842d 100644 --- a/biscuit-auth/src/token/mod.rs +++ b/biscuit-auth/src/token/mod.rs @@ -15,7 +15,7 @@ use super::crypto::{KeyPair, PublicKey, Signature}; use super::datalog::SymbolTable; use super::error; use super::format::SerializedBiscuit; -use crate::crypto::{self}; +use crate::crypto::{self, Signer}; use crate::format::convert::proto_block_to_token_block; use crate::format::schema::{self, ThirdPartyBlockContents}; use crate::format::{ThirdPartyVerificationMode, THIRD_PARTY_SIGNATURE_VERSION}; @@ -254,7 +254,7 @@ impl Biscuit { pub(crate) fn new_with_rng( rng: &mut T, root_key_id: Option, - root: &KeyPair, + root: &impl Signer, symbols: SymbolTable, authority: Block, ) -> Result { @@ -272,7 +272,7 @@ impl Biscuit { /// the public part of the root keypair must be used for verification pub(crate) fn new_with_key_pair( root_key_id: Option, - root: &KeyPair, + root: &impl Signer, next_keypair: &KeyPair, mut symbols: SymbolTable, authority: Block, diff --git a/biscuit-auth/tests/signer.rs b/biscuit-auth/tests/signer.rs new file mode 100644 index 00000000..09090e66 --- /dev/null +++ b/biscuit-auth/tests/signer.rs @@ -0,0 +1,66 @@ +use biscuit_auth::{datalog::SymbolTable, error, Algorithm, Biscuit, PublicKey, Signature, Signer}; +use rand::{rngs::StdRng, SeedableRng}; + +struct Ed25519Signer(ed25519_dalek::SigningKey); + +impl Signer for Ed25519Signer { + fn algorithm(&self) -> Algorithm { + Algorithm::Ed25519 + } + + fn sign(&self, data: &[u8]) -> Result { + use ed25519_dalek::Signer; + Ok(self.0.sign(data).into()) + } +} + +struct P256Signer(p256::ecdsa::SigningKey); + +impl Signer for P256Signer { + fn algorithm(&self) -> Algorithm { + Algorithm::Secp256r1 + } + + fn sign(&self, data: &[u8]) -> Result { + use p256::ecdsa::signature::Signer; + + let sig: p256::ecdsa::Signature = self.0.sign(data); + Ok(sig.into()) + } +} + +fn assert_signer_roundtrip(signer: &impl Signer, root_public: PublicKey) { + let mut rng = StdRng::seed_from_u64(42); + + let token = Biscuit::builder() + .fact(r#"user("alice")"#) + .unwrap() + .build_with_rng(signer, SymbolTable::default(), &mut rng) + .unwrap(); + + let serialized = token.to_vec().unwrap(); + + // This cryptographically verifies the custom signer's authority signature. + Biscuit::from(&serialized, root_public).unwrap(); +} + +#[test] +fn external_ed25519_signer_builds_valid_token() { + let mut rng = StdRng::seed_from_u64(0); + let signing_key = ed25519_dalek::SigningKey::generate(&mut rng); + let root_public = + PublicKey::from_bytes(signing_key.verifying_key().as_bytes(), Algorithm::Ed25519).unwrap(); + + assert_signer_roundtrip(&Ed25519Signer(signing_key), root_public); +} + +#[test] +fn external_p256_signer_builds_valid_token() { + let mut rng = StdRng::seed_from_u64(1); + let signing_key = p256::ecdsa::SigningKey::random(&mut rng); + let encoded_public_key = signing_key.verifying_key().to_encoded_point(true); + let root_public = + PublicKey::from_bytes(encoded_public_key.as_bytes(), Algorithm::Secp256r1).unwrap(); + + assert_signer_roundtrip(&P256Signer(signing_key), root_public); +}