Skip to content

Commit d4b5e2c

Browse files
authored
Allow selecting root public key by ID (#154)
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 e51c1c2 commit d4b5e2c

File tree

2 files changed

+92
-16
lines changed

2 files changed

+92
-16
lines changed

Diff for: 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)

Diff for: biscuit_test.go

+25-12
Original file line numberDiff line numberDiff line change
@@ -100,22 +100,22 @@ func TestBiscuit(t *testing.T) {
100100
b3deser, err := Unmarshal(b3ser)
101101
require.NoError(t, err)
102102

103-
v3, err := b3deser.Authorizer(publicRoot)
103+
v3, err := b3deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot))
104104
require.NoError(t, err)
105105

106106
v3.AddFact(Fact{Predicate: Predicate{Name: "resource", IDs: []Term{String("/a/file1")}}})
107107
v3.AddFact(Fact{Predicate: Predicate{Name: "operation", IDs: []Term{String("read")}}})
108108
v3.AddPolicy(DefaultAllowPolicy)
109109
require.NoError(t, v3.Authorize())
110110

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

118-
v3, err = b3deser.Authorizer(publicRoot)
118+
v3, err = b3deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot))
119119
require.NoError(t, err)
120120
v3.AddFact(Fact{Predicate: Predicate{Name: "resource", IDs: []Term{String("/a/file1")}}})
121121
v3.AddFact(Fact{Predicate: Predicate{Name: "operation", IDs: []Term{String("write")}}})
@@ -176,7 +176,7 @@ func TestSealedBiscuit(t *testing.T) {
176176
b2deser, err := Unmarshal(b2ser)
177177
require.NoError(t, err)
178178

179-
_, err = b2deser.Authorizer(publicRoot)
179+
_, err = b2deser.AuthorizerFor(WithSingularRootPublicKey(publicRoot))
180180
require.NoError(t, err)
181181
}
182182

@@ -260,7 +260,7 @@ func TestBiscuitRules(t *testing.T) {
260260

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

266266
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
288288

289289
func TestCheckRootKey(t *testing.T) {
290290
rng := rand.Reader
291+
const rootKeyID = 123
291292
publicRoot, privateRoot, _ := ed25519.GenerateKey(rng)
292293

293-
builder := NewBuilder(privateRoot)
294+
builder := NewBuilder(privateRoot, WithRootKeyID(rootKeyID))
294295

295296
b, err := builder.Build()
296297
require.NoError(t, err)
297298

298-
_, err = b.Authorizer(publicRoot)
299+
_, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{
300+
rootKeyID: publicRoot,
301+
}, nil))
299302
require.NoError(t, err)
300303

304+
_, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{
305+
rootKeyID + 1: publicRoot,
306+
}, nil))
307+
require.ErrorIs(t, err, ErrNoPublicKeyAvailable)
308+
309+
_, err = b.AuthorizerFor(WithRootPublicKeys(map[uint32]ed25519.PublicKey{
310+
rootKeyID: nil,
311+
}, nil))
312+
require.ErrorIs(t, err, ErrNoPublicKeyAvailable)
313+
301314
publicNotRoot, _, _ := ed25519.GenerateKey(rng)
302-
_, err = b.Authorizer(publicNotRoot)
315+
_, err = b.AuthorizerFor(WithSingularRootPublicKey(publicNotRoot))
303316
require.Equal(t, ErrInvalidSignature, err)
304317
}
305318

@@ -434,11 +447,11 @@ func TestBiscuitVerifyErrors(t *testing.T) {
434447
b, err := builder.Build()
435448
require.NoError(t, err)
436449

437-
_, err = b.Authorizer(publicRoot)
450+
_, err = b.AuthorizerFor(WithSingularRootPublicKey(publicRoot))
438451
require.NoError(t, err)
439452

440453
publicTest, _, _ := ed25519.GenerateKey(rng)
441-
_, err = b.Authorizer(publicTest)
454+
_, err = b.AuthorizerFor(WithSingularRootPublicKey(publicTest))
442455
require.Error(t, err)
443456
}
444457

@@ -465,7 +478,7 @@ func TestBiscuitSha256Sum(t *testing.T) {
465478
b, err = b.Append(rng, root, blockBuilder.Build())
466479
require.NoError(t, err)
467480
require.Equal(t, 1, b.BlockCount())
468-
481+
p
469482
h10, err := b.SHA256Sum(0)
470483
require.NoError(t, err)
471484
require.Equal(t, h0, h10)
@@ -591,7 +604,7 @@ func TestInvalidRuleGeneration(t *testing.T) {
591604
require.NoError(t, err)
592605
t.Log(b.String())
593606

594-
verifier, err := b.Authorizer(publicRoot)
607+
verifier, err := b.AuthorizerFor(WithSingularRootPublicKey(publicRoot))
595608
require.NoError(t, err)
596609

597610
verifier.AddFact(Fact{Predicate: Predicate{

0 commit comments

Comments
 (0)