Skip to content

Commit 8bf6ea3

Browse files
committed
Allow selecting root public key by ID
In order to more easily accommodate rotating of root private keys when issuing biscuits, allow consumers to choose which root public key to use when verifying the biscuit based on the key ID embedded within it at composition time, if any. Consumers can then accept biscuits signed with several root keys, learning to accept signatures from a rolling set of both older and newer keys. Introduce the "(*Biscuit).AuthorizerFor" method as an eventual replacement for the longstanding "(*Biscuit).Authorizer" method, along with with two new options for supplying either a single public key or a mapping from ID to public key (together with an optional default public key to use when the biscuit in question embeds no root key ID). Alternately, callers may supply a projection function that consumes an optional root key ID.
1 parent 6cde69d commit 8bf6ea3

File tree

2 files changed

+92
-16
lines changed

2 files changed

+92
-16
lines changed

biscuit.go

+67-4
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ var (
4141
ErrInvalidBlockRule = errors.New("biscuit: invalid block rule")
4242
// ErrEmptyKeys is returned when verifying a biscuit having no keys
4343
ErrEmptyKeys = errors.New("biscuit: empty keys")
44+
// ErrNoPublicKeyAvailable is returned when no public root key is available to verify the
45+
// signatures on a biscuit's blocks.
46+
ErrNoPublicKeyAvailable = errors.New("biscuit: no public key available")
4447
// ErrUnknownPublicKey is returned when verifying a biscuit with the wrong public key
4548
ErrUnknownPublicKey = errors.New("biscuit: unknown public key")
4649

@@ -291,10 +294,42 @@ func (b *Biscuit) Seal(rng io.Reader) (*Biscuit, error) {
291294
}, nil
292295
}
293296

