Skip to content

[BREAKING] Abstract biscuit-auth over crypto implementation - #334

Open
saoirse-a wants to merge 3 commits into
eclipse-biscuit:mainfrom
saoirse-a:crypto-traits-2
Open

[BREAKING] Abstract biscuit-auth over crypto implementation#334
saoirse-a wants to merge 3 commits into
eclipse-biscuit:mainfrom
saoirse-a:crypto-traits-2

Conversation

@saoirse-a

Copy link
Copy Markdown
Contributor

This adds traits to the biscuit-auth public interface for cryptographic operations and abstracts the Biscuit and related types over those traits.

The traits added are Sign, Verify, SerializePublicKey and SerializePrivateKey. Together, these represent the necessary behavior for public and private keys, distinguishing those which can be serialised into the token and those which can't:

pub trait Sign {
    type PublicKey: Verify;

    fn sign(&self, data: &[u8]) -> Result<Signature, error::Format>;
    fn public(&self) -> Self::PublicKey;
    fn algorithm(&self) -> Algorithm;
}

pub trait Verify {
    fn verify_signature(
        &self,
        data: &[u8],
        signature: &Signature,
    ) -> Result<(), error::Format>;
    fn algorithm(&self) -> Algorithm;
}

pub trait SerializePrivateKey: Sign<PublicKey: SerializePublicKey> + Clone + Sized {
    fn new_with_rng<R: RngCore + CryptoRng>(algorithm: Algorithm, rng: &mut R) -> Self;
    fn from_bytes_and_algorithm(algorithm: Algorithm, bytes: &[u8]) -> Result<Self, error::Format>;
    fn to_bytes(&self) -> Zeroizing<Vec<u8>>;
}

pub trait SerializePublicKey: Verify + Clone + PartialEq + Sized {
    fn from_bytes_and_algorithm(algorithm: Algorithm, bytes: &[u8]) -> Result<Self, error::Format>;
    fn to_bytes(&self) -> Vec<u8>;
}

The Biscuit type is parameterised by a SerializePrivateKey, which is the type of the key contain in its proof. All of the public keys in the token are the associated serialisable public key type for that private key.

The issuer key and third party signing keys can be different types from the key contained in the token; for the issuer neither the private key nor the public key need be serialisable, for the third party key only the public key needs to be serialisable because it appears in the token.

The existing PublicKey and PrivateKey types implement these traits; no other implementations are added directly to biscuit-auth with this PR (we could either add implementations for other crypto libraries like aws-lc-rs, maybe feature gated, or just require users to write their own implementations).

To support this change, two significant refactors were performed:

  1. The Keypair type was eliminated. This was effectively redundant with the PrivateKey type, removing it made the set of traits required simpler.
  2. The PublicKeys table used in authorization execution now stores an inert representation of the public key instead of the PublicKey type, because it doesn't need to verify signatures, just compare keys for equality. This avoids more abstraction points over crypto types in the Datalog code.

KeyPair is really just the same as PrivateKey, it's not necessary to
have two distinct types for these purposes and it makes trait
abstraction more complex.
@saoirse-a

Copy link
Copy Markdown
Contributor Author

I think there is more work to be done to fully integrate the traits:

  1. Some APIs in the builders aren't generic over crypto providers still.
  2. Some of the Datalog auth code (like the Authorizer itself) is abstract over key types because it stores more data from the token than it really needs to.

Before I go through all of that with a fine tooth comb I wanted to get confirmation that this is the right direction.

@divarvel

Copy link
Copy Markdown
Contributor

I like this approach as it neatly handles both KMS and pluggable crypto providers.
It also opens the door to unifying Biscuit and UnverifiedBiscuit (I think)

This commit adds four traits to biscuit-auth: `Sign`, `Verify`,
`SerializePublicKey` and `SerializePrivateKey`. Together, these
represent the behaviors of private keys and public keys, including those
which can be serialized into the token and those which can't.

Biscuit themselves become parameterized by the private key type that
their proof uses (the public keys used in the token being determined as
associated types of that private key type). These public and private
keys must be serializable. For issuing a token, the keys used do not
need to be serializable, and do not need to be the same as used for the
token-internal keys. For third party signing, only the public key needs
to be serializable.

Some refactors were made to enable this change:

1. The `Keypair` type was eliminated from the code; it's function is
   fulfilled by the private key type and a third type was not necessary.
2. The useage of public keys in datalog authorizer running doesn't need
   an actual crypto implementation, just key data, so its replaced by an
   inert public key type that contains the algorithm tag and bytes.
@saoirse-a

Copy link
Copy Markdown
Contributor Author

Updated, I think this is ready now.

@divarvel divarvel left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This generally looks good to me. got a few questions and nit-level comments.

)
.unwrap();
hex::decode("6e9e6d5a75cf0c0e87ec1256b4dfed0ca3ba452912d213fcc70f8516583db9db").unwrap(),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: it would make sense to use the from_str implementations here now that we have those (not directly related to the PR, but since we have to update these lines)

}

pub fn insert_fallible(&mut self, k: &PublicKey) -> Result<u64, error::Format> {
pub(crate) fn insert_serialize<K: SerializePublicKey>(&mut self, k: &K) -> u64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: insert_serialize makes it sound like serialize is part of the action being performed, whereas it’s because of the trait name. Maybe making the trait name more explicit would help?


pub fn get(&self, k: &PublicKey) -> Option<u64> {
self.keys.iter().position(|key| key == k).map(|i| i as u64)
pub(crate) fn insert_proto(&mut self, k: &schema::PublicKey) -> u64 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

i’m not sure having a dependency to the proto module makes sense here. i’d rather have all the dependencies to proto be located in convert

Comment thread biscuit-auth/src/token/authorizer.rs
use crate::crypto::Signature;
use crate::error;

pub trait Verify {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

nit: the trait names make sense in the context of this trait, but i’ve found those to be a bit harder to understand when reviewing call sites.

Comment thread biscuit-auth/src/token/builder/authorizer.rs
Comment thread biscuit-auth/CHANGELOG.md
@@ -1,3 +1,7 @@
# `7.0.0`

- Abstract biscuits over the crypto implementation (#334)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe a "breaking" section indicating what changed and how to migrate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants