From 6d94fadc54b73205ba9711ebe03eab205675f965 Mon Sep 17 00:00:00 2001 From: Flavien Binet Date: Thu, 5 Nov 2020 11:29:37 +0100 Subject: [PATCH] Improve biscuit signature cookbook interface Signature cookbook now exposes more generic endpoints, allowing to integrate more easily in real use cases context. The endpoints now works on biscuit.Builder or biscuit.Verifier instead of raw biscuits, so the signature flow can be chained with others Verifier now exposes a SHA256Sum proxy to the biscuit one to avoid an extra biscuit parameter requirement on signature verifcation. Verifier PrintWorld now run the world on call to ease debugging. This ensure rules have generated their facts before printing. --- cookbook/signedbiscuit/biscuit.go | 75 +++++++++++++------------- cookbook/signedbiscuit/biscuit_test.go | 29 ++++++++-- verifier.go | 8 +++ 3 files changed, 69 insertions(+), 43 deletions(-) diff --git a/cookbook/signedbiscuit/biscuit.go b/cookbook/signedbiscuit/biscuit.go index 94907d2..59e0fde 100644 --- a/cookbook/signedbiscuit/biscuit.go +++ b/cookbook/signedbiscuit/biscuit.go @@ -39,11 +39,11 @@ func NewECDSAKeyPair(priv *ecdsa.PrivateKey) (*UserKeyPair, error) { }, nil } -// GenerateSignable returns a biscuit which will only verify after being +// WithSignableFacts returns a biscuit which will only verify after being // signed with the private key matching the given userPubkey. -func GenerateSignable(rootKey sig.Keypair, audience string, audienceKey crypto.Signer, userPublicKey []byte, expireTime time.Time, m *Metadata) ([]byte, error) { +func WithSignableFacts(b biscuit.Builder, audience string, audienceKey crypto.Signer, userPublicKey []byte, expireTime time.Time, m *Metadata) (biscuit.Builder, error) { builder := &hubauthBuilder{ - biscuit.NewBuilder(rand.Reader, rootKey), + Builder: b, } if err := builder.withAudienceSignature(audience, audienceKey); err != nil { @@ -62,12 +62,7 @@ func GenerateSignable(rootKey sig.Keypair, audience string, audienceKey crypto.S return nil, err } - b, err := builder.Build() - if err != nil { - return nil, err - } - - return b.Serialize() + return builder.Builder, nil } // Sign append a user signature on the given token and return it. @@ -123,70 +118,72 @@ func Sign(token []byte, rootPubKey sig.PublicKey, userKey *UserKeyPair) ([]byte, return b.Serialize() } -type VerifiedMetadata struct { +type UserSignatureMetadata struct { *Metadata UserSignatureNonce []byte UserSignatureTimestamp time.Time } -// Verify will verify the biscuit, the included audience and user signature, and return an error -// when anything is invalid. -func Verify(token []byte, rootPubKey sig.PublicKey, audience string, audienceKey *ecdsa.PublicKey) (*VerifiedMetadata, error) { - b, err := biscuit.Unmarshal(token) - if err != nil { - return nil, fmt.Errorf("biscuit: failed to unmarshal: %w", err) - } - - v, err := b.Verify(rootPubKey) - if err != nil { - return nil, fmt.Errorf("biscuit: failed to verify: %w", err) +// WithSignatureVerification prepares the given verifier in order to verify the audience and user signatures. +// The user signature metadata are returned to the caller to handle the anti replay checks, but they shouldn't be used +// before having called verifier.Verify() +func WithSignatureVerification(v biscuit.Verifier, audience string, audienceKey *ecdsa.PublicKey) (biscuit.Verifier, *UserSignatureMetadata, error) { + verifier := &hubauthVerifier{ + Verifier: v, } - verifier := &hubauthVerifier{v} audienceVerificationData, err := verifier.getAudienceVerificationData(audience) if err != nil { - return nil, fmt.Errorf("biscuit: failed to retrieve audience signature data: %w", err) + return nil, nil, fmt.Errorf("biscuit: failed to retrieve audience signature data: %w", err) } if err := verifyAudienceSignature(audienceKey, audienceVerificationData); err != nil { - return nil, fmt.Errorf("biscuit: failed to verify audience signature: %w", err) + return nil, nil, fmt.Errorf("biscuit: failed to verify audience signature: %w", err) } if err := verifier.withValidatedAudienceSignature(audienceVerificationData); err != nil { - return nil, fmt.Errorf("biscuit: failed to add validated signature: %w", err) + return nil, nil, fmt.Errorf("biscuit: failed to add validated signature: %w", err) } userVerificationData, err := verifier.getUserVerificationData() if err != nil { - return nil, fmt.Errorf("biscuit: failed to retrieve user signature data: %w", err) + return nil, nil, fmt.Errorf("biscuit: failed to retrieve user signature data: %w", err) + } + + signatureBlockID, err := v.GetBlockID(biscuit.Fact{Predicate: biscuit.Predicate{ + Name: "signature", + IDs: []biscuit.Atom{ + userVerificationData.DataID, + userVerificationData.UserPubKey, + userVerificationData.Signature, + userVerificationData.Nonce, + userVerificationData.Timestamp, + }, + }}) + if err != nil { + return nil, nil, fmt.Errorf("biscuit: failed to retrieve signature blockID: %w", err) } - // TODO: improve biscuit API to allow retrieve the block index the signature is at - // so that we can still append other blocks if needed. Right now the signature MUST BE the last block. - signedTokenHash, err := b.SHA256Sum(b.BlockCount() - 1) + signedTokenHash, err := v.SHA256Sum(signatureBlockID - 1) if err != nil { - return nil, fmt.Errorf("biscuit: failed to generate token hash: %w", err) + return nil, nil, fmt.Errorf("biscuit: failed to generate token hash: %w", err) } if err := verifyUserSignature(signedTokenHash, userVerificationData); err != nil { - return nil, fmt.Errorf("biscuit: failed to verify user signature: %w", err) + return nil, nil, fmt.Errorf("biscuit: failed to verify user signature: %w", err) } if err := verifier.withValidatedUserSignature(userVerificationData); err != nil { - return nil, fmt.Errorf("biscuit: failed to add validated signature: %w", err) + return nil, nil, fmt.Errorf("biscuit: failed to add validated signature: %w", err) } if err := verifier.withCurrentTime(time.Now()); err != nil { - return nil, fmt.Errorf("biscuit: failed to add current time: %w", err) - } - - if err := verifier.Verify(); err != nil { - return nil, fmt.Errorf("biscuit: failed to verify: %w", err) + return nil, nil, fmt.Errorf("biscuit: failed to add current time: %w", err) } metas, err := verifier.getMetadata() if err != nil { - return nil, fmt.Errorf("biscuit: failed to get metadata: %v", err) + return nil, nil, fmt.Errorf("biscuit: failed to get metadata: %v", err) } - return &VerifiedMetadata{ + return v, &UserSignatureMetadata{ Metadata: metas, UserSignatureNonce: userVerificationData.Nonce, UserSignatureTimestamp: time.Time(userVerificationData.Timestamp), diff --git a/cookbook/signedbiscuit/biscuit_test.go b/cookbook/signedbiscuit/biscuit_test.go index 3a6f37c..b75af16 100644 --- a/cookbook/signedbiscuit/biscuit_test.go +++ b/cookbook/signedbiscuit/biscuit_test.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/flynn/biscuit-go" "github.com/flynn/biscuit-go/sig" "github.com/stretchr/testify/require" ) @@ -25,7 +26,15 @@ func TestBiscuit(t *testing.T) { UserID: "1234", IssueTime: time.Now(), } - signableBiscuit, err := GenerateSignable(rootKey, audience, audienceKey, userKey.Public, time.Now().Add(5*time.Minute), metas) + + builder := biscuit.NewBuilder(rand.Reader, rootKey) + + builder, err = WithSignableFacts(builder, audience, audienceKey, userKey.Public, time.Now().Add(5*time.Minute), metas) + require.NoError(t, err) + + b, err := builder.Build() + require.NoError(t, err) + signableBiscuit, err := b.Serialize() require.NoError(t, err) t.Logf("signable biscuit size: %d", len(signableBiscuit)) @@ -34,7 +43,14 @@ func TestBiscuit(t *testing.T) { require.NoError(t, err) t.Logf("signed biscuit size: %d", len(signedBiscuit)) - res, err := Verify(signedBiscuit, rootKey.Public(), audience, audienceKey.Public().(*ecdsa.PublicKey)) + b, err := biscuit.Unmarshal(signedBiscuit) + require.NoError(t, err) + verifier, err := b.Verify(rootKey.Public()) + require.NoError(t, err) + + verifier, res, err := WithSignatureVerification(verifier, audience, audienceKey.Public().(*ecdsa.PublicKey)) + require.NoError(t, verifier.Verify()) + require.NoError(t, err) require.Equal(t, metas.ClientID, res.ClientID) require.Equal(t, metas.UserID, res.UserID) @@ -53,13 +69,18 @@ func TestBiscuit(t *testing.T) { signedBiscuit, err := Sign(signableBiscuit, rootKey.Public(), userKey) require.NoError(t, err) - _, err = Verify(signedBiscuit, rootKey.Public(), "http://another.audience.url", audienceKey.Public().(*ecdsa.PublicKey)) + b, err := biscuit.Unmarshal(signedBiscuit) + require.NoError(t, err) + verifier, err := b.Verify(rootKey.Public()) + require.NoError(t, err) + + _, _, err = WithSignatureVerification(verifier, "http://another.audience.url", audienceKey.Public().(*ecdsa.PublicKey)) require.Error(t, err) wrongAudienceKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) - _, err = Verify(signedBiscuit, rootKey.Public(), audience, wrongAudienceKey.Public().(*ecdsa.PublicKey)) + _, _, err = WithSignatureVerification(verifier, audience, wrongAudienceKey.Public().(*ecdsa.PublicKey)) require.Error(t, err) }) } diff --git a/verifier.go b/verifier.go index d8ca5dd..7a8d7b6 100644 --- a/verifier.go +++ b/verifier.go @@ -20,6 +20,7 @@ type Verifier interface { Verify() error Query(rule Rule) (FactSet, error) GetBlockID(fact Fact) (int, error) + SHA256Sum(count int) ([]byte, error) Reset() PrintWorld() string } @@ -166,11 +167,18 @@ func (v *verifier) GetBlockID(fact Fact) (int, error) { return 0, ErrFactNotFound } +// SHA256Sum proxy to the SHA256Sum of the underlying verified biscuit +func (v *verifier) SHA256Sum(count int) ([]byte, error) { + return v.biscuit.SHA256Sum(count) +} + func (v *verifier) PrintWorld() string { debug := datalog.SymbolDebugger{ SymbolTable: v.symbols, } + v.world.Run() // ensure rules have generated their facts + return debug.World(v.world) }