Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 7 additions & 9 deletions biscuit-auth/src/crypto/ed25519.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,15 +53,13 @@ impl KeyPair {
}

pub fn sign(&self, data: &[u8]) -> Result<Signature, error::Format> {
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 {
Expand Down
61 changes: 46 additions & 15 deletions biscuit-auth/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,13 +25,32 @@ 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 {
Ed25519(ed25519::KeyPair),
P256(p256::KeyPair),
}

impl Signer for KeyPair {
fn sign(&self, data: &[u8]) -> Result<Signature, error::Format> {
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 {
Expand Down Expand Up @@ -74,8 +93,8 @@ impl KeyPair {

pub fn sign(&self, data: &[u8]) -> Result<Signature, error::Format> {
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),
}
}

Expand Down Expand Up @@ -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,
}
}
}
Expand Down Expand Up @@ -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,
}
}
}
Expand Down Expand Up @@ -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<u8>);
Comment on lines +451 to 455

@wbourne0 wbourne0 Aug 2, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously this was implicitly public as a return type; now it's explicit (hence the added comment) and addressable.

There may be some merit with regards to changing this into an enum type, although this may technically be a breaking change.


impl Signature {
pub fn from_bytes(data: &[u8]) -> Result<Self, error::Format> {
Ok(Signature(data.to_owned()))
}

Comment on lines -435 to -438

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was never usable before as Signature wasn't exposed publicly. I've opted to remove it since it's fairly unsafe. Happy to add it back.

pub(crate) fn from_vec(data: Vec<u8>) -> Self {
Signature(data)
}
Expand All @@ -445,6 +464,18 @@ impl Signature {
}
}

impl From<ed25519_dalek::Signature> for Signature {
fn from(value: ed25519_dalek::Signature) -> Self {
Self(value.to_vec())
}
}

impl From<ecdsa::Signature<::p256::NistP256>> 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;

Expand Down Expand Up @@ -484,7 +515,7 @@ pub enum TokenNext {
}

pub fn sign_authority_block(
keypair: &KeyPair,
signer: &impl Signer,
next_key: &KeyPair,
message: &[u8],
version: u32,
Expand All @@ -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()))
}
Expand Down
2 changes: 1 addition & 1 deletion biscuit-auth/src/crypto/p256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
18 changes: 18 additions & 0 deletions biscuit-auth/src/crypto/traits.rs
Original file line number Diff line number Diff line change
@@ -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<Signature, error::Format>;
}
Comment on lines +11 to +18

@wbourne0 wbourne0 Aug 2, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't super like this API, but it should do fine. The other option would be to have sign (or the Signer trait itself) take an algorithm type as an argument, where that type must impl Algorithm or similar.

Also, this may be better named as try_sign to follow the convention used in the ed25519_dalek and ecdsa crates.

Could also add an associated Error type, although then we'd have to pass it back.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Notably we want this separate from a potential later PrivateKey trait, as root keys don't actually need to provide public keys. At most they may optionally have an ID.

16 changes: 9 additions & 7 deletions biscuit-auth/src/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
//!
//! - serialization of Biscuit blocks to Protobuf then `Vec<u8>`
//! - 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;

Expand All @@ -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; /*{
Expand Down Expand Up @@ -292,7 +294,7 @@ impl SerializedBiscuit {
/// creates a new token
pub fn new(
root_key_id: Option<u32>,
root_keypair: &KeyPair,
root_keypair: &impl Signer,
next_keypair: &KeyPair,
authority: &Block,
) -> Result<Self, error::Token> {
Expand All @@ -315,7 +317,7 @@ impl SerializedBiscuit {
/// creates a new token
pub(crate) fn new_inner(
root_key_id: Option<u32>,
root_keypair: &KeyPair,
root_signer: &impl Signer,
next_keypair: &KeyPair,
authority: &Block,
authority_signature_version: u32,
Expand All @@ -328,7 +330,7 @@ impl SerializedBiscuit {
})?;

let signature = crypto::sign_authority_block(
root_keypair,
root_signer,
next_keypair,
&v,
authority_signature_version,
Expand Down Expand Up @@ -548,7 +550,7 @@ pub(crate) enum ThirdPartyVerificationMode {
}

fn block_signature_version<I>(
block_keypair: &KeyPair,
block_keypair: &impl Signer,
next_keypair: &KeyPair,
external_signature: &Option<ExternalSignature>,
block_version: &Option<u32>,
Expand All @@ -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) => {}
Comment on lines +573 to +574

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda gross here (the SchemaAlgorithm) but I think it's a little neater than relying on the underlying enum structure.

_ => {
return NON_ED25519_SIGNATURE_VERSION;
}
Expand Down
2 changes: 1 addition & 1 deletion biscuit-auth/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down
14 changes: 7 additions & 7 deletions biscuit-auth/src/token/builder/biscuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -124,21 +124,21 @@ impl BiscuitBuilder {
f
}

pub fn build(self, root_key: &KeyPair) -> Result<Biscuit, error::Token> {
self.build_with_symbols(root_key, default_symbol_table())
pub fn build(self, root: &impl Signer) -> Result<Biscuit, error::Token> {
self.build_with_symbols(root, default_symbol_table())
}

pub fn build_with_symbols(
self,
root_key: &KeyPair,
root: &impl Signer,
symbols: SymbolTable,
) -> Result<Biscuit, error::Token> {
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<R: RngCore + CryptoRng>(
self,
root: &KeyPair,
root: &impl Signer,
symbols: SymbolTable,
rng: &mut R,
) -> Result<Biscuit, error::Token> {
Expand All @@ -148,7 +148,7 @@ impl BiscuitBuilder {

pub fn build_with_key_pair(
self,
root: &KeyPair,
root: &impl Signer,
symbols: SymbolTable,
next: &KeyPair,
) -> Result<Biscuit, error::Token> {
Expand Down
6 changes: 3 additions & 3 deletions biscuit-auth/src/token/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -254,7 +254,7 @@ impl Biscuit {
pub(crate) fn new_with_rng<T: RngCore + CryptoRng>(
rng: &mut T,
root_key_id: Option<u32>,
root: &KeyPair,
root: &impl Signer,
symbols: SymbolTable,
authority: Block,
) -> Result<Biscuit, error::Token> {
Expand All @@ -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<u32>,
root: &KeyPair,
root: &impl Signer,
next_keypair: &KeyPair,
mut symbols: SymbolTable,
authority: Block,
Expand Down
Loading