Skip to content

Commit

Permalink
Improve biscuit signature cookbook interface
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
daeMOn63 committed Nov 12, 2020
1 parent 5b60d39 commit 6d94fad
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 43 deletions.
75 changes: 36 additions & 39 deletions cookbook/signedbiscuit/biscuit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.
Expand Down Expand Up @@ -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),
Expand Down
29 changes: 25 additions & 4 deletions cookbook/signedbiscuit/biscuit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"testing"
"time"

"github.com/flynn/biscuit-go"
"github.com/flynn/biscuit-go/sig"
"github.com/stretchr/testify/require"
)
Expand All @@ -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))

Expand All @@ -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)
Expand All @@ -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)
})
}
8 changes: 8 additions & 0 deletions verifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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)
}

Expand Down

0 comments on commit 6d94fad

Please sign in to comment.