diff --git a/biscuit.go b/biscuit.go index b5cf545..2f532e7 100644 --- a/biscuit.go +++ b/biscuit.go @@ -41,6 +41,9 @@ var ( ErrInvalidBlockRule = errors.New("biscuit: invalid block rule") // ErrEmptyKeys is returned when verifying a biscuit having no keys ErrEmptyKeys = errors.New("biscuit: empty keys") + // ErrNoPublicKeyAvailable is returned when no public root key is available to verify the + // signatures on a biscuit's blocks. + ErrNoPublicKeyAvailable = errors.New("biscuit: no public key available") // ErrUnknownPublicKey is returned when verifying a biscuit with the wrong public key ErrUnknownPublicKey = errors.New("biscuit: unknown public key") @@ -291,10 +294,42 @@ func (b *Biscuit) Seal(rng io.Reader) (*Biscuit, error) { }, nil } -// Checks the signature and creates an Authorizer -// The Authorizer can then test the authorizaion policies and -// accept or refuse the request -func (b *Biscuit) Authorizer(root ed25519.PublicKey, opts ...AuthorizerOption) (Authorizer, error) { +type ( + // A PublickKeyByIDProjection inspects an optional ID for a public key and returns the + // corresponding public key, if any. If it doesn't recognize the ID or can't find the public + // key, or no ID is supplied and there is no default public key available, it should return an + // error satisfying errors.Is(err, ErrNoPublicKeyAvailable). + PublickKeyByIDProjection func(*uint32) (ed25519.PublicKey, error) +) + +// WithSingularRootPublicKey supplies one public key to use as the root key with which to verify the +// signatures on a biscuit's blocks. +func WithSingularRootPublicKey(key ed25519.PublicKey) PublickKeyByIDProjection { + return func(*uint32) (ed25519.PublicKey, error) { + return key, nil + } +} + +// WithRootPublicKeys supplies a mapping to public keys from their corresponding IDs, used to select +// which public key to use to verify the signatures on a biscuit's blocks based on the key ID +// embedded within the biscuit when it was created. If the biscuit has no key ID available, this +// function selects the optional default key instead. If no public key is available—whether for the +// biscuit's embedded key ID or a default key when no such ID is present—it returns +// [ErrNoPublicKeyAvailable]. +func WithRootPublicKeys(keysByID map[uint32]ed25519.PublicKey, defaultKey *ed25519.PublicKey) PublickKeyByIDProjection { + return func(id *uint32) (ed25519.PublicKey, error) { + if id == nil { + if defaultKey != nil { + return *defaultKey, nil + } + } else if key, ok := keysByID[*id]; ok { + return key, nil + } + return nil, ErrNoPublicKeyAvailable + } +} + +func (b *Biscuit) authorizerFor(root ed25519.PublicKey, opts ...AuthorizerOption) (Authorizer, error) { currentKey := root // for now we only support Ed25519 @@ -377,6 +412,34 @@ func (b *Biscuit) Authorizer(root ed25519.PublicKey, opts ...AuthorizerOption) ( return NewVerifier(b, opts...) } +// AuthorizerFor selects from the supplied source a root public key to use to verify the signatures +// on the biscuit's blocks, returning an error satisfying errors.Is(err, ErrNoPublicKeyAvailable) if +// no such public key is available. If the signatures are valid, it creates an [Authorizer], which +// can then test the authorization policies and accept or refuse the request. +func (b *Biscuit) AuthorizerFor(keySource PublickKeyByIDProjection, opts ...AuthorizerOption) (Authorizer, error) { + if keySource == nil { + return nil, errors.New("root public key source must not be nil") + } + rootPublicKey, err := keySource(b.RootKeyID()) + if err != nil { + return nil, fmt.Errorf("choosing root public key: %w", err) + } + if len(rootPublicKey) == 0 { + return nil, ErrNoPublicKeyAvailable + } + return b.authorizerFor(rootPublicKey, opts...) +} + +// TODO: Add "Deprecated" note to the "(*Biscuit).Authorizer" method, recommending use of +// "(*Biscuit).AuthorizerFor" instead. Wait until after we release the module with the latter +// available, per https://go.dev/wiki/Deprecated. + +// Authorizer checks the signature and creates an [Authorizer]. The Authorizer can then test the +// authorizaion policies and accept or refuse the request. +func (b *Biscuit) Authorizer(root ed25519.PublicKey, opts ...AuthorizerOption) (Authorizer, error) { + return b.authorizerFor(root) +} + func (b *Biscuit) Checks() [][]datalog.Check { result := make([][]datalog.Check, 0, len(b.blocks)+1) result = append(result, b.authority.checks) diff --git a/biscuit_test.go b/biscuit_test.go index 0aae6d5..3edaf09 100644 --- a/biscuit_test.go +++ b/biscuit_test.go @@ -100,7 +100,7 @@ func TestBiscuit(t *testing.T) { b3deser, err := Unmarshal(b3ser) require.NoError(t, err) - v3, err := b3deser.Authorizer(publicRoot) + v3, err := b3deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) require.NoError(t, err) v3.AddFact(Fact{Predicate: Predicate{Name: "resource", IDs: []Term{String("/a/file1")}}}) @@ -108,14 +108,14 @@ func TestBiscuit(t *testing.T) { v3.AddPolicy(DefaultAllowPolicy) require.NoError(t, v3.Authorize()) - v3, err = b3deser.Authorizer(publicRoot) + v3, err = b3deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) require.NoError(t, err) v3.AddFact(Fact{Predicate: Predicate{Name: "resource", IDs: []Term{String("/a/file2")}}}) v3.AddFact(Fact{Predicate: Predicate{Name: "operation", IDs: []Term{String("read")}}}) v3.AddPolicy(DefaultAllowPolicy) require.Error(t, v3.Authorize()) - v3, err = b3deser.Authorizer(publicRoot) + v3, err = b3deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) require.NoError(t, err) v3.AddFact(Fact{Predicate: Predicate{Name: "resource", IDs: []Term{String("/a/file1")}}}) v3.AddFact(Fact{Predicate: Predicate{Name: "operation", IDs: []Term{String("write")}}}) @@ -176,7 +176,7 @@ func TestSealedBiscuit(t *testing.T) { b2deser, err := Unmarshal(b2ser) require.NoError(t, err) - _, err = b2deser.Authorizer(publicRoot) + _, err = b2deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) require.NoError(t, err) } @@ -260,7 +260,7 @@ func TestBiscuitRules(t *testing.T) { func verifyOwner(t *testing.T, b Biscuit, publicRoot ed25519.PublicKey, owners map[string]bool) { for user, valid := range owners { - v, err := b.Authorizer(publicRoot) + v, err := b.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) require.NoError(t, err) t.Run(fmt.Sprintf("verify owner %s", user), func(t *testing.T) { @@ -288,18 +288,31 @@ func verifyOwner(t *testing.T, b Biscuit, publicRoot ed25519.PublicKey, owners m func TestCheckRootKey(t *testing.T) { rng := rand.Reader + const rootKeyID = 123 publicRoot, privateRoot, _ := ed25519.GenerateKey(rng) - builder := NewBuilder(privateRoot) + builder := NewBuilder(privateRoot, WithRootKeyID(rootKeyID)) b, err := builder.Build() require.NoError(t, err) - _, err = b.Authorizer(publicRoot) + _, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{ + rootKeyID: publicRoot, + }, nil)) require.NoError(t, err) + _, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{ + rootKeyID + 1: publicRoot, + }, nil)) + require.ErrorIs(t, err, ErrNoPublicKeyAvailable) + + _, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{ + rootKeyID: nil, + }, nil)) + require.ErrorIs(t, err, ErrNoPublicKeyAvailable) + publicNotRoot, _, _ := ed25519.GenerateKey(rng) - _, err = b.Authorizer(publicNotRoot) + _, err = b.AuthorizerFor(WithSingularRootPublicKey(publicNotRoot)) require.Equal(t, ErrInvalidSignature, err) } @@ -434,11 +447,11 @@ func TestBiscuitVerifyErrors(t *testing.T) { b, err := builder.Build() require.NoError(t, err) - _, err = b.Authorizer(publicRoot) + _, err = b.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) require.NoError(t, err) publicTest, _, _ := ed25519.GenerateKey(rng) - _, err = b.Authorizer(publicTest) + _, err = b.AuthorizerFor(WithSingularRootPublicKey(publicTest)) require.Error(t, err) } @@ -465,7 +478,7 @@ func TestBiscuitSha256Sum(t *testing.T) { b, err = b.Append(rng, root, blockBuilder.Build()) require.NoError(t, err) require.Equal(t, 1, b.BlockCount()) - +p h10, err := b.SHA256Sum(0) require.NoError(t, err) require.Equal(t, h0, h10) @@ -591,7 +604,7 @@ func TestInvalidRuleGeneration(t *testing.T) { require.NoError(t, err) t.Log(b.String()) - verifier, err := b.Authorizer(publicRoot) + verifier, err := b.AuthorizerFor(WithSingularRootPublicKey(publicRoot)) require.NoError(t, err) verifier.AddFact(Fact{Predicate: Predicate{