294-
// Checks the signature and creates an Authorizer
295-
// The Authorizer can then test the authorizaion policies and
296-
// accept or refuse the request
297-
func (b *Biscuit) Authorizer(root ed25519.PublicKey, opts ...AuthorizerOption) (Authorizer, error) {
297+
type (
298+
// A PublickKeyByIDProjection inspects an optional ID for a public key and returns the
299+
// corresponding public key, if any. If it doesn't recognize the ID or can't find the public
300+
// key, or no ID is supplied and there is no default public key available, it should return an
301+
// error satisfying errors.Is(err, ErrNoPublicKeyAvailable).
302+
PublickKeyByIDProjection func(*uint32) (ed25519.PublicKey, error)
303+
)
304+
305+
// WithSingularRootPublicKey supplies one public key to use as the root key with which to verify the
306+
// signatures on a biscuit's blocks.
307+
func WithSingularRootPublicKey(key ed25519.PublicKey) PublickKeyByIDProjection {
308+
return func(*uint32) (ed25519.PublicKey, error) {
309+
return key, nil
310+
}
311+
}
312+
313+
// WithRootPublicKeys supplies a mapping to public keys from their corresponding IDs, used to select
314+
// which public key to use to verify the signatures on a biscuit's blocks based on the key ID
315+
// embedded within the biscuit when it was created. If the biscuit has no key ID available, this
316+
// function selects the optional default key instead. If no public key is available—whether for the
317+
// biscuit's embedded key ID or a default key when no such ID is present—it returns
318+
// [ErrNoPublicKeyAvailable].
319+
func WithRootPublicKeys(keysByID map[uint32]ed25519.PublicKey, defaultKey *ed25519.PublicKey) PublickKeyByIDProjection {
320+
return func(id *uint32) (ed25519.PublicKey, error) {
321+
if id == nil {
322+
if defaultKey != nil {
323+
return *defaultKey, nil
324+
}
325+
} else if key, ok := keysByID[*id]; ok {
326+
return key, nil
327+
}
328+
return nil, ErrNoPublicKeyAvailable
329+
}
330+
}
331+
332+
func (b *Biscuit) authorizerFor(root ed25519.PublicKey, opts ...AuthorizerOption) (Authorizer, error) {
298333
currentKey := root
299334

300335
// for now we only support Ed25519
@@ -377,6 +412,34 @@ func (b *Biscuit) Authorizer(root ed25519.PublicKey, opts ...AuthorizerOption) (
377412
return NewVerifier(b, opts...)
378413
}
379414

415+
// AuthorizerFor selects from the supplied source a root public key to use to verify the signatures
416+
// on the biscuit's blocks, returning an error satisfying errors.Is(err, ErrNoPublicKeyAvailable) if
417+
// no such public key is available. If the signatures are valid, it creates an [Authorizer], which
418+
// can then test the authorization policies and accept or refuse the request.
419+
func (b *Biscuit) AuthorizerFor(keySource PublickKeyByIDProjection, opts ...AuthorizerOption) (Authorizer, error) {
420+
if keySource == nil {
421+
return nil, errors.New("root public key source must not be nil")
422+
}
423+
rootPublicKey, err := keySource(b.RootKeyID())
424+
if err != nil {
425+
return nil, fmt.Errorf("choosing root public key: %w", err)
426+
}
427+
if len(rootPublicKey) == 0 {
428+
return nil, ErrNoPublicKeyAvailable
429+
}
430+
return b.authorizerFor(rootPublicKey, opts...)
431+
}
432+
433+
// TODO: Add "Deprecated" note to the "(*Biscuit).Authorizer" method, recommending use of
434+
// "(*Biscuit).AuthorizerFor" instead. Wait until after we release the module with the latter
435+
// available, per https://go.dev/wiki/Deprecated.
436+
437+
// Authorizer checks the signature and creates an [Authorizer]. The Authorizer can then test the
438+
// authorizaion policies and accept or refuse the request.
439+
func (b *Biscuit) Authorizer(root ed25519.PublicKey, opts ...AuthorizerOption) (Authorizer, error) {
440+
return b.authorizerFor(root)
441+
}
442+
380443
func (b *Biscuit) Checks() [][]datalog.Check {
381444
result := make([][]datalog.Check, 0, len(b.blocks)+1)
382445
result = append(result, b.authority.checks)

biscuit_test.go

+25-12
Original file line numberDiff line numberDiff line change
@@ -96,22 +96,22 @@ func TestBiscuit(t *testing.T) {
9696
b3deser, err := Unmarshal(b3ser)
9797
require.NoError(t, err)
9898

99-
v3, err := b3deser.Authorizer(publicRoot)
99+
v3, err := b3deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot))
100100
require.NoError(t, err)
101101

102102
v3.AddFact(Fact{Predicate: Predicate{Name: "resource", IDs: []Term{String("/a/file1")}}})
103103
v3.AddFact(Fact{Predicate: Predicate{Name: "operation", IDs: []Term{String("read")}}})
104104
v3.AddPolicy(DefaultAllowPolicy)
105105
require.NoError(t, v3.Authorize())
106106

107-
v3, err = b3deser.Authorizer(publicRoot)
107+
v3, err = b3deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot))
108108
require.NoError(t, err)
109109
v3.AddFact(Fact{Predicate: Predicate{Name: "resource", IDs: []Term{String("/a/file2")}}})
110110
v3.AddFact(Fact{Predicate: Predicate{Name: "operation", IDs: []Term{String("read")}}})
111111
v3.AddPolicy(DefaultAllowPolicy)
112112
require.Error(t, v3.Authorize())
113113

114-
v3, err = b3deser.Authorizer(publicRoot)
114+
v3, err = b3deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot))
115115
require.NoError(t, err)
116116
v3.AddFact(Fact{Predicate: Predicate{Name: "resource", IDs: []Term{String("/a/file1")}}})
117117
v3.AddFact(Fact{Predicate: Predicate{Name: "operation", IDs: []Term{String("write")}}})
@@ -172,7 +172,7 @@ func TestSealedBiscuit(t *testing.T) {
172172
b2deser, err := Unmarshal(b2ser)
173173
require.NoError(t, err)
174174

175-
_, err = b2deser.Authorizer(publicRoot)
175+
_, err = b2deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot))
176176
require.NoError(t, err)
177177
}
178178

