Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for third party block reuse #230

Open
wants to merge 16 commits into
base: v5
Choose a base branch
from
125 changes: 29 additions & 96 deletions biscuit-auth/src/crypto/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
//!
//! The implementation is based on [ed25519_dalek](https://github.com/dalek-cryptography/ed25519-dalek).
#![allow(non_snake_case)]
use crate::format::ThirdPartyVerificationMode;
use crate::{error::Format, format::schema};

use super::error;
Expand Down Expand Up @@ -211,6 +212,7 @@ pub struct Block {
pub(crate) next_key: PublicKey,
pub signature: ed25519_dalek::Signature,
pub external_signature: Option<ExternalSignature>,
pub version: u32,
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -252,7 +254,12 @@ pub fn sign(
Ok(signature)
}

pub fn verify_block_signature(block: &Block, public_key: &PublicKey) -> Result<(), error::Format> {
pub fn verify_block_signature(
block: &Block,
public_key: &PublicKey,
previous_signature: Option<&Signature>,
verification_mode: ThirdPartyVerificationMode,
) -> Result<(), error::Format> {
//FIXME: replace with SHA512 hashing
let mut to_verify = block.data.to_vec();

Expand All @@ -262,19 +269,40 @@ pub fn verify_block_signature(block: &Block, public_key: &PublicKey) -> Result<(
to_verify.extend(&(crate::format::schema::public_key::Algorithm::Ed25519 as i32).to_le_bytes());
to_verify.extend(&block.next_key.to_bytes());

println!("will verify block with key {:?}", public_key);
Geal marked this conversation as resolved.
Show resolved Hide resolved
public_key
.0
.verify_strict(&to_verify, &block.signature)
.map_err(|s| s.to_string())
.map_err(error::Signature::InvalidSignature)
.map_err(error::Format::Signature)?;
println!("block signature verified");
Geal marked this conversation as resolved.
Show resolved Hide resolved

if let Some(external_signature) = block.external_signature.as_ref() {
let mut to_verify = block.data.to_vec();
to_verify
.extend(&(crate::format::schema::public_key::Algorithm::Ed25519 as i32).to_le_bytes());
to_verify.extend(&public_key.to_bytes());

println!(
"verifying external signature in mode {:?}",
verification_mode
);
Geal marked this conversation as resolved.
Show resolved Hide resolved
if verification_mode == ThirdPartyVerificationMode::PreviousSignatureHashing {
let previous_signature = match previous_signature {
Some(s) => s,
None => {
return Err(error::Format::Signature(
error::Signature::InvalidSignature(
"the authority block must not contain an external signature"
.to_string(),
),
))
}
};
to_verify.extend(&previous_signature.to_bytes());
}

external_signature
.public_key
.0
Expand All @@ -287,101 +315,6 @@ pub fn verify_block_signature(block: &Block, public_key: &PublicKey) -> Result<(
Ok(())
}

impl Token {
#[allow(dead_code)]
pub fn new<T: RngCore + CryptoRng>(
keypair: &KeyPair,
next_key: &KeyPair,
message: &[u8],
) -> Result<Self, error::Token> {
let signature = sign(keypair, next_key, message)?;

let block = Block {
data: message.to_vec(),
next_key: next_key.public(),
signature,
external_signature: None,
};

Ok(Token {
root: keypair.public(),
blocks: vec![block],
next: TokenNext::Secret(next_key.private()),
})
}

#[allow(dead_code)]
pub fn append<T: RngCore + CryptoRng>(
&self,
next_key: &KeyPair,
message: &[u8],
external_signature: Option<ExternalSignature>,
) -> Result<Self, error::Token> {
let keypair = match self.next.keypair() {
Err(error::Token::AlreadySealed) => Err(error::Token::AppendOnSealed),
other => other,
}?;

let signature = sign(&keypair, next_key, message)?;

let block = Block {
data: message.to_vec(),
next_key: next_key.public(),
signature,
external_signature,
};

let mut t = Token {
root: self.root,
blocks: self.blocks.clone(),
next: TokenNext::Secret(next_key.private()),
};

t.blocks.push(block);

Ok(t)
}

#[allow(dead_code)]
pub fn verify(&self, root: PublicKey) -> Result<(), error::Token> {
//FIXME: try batched signature verification
let mut current_pub = root;

for block in &self.blocks {
verify_block_signature(block, &current_pub)?;
current_pub = block.next_key;
}

match &self.next {
TokenNext::Secret(private) => {
if current_pub != private.public() {
return Err(error::Format::Signature(error::Signature::InvalidSignature(
"the last public key does not match the private key".to_string(),
))
.into());
}
}
TokenNext::Seal(signature) => {
//FIXME: replace with SHA512 hashing
let mut to_verify = Vec::new();
for block in &self.blocks {
to_verify.extend(&block.data);
to_verify.extend(&block.next_key.to_bytes());
}

current_pub
.0
.verify_strict(&to_verify, signature)
.map_err(|s| s.to_string())
.map_err(error::Signature::InvalidSignature)
.map_err(error::Format::Signature)?;
}
}

Ok(())
}
}

impl TokenNext {
pub fn keypair(&self) -> Result<KeyPair, error::Token> {
match &self {
Expand Down
86 changes: 81 additions & 5 deletions biscuit-auth/src/format/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ pub mod convert;

use self::convert::*;

pub(crate) const THIRD_PARTY_SIGNATURE_VERSION: u32 = 1;
/// Intermediate structure for token serialization
///
/// This structure contains the blocks serialized to byte arrays. Those arrays
Expand All @@ -43,15 +44,37 @@ impl SerializedBiscuit {
where
KP: RootKeyProvider,
{
let deser = SerializedBiscuit::deserialize(slice)?;
let deser = SerializedBiscuit::deserialize(
slice,
ThirdPartyVerificationMode::PreviousSignatureHashing,
)?;

let root = key_provider.choose(deser.root_key_id)?;
deser.verify(&root)?;

Ok(deser)
}

pub(crate) fn deserialize(slice: &[u8]) -> Result<Self, error::Format> {
pub(crate) fn unsafe_from_slice<KP>(
slice: &[u8],
key_provider: KP,
) -> Result<Self, error::Format>
where
KP: RootKeyProvider,
{
let deser =
SerializedBiscuit::deserialize(slice, ThirdPartyVerificationMode::UnsafeLegacy)?;

let root = key_provider.choose(deser.root_key_id)?;
deser.verify_inner(&root, ThirdPartyVerificationMode::UnsafeLegacy)?;

Ok(deser)
}

pub(crate) fn deserialize(
slice: &[u8],
verification_mode: ThirdPartyVerificationMode,
) -> Result<Self, error::Format> {
let data = schema::Biscuit::decode(slice).map_err(|e| {
error::Format::DeserializationError(format!("deserialization error: {:?}", e))
})?;
Expand All @@ -75,6 +98,7 @@ impl SerializedBiscuit {
next_key,
signature,
external_signature: None,
version: data.authority.version.unwrap_or_default(),
};

let mut blocks = Vec::new();
Expand All @@ -88,6 +112,14 @@ impl SerializedBiscuit {
let signature = ed25519_dalek::Signature::from_bytes(&bytes);

let external_signature = if let Some(ex) = block.external_signature.as_ref() {
if verification_mode == ThirdPartyVerificationMode::PreviousSignatureHashing {
if block.version != Some(THIRD_PARTY_SIGNATURE_VERSION) {
return Err(error::Format::DeserializationError(
"Unsupported third party block version".to_string(),
));
}
}

let public_key = PublicKey::from_proto(&ex.public_key)?;

let bytes: [u8; 64] = (&ex.signature[..])
Expand All @@ -109,6 +141,7 @@ impl SerializedBiscuit {
next_key,
signature,
external_signature,
version: block.version.unwrap_or_default(),
});
}

Expand Down Expand Up @@ -219,6 +252,7 @@ impl SerializedBiscuit {
next_key: self.authority.next_key.to_proto(),
signature: self.authority.signature.to_bytes().to_vec(),
external_signature: None,
version: None,
};

let mut blocks = Vec::new();
Expand All @@ -233,6 +267,11 @@ impl SerializedBiscuit {
public_key: external_signature.public_key.to_proto(),
}
}),
version: if block.external_signature.is_some() {
Some(THIRD_PARTY_SIGNATURE_VERSION)
} else {
None
},
};

blocks.push(b);
Expand Down Expand Up @@ -293,6 +332,7 @@ impl SerializedBiscuit {
next_key: next_keypair.public(),
signature,
external_signature: None,
version: THIRD_PARTY_SIGNATURE_VERSION,
},
blocks: vec![],
proof: TokenNext::Secret(next_keypair.private()),
Expand Down Expand Up @@ -327,6 +367,7 @@ impl SerializedBiscuit {
next_key: next_keypair.public(),
signature,
external_signature,
version: THIRD_PARTY_SIGNATURE_VERSION,
});

Ok(SerializedBiscuit {
Expand Down Expand Up @@ -360,6 +401,7 @@ impl SerializedBiscuit {
next_key: next_keypair.public(),
signature,
external_signature,
version: THIRD_PARTY_SIGNATURE_VERSION,
});

Ok(SerializedBiscuit {
Expand All @@ -372,15 +414,43 @@ impl SerializedBiscuit {

/// checks the signature on a deserialized token
pub fn verify(&self, root: &PublicKey) -> Result<(), error::Format> {
self.verify_inner(root, ThirdPartyVerificationMode::PreviousSignatureHashing)
}

pub(crate) fn verify_inner(
&self,
root: &PublicKey,
verification_mode: ThirdPartyVerificationMode,
) -> Result<(), error::Format> {
//FIXME: try batched signature verification
let mut current_pub = root;

crypto::verify_block_signature(&self.authority, current_pub)?;
let mut previous_signature = None;

crypto::verify_block_signature(
&self.authority,
current_pub,
previous_signature,
ThirdPartyVerificationMode::PreviousSignatureHashing,
)?;
current_pub = &self.authority.next_key;
previous_signature = Some(&self.authority.signature);

for block in &self.blocks {
crypto::verify_block_signature(block, current_pub)?;
let verification_mode = match (block.version, verification_mode) {
(0, ThirdPartyVerificationMode::UnsafeLegacy) => {
ThirdPartyVerificationMode::UnsafeLegacy
}
_ => ThirdPartyVerificationMode::PreviousSignatureHashing,
};

crypto::verify_block_signature(
block,
current_pub,
previous_signature,
verification_mode,
)?;
current_pub = &block.next_key;
previous_signature = Some(&block.signature);
}

match &self.proof {
Expand Down Expand Up @@ -453,6 +523,12 @@ impl SerializedBiscuit {
}
}

#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) enum ThirdPartyVerificationMode {
UnsafeLegacy,
PreviousSignatureHashing,
}

#[cfg(test)]
mod tests {
use std::io::Read;
Expand Down
3 changes: 3 additions & 0 deletions biscuit-auth/src/format/schema.proto
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ message SignedBlock {
required PublicKey nextKey = 2;
required bytes signature = 3;
optional ExternalSignature externalSignature = 4;
optional uint32 version = 5;
}

message ExternalSignature {
Expand Down Expand Up @@ -190,6 +191,8 @@ message AuthorizerPolicies {
message ThirdPartyBlockRequest {
required PublicKey previousKey = 1;
repeated PublicKey publicKeys = 2;
required bytes previousSignature = 3;

}

message ThirdPartyBlockContents {
Expand Down
4 changes: 4 additions & 0 deletions biscuit-auth/src/format/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ pub struct SignedBlock {
pub signature: ::prost::alloc::vec::Vec<u8>,
#[prost(message, optional, tag="4")]
pub external_signature: ::core::option::Option<ExternalSignature>,
#[prost(uint32, optional, tag="5")]
pub version: ::core::option::Option<u32>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ExternalSignature {
Expand Down Expand Up @@ -291,6 +293,8 @@ pub struct ThirdPartyBlockRequest {
pub previous_key: PublicKey,
#[prost(message, repeated, tag="2")]
pub public_keys: ::prost::alloc::vec::Vec<PublicKey>,
#[prost(bytes="vec", required, tag="3")]
pub previous_signature: ::prost::alloc::vec::Vec<u8>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ThirdPartyBlockContents {
Expand Down
2 changes: 2 additions & 0 deletions biscuit-auth/src/token/authorizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1559,6 +1559,8 @@ mod tests {
.unwrap();
let res = req.create_block(&external.private(), builder).unwrap();
let biscuit2 = biscuit1.append_third_party(external.public(), res).unwrap();
let serialized = biscuit2.to_vec().unwrap();
let biscuit2 = Biscuit::from(serialized, root.public()).unwrap();

let mut authorizer = Authorizer::new();
let external2 = KeyPair::new();
Expand Down
Loading