@@ -256,7 +256,7 @@ func TestBiscuitRules(t *testing.T) {
256256

257257
func verifyOwner(t *testing.T, b Biscuit, publicRoot ed25519.PublicKey, owners map[string]bool) {
258258
for user, valid := range owners {
259-
v, err := b.Authorizer(publicRoot)
259+
v, err := b.AuthorizerFor(WithSingularRootPublicKey(publicRoot))
260260
require.NoError(t, err)
261261

262262
t.Run(fmt.Sprintf("verify owner %s", user), func(t *testing.T) {
@@ -284,18 +284,31 @@ func verifyOwner(t *testing.T, b Biscuit, publicRoot ed25519.PublicKey, owners m
284284

285285
func TestCheckRootKey(t *testing.T) {
286286
rng := rand.Reader
287+
const rootKeyID = 123
287288
publicRoot, privateRoot, _ := ed25519.GenerateKey(rng)
288289

289-
builder := NewBuilder(privateRoot)
290+
builder := NewBuilder(privateRoot, WithRootKeyID(rootKeyID))
290291

291292
b, err := builder.Build()
292293
require.NoError(t, err)
293294

294-
_, err = b.Authorizer(publicRoot)
295+
_, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{
296+
rootKeyID: publicRoot,
297+
}, nil))
295298
require.NoError(t, err)
296299

300+
_, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{
301+
rootKeyID + 1: publicRoot,
302+
}, nil))
303+
require.ErrorIs(t, err, ErrNoPublicKeyAvailable)
304+
305+
_, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{
306+
rootKeyID: nil,
307+
}, nil))
308+
require.ErrorIs(t, err, ErrNoPublicKeyAvailable)
309+
297310
publicNotRoot, _, _ := ed25519.GenerateKey(rng)
298-
_, err = b.Authorizer(publicNotRoot)
311+
_, err = b.AuthorizerFor(WithSingularRootPublicKey(publicNotRoot))
299312
require.Equal(t, ErrInvalidSignature, err)
300313
}
301314

@@ -430,11 +443,11 @@ func TestBiscuitVerifyErrors(t *testing.T) {
430443
b, err := builder.Build()
431444
require.NoError(t, err)
432445

433-
_, err = b.Authorizer(publicRoot)
446+
_, err = b.AuthorizerFor(WithSingularRootPublicKey(publicRoot))
434447
require.NoError(t, err)
435448

436449
publicTest, _, _ := ed25519.GenerateKey(rng)
437-
_, err = b.Authorizer(publicTest)
450+
_, err = b.AuthorizerFor(WithSingularRootPublicKey(publicTest))
438451
require.Error(t, err)
439452
}
440453

@@ -461,7 +474,7 @@ func TestBiscuitSha256Sum(t *testing.T) {
461474
b, err = b.Append(rng, root, blockBuilder.Build())
462475
require.NoError(t, err)
463476
require.Equal(t, 1, b.BlockCount())
464-
477+
p
465478
h10, err := b.SHA256Sum(0)
466479
require.NoError(t, err)
467480
require.Equal(t, h0, h10)
@@ -587,7 +600,7 @@ func TestInvalidRuleGeneration(t *testing.T) {
587600
require.NoError(t, err)
588601
t.Log(b.String())
589602

590-
verifier, err := b.Authorizer(publicRoot)
603+
verifier, err := b.AuthorizerFor(WithSingularRootPublicKey(publicRoot))
591604
require.NoError(t, err)
592605

593606
verifier.AddFact(Fact{Predicate: Predicate{

0 commit comments

Comments
 (0)