From 1058a8380e4de6472074619d3c0d82cce6894b69 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Tue, 6 Oct 2020 15:03:44 +0200 Subject: [PATCH 01/17] idp: extract access token generation --- cmd/hubauth-ext/main.go | 7 ++-- pkg/idp/oauth.go | 62 +++++++++++++---------------------- pkg/idp/oauth_test.go | 42 +++++++----------------- pkg/idp/steps.go | 37 +++++++-------------- pkg/idp/steps_test.go | 53 ++++++++++++++++-------------- pkg/idp/token/builder.go | 57 ++++++++++++++++++++++++++++++++ pkg/idp/token/builder_test.go | 57 ++++++++++++++++++++++++++++++++ pkg/kmssign/kms.go | 14 ++++++++ 8 files changed, 208 insertions(+), 121 deletions(-) create mode 100644 pkg/idp/token/builder.go create mode 100644 pkg/idp/token/builder_test.go diff --git a/cmd/hubauth-ext/main.go b/cmd/hubauth-ext/main.go index d78b7c7..ef75f1f 100644 --- a/cmd/hubauth-ext/main.go +++ b/cmd/hubauth-ext/main.go @@ -15,6 +15,7 @@ import ( "github.com/flynn/hubauth/pkg/datastore" "github.com/flynn/hubauth/pkg/httpapi" "github.com/flynn/hubauth/pkg/idp" + "github.com/flynn/hubauth/pkg/idp/token" "github.com/flynn/hubauth/pkg/kmssign" "github.com/flynn/hubauth/pkg/rp/google" "go.opencensus.io/plugin/ochttp" @@ -77,10 +78,12 @@ func main() { os.Getenv("RP_GOOGLE_CLIENT_SECRET"), os.Getenv("BASE_URL")+"/rp/google", ), - kmsClient, []byte(secret("CODE_KEY_SECRET")), refreshKey, - idp.AudienceKeyNameFunc(os.Getenv("PROJECT_ID"), os.Getenv("KMS_LOCATION"), os.Getenv("KMS_KEYRING")), + token.NewSignedPBBuilder( + kmsClient, + kmssign.AudienceKeyNameFunc(os.Getenv("PROJECT_ID"), os.Getenv("KMS_LOCATION"), os.Getenv("KMS_KEYRING")), + ), ), CookieKey: []byte(secret("COOKIE_KEY_SECRET")), ProjectID: os.Getenv("PROJECT_ID"), diff --git a/pkg/idp/oauth.go b/pkg/idp/oauth.go index 4021576..a6f44d3 100644 --- a/pkg/idp/oauth.go +++ b/pkg/idp/oauth.go @@ -2,16 +2,14 @@ package idp import ( "context" - "crypto" "encoding/base64" - "net/url" "strings" "time" "github.com/flynn/hubauth/pkg/clog" "github.com/flynn/hubauth/pkg/hmacpb" "github.com/flynn/hubauth/pkg/hubauth" - "github.com/flynn/hubauth/pkg/kmssign" + "github.com/flynn/hubauth/pkg/idp/token" "github.com/flynn/hubauth/pkg/pb" "github.com/flynn/hubauth/pkg/rp" "github.com/flynn/hubauth/pkg/signpb" @@ -22,22 +20,10 @@ import ( "golang.org/x/sync/errgroup" ) -type AudienceKeyNamer func(audience string) string - const oobRedirectURI = "urn:ietf:wg:oauth:2.0:oob" const codeExpiry = 30 * time.Second const accessTokenDuration = 5 * time.Minute -func AudienceKeyNameFunc(projectID, location, keyRing string) func(string) string { - return func(aud string) string { - u, err := url.Parse(aud) - if err != nil { - return "" - } - return fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s/cryptoKeyVersions/1", projectID, location, keyRing, strings.Replace(u.Host, ".", "_", -1)) - } -} - type clock interface { Now() time.Time } @@ -61,17 +47,15 @@ type idpSteps interface { SignRefreshToken(ctx context.Context, signKey signpb.PrivateKey, t *signedRefreshTokenData) (string, error) RenewRefreshToken(ctx context.Context, clientID, oldTokenID string, oldTokenIssueTime, now time.Time) (*hubauth.RefreshToken, error) VerifyRefreshToken(ctx context.Context, rt *hubauth.RefreshToken, now time.Time) error - SignAccessToken(ctx context.Context, signKey signpb.PrivateKey, t *accessTokenData, now time.Time) (string, error) + SignAccessToken(ctx context.Context, audience string, t *token.AccessTokenData, now time.Time) (string, error) } type idpService struct { - db hubauth.DataStore - rp rp.AuthService - kms kmssign.KMSClient + db hubauth.DataStore + rp rp.AuthService - codeKey hmacpb.Key - refreshKey signpb.Key - audienceKey AudienceKeyNamer + codeKey hmacpb.Key + refreshKey signpb.Key steps idpSteps clock clock @@ -79,16 +63,15 @@ type idpService struct { var _ hubauth.IdPService = (*idpService)(nil) -func New(db hubauth.DataStore, rp rp.AuthService, kms kmssign.KMSClient, codeKey hmacpb.Key, refreshKey signpb.Key, audienceKey AudienceKeyNamer) hubauth.IdPService { +func New(db hubauth.DataStore, rp rp.AuthService, codeKey hmacpb.Key, refreshKey signpb.Key, tokenBuilder token.AccessTokenBuilder) hubauth.IdPService { return &idpService{ - db: db, - rp: rp, - kms: kms, - codeKey: codeKey, - refreshKey: refreshKey, - audienceKey: audienceKey, + db: db, + rp: rp, + codeKey: codeKey, + refreshKey: refreshKey, steps: &steps{ - db: db, + db: db, + builder: tokenBuilder, }, clock: clockImpl{}, } @@ -325,11 +308,11 @@ func (s *idpService) ExchangeCode(parentCtx context.Context, req *hubauth.Exchan if req.Audience == "" { return nil } - signKey := kmssign.NewPrivateKey(s.kms, s.audienceKey(req.Audience), crypto.SHA256) - accessToken, err = s.steps.SignAccessToken(ctx, signKey, &accessTokenData{ - clientID: req.ClientID, - userID: codeInfo.UserId, - userEmail: codeInfo.UserEmail, + + accessToken, err = s.steps.SignAccessToken(ctx, req.Audience, &token.AccessTokenData{ + ClientID: req.ClientID, + UserID: codeInfo.UserId, + UserEmail: codeInfo.UserEmail, }, now) return err }) @@ -399,11 +382,10 @@ func (s *idpService) RefreshToken(ctx context.Context, req *hubauth.RefreshToken if req.Audience == "" { return nil } - signKey := kmssign.NewPrivateKey(s.kms, s.audienceKey(req.Audience), crypto.SHA256) - accessToken, err = s.steps.SignAccessToken(ctx, signKey, &accessTokenData{ - clientID: req.ClientID, - userID: oldToken.UserID, - userEmail: oldToken.UserEmail, + accessToken, err = s.steps.SignAccessToken(ctx, req.Audience, &token.AccessTokenData{ + ClientID: req.ClientID, + UserID: oldToken.UserID, + UserEmail: oldToken.UserEmail, }, now) return err }) diff --git a/pkg/idp/oauth_test.go b/pkg/idp/oauth_test.go index 8b6fc09..827c2df 100644 --- a/pkg/idp/oauth_test.go +++ b/pkg/idp/oauth_test.go @@ -2,7 +2,6 @@ package idp import ( "context" - "crypto" "crypto/rand" "errors" "fmt" @@ -14,6 +13,7 @@ import ( "github.com/flynn/hubauth/pkg/datastore" "github.com/flynn/hubauth/pkg/hmacpb" "github.com/flynn/hubauth/pkg/hubauth" + "github.com/flynn/hubauth/pkg/idp/token" "github.com/flynn/hubauth/pkg/kmssign" "github.com/flynn/hubauth/pkg/kmssign/kmssim" "github.com/flynn/hubauth/pkg/pb" @@ -40,10 +40,6 @@ func (m *mockAuthService) Exchange(ctx context.Context, rr *rp.RedirectResult) ( return args.Get(0).(*rp.Token), args.Error(1) } -func audienceKeyNamer(s string) string { - return fmt.Sprintf("%s_named", s) -} - type mockSteps struct { mock.Mock } @@ -82,8 +78,8 @@ func (m *mockSteps) SignRefreshToken(ctx context.Context, signKey signpb.Private args := m.Called(ctx, signKey, t) return args.String(0), args.Error(1) } -func (m *mockSteps) SignAccessToken(ctx context.Context, signKey signpb.PrivateKey, t *accessTokenData, now time.Time) (string, error) { - args := m.Called(ctx, signKey, t, now) +func (m *mockSteps) SignAccessToken(ctx context.Context, audience string, t *token.AccessTokenData, now time.Time) (string, error) { + args := m.Called(ctx, audience, t, now) return args.String(0), args.Error(1) } func (m *mockSteps) RenewRefreshToken(ctx context.Context, clientID, oldTokenID string, oldTokenIssueTime, now time.Time) (*hubauth.RefreshToken, error) { @@ -124,7 +120,7 @@ func newTestIdPService(t *testing.T, kmsKeys ...string) *idpService { refreshKey, err := kmssign.NewKey(context.Background(), kms, refreshKeyName) require.NoError(t, err) - s := New(db, authService, kms, codeKey, refreshKey, audienceKeyNamer).(*idpService) + s := New(db, authService, codeKey, refreshKey, nil).(*idpService) s.steps = &mockSteps{} s.clock = &mockClock{} @@ -669,10 +665,10 @@ func TestExchangeCode(t *testing.T) { }).Return(verifiedCode, nil) idpService.steps.(*mockSteps).On("SaveRefreshToken", mock.Anything, b64CodeID, redirectURI, rtData).Return(client, nil) idpService.steps.(*mockSteps).On("SignRefreshToken", mock.Anything, idpService.refreshKey, signedRTData).Return(refreshToken, nil) - idpService.steps.(*mockSteps).On("SignAccessToken", mock.Anything, kmssign.NewPrivateKey(idpService.kms, audienceKeyNamer(audienceURL), crypto.SHA256), &accessTokenData{ - clientID: clientID, - userID: userID, - userEmail: userEmail, + idpService.steps.(*mockSteps).On("SignAccessToken", mock.Anything, audienceURL, &token.AccessTokenData{ + ClientID: clientID, + UserID: userID, + UserEmail: userEmail, }, now).Return(accessToken, nil) req := &hubauth.ExchangeCodeRequest{ @@ -898,11 +894,10 @@ func TestRefreshToken(t *testing.T) { }, ExpiryTime: expireTimeProto.AsTime(), }).Return(newRefreshTokenStr, nil) - signKey := kmssign.NewPrivateKey(idpService.kms, audienceKeyNamer(testCase.AudienceURL), crypto.SHA256) - idpService.steps.(*mockSteps).On("SignAccessToken", mock.Anything, signKey, &accessTokenData{ - clientID: b64ClientID, - userID: userID, - userEmail: userEmail, + idpService.steps.(*mockSteps).On("SignAccessToken", mock.Anything, testCase.AudienceURL, &token.AccessTokenData{ + ClientID: b64ClientID, + UserID: userID, + UserEmail: userEmail, }, now).Return(newAccessTokenStr, nil) oldTokenSigned, err := signpb.SignMarshal(context.Background(), idpService.refreshKey, &pb.RefreshToken{ @@ -1015,12 +1010,6 @@ func TestRefreshTokenStepErrors(t *testing.T) { } func prepareInvalidRefreshTokenTestCases(t *testing.T, idpService *idpService, wrongKeyName string) []*invalidRefreshTokenTestCase { - wrongKey, err := kmssign.NewKey(context.Background(), idpService.kms, wrongKeyName) - require.NoError(t, err) - - wrongKeyRefreshToken, err := signpb.SignMarshal(context.Background(), wrongKey, &pb.RefreshToken{}) - require.NoError(t, err) - now := time.Now() expiredTime, _ := ptypes.TimestampProto(now.Add(-1 * time.Second)) expiredRefreshToken, err := signpb.SignMarshal(context.Background(), idpService.refreshKey, &pb.RefreshToken{ @@ -1045,13 +1034,6 @@ func prepareInvalidRefreshTokenTestCases(t *testing.T, idpService *idpService, w Description: "invalid refresh_token", }, }, - { - RefreshToken: base64Encode(wrongKeyRefreshToken), - Err: &hubauth.OAuthError{ - Code: "invalid_grant", - Description: "invalid refresh_token", - }, - }, { RefreshToken: base64Encode(expiredRefreshToken), Err: &hubauth.OAuthError{ diff --git a/pkg/idp/steps.go b/pkg/idp/steps.go index c4a7095..440b8ea 100644 --- a/pkg/idp/steps.go +++ b/pkg/idp/steps.go @@ -9,6 +9,7 @@ import ( "github.com/flynn/hubauth/pkg/clog" "github.com/flynn/hubauth/pkg/hmacpb" "github.com/flynn/hubauth/pkg/hubauth" + "github.com/flynn/hubauth/pkg/idp/token" "github.com/flynn/hubauth/pkg/pb" "github.com/flynn/hubauth/pkg/signpb" "github.com/golang/protobuf/ptypes" @@ -19,7 +20,8 @@ import ( ) type steps struct { - db hubauth.DataStore + db hubauth.DataStore + builder token.AccessTokenBuilder } var _ idpSteps = (*steps)(nil) @@ -349,37 +351,21 @@ func (s *steps) VerifyRefreshToken(ctx context.Context, rt *hubauth.RefreshToken return nil } -type accessTokenData struct { - clientID string - userID string - userEmail string -} - -func (s *steps) SignAccessToken(ctx context.Context, signKey signpb.PrivateKey, t *accessTokenData, now time.Time) (token string, err error) { +func (s *steps) SignAccessToken(ctx context.Context, audience string, t *token.AccessTokenData, now time.Time) (token string, err error) { ctx, span := trace.StartSpan(ctx, "idp.SignAccessToken") span.AddAttributes( - trace.StringAttribute("client_id", t.clientID), - trace.StringAttribute("user_id", t.userID), - trace.StringAttribute("user_email", t.userEmail), + trace.StringAttribute("client_id", t.ClientID), + trace.StringAttribute("user_id", t.UserID), + trace.StringAttribute("user_email", t.UserEmail), ) defer span.End() - exp, _ := ptypes.TimestampProto(now.Add(accessTokenDuration)) - iss, _ := ptypes.TimestampProto(now) - msg := &pb.AccessToken{ - ClientId: t.clientID, - UserId: t.userID, - UserEmail: t.userEmail, - IssueTime: iss, - ExpireTime: exp, - } - tokenBytes, err := signpb.SignMarshal(ctx, signKey, msg) + tokenBytes, err := s.builder.Build(ctx, audience, t, now, accessTokenDuration) if err != nil { - return "", fmt.Errorf("idp: error signing access token: %w", err) + return "", fmt.Errorf("idp: error building access token: %w", err) } - idBytes := sha256.Sum256(tokenBytes) - token = base64.URLEncoding.EncodeToString(tokenBytes) + idBytes := sha256.Sum256(tokenBytes) accessTokenID := base64Encode(idBytes[:]) span.AddAttributes(trace.StringAttribute("access_token_id", accessTokenID)) @@ -387,5 +373,6 @@ func (s *steps) SignAccessToken(ctx context.Context, signKey signpb.PrivateKey, zap.String("issued_access_token_id", accessTokenID), zap.Duration("issued_access_token_expires_in", accessTokenDuration), ) - return token, nil + + return base64.URLEncoding.EncodeToString(tokenBytes), nil } diff --git a/pkg/idp/steps_test.go b/pkg/idp/steps_test.go index b886448..8ec7662 100644 --- a/pkg/idp/steps_test.go +++ b/pkg/idp/steps_test.go @@ -12,21 +12,39 @@ import ( "github.com/flynn/hubauth/pkg/datastore" "github.com/flynn/hubauth/pkg/hmacpb" "github.com/flynn/hubauth/pkg/hubauth" + "github.com/flynn/hubauth/pkg/idp/token" "github.com/flynn/hubauth/pkg/kmssign" "github.com/flynn/hubauth/pkg/kmssign/kmssim" "github.com/flynn/hubauth/pkg/pb" "github.com/flynn/hubauth/pkg/signpb" "github.com/golang/protobuf/ptypes" + "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "google.golang.org/protobuf/proto" ) +const ( + testAudienceName = "audienceXYZ" +) + +type mockAccessTokenBuilder struct { + mock.Mock +} + +var _ token.AccessTokenBuilder = (*mockAccessTokenBuilder)(nil) + +func (m *mockAccessTokenBuilder) Build(ctx context.Context, audience string, t *token.AccessTokenData, now time.Time, duration time.Duration) ([]byte, error) { + args := m.Called(ctx, audience, t, now, duration) + return args.Get(0).([]byte), args.Error(1) +} + func newTestSteps(t *testing.T) *steps { dsc, err := gdatastore.NewClient(context.Background(), "test") require.NoError(t, err) return &steps{ - db: datastore.New(dsc), + db: datastore.New(dsc), + builder: &mockAccessTokenBuilder{}, } } @@ -754,36 +772,23 @@ func TestVerifyRefreshTokenErrors(t *testing.T) { func TestSignAccessToken(t *testing.T) { s := newTestSteps(t) - signKeyName := "refreshKey" - kms := kmssim.NewClient([]string{signKeyName}) - signKey, err := kmssign.NewKey(context.Background(), kms, signKeyName) - require.NoError(t, err) - now := time.Now() - data := &accessTokenData{ - clientID: "clientID", - userID: "userID", - userEmail: "userEmail", + data := &token.AccessTokenData{ + ClientID: "clientID", + UserID: "userID", + UserEmail: "userEmail", } - accessToken, err := s.SignAccessToken(context.Background(), signKey, data, now) + expectedAccessToken := []byte("expected-access-token") + + s.builder.(*mockAccessTokenBuilder).On("Build", mock.Anything, testAudienceName, data, now, accessTokenDuration).Return(expectedAccessToken, nil) + + accessToken, err := s.SignAccessToken(context.Background(), testAudienceName, data, now) require.NoError(t, err) require.NotEmpty(t, accessToken) - got := new(pb.AccessToken) - accessTokenBytes, err := base64Decode(accessToken) require.NoError(t, err) - - require.NoError(t, signpb.VerifyUnmarshal(signKey, accessTokenBytes, got)) - require.Equal(t, data.clientID, got.ClientId) - require.Equal(t, data.userID, got.UserId) - require.Equal(t, data.userEmail, got.UserEmail) - - nowPb, _ := ptypes.TimestampProto(now) - require.Equal(t, nowPb, got.IssueTime) - - expirePb, _ := ptypes.TimestampProto(now.Add(accessTokenDuration)) - require.Equal(t, expirePb, got.ExpireTime) + require.Equal(t, expectedAccessToken, accessTokenBytes) } diff --git a/pkg/idp/token/builder.go b/pkg/idp/token/builder.go new file mode 100644 index 0000000..5a60575 --- /dev/null +++ b/pkg/idp/token/builder.go @@ -0,0 +1,57 @@ +package token + +import ( + "context" + "crypto" + "fmt" + "time" + + "github.com/flynn/hubauth/pkg/kmssign" + "github.com/flynn/hubauth/pkg/pb" + "github.com/flynn/hubauth/pkg/signpb" + "github.com/golang/protobuf/ptypes" +) + +type AccessTokenData struct { + ClientID string + UserID string + UserEmail string +} + +type AccessTokenBuilder interface { + Build(ctx context.Context, audience string, t *AccessTokenData, now time.Time, duration time.Duration) ([]byte, error) +} + +type signedPbBuilder struct { + kms kmssign.KMSClient + audienceKey kmssign.AudienceKeyNamer +} + +var _ AccessTokenBuilder = (*signedPbBuilder)(nil) + +func NewSignedPBBuilder(kms kmssign.KMSClient, audienceKey kmssign.AudienceKeyNamer) AccessTokenBuilder { + return &signedPbBuilder{ + kms: kms, + audienceKey: audienceKey, + } +} + +func (b *signedPbBuilder) Build(ctx context.Context, audience string, t *AccessTokenData, now time.Time, duration time.Duration) ([]byte, error) { + signKey := kmssign.NewPrivateKey(b.kms, b.audienceKey(audience), crypto.SHA256) + + exp, _ := ptypes.TimestampProto(now.Add(duration)) + iss, _ := ptypes.TimestampProto(now) + msg := &pb.AccessToken{ + ClientId: t.ClientID, + UserId: t.UserID, + UserEmail: t.UserEmail, + IssueTime: iss, + ExpireTime: exp, + } + tokenBytes, err := signpb.SignMarshal(ctx, signKey, msg) + if err != nil { + return nil, fmt.Errorf("token: error signing access token: %w", err) + } + + return tokenBytes, nil +} diff --git a/pkg/idp/token/builder_test.go b/pkg/idp/token/builder_test.go new file mode 100644 index 0000000..9785c77 --- /dev/null +++ b/pkg/idp/token/builder_test.go @@ -0,0 +1,57 @@ +package token + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/flynn/hubauth/pkg/kmssign" + "github.com/flynn/hubauth/pkg/kmssign/kmssim" + "github.com/flynn/hubauth/pkg/pb" + "github.com/flynn/hubauth/pkg/signpb" + "github.com/golang/protobuf/ptypes" + "github.com/stretchr/testify/require" +) + +func audienceKeyNamer(s string) string { + return fmt.Sprintf("%s_named", s) +} + +func TestSignedPBBuilder(t *testing.T) { + audienceName := "audience_url" + audienceKeyName := audienceKeyNamer(audienceName) + kms := kmssim.NewClient([]string{audienceKeyName}) + + builder := NewSignedPBBuilder(kms, audienceKeyNamer) + + signKey, err := kmssign.NewKey(context.Background(), kms, audienceKeyName) + require.NoError(t, err) + + now := time.Now() + ctx := context.Background() + + data := &AccessTokenData{ + ClientID: "clientID", + UserEmail: "userEmail", + UserID: "userID", + } + + accessTokenDuration := 5 * time.Minute + + accessTokenBytes, err := builder.Build(ctx, audienceName, data, now, accessTokenDuration) + require.NoError(t, err) + + got := new(pb.AccessToken) + require.NoError(t, signpb.VerifyUnmarshal(signKey, accessTokenBytes, got)) + + require.Equal(t, data.ClientID, got.ClientId) + require.Equal(t, data.UserID, got.UserId) + require.Equal(t, data.UserEmail, got.UserEmail) + + nowPb, _ := ptypes.TimestampProto(now) + require.Equal(t, nowPb, got.IssueTime) + + expirePb, _ := ptypes.TimestampProto(now.Add(accessTokenDuration)) + require.Equal(t, expirePb, got.ExpireTime) +} diff --git a/pkg/kmssign/kms.go b/pkg/kmssign/kms.go index 8671e3e..766ffe3 100644 --- a/pkg/kmssign/kms.go +++ b/pkg/kmssign/kms.go @@ -8,6 +8,8 @@ import ( "encoding/pem" "io" "math/big" + "net/url" + "strings" gax "github.com/googleapis/gax-go/v2" "golang.org/x/crypto/cryptobyte" @@ -16,6 +18,18 @@ import ( kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1" ) +type AudienceKeyNamer func(audience string) string + +func AudienceKeyNameFunc(projectID, location, keyRing string) func(string) string { + return func(aud string) string { + u, err := url.Parse(aud) + if err != nil { + return "" + } + return fmt.Sprintf("projects/%s/locations/%s/keyRings/%s/cryptoKeys/%s/cryptoKeyVersions/1", projectID, location, keyRing, strings.Replace(u.Host, ".", "_", -1)) + } +} + type KMSClient interface { AsymmetricSign(ctx context.Context, req *kmspb.AsymmetricSignRequest, opts ...gax.CallOption) (*kmspb.AsymmetricSignResponse, error) GetPublicKey(ctx context.Context, req *kmspb.GetPublicKeyRequest, opts ...gax.CallOption) (*kmspb.PublicKey, error) From 1e1e57b7f644823234cdd4b52c8a7a641b65b932 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Thu, 8 Oct 2020 16:53:04 +0200 Subject: [PATCH 02/17] add biscuit wrappers and helpers to generate, sign and verify hubauth biscuits --- go.mod | 1 + go.sum | 88 +-------- pkg/biscuit/biscuit.go | 137 ++++++++++++++ pkg/biscuit/biscuit_test.go | 57 ++++++ pkg/biscuit/signature.go | 162 ++++++++++++++++ pkg/biscuit/signature_test.go | 258 ++++++++++++++++++++++++++ pkg/biscuit/wrapper.go | 336 ++++++++++++++++++++++++++++++++++ 7 files changed, 957 insertions(+), 82 deletions(-) create mode 100644 pkg/biscuit/biscuit.go create mode 100644 pkg/biscuit/biscuit_test.go create mode 100644 pkg/biscuit/signature.go create mode 100644 pkg/biscuit/signature_test.go create mode 100644 pkg/biscuit/wrapper.go diff --git a/go.mod b/go.mod index cc0dd6a..e95d318 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/alecthomas/kong v0.2.12 github.com/aws/aws-sdk-go v1.36.7 // indirect github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect + github.com/flynn/biscuit-go v0.0.0-20200907174027-193b7bdbbdca github.com/golang/protobuf v1.4.3 github.com/googleapis/gax-go/v2 v2.0.5 github.com/jedib0t/go-pretty/v6 v6.0.5 diff --git a/go.sum b/go.sum index 32643c5..add3e98 100644 --- a/go.sum +++ b/go.sum @@ -54,11 +54,12 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/alecthomas/kong v0.2.12 h1:X3kkCOXGUNzLmiu+nQtoxWqj4U2a39MpSJR3QdQXOwI= github.com/alecthomas/kong v0.2.12/go.mod h1:kQOmtJgV+Lb4aj+I2LEn40cbtawdWJ9Y8QLq+lElKxE= +github.com/alecthomas/participle v0.6.0/go.mod h1:HfdmEuwvr12HXQN44HPWXR0lHmVolVYe4dyL6lQ3duY= +github.com/alecthomas/repr v0.0.0-20181024024818-d37bc2a10ba1/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= github.com/aws/aws-sdk-go v1.23.20 h1:2CBuL21P0yKdZN5urf2NxKa1ha8fhnY+A3pBCHFeZoA= github.com/aws/aws-sdk-go v1.23.20/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.36.7 h1:XoJPAjKoqvdL531XGWxKYn5eGX/xMoXzMN5fBtoyfSY= github.com/aws/aws-sdk-go v1.36.7/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= -github.com/census-instrumentation/opencensus-proto v0.2.1 h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/census-instrumentation/opencensus-proto v0.3.0 h1:t/LhUZLVitR1Ow2YOnduCsavhwFUklBMoGVYUCqmCqk= github.com/census-instrumentation/opencensus-proto v0.3.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -68,7 +69,6 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -77,10 +77,11 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/flynn/biscuit-go v0.0.0-20200907174027-193b7bdbbdca h1:LUZQQzaCT+gltxii4icyPH5oMdAP38JmbvO9aI0E4qM= +github.com/flynn/biscuit-go v0.0.0-20200907174027-193b7bdbbdca/go.mod h1:EMJZ3stAYtwaP763F5HcGjPjCnYu21V2TEsg/iw88I8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -93,23 +94,18 @@ github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5 h1:F768QJ1E9tib+q5Sc8MkdJi1RxLTbRcTf8LJV56aRls= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0 h1:oOuy+ugB+P/kBdUnG5QaMXSIyJ1q38wWSojYCb3z5VQ= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= @@ -118,19 +114,13 @@ github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= @@ -148,13 +138,14 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gtank/ristretto255 v0.1.2 h1:JEqUCPA1NvLq5DwYtuzigd7ss8fwbYay9fi4/5uMzcc= +github.com/gtank/ristretto255 v0.1.2/go.mod h1:Ph5OpO6c7xKUGROZfWVLiJf9icMDwUeIvY4OmlYW69o= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jedib0t/go-pretty/v6 v6.0.5 h1:oOo0/jSb3NEYKT6l1hhFXoX2UZnkanMuCE2DVT1mqnE= github.com/jedib0t/go-pretty/v6 v6.0.5/go.mod h1:MTr6FgcfNdnN5wPVBzJ6mhJeDyiF0yBvS2TMXEV/XSU= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= @@ -171,7 +162,6 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= -github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -185,7 +175,6 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -196,28 +185,22 @@ github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9dec go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.5.0 h1:KCa4XfM8CWFCpxXRGok+Q0SS/0XBhMDbHHGABQLvD2A= go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201208171446-5f87f3452ae9 h1:sYNJzB4J8toYPQTM6pAkcmBRgw9SnQKP9oXCHfgy604= @@ -230,7 +213,6 @@ golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd h1:zkO/Lhoka23X63N9OSzpSeROEUQ5ODw47tM3YWjygbs= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6 h1:QE6XYQK6naiK1EPAe1g/ILLxN5RBoH5xkJk3CqlMI/Y= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= @@ -246,9 +228,7 @@ golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367 h1:0IiAsCRByjO2QjX7ZPkw5oU9x+n1YqRL802rjC0c3Aw= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5 h1:2M3HP5CCK1Si9FQhwnzYhXdG6DXeebvUHFpre8QvbyI= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= @@ -258,15 +238,12 @@ golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKG golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0 h1:8pl+sMODzuvGJkmj2W4kZihvVb5mKm8pB/X44PIQHv8= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -280,20 +257,16 @@ golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0 h1:MsuvTghUPjX762sGLnGsxC3HM0B5r83wEtYcYR8/vRs= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7+slrESplyjG25HgL+k= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= @@ -305,9 +278,7 @@ golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAG golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5 h1:Lm4OryKCca1vehdsWogr9N4t7NfZxLbJoc/H0w4K4S4= @@ -317,13 +288,9 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a h1:WXEvlFVvvGxCJLG6REjsT03iWnKLEWinaScsxF2Vm2o= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9 h1:SQFwaSi55rU7vdNs9Yr0Z324VNlrF+0wMqRXT4St8ck= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -344,9 +311,7 @@ golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae h1:/WDfKMnPU+m5M4xB+6x4kaepxRw6jWvR5iDRdvjHgy8= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -355,14 +320,10 @@ golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3 h1:kzM6+9dur93BcC2kVlYl34cHU+TYZLanmpSJHVMmL64= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201214095126-aec9a390925b h1:tv7/y4pd+sR8bcNb2D6o7BNU6zjWm0VjQLac+w7fNNM= golang.org/x/sys v0.0.0-20201214095126-aec9a390925b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -371,15 +332,12 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -411,7 +369,6 @@ golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapK golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56 h1:DFtSed2q3HtNuVazwVDZ4nSRS/JrZEig0gz2BY4VNrg= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= @@ -422,22 +379,18 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7 h1:LHW24ah7B+uV/OePwNP0p/t889F3QSyLvY8Sg/bK0SY= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d h1:szSOL78iTCl0LF1AMjhSWJj8tIM0KixlUUnBtYXsmd8= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20200916150407-587cf2330ce8/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2 h1:vEtypaVub6UvKkiXZ2xx9QIvp9TL7sI7xp7vdi2kezA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58 h1:1Bs6RVeBFtLZ8Yi1Hk07DiOqzvwLD/4hln4iahvFlag= golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -449,34 +402,25 @@ google.golang.org/api v0.10.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhE google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0 h1:0q95w+VuFtv4PAx4PZVQdBMmYbaCHbnfKaEiDIcVyag= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0 h1:TgDr+1inK2XVUKZx3BYAqQg/GwucGdBkzZjWaTg/I+A= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0 h1:jMF5hhVfMkTZwHW1SDpKq5CkgWLXOb31Foaca9Zr3oM= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0 h1:BaiDisFir8O4IJxvAabCGGkQ6yCJegNQqSVoYUNAnbk= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0 h1:yfrXXP61wVuLb0vBcG6qaOoIoqYEzOQS8jum51jkv2w= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.32.0 h1:Le77IccnTqEa8ryp9wIpX5W3zYm7Gf9LhOp9PHcwFts= google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0 h1:l2Nfbl2GPXdWorv+dT2XfinX2jOOw4zv1VhLstx+6rE= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.2/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= @@ -495,9 +439,7 @@ google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvx google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce h1:1mbrb1tUU+Zmt5C94IGKADBTJZjZXAd+BubWi7r9EiI= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63 h1:YzfoEYWbODU5Fbt37+h7X16BWQbad7Q4S6gclTKFXM8= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= @@ -506,20 +448,15 @@ google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013 h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790 h1:FGjyjrQGURdc98leD1P65IdQD9Zlr4McvRcqIlV6OSs= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f h1:ohwtWcCwB/fZUxh/vjazHorYmBnua3NmY3CAjwC7mEA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c h1:Lq4llNryJoaVFRmvrIwC/ZHH7tNt4tUYIu8+se2aayY= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200916143405-f6a2fa72f0c4/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc h1:BgQmMjmd7K1zov8j8lYULHW0WnmBGUIMp6+VDwlGErc= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201211151036-40ec1c210f7a h1:GnJAhasbD8HiT8DZMvsEx3QLVy/X0icq/MGr0MqRJ2M= google.golang.org/genproto v0.0.0-20201211151036-40ec1c210f7a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= @@ -531,19 +468,12 @@ google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyac google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1 h1:zvIju4sqAGvwKspUQOhwnpcqSbzi7/H6QomNNjTL4sk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0 h1:bO/TA4OxCOummhSf10siHuG7vJOiwh7SpRpFZDkOgl4= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0 h1:T7P4R73V3SSDPhH7WW7ATbfViLtmamH0DKrP3f9AuDI= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1 h1:SfXqXS5hkufcdZ/mHtYCh53P2b+92WQq/DZcKLgsFRs= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0 h1:raiipEjMOIC/TO2AvyTxP25XFdLxNIBwzDh3FM3XztI= @@ -552,13 +482,10 @@ google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLY google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0 h1:qdOKuR/EIArgaWNjetjgTzgVTAZ+S/WXVrq9HW9zimw= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= @@ -566,7 +493,6 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -576,9 +502,7 @@ honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3 h1:sXmLre5bzIR6ypkjXCDI3jHPssRhc8KD/Ome589sc3U= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= diff --git a/pkg/biscuit/biscuit.go b/pkg/biscuit/biscuit.go new file mode 100644 index 0000000..ef1a452 --- /dev/null +++ b/pkg/biscuit/biscuit.go @@ -0,0 +1,137 @@ +package biscuit + +import ( + "crypto/rand" + "fmt" + + "github.com/flynn/biscuit-go" + "github.com/flynn/biscuit-go/sig" + "github.com/flynn/hubauth/pkg/kmssign" +) + +type UserKeyPair struct { + Public []byte + Private []byte +} + +// GenerateSignable 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 *kmssign.Key, userPubkey []byte) ([]byte, error) { + builder := &hubauthBuilder{ + Builder: biscuit.NewBuilder(rand.Reader, rootKey), + } + + if err := builder.withAudienceSignature(audience, audienceKey); err != nil { + return nil, err + } + + if err := builder.withUserToSignFact(userPubkey); err != nil { + return nil, err + } + + b, err := builder.Build() + if err != nil { + return nil, err + } + + return b.Serialize() +} + +// Sign append a user signature on the given token and return it. +// The UserKeyPair key format to provide depends on the signature algorithm: +// - for ECDSA_P256_SHA256, the private key must be encoded in SEC 1, ASN.1 DER form, +// and the public key in PKIX, ASN.1 DER form. +func Sign(token []byte, rootPubKey sig.PublicKey, userKey *UserKeyPair) ([]byte, 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) + } + verifier := &hubauthVerifier{ + Verifier: v, + } + + toSignData, err := verifier.getUserToSignData(userKey.Public, b.BlockCount()) + if err != nil { + return nil, fmt.Errorf("biscuit: failed to get to_sign data: %w", err) + } + + if err := verifier.ensureNotAlreadyUserSigned(toSignData.DataID, userKey.Public); err != nil { + return nil, fmt.Errorf("biscuit: previous signature check failed: %w", err) + } + + tokenHash, err := b.SHA256Sum(b.BlockCount()) + if err != nil { + return nil, err + } + + signData, err := userSign(tokenHash, userKey, toSignData) + if err != nil { + return nil, fmt.Errorf("biscuit: signature failed: %w", err) + } + + builder := &hubauthBlockBuilder{ + BlockBuilder: b.CreateBlock(), + } + if err := builder.withUserSignature(signData); err != nil { + return nil, fmt.Errorf("biscuit: failed to create signature block: %w", err) + } + + clientKey := sig.GenerateKeypair(rand.Reader) + b, err = b.Append(rand.Reader, clientKey, builder.Build()) + if err != nil { + return nil, fmt.Errorf("biscuit: failed to append signature block: %w", err) + } + + return b.Serialize() +} + +// 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 *kmssign.Key) error { + b, err := biscuit.Unmarshal(token) + if err != nil { + return fmt.Errorf("biscuit: failed to unmarshal: %w", err) + } + + v, err := b.Verify(rootPubKey) + if err != nil { + return fmt.Errorf("biscuit: failed to verify: %w", err) + } + verifier := &hubauthVerifier{v} + + audienceVerificationData, err := verifier.getAudienceVerificationData(audience) + if err != nil { + return fmt.Errorf("biscuit: failed to retrieve audience signature data: %w", err) + } + + if err := verifyAudienceSignature(audienceKey, audienceVerificationData); err != nil { + return fmt.Errorf("biscuit: failed to verify audience signature: %w", err) + } + if err := verifier.withValidatedAudienceSignature(audienceVerificationData); err != nil { + return fmt.Errorf("biscuit: failed to add validated signature: %w", err) + } + + userVerificationData, err := verifier.getUserVerificationData() + if err != nil { + return fmt.Errorf("biscuit: failed to retrieve user signature data: %w", err) + } + + signedTokenHash, err := b.SHA256Sum(int(userVerificationData.SignedBlockCount)) + if err != nil { + return fmt.Errorf("biscuit: failed to generate token hash: %w", err) + } + + if err := verifyUserSignature(signedTokenHash, userVerificationData); err != nil { + return fmt.Errorf("biscuit: failed to verify user signature: %w", err) + } + if err := verifier.withValidatedUserSignature(userVerificationData); err != nil { + return fmt.Errorf("biscuit: failed to add validated signature: %w", err) + } + + return verifier.Verify() +} diff --git a/pkg/biscuit/biscuit_test.go b/pkg/biscuit/biscuit_test.go new file mode 100644 index 0000000..ed7b273 --- /dev/null +++ b/pkg/biscuit/biscuit_test.go @@ -0,0 +1,57 @@ +package biscuit + +import ( + "context" + "crypto/rand" + "testing" + + "github.com/flynn/biscuit-go/sig" + "github.com/flynn/hubauth/pkg/kmssign" + "github.com/flynn/hubauth/pkg/kmssign/kmssim" + "github.com/stretchr/testify/require" +) + +func TestBiscuit(t *testing.T) { + rootKey := sig.GenerateKeypair(rand.Reader) + audience := "http://random.audience.url" + + kms := kmssim.NewClient([]string{audience}) + audienceKey, err := kmssign.NewKey(context.Background(), kms, audience) + require.NoError(t, err) + + userKey := generateUserKeyPair(t) + + signableBiscuit, err := GenerateSignable(rootKey, audience, audienceKey, userKey.Public) + require.NoError(t, err) + t.Logf("signable biscuit size: %d", len(signableBiscuit)) + + t.Run("happy path", func(t *testing.T) { + signedBiscuit, err := Sign(signableBiscuit, rootKey.Public(), userKey) + require.NoError(t, err) + t.Logf("signed biscuit size: %d", len(signedBiscuit)) + + err = Verify(signedBiscuit, rootKey.Public(), audience, audienceKey) + require.NoError(t, err) + }) + + t.Run("user sign with wrong key", func(t *testing.T) { + _, err := Sign(signableBiscuit, rootKey.Public(), generateUserKeyPair(t)) + require.Error(t, err) + }) + + t.Run("verify wrong audience", func(t *testing.T) { + signedBiscuit, err := Sign(signableBiscuit, rootKey.Public(), userKey) + require.NoError(t, err) + + err = Verify(signedBiscuit, rootKey.Public(), "http://another.audience.url", audienceKey) + require.Error(t, err) + + wrongAudience := "http://another.audience.url" + kms := kmssim.NewClient([]string{wrongAudience}) + wrongAudienceKey, err := kmssign.NewKey(context.Background(), kms, wrongAudience) + require.NoError(t, err) + + err = Verify(signedBiscuit, rootKey.Public(), audience, wrongAudienceKey) + require.Error(t, err) + }) +} diff --git a/pkg/biscuit/signature.go b/pkg/biscuit/signature.go new file mode 100644 index 0000000..385e4bf --- /dev/null +++ b/pkg/biscuit/signature.go @@ -0,0 +1,162 @@ +package biscuit + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "errors" + "time" + + "github.com/flynn/biscuit-go" + "github.com/flynn/hubauth/pkg/kmssign" +) + +var ( + ErrUnsupportedSignatureAlg = errors.New("unsupported signature algorithm") + ErrInvalidSignature = errors.New("invalid signature") +) + +type SignatureAlg biscuit.Symbol + +const ( + ECDSA_P256_SHA256 SignatureAlg = "ECDSA_P256_SHA256" +) + +type userToSignData struct { + DataID biscuit.Integer + Alg biscuit.Symbol + Data biscuit.Bytes + SignedBlockCount biscuit.Integer +} + +type userSignatureData struct { + DataID biscuit.Integer + UserPubKey biscuit.Bytes + Signature biscuit.Bytes + SignedBlockCount biscuit.Integer + Nonce biscuit.Bytes + Timestamp biscuit.Date +} + +type userVerificationData struct { + DataID biscuit.Integer + Alg biscuit.Symbol + Data biscuit.Bytes + UserPubKey biscuit.Bytes + Signature biscuit.Bytes + SignedBlockCount biscuit.Integer + Nonce biscuit.Bytes + Timestamp biscuit.Date +} + +func userSign(tokenHash []byte, userKey *UserKeyPair, toSignData *userToSignData) (*userSignatureData, error) { + if len(tokenHash) == 0 { + return nil, errors.New("invalid tokenHash") + } + + signerTimestamp := time.Now() + signerNonce := make([]byte, nonceSize) + if _, err := rand.Read(signerNonce); err != nil { + return nil, err + } + + var dataToSign []byte + dataToSign = append(dataToSign, toSignData.Data...) + dataToSign = append(dataToSign, tokenHash...) + dataToSign = append(dataToSign, signerNonce...) + dataToSign = append(dataToSign, []byte(signerTimestamp.Format(time.RFC3339))...) + dataToSign = append(dataToSign, []byte(toSignData.SignedBlockCount.String())...) + + var signedData biscuit.Bytes + switch SignatureAlg(toSignData.Alg) { + case ECDSA_P256_SHA256: + privKey, err := x509.ParseECPrivateKey(userKey.Private) + if err != nil { + return nil, err + } + hash := sha256.Sum256(dataToSign) + signedData, err = ecdsa.SignASN1(rand.Reader, privKey, hash[:]) + if err != nil { + return nil, err + } + default: + return nil, ErrUnsupportedSignatureAlg + } + + return &userSignatureData{ + DataID: toSignData.DataID, + Nonce: signerNonce, + Signature: signedData, + SignedBlockCount: toSignData.SignedBlockCount, + Timestamp: biscuit.Date(signerTimestamp), + UserPubKey: userKey.Public, + }, nil +} + +func verifyUserSignature(signedTokenHash []byte, data *userVerificationData) error { + var signedData []byte + signedData = append(signedData, data.Data...) + signedData = append(signedData, signedTokenHash...) + signedData = append(signedData, data.Nonce...) + signedData = append(signedData, []byte(time.Time(data.Timestamp).Format(time.RFC3339))...) + signedData = append(signedData, []byte(data.SignedBlockCount.String())...) + + switch SignatureAlg(data.Alg) { + case ECDSA_P256_SHA256: + pk, err := x509.ParsePKIXPublicKey(data.UserPubKey) + if err != nil { + return err + } + pubkey, ok := pk.(*ecdsa.PublicKey) + if !ok { + return errors.New("invalid pubkey, not an *ecdsa.PublicKey") + } + + hash := sha256.Sum256(signedData) + if !ecdsa.VerifyASN1(pubkey, hash[:], data.Signature) { + return ErrInvalidSignature + } + return nil + default: + return ErrUnsupportedSignatureAlg + } +} + +type audienceVerificationData struct { + Audience biscuit.Symbol + Challenge biscuit.Bytes + Signature biscuit.Bytes +} + +func audienceSign(audience string, audienceKey *kmssign.Key) (*audienceVerificationData, error) { + challenge := make([]byte, challengeSize) + if _, err := rand.Reader.Read(challenge); err != nil { + return nil, err + } + + signedData := append(signStaticCtx, challenge...) + signedData = append(signedData, []byte(audience)...) + signedHash := sha256.Sum256(signedData) + signature, err := audienceKey.Sign(rand.Reader, signedHash[:], crypto.SHA256) + if err != nil { + return nil, err + } + + return &audienceVerificationData{ + Audience: biscuit.Symbol(audience), + Challenge: challenge, + Signature: signature, + }, nil +} + +func verifyAudienceSignature(audiencePubkey *kmssign.Key, data *audienceVerificationData) error { + signedData := append(signStaticCtx, data.Challenge...) + signedData = append(signedData, []byte(data.Audience)...) + hash := sha256.Sum256(signedData) + if !audiencePubkey.Verify(hash[:], data.Signature) { + return errors.New("invalid signature") + } + return nil +} diff --git a/pkg/biscuit/signature_test.go b/pkg/biscuit/signature_test.go new file mode 100644 index 0000000..5cb5d8b --- /dev/null +++ b/pkg/biscuit/signature_test.go @@ -0,0 +1,258 @@ +package biscuit + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "testing" + "time" + + "github.com/flynn/biscuit-go" + "github.com/stretchr/testify/require" +) + +func TestUserSignVerify(t *testing.T) { + tokenHash := make([]byte, 32) + _, err := rand.Read(tokenHash) + require.NoError(t, err) + + challenge := make([]byte, challengeSize) + _, err = rand.Read(challenge) + require.NoError(t, err) + + userKey := generateUserKeyPair(t) + + toSignData := &userToSignData{ + DataID: 1, + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + Data: []byte("challenge"), + SignedBlockCount: 2, + } + + signedData, err := userSign(tokenHash, userKey, toSignData) + require.NoError(t, err) + require.NotEmpty(t, signedData.Signature) + require.Equal(t, biscuit.Integer(2), signedData.SignedBlockCount) + require.Equal(t, biscuit.Integer(1), signedData.DataID) + require.Equal(t, biscuit.Bytes(userKey.Public), signedData.UserPubKey) + + require.Len(t, signedData.Nonce, nonceSize) + zeroNonce := make([]byte, nonceSize) + require.NotEqual(t, biscuit.Bytes(zeroNonce), signedData.Nonce) + + require.WithinDuration(t, time.Now(), time.Time(signedData.Timestamp), 1*time.Second) + + require.NoError(t, verifyUserSignature(tokenHash, &userVerificationData{ + DataID: toSignData.DataID, + Alg: toSignData.Alg, + Data: toSignData.Data, + Nonce: signedData.Nonce, + Signature: signedData.Signature, + SignedBlockCount: signedData.SignedBlockCount, + Timestamp: signedData.Timestamp, + UserPubKey: signedData.UserPubKey, + })) +} + +func TestUserSignFail(t *testing.T) { + validTokenHash := make([]byte, 32) + _, err := rand.Read(validTokenHash) + require.NoError(t, err) + + validChallenge := make([]byte, challengeSize) + _, err = rand.Read(validChallenge) + require.NoError(t, err) + + invalidPrivateKey := &UserKeyPair{ + Private: make([]byte, 32), + } + + testCases := []struct { + desc string + tokenHash []byte + userKey *UserKeyPair + data *userToSignData + expectedErr error + }{ + { + desc: "empty tokenHash", + tokenHash: []byte{}, + }, + { + desc: "unsupported alg", + tokenHash: validTokenHash, + data: &userToSignData{ + Alg: "unsupported", + }, + expectedErr: ErrUnsupportedSignatureAlg, + }, + { + desc: "wrong private key encoding", + tokenHash: validTokenHash, + data: &userToSignData{ + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + }, + userKey: invalidPrivateKey, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.desc, func(t *testing.T) { + _, err := userSign(testCase.tokenHash, testCase.userKey, testCase.data) + require.Error(t, err) + if testCase.expectedErr != nil { + require.Equal(t, testCase.expectedErr, err) + } + }) + } +} + +func TestVerifyUserSignatureFail(t *testing.T) { + tokenHash := []byte("token hash") + toSignData := &userToSignData{ + DataID: 1, + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + Data: []byte("challenge"), + SignedBlockCount: 2, + } + + userKey := generateUserKeyPair(t) + invalidKey := generateUserKeyPair(t) + + signedData, err := userSign(tokenHash, userKey, toSignData) + require.NoError(t, err) + + rsaKey, err := rsa.GenerateKey(rand.Reader, 1024) + require.NoError(t, err) + wrongKeyKind, err := x509.MarshalPKIXPublicKey(&rsaKey.PublicKey) + require.NoError(t, err) + + testCases := []struct { + desc string + tokenHash []byte + data *userVerificationData + expectedErr error + }{ + { + desc: "unsupported alg", + expectedErr: ErrUnsupportedSignatureAlg, + data: &userVerificationData{ + Alg: "unknown", + }, + }, + { + desc: "invalid pubkey encoding", + data: &userVerificationData{ + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + UserPubKey: make([]byte, 32), + }, + }, + { + desc: "invalid pubkey kind", + data: &userVerificationData{ + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + UserPubKey: wrongKeyKind, + }, + }, + { + desc: "wrong pubkey", + tokenHash: tokenHash, + data: &userVerificationData{ + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + UserPubKey: invalidKey.Public, + Data: toSignData.Data, + DataID: toSignData.DataID, + Nonce: signedData.Nonce, + Signature: signedData.Signature, + Timestamp: signedData.Timestamp, + SignedBlockCount: signedData.SignedBlockCount, + }, + }, + { + desc: "tampered token hash", + expectedErr: ErrInvalidSignature, + tokenHash: []byte("wrong"), + data: &userVerificationData{ + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + UserPubKey: userKey.Public, + Data: toSignData.Data, + DataID: toSignData.DataID, + Nonce: signedData.Nonce, + Signature: signedData.Signature, + Timestamp: signedData.Timestamp, + SignedBlockCount: signedData.SignedBlockCount, + }, + }, + { + desc: "tampered nonce", + expectedErr: ErrInvalidSignature, + tokenHash: tokenHash, + data: &userVerificationData{ + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + UserPubKey: userKey.Public, + Data: toSignData.Data, + DataID: toSignData.DataID, + Nonce: []byte("another nonce"), + Signature: signedData.Signature, + SignedBlockCount: signedData.SignedBlockCount, + Timestamp: signedData.Timestamp, + }, + }, + { + desc: "tampered timestamp", + expectedErr: ErrInvalidSignature, + tokenHash: tokenHash, + data: &userVerificationData{ + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + UserPubKey: userKey.Public, + Data: toSignData.Data, + DataID: toSignData.DataID, + Nonce: signedData.Nonce, + Signature: signedData.Signature, + Timestamp: biscuit.Date(time.Now().Add(1 * time.Second)), + SignedBlockCount: signedData.SignedBlockCount, + }, + }, + { + desc: "tampered signedBlockCount", + expectedErr: ErrInvalidSignature, + tokenHash: tokenHash, + data: &userVerificationData{ + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + UserPubKey: userKey.Public, + Data: toSignData.Data, + DataID: toSignData.DataID, + Nonce: signedData.Nonce, + Signature: signedData.Signature, + Timestamp: signedData.Timestamp, + SignedBlockCount: signedData.SignedBlockCount + 1, + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.desc, func(t *testing.T) { + + err := verifyUserSignature(testCase.tokenHash, testCase.data) + require.Error(t, err) + if testCase.expectedErr != nil { + require.Equal(t, testCase.expectedErr, err) + } + }) + } +} + +func generateUserKeyPair(t *testing.T) *UserKeyPair { + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + privBytes, err := x509.MarshalECPrivateKey(priv) + require.NoError(t, err) + pubBytes, err := x509.MarshalPKIXPublicKey(&priv.PublicKey) + require.NoError(t, err) + return &UserKeyPair{ + Private: privBytes, + Public: pubBytes, + } +} diff --git a/pkg/biscuit/wrapper.go b/pkg/biscuit/wrapper.go new file mode 100644 index 0000000..ec16ebd --- /dev/null +++ b/pkg/biscuit/wrapper.go @@ -0,0 +1,336 @@ +package biscuit + +import ( + "bytes" + "crypto/rand" + "errors" + "fmt" + + "github.com/flynn/biscuit-go" + "github.com/flynn/hubauth/pkg/kmssign" +) + +var ( + ErrAlreadySigned = errors.New("already signed") + ErrInvalidToSignDataPrefix = errors.New("invalid to_sign data prefix") +) + +var ( + signStaticCtx = []byte("biscuit-pop-v0") + challengeSize = 16 + nonceSize = 16 +) + +type hubauthBuilder struct { + biscuit.Builder +} + +// withUserToSignFact add an authority should_sign fact and associated data to the biscuit +// with an authority caveat requiring the verifier to provide a valid_signature fact. +// the verifier is responsible of ensuring that a valid signature exists over the data. +func (b *hubauthBuilder) withUserToSignFact(userPubkey []byte) error { + dataID := biscuit.Integer(0) + + if err := b.AddAuthorityFact(biscuit.Fact{Predicate: biscuit.Predicate{ + Name: "should_sign", + IDs: []biscuit.Atom{ + dataID, + biscuit.Symbol(ECDSA_P256_SHA256), + biscuit.Bytes(userPubkey), + }, + }}); err != nil { + return err + } + + challenge := make([]byte, challengeSize) + if _, err := rand.Reader.Read(challenge); err != nil { + return err + } + + if err := b.AddAuthorityFact(biscuit.Fact{Predicate: biscuit.Predicate{ + Name: "data", + IDs: []biscuit.Atom{ + dataID, + biscuit.Bytes(append(signStaticCtx, challenge...)), + }, + }}); err != nil { + return err + } + + if err := b.AddAuthorityCaveat(biscuit.Rule{ + Head: biscuit.Predicate{Name: "valid", IDs: []biscuit.Atom{biscuit.Variable(0)}}, + Body: []biscuit.Predicate{ + {Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}}, + {Name: "valid_signature", IDs: []biscuit.Atom{biscuit.Symbol("ambient"), biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}}, + }, + }); err != nil { + return err + } + + return nil +} + +// withAudienceSignature add an authority audience_signature fact, containing a challenge and +// a matching signature using the audience key. +// the verifier is responsible of providing a valid_audience_signature fact, after +// verifying the signature using the audience pubkey. +func (b *hubauthBuilder) withAudienceSignature(audience string, audienceKey *kmssign.Key) error { + if len(audience) == 0 { + return errors.New("audience is required") + } + + data, err := audienceSign(audience, audienceKey) + if err != nil { + return err + } + + if err := b.AddAuthorityFact(biscuit.Fact{Predicate: biscuit.Predicate{ + Name: "audience_signature", + IDs: []biscuit.Atom{ + data.Audience, + data.Challenge, + data.Signature, + }, + }}); err != nil { + return err + } + + if err := b.AddAuthorityCaveat(biscuit.Rule{ + Head: biscuit.Predicate{Name: "valid_audience", IDs: []biscuit.Atom{biscuit.Variable(0)}}, + Body: []biscuit.Predicate{ + {Name: "audience_signature", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}}, + {Name: "valid_audience_signature", IDs: []biscuit.Atom{biscuit.Symbol("ambient"), biscuit.Variable(0), biscuit.Variable(2)}}, + }, + }); err != nil { + return err + } + + return nil +} + +type hubauthBlockBuilder struct { + biscuit.BlockBuilder +} + +func (b *hubauthBlockBuilder) withUserSignature(sigData *userSignatureData) error { + return b.AddFact(biscuit.Fact{Predicate: biscuit.Predicate{ + Name: "signature", + IDs: []biscuit.Atom{ + sigData.DataID, + sigData.UserPubKey, + sigData.Signature, + sigData.Nonce, + sigData.Timestamp, + sigData.SignedBlockCount, + }, + }}) +} + +type hubauthVerifier struct { + biscuit.Verifier +} + +func (v *hubauthVerifier) getUserToSignData(userPubKey biscuit.Bytes, signedBlockCount int) (*userToSignData, error) { + toSign, err := v.Query(biscuit.Rule{ + Head: biscuit.Predicate{ + Name: "to_sign", + IDs: []biscuit.Atom{biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}, + }, + Body: []biscuit.Predicate{ + { + Name: "should_sign", IDs: []biscuit.Atom{ + biscuit.SymbolAuthority, + biscuit.Variable(0), + biscuit.Variable(1), + biscuit.Bytes(userPubKey), + }, + }, { + Name: "data", IDs: []biscuit.Atom{ + biscuit.SymbolAuthority, + biscuit.Variable(0), + biscuit.Variable(2), + }, + }, + }, + }) + if err != nil { + return nil, err + } + + if g, w := len(toSign), 1; g != w { + return nil, fmt.Errorf("invalid to_sign fact count, got %d, want %d", g, w) + } + + toSignFact := toSign[0] + if g, w := len(toSignFact.IDs), 3; g != w { + return nil, fmt.Errorf("invalid to_sign fact, got %d atoms, want %d", g, w) + } + + sigData := &userToSignData{} + var ok bool + sigData.DataID, ok = toSign[0].IDs[0].(biscuit.Integer) + if !ok { + return nil, errors.New("invalid to_sign atom: dataID") + } + sigData.Alg, ok = toSign[0].IDs[1].(biscuit.Symbol) + if !ok { + return nil, errors.New("invalid to_sign atom: alg") + } + sigData.Data, ok = toSign[0].IDs[2].(biscuit.Bytes) + if !ok { + return nil, errors.New("invalid to_sign atom: data") + } + + if !bytes.HasPrefix(sigData.Data, signStaticCtx) { + return nil, ErrInvalidToSignDataPrefix + } + + sigData.SignedBlockCount = biscuit.Integer(signedBlockCount) + + return sigData, nil +} + +func (v *hubauthVerifier) ensureNotAlreadyUserSigned(dataID biscuit.Integer, userPubKey biscuit.Bytes) error { + alreadySigned, err := v.Query(biscuit.Rule{ + Head: biscuit.Predicate{Name: "already_signed", IDs: []biscuit.Atom{biscuit.Variable(0)}}, + Body: []biscuit.Predicate{ + {Name: "signature", IDs: []biscuit.Atom{dataID, userPubKey, biscuit.Variable(0)}}, + }, + }) + if err != nil { + return err + } + if len(alreadySigned) != 0 { + return ErrAlreadySigned + } + + return nil +} + +func (v *hubauthVerifier) getUserVerificationData() (*userVerificationData, error) { + toValidate, err := v.Query(biscuit.Rule{ + Head: biscuit.Predicate{ + Name: "to_validate", + IDs: []biscuit.Atom{ + biscuit.Variable(0), // dataID + biscuit.Variable(1), // alg + biscuit.Variable(2), // pubkey + biscuit.Variable(3), // data + biscuit.Variable(4), // signature + biscuit.Variable(5), // signerNonce + biscuit.Variable(6), // signerTimestamp + biscuit.Variable(7), // signedBlockCount + }}, + Body: []biscuit.Predicate{ + {Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}}, + {Name: "data", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(3)}}, + {Name: "signature", IDs: []biscuit.Atom{biscuit.Variable(0), biscuit.Variable(2), biscuit.Variable(4), biscuit.Variable(5), biscuit.Variable(6), biscuit.Variable(7)}}, + }, + }) + if err != nil { + return nil, err + } + + if g, w := len(toValidate), 1; g != w { + return nil, fmt.Errorf("invalid to_validate fact count, got %d, want %d", g, w) + } + + toValidateFact := toValidate[0] + if g, w := len(toValidateFact.IDs), 8; g != w { + return nil, fmt.Errorf("invalid to_valid fact atom count, got %d, want %d", g, w) + } + + toVerify := &userVerificationData{} + var ok bool + toVerify.DataID, ok = toValidateFact.IDs[0].(biscuit.Integer) + if !ok { + return nil, errors.New("invalid to_validate atom: dataID") + } + toVerify.Alg, ok = toValidateFact.IDs[1].(biscuit.Symbol) + if !ok { + return nil, errors.New("invalid to_validate atom: alg") + } + toVerify.UserPubKey, ok = toValidateFact.IDs[2].(biscuit.Bytes) + if !ok { + return nil, errors.New("invalid to_validate atom: userPubKey") + } + toVerify.Data, ok = toValidateFact.IDs[3].(biscuit.Bytes) + if !ok { + return nil, errors.New("invalid to_validate atom: data") + } + toVerify.Signature, ok = toValidateFact.IDs[4].(biscuit.Bytes) + if !ok { + return nil, errors.New("invalid to_validate atom: signature") + } + toVerify.Nonce, ok = toValidateFact.IDs[5].(biscuit.Bytes) + if !ok { + return nil, errors.New("invalid to_validate atom: nonce") + } + toVerify.Timestamp, ok = toValidateFact.IDs[6].(biscuit.Date) + if !ok { + return nil, errors.New("invalid to_validate atom: timestamp") + } + toVerify.SignedBlockCount, ok = toValidateFact.IDs[7].(biscuit.Integer) + if !ok { + return nil, errors.New("invalid to_validate atom: signedBlockCount") + } + + return toVerify, nil +} + +func (v *hubauthVerifier) withValidatedUserSignature(data *userVerificationData) error { + v.AddFact(biscuit.Fact{Predicate: biscuit.Predicate{ + Name: "valid_signature", + IDs: []biscuit.Atom{biscuit.Symbol("ambient"), data.DataID, data.Alg, data.UserPubKey}, + }}) + + return nil +} + +func (v *hubauthVerifier) getAudienceVerificationData(audience string) (*audienceVerificationData, error) { + toValidate, err := v.Query(biscuit.Rule{ + Head: biscuit.Predicate{ + Name: "audience_to_validate", + IDs: []biscuit.Atom{ + biscuit.Variable(0), // challenge + biscuit.Variable(1), // signature + }}, + Body: []biscuit.Predicate{ + {Name: "audience_signature", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Symbol(audience), biscuit.Variable(0), biscuit.Variable(1)}}, + }, + }) + if err != nil { + return nil, err + } + + if g, w := len(toValidate), 1; g != w { + return nil, fmt.Errorf("invalid audience_to_validate fact count, got %d, want %d", g, w) + } + + toValidateFact := toValidate[0] + if g, w := len(toValidateFact.IDs), 2; g != w { + return nil, fmt.Errorf("invalid audience_to_validate fact atom count, got %d, want %d", g, w) + } + + toVerify := &audienceVerificationData{Audience: biscuit.Symbol(audience)} + var ok bool + toVerify.Challenge, ok = toValidateFact.IDs[0].(biscuit.Bytes) + if !ok { + return nil, errors.New("invalid audience_to_validate atom: challenge") + } + toVerify.Signature, ok = toValidateFact.IDs[1].(biscuit.Bytes) + if !ok { + return nil, errors.New("invalid audience_to_validate atom: signature") + } + + return toVerify, nil +} + +func (v *hubauthVerifier) withValidatedAudienceSignature(data *audienceVerificationData) error { + v.AddFact(biscuit.Fact{Predicate: biscuit.Predicate{ + Name: "valid_audience_signature", + IDs: []biscuit.Atom{biscuit.Symbol("ambient"), data.Audience, data.Signature}, + }}) + + return nil +} From d93cd0cb046ce76d321e9085a889b623901afb92 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Thu, 8 Oct 2020 16:56:01 +0200 Subject: [PATCH 03/17] idp/token: split builder in multiple files --- pkg/idp/token/builder.go | 41 ---------------- pkg/idp/token/oauth.go | 47 +++++++++++++++++++ .../token/{builder_test.go => oauth_test.go} | 0 3 files changed, 47 insertions(+), 41 deletions(-) create mode 100644 pkg/idp/token/oauth.go rename pkg/idp/token/{builder_test.go => oauth_test.go} (100%) diff --git a/pkg/idp/token/builder.go b/pkg/idp/token/builder.go index 5a60575..15cd05e 100644 --- a/pkg/idp/token/builder.go +++ b/pkg/idp/token/builder.go @@ -2,14 +2,7 @@ package token import ( "context" - "crypto" - "fmt" "time" - - "github.com/flynn/hubauth/pkg/kmssign" - "github.com/flynn/hubauth/pkg/pb" - "github.com/flynn/hubauth/pkg/signpb" - "github.com/golang/protobuf/ptypes" ) type AccessTokenData struct { @@ -21,37 +14,3 @@ type AccessTokenData struct { type AccessTokenBuilder interface { Build(ctx context.Context, audience string, t *AccessTokenData, now time.Time, duration time.Duration) ([]byte, error) } - -type signedPbBuilder struct { - kms kmssign.KMSClient - audienceKey kmssign.AudienceKeyNamer -} - -var _ AccessTokenBuilder = (*signedPbBuilder)(nil) - -func NewSignedPBBuilder(kms kmssign.KMSClient, audienceKey kmssign.AudienceKeyNamer) AccessTokenBuilder { - return &signedPbBuilder{ - kms: kms, - audienceKey: audienceKey, - } -} - -func (b *signedPbBuilder) Build(ctx context.Context, audience string, t *AccessTokenData, now time.Time, duration time.Duration) ([]byte, error) { - signKey := kmssign.NewPrivateKey(b.kms, b.audienceKey(audience), crypto.SHA256) - - exp, _ := ptypes.TimestampProto(now.Add(duration)) - iss, _ := ptypes.TimestampProto(now) - msg := &pb.AccessToken{ - ClientId: t.ClientID, - UserId: t.UserID, - UserEmail: t.UserEmail, - IssueTime: iss, - ExpireTime: exp, - } - tokenBytes, err := signpb.SignMarshal(ctx, signKey, msg) - if err != nil { - return nil, fmt.Errorf("token: error signing access token: %w", err) - } - - return tokenBytes, nil -} diff --git a/pkg/idp/token/oauth.go b/pkg/idp/token/oauth.go new file mode 100644 index 0000000..3b96b0c --- /dev/null +++ b/pkg/idp/token/oauth.go @@ -0,0 +1,47 @@ +package token + +import ( + "context" + "crypto" + "fmt" + "time" + + "github.com/flynn/hubauth/pkg/kmssign" + "github.com/flynn/hubauth/pkg/pb" + "github.com/flynn/hubauth/pkg/signpb" + "github.com/golang/protobuf/ptypes" +) + +type signedPbBuilder struct { + kms kmssign.KMSClient + audienceKey kmssign.AudienceKeyNamer +} + +var _ AccessTokenBuilder = (*signedPbBuilder)(nil) + +func NewSignedPBBuilder(kms kmssign.KMSClient, audienceKey kmssign.AudienceKeyNamer) AccessTokenBuilder { + return &signedPbBuilder{ + kms: kms, + audienceKey: audienceKey, + } +} + +func (b *signedPbBuilder) Build(ctx context.Context, audience string, t *AccessTokenData, now time.Time, duration time.Duration) ([]byte, error) { + signKey := kmssign.NewPrivateKey(b.kms, b.audienceKey(audience), crypto.SHA256) + + exp, _ := ptypes.TimestampProto(now.Add(duration)) + iss, _ := ptypes.TimestampProto(now) + msg := &pb.AccessToken{ + ClientId: t.ClientID, + UserId: t.UserID, + UserEmail: t.UserEmail, + IssueTime: iss, + ExpireTime: exp, + } + tokenBytes, err := signpb.SignMarshal(ctx, signKey, msg) + if err != nil { + return nil, fmt.Errorf("token: error signing access token: %w", err) + } + + return tokenBytes, nil +} diff --git a/pkg/idp/token/builder_test.go b/pkg/idp/token/oauth_test.go similarity index 100% rename from pkg/idp/token/builder_test.go rename to pkg/idp/token/oauth_test.go From 70ca6eb931819c6d26f56f54080af056b3d827cd Mon Sep 17 00:00:00 2001 From: daeMOn Date: Mon, 12 Oct 2020 09:39:15 +0200 Subject: [PATCH 04/17] add biscuit metadata and expiration date Verifiers must now provide the current time for verifying the biscuit, and can extract user informations. User's pubkeys are now provided in http param when exchanging code. Removed block count from biscuit weakening the signature. --- go.mod | 2 +- go.sum | 2 + pkg/biscuit/biscuit.go | 56 ++++++++++++----- pkg/biscuit/biscuit_test.go | 20 ++++-- pkg/biscuit/signature.go | 66 ++++++++++++-------- pkg/biscuit/signature_test.go | 105 +++++++++++++------------------ pkg/biscuit/wrapper.go | 112 ++++++++++++++++++++++++++++++---- pkg/httpapi/http.go | 11 ++-- pkg/hubauth/idp.go | 18 +++--- pkg/idp/oauth.go | 47 ++++++++++---- pkg/idp/oauth_test.go | 36 ++++++----- pkg/idp/steps.go | 6 +- pkg/idp/steps_test.go | 18 +++--- pkg/idp/token/biscuit.go | 41 +++++++++++++ pkg/idp/token/builder.go | 11 ++-- pkg/idp/token/oauth.go | 7 +-- pkg/idp/token/oauth_test.go | 14 +++-- 17 files changed, 385 insertions(+), 187 deletions(-) create mode 100644 pkg/idp/token/biscuit.go diff --git a/go.mod b/go.mod index e95d318..5dde64c 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/alecthomas/kong v0.2.12 github.com/aws/aws-sdk-go v1.36.7 // indirect github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect - github.com/flynn/biscuit-go v0.0.0-20200907174027-193b7bdbbdca + github.com/flynn/biscuit-go v0.0.0-20201009174859-e7eb59a90195 github.com/golang/protobuf v1.4.3 github.com/googleapis/gax-go/v2 v2.0.5 github.com/jedib0t/go-pretty/v6 v6.0.5 diff --git a/go.sum b/go.sum index add3e98..425d2a8 100644 --- a/go.sum +++ b/go.sum @@ -79,6 +79,8 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/flynn/biscuit-go v0.0.0-20200907174027-193b7bdbbdca h1:LUZQQzaCT+gltxii4icyPH5oMdAP38JmbvO9aI0E4qM= github.com/flynn/biscuit-go v0.0.0-20200907174027-193b7bdbbdca/go.mod h1:EMJZ3stAYtwaP763F5HcGjPjCnYu21V2TEsg/iw88I8= +github.com/flynn/biscuit-go v0.0.0-20201009174859-e7eb59a90195 h1:TP3jMHmhjz8XxqqigEd5OQffNAO/6KPvGUYII6TFdmI= +github.com/flynn/biscuit-go v0.0.0-20201009174859-e7eb59a90195/go.mod h1:EMJZ3stAYtwaP763F5HcGjPjCnYu21V2TEsg/iw88I8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= diff --git a/pkg/biscuit/biscuit.go b/pkg/biscuit/biscuit.go index ef1a452..e335e60 100644 --- a/pkg/biscuit/biscuit.go +++ b/pkg/biscuit/biscuit.go @@ -3,12 +3,20 @@ package biscuit import ( "crypto/rand" "fmt" + "time" "github.com/flynn/biscuit-go" "github.com/flynn/biscuit-go/sig" "github.com/flynn/hubauth/pkg/kmssign" ) +type Metadata struct { + ClientID string + UserID string + UserEmail string + IssueTime time.Time +} + type UserKeyPair struct { Public []byte Private []byte @@ -16,7 +24,7 @@ type UserKeyPair struct { // GenerateSignable 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 *kmssign.Key, userPubkey []byte) ([]byte, error) { +func GenerateSignable(rootKey sig.Keypair, audience string, audienceKey *kmssign.Key, userPublicKey []byte, expireTime time.Time, m *Metadata) ([]byte, error) { builder := &hubauthBuilder{ Builder: biscuit.NewBuilder(rand.Reader, rootKey), } @@ -25,7 +33,15 @@ func GenerateSignable(rootKey sig.Keypair, audience string, audienceKey *kmssign return nil, err } - if err := builder.withUserToSignFact(userPubkey); err != nil { + if err := builder.withUserToSignFact(userPublicKey); err != nil { + return nil, err + } + + if err := builder.withExpire(expireTime); err != nil { + return nil, err + } + + if err := builder.withMetadata(m); err != nil { return nil, err } @@ -55,7 +71,7 @@ func Sign(token []byte, rootPubKey sig.PublicKey, userKey *UserKeyPair) ([]byte, Verifier: v, } - toSignData, err := verifier.getUserToSignData(userKey.Public, b.BlockCount()) + toSignData, err := verifier.getUserToSignData(userKey.Public) if err != nil { return nil, fmt.Errorf("biscuit: failed to get to_sign data: %w", err) } @@ -92,46 +108,56 @@ func Sign(token []byte, rootPubKey sig.PublicKey, userKey *UserKeyPair) ([]byte, // 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 *kmssign.Key) error { +func Verify(token []byte, rootPubKey sig.PublicKey, audience string, audienceKey *kmssign.Key) (*Metadata, error) { b, err := biscuit.Unmarshal(token) if err != nil { - return fmt.Errorf("biscuit: failed to unmarshal: %w", err) + return nil, fmt.Errorf("biscuit: failed to unmarshal: %w", err) } v, err := b.Verify(rootPubKey) if err != nil { - return fmt.Errorf("biscuit: failed to verify: %w", err) + return nil, fmt.Errorf("biscuit: failed to verify: %w", err) } verifier := &hubauthVerifier{v} audienceVerificationData, err := verifier.getAudienceVerificationData(audience) if err != nil { - return fmt.Errorf("biscuit: failed to retrieve audience signature data: %w", err) + return nil, fmt.Errorf("biscuit: failed to retrieve audience signature data: %w", err) } if err := verifyAudienceSignature(audienceKey, audienceVerificationData); err != nil { - return fmt.Errorf("biscuit: failed to verify audience signature: %w", err) + return nil, fmt.Errorf("biscuit: failed to verify audience signature: %w", err) } if err := verifier.withValidatedAudienceSignature(audienceVerificationData); err != nil { - return fmt.Errorf("biscuit: failed to add validated signature: %w", err) + return nil, fmt.Errorf("biscuit: failed to add validated signature: %w", err) } userVerificationData, err := verifier.getUserVerificationData() if err != nil { - return fmt.Errorf("biscuit: failed to retrieve user signature data: %w", err) + return nil, fmt.Errorf("biscuit: failed to retrieve user signature data: %w", err) } - signedTokenHash, err := b.SHA256Sum(int(userVerificationData.SignedBlockCount)) + // 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) if err != nil { - return fmt.Errorf("biscuit: failed to generate token hash: %w", err) + return nil, fmt.Errorf("biscuit: failed to generate token hash: %w", err) } if err := verifyUserSignature(signedTokenHash, userVerificationData); err != nil { - return fmt.Errorf("biscuit: failed to verify user signature: %w", err) + return nil, fmt.Errorf("biscuit: failed to verify user signature: %w", err) } if err := verifier.withValidatedUserSignature(userVerificationData); err != nil { - return fmt.Errorf("biscuit: failed to add validated signature: %w", err) + return 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 verifier.Verify() + return verifier.getMetadata() } diff --git a/pkg/biscuit/biscuit_test.go b/pkg/biscuit/biscuit_test.go index ed7b273..bf6199b 100644 --- a/pkg/biscuit/biscuit_test.go +++ b/pkg/biscuit/biscuit_test.go @@ -4,6 +4,7 @@ import ( "context" "crypto/rand" "testing" + "time" "github.com/flynn/biscuit-go/sig" "github.com/flynn/hubauth/pkg/kmssign" @@ -20,8 +21,13 @@ func TestBiscuit(t *testing.T) { require.NoError(t, err) userKey := generateUserKeyPair(t) - - signableBiscuit, err := GenerateSignable(rootKey, audience, audienceKey, userKey.Public) + metas := &Metadata{ + ClientID: "abcd", + UserEmail: "1234@example.com", + UserID: "1234", + IssueTime: time.Now(), + } + signableBiscuit, err := GenerateSignable(rootKey, audience, audienceKey, userKey.Public, time.Now().Add(5*time.Minute), metas) require.NoError(t, err) t.Logf("signable biscuit size: %d", len(signableBiscuit)) @@ -30,8 +36,12 @@ func TestBiscuit(t *testing.T) { require.NoError(t, err) t.Logf("signed biscuit size: %d", len(signedBiscuit)) - err = Verify(signedBiscuit, rootKey.Public(), audience, audienceKey) + res, err := Verify(signedBiscuit, rootKey.Public(), audience, audienceKey) require.NoError(t, err) + require.Equal(t, metas.ClientID, res.ClientID) + require.Equal(t, metas.UserID, res.UserID) + require.Equal(t, metas.UserEmail, res.UserEmail) + require.WithinDuration(t, metas.IssueTime, res.IssueTime, 1*time.Second) }) t.Run("user sign with wrong key", func(t *testing.T) { @@ -43,7 +53,7 @@ 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) + _, err = Verify(signedBiscuit, rootKey.Public(), "http://another.audience.url", audienceKey) require.Error(t, err) wrongAudience := "http://another.audience.url" @@ -51,7 +61,7 @@ func TestBiscuit(t *testing.T) { wrongAudienceKey, err := kmssign.NewKey(context.Background(), kms, wrongAudience) require.NoError(t, err) - err = Verify(signedBiscuit, rootKey.Public(), audience, wrongAudienceKey) + _, err = Verify(signedBiscuit, rootKey.Public(), audience, wrongAudienceKey) require.Error(t, err) }) } diff --git a/pkg/biscuit/signature.go b/pkg/biscuit/signature.go index 385e4bf..078eaa6 100644 --- a/pkg/biscuit/signature.go +++ b/pkg/biscuit/signature.go @@ -3,10 +3,12 @@ package biscuit import ( "crypto" "crypto/ecdsa" + "crypto/elliptic" "crypto/rand" "crypto/sha256" "crypto/x509" "errors" + "fmt" "time" "github.com/flynn/biscuit-go" @@ -25,30 +27,27 @@ const ( ) type userToSignData struct { - DataID biscuit.Integer - Alg biscuit.Symbol - Data biscuit.Bytes - SignedBlockCount biscuit.Integer + DataID biscuit.Integer + Alg biscuit.Symbol + Data biscuit.Bytes } type userSignatureData struct { - DataID biscuit.Integer - UserPubKey biscuit.Bytes - Signature biscuit.Bytes - SignedBlockCount biscuit.Integer - Nonce biscuit.Bytes - Timestamp biscuit.Date + DataID biscuit.Integer + UserPubKey biscuit.Bytes + Signature biscuit.Bytes + Nonce biscuit.Bytes + Timestamp biscuit.Date } type userVerificationData struct { - DataID biscuit.Integer - Alg biscuit.Symbol - Data biscuit.Bytes - UserPubKey biscuit.Bytes - Signature biscuit.Bytes - SignedBlockCount biscuit.Integer - Nonce biscuit.Bytes - Timestamp biscuit.Date + DataID biscuit.Integer + Alg biscuit.Symbol + Data biscuit.Bytes + UserPubKey biscuit.Bytes + Signature biscuit.Bytes + Nonce biscuit.Bytes + Timestamp biscuit.Date } func userSign(tokenHash []byte, userKey *UserKeyPair, toSignData *userToSignData) (*userSignatureData, error) { @@ -67,7 +66,6 @@ func userSign(tokenHash []byte, userKey *UserKeyPair, toSignData *userToSignData dataToSign = append(dataToSign, tokenHash...) dataToSign = append(dataToSign, signerNonce...) dataToSign = append(dataToSign, []byte(signerTimestamp.Format(time.RFC3339))...) - dataToSign = append(dataToSign, []byte(toSignData.SignedBlockCount.String())...) var signedData biscuit.Bytes switch SignatureAlg(toSignData.Alg) { @@ -86,12 +84,11 @@ func userSign(tokenHash []byte, userKey *UserKeyPair, toSignData *userToSignData } return &userSignatureData{ - DataID: toSignData.DataID, - Nonce: signerNonce, - Signature: signedData, - SignedBlockCount: toSignData.SignedBlockCount, - Timestamp: biscuit.Date(signerTimestamp), - UserPubKey: userKey.Public, + DataID: toSignData.DataID, + Nonce: signerNonce, + Signature: signedData, + Timestamp: biscuit.Date(signerTimestamp), + UserPubKey: userKey.Public, }, nil } @@ -101,7 +98,6 @@ func verifyUserSignature(signedTokenHash []byte, data *userVerificationData) err signedData = append(signedData, signedTokenHash...) signedData = append(signedData, data.Nonce...) signedData = append(signedData, []byte(time.Time(data.Timestamp).Format(time.RFC3339))...) - signedData = append(signedData, []byte(data.SignedBlockCount.String())...) switch SignatureAlg(data.Alg) { case ECDSA_P256_SHA256: @@ -160,3 +156,21 @@ func verifyAudienceSignature(audiencePubkey *kmssign.Key, data *audienceVerifica } return nil } + +func validatePKIXP256PublicKey(pubkey []byte) error { + key, err := x509.ParsePKIXPublicKey(pubkey) + if err != nil { + return fmt.Errorf("failed to parse PKIX, ASN.1 DER public key: %v", err) + } + + ecKey, ok := key.(*ecdsa.PublicKey) + if !ok { + return errors.New("public key is not an *ecdsa.PublicKey") + } + + if ecKey.Curve != elliptic.P256() { + return fmt.Errorf("publickey is on wrong curve, expected P256") + } + + return nil +} diff --git a/pkg/biscuit/signature_test.go b/pkg/biscuit/signature_test.go index 5cb5d8b..d4d4cc2 100644 --- a/pkg/biscuit/signature_test.go +++ b/pkg/biscuit/signature_test.go @@ -25,16 +25,14 @@ func TestUserSignVerify(t *testing.T) { userKey := generateUserKeyPair(t) toSignData := &userToSignData{ - DataID: 1, - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - Data: []byte("challenge"), - SignedBlockCount: 2, + DataID: 1, + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + Data: []byte("challenge"), } signedData, err := userSign(tokenHash, userKey, toSignData) require.NoError(t, err) require.NotEmpty(t, signedData.Signature) - require.Equal(t, biscuit.Integer(2), signedData.SignedBlockCount) require.Equal(t, biscuit.Integer(1), signedData.DataID) require.Equal(t, biscuit.Bytes(userKey.Public), signedData.UserPubKey) @@ -45,14 +43,13 @@ func TestUserSignVerify(t *testing.T) { require.WithinDuration(t, time.Now(), time.Time(signedData.Timestamp), 1*time.Second) require.NoError(t, verifyUserSignature(tokenHash, &userVerificationData{ - DataID: toSignData.DataID, - Alg: toSignData.Alg, - Data: toSignData.Data, - Nonce: signedData.Nonce, - Signature: signedData.Signature, - SignedBlockCount: signedData.SignedBlockCount, - Timestamp: signedData.Timestamp, - UserPubKey: signedData.UserPubKey, + DataID: toSignData.DataID, + Alg: toSignData.Alg, + Data: toSignData.Data, + Nonce: signedData.Nonce, + Signature: signedData.Signature, + Timestamp: signedData.Timestamp, + UserPubKey: signedData.UserPubKey, })) } @@ -112,10 +109,9 @@ func TestUserSignFail(t *testing.T) { func TestVerifyUserSignatureFail(t *testing.T) { tokenHash := []byte("token hash") toSignData := &userToSignData{ - DataID: 1, - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - Data: []byte("challenge"), - SignedBlockCount: 2, + DataID: 1, + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + Data: []byte("challenge"), } userKey := generateUserKeyPair(t) @@ -160,14 +156,13 @@ func TestVerifyUserSignatureFail(t *testing.T) { desc: "wrong pubkey", tokenHash: tokenHash, data: &userVerificationData{ - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - UserPubKey: invalidKey.Public, - Data: toSignData.Data, - DataID: toSignData.DataID, - Nonce: signedData.Nonce, - Signature: signedData.Signature, - Timestamp: signedData.Timestamp, - SignedBlockCount: signedData.SignedBlockCount, + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + UserPubKey: invalidKey.Public, + Data: toSignData.Data, + DataID: toSignData.DataID, + Nonce: signedData.Nonce, + Signature: signedData.Signature, + Timestamp: signedData.Timestamp, }, }, { @@ -175,14 +170,13 @@ func TestVerifyUserSignatureFail(t *testing.T) { expectedErr: ErrInvalidSignature, tokenHash: []byte("wrong"), data: &userVerificationData{ - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - UserPubKey: userKey.Public, - Data: toSignData.Data, - DataID: toSignData.DataID, - Nonce: signedData.Nonce, - Signature: signedData.Signature, - Timestamp: signedData.Timestamp, - SignedBlockCount: signedData.SignedBlockCount, + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + UserPubKey: userKey.Public, + Data: toSignData.Data, + DataID: toSignData.DataID, + Nonce: signedData.Nonce, + Signature: signedData.Signature, + Timestamp: signedData.Timestamp, }, }, { @@ -190,14 +184,13 @@ func TestVerifyUserSignatureFail(t *testing.T) { expectedErr: ErrInvalidSignature, tokenHash: tokenHash, data: &userVerificationData{ - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - UserPubKey: userKey.Public, - Data: toSignData.Data, - DataID: toSignData.DataID, - Nonce: []byte("another nonce"), - Signature: signedData.Signature, - SignedBlockCount: signedData.SignedBlockCount, - Timestamp: signedData.Timestamp, + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + UserPubKey: userKey.Public, + Data: toSignData.Data, + DataID: toSignData.DataID, + Nonce: []byte("another nonce"), + Signature: signedData.Signature, + Timestamp: signedData.Timestamp, }, }, { @@ -205,29 +198,13 @@ func TestVerifyUserSignatureFail(t *testing.T) { expectedErr: ErrInvalidSignature, tokenHash: tokenHash, data: &userVerificationData{ - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - UserPubKey: userKey.Public, - Data: toSignData.Data, - DataID: toSignData.DataID, - Nonce: signedData.Nonce, - Signature: signedData.Signature, - Timestamp: biscuit.Date(time.Now().Add(1 * time.Second)), - SignedBlockCount: signedData.SignedBlockCount, - }, - }, - { - desc: "tampered signedBlockCount", - expectedErr: ErrInvalidSignature, - tokenHash: tokenHash, - data: &userVerificationData{ - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - UserPubKey: userKey.Public, - Data: toSignData.Data, - DataID: toSignData.DataID, - Nonce: signedData.Nonce, - Signature: signedData.Signature, - Timestamp: signedData.Timestamp, - SignedBlockCount: signedData.SignedBlockCount + 1, + Alg: biscuit.Symbol(ECDSA_P256_SHA256), + UserPubKey: userKey.Public, + Data: toSignData.Data, + DataID: toSignData.DataID, + Nonce: signedData.Nonce, + Signature: signedData.Signature, + Timestamp: biscuit.Date(time.Now().Add(1 * time.Second)), }, }, } diff --git a/pkg/biscuit/wrapper.go b/pkg/biscuit/wrapper.go index ec16ebd..f39681f 100644 --- a/pkg/biscuit/wrapper.go +++ b/pkg/biscuit/wrapper.go @@ -5,8 +5,10 @@ import ( "crypto/rand" "errors" "fmt" + "time" "github.com/flynn/biscuit-go" + "github.com/flynn/biscuit-go/datalog" "github.com/flynn/hubauth/pkg/kmssign" ) @@ -31,6 +33,10 @@ type hubauthBuilder struct { func (b *hubauthBuilder) withUserToSignFact(userPubkey []byte) error { dataID := biscuit.Integer(0) + if err := validatePKIXP256PublicKey(userPubkey); err != nil { + return err + } + if err := b.AddAuthorityFact(biscuit.Fact{Predicate: biscuit.Predicate{ Name: "should_sign", IDs: []biscuit.Atom{ @@ -108,6 +114,38 @@ func (b *hubauthBuilder) withAudienceSignature(audience string, audienceKey *kms return nil } +func (b *hubauthBuilder) withMetadata(m *Metadata) error { + return b.AddAuthorityFact(biscuit.Fact{Predicate: biscuit.Predicate{ + Name: "metadata", + IDs: []biscuit.Atom{ + biscuit.String(m.ClientID), + biscuit.String(m.UserID), + biscuit.String(m.UserEmail), + biscuit.Date(m.IssueTime), + }, + }}) +} + +func (b *hubauthBuilder) withExpire(exp time.Time) error { + if err := b.AddAuthorityCaveat(biscuit.Rule{ + Head: biscuit.Predicate{Name: "not_expired", IDs: []biscuit.Atom{biscuit.Variable(0)}}, + Body: []biscuit.Predicate{ + {Name: "current_time", IDs: []biscuit.Atom{biscuit.Symbol("ambient"), biscuit.Variable(0)}}, + }, + Constraints: []biscuit.Constraint{{ + Name: biscuit.Variable(0), + Checker: biscuit.DateComparisonChecker{ + Comparison: datalog.DateComparisonBefore, + Date: biscuit.Date(exp), + }, + }}, + }); err != nil { + return err + } + + return nil +} + type hubauthBlockBuilder struct { biscuit.BlockBuilder } @@ -121,7 +159,6 @@ func (b *hubauthBlockBuilder) withUserSignature(sigData *userSignatureData) erro sigData.Signature, sigData.Nonce, sigData.Timestamp, - sigData.SignedBlockCount, }, }}) } @@ -130,7 +167,7 @@ type hubauthVerifier struct { biscuit.Verifier } -func (v *hubauthVerifier) getUserToSignData(userPubKey biscuit.Bytes, signedBlockCount int) (*userToSignData, error) { +func (v *hubauthVerifier) getUserToSignData(userPubKey biscuit.Bytes) (*userToSignData, error) { toSign, err := v.Query(biscuit.Rule{ Head: biscuit.Predicate{ Name: "to_sign", @@ -185,8 +222,6 @@ func (v *hubauthVerifier) getUserToSignData(userPubKey biscuit.Bytes, signedBloc return nil, ErrInvalidToSignDataPrefix } - sigData.SignedBlockCount = biscuit.Integer(signedBlockCount) - return sigData, nil } @@ -219,12 +254,11 @@ func (v *hubauthVerifier) getUserVerificationData() (*userVerificationData, erro biscuit.Variable(4), // signature biscuit.Variable(5), // signerNonce biscuit.Variable(6), // signerTimestamp - biscuit.Variable(7), // signedBlockCount }}, Body: []biscuit.Predicate{ {Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}}, {Name: "data", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(3)}}, - {Name: "signature", IDs: []biscuit.Atom{biscuit.Variable(0), biscuit.Variable(2), biscuit.Variable(4), biscuit.Variable(5), biscuit.Variable(6), biscuit.Variable(7)}}, + {Name: "signature", IDs: []biscuit.Atom{biscuit.Variable(0), biscuit.Variable(2), biscuit.Variable(4), biscuit.Variable(5), biscuit.Variable(6)}}, }, }) if err != nil { @@ -236,7 +270,7 @@ func (v *hubauthVerifier) getUserVerificationData() (*userVerificationData, erro } toValidateFact := toValidate[0] - if g, w := len(toValidateFact.IDs), 8; g != w { + if g, w := len(toValidateFact.IDs), 7; g != w { return nil, fmt.Errorf("invalid to_valid fact atom count, got %d, want %d", g, w) } @@ -270,10 +304,6 @@ func (v *hubauthVerifier) getUserVerificationData() (*userVerificationData, erro if !ok { return nil, errors.New("invalid to_validate atom: timestamp") } - toVerify.SignedBlockCount, ok = toValidateFact.IDs[7].(biscuit.Integer) - if !ok { - return nil, errors.New("invalid to_validate atom: signedBlockCount") - } return toVerify, nil } @@ -326,6 +356,54 @@ func (v *hubauthVerifier) getAudienceVerificationData(audience string) (*audienc return toVerify, nil } +func (v *hubauthVerifier) getMetadata() (*Metadata, error) { + metaFacts, err := v.Query(biscuit.Rule{ + Head: biscuit.Predicate{ + Name: "metadata", + IDs: []biscuit.Atom{ + biscuit.Variable(0), // clientID + biscuit.Variable(1), // userID + biscuit.Variable(2), // userEmail + biscuit.Variable(3), // issueTime + }}, + Body: []biscuit.Predicate{ + {Name: "metadata", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2), biscuit.Variable(3)}}, + }, + }) + if err != nil { + return nil, err + } + + if g, w := len(metaFacts), 1; g != w { + return nil, fmt.Errorf("invalid metadata fact count, got %d, want %d", g, w) + } + + metaFact := metaFacts[0] + + clientID, ok := metaFact.IDs[0].(biscuit.String) + if !ok { + return nil, errors.New("invalid metadata atom: clientID") + } + userID, ok := metaFact.IDs[1].(biscuit.String) + if !ok { + return nil, errors.New("invalid metadata atom: userID") + } + userEmail, ok := metaFact.IDs[2].(biscuit.String) + if !ok { + return nil, errors.New("invalid metadata atom: userEmail") + } + issueTime, ok := metaFact.IDs[3].(biscuit.Date) + if !ok { + return nil, errors.New("invalid metadata atom: issueTime") + } + return &Metadata{ + ClientID: string(clientID), + UserID: string(userID), + UserEmail: string(userEmail), + IssueTime: time.Time(issueTime), + }, nil +} + func (v *hubauthVerifier) withValidatedAudienceSignature(data *audienceVerificationData) error { v.AddFact(biscuit.Fact{Predicate: biscuit.Predicate{ Name: "valid_audience_signature", @@ -334,3 +412,15 @@ func (v *hubauthVerifier) withValidatedAudienceSignature(data *audienceVerificat return nil } + +func (v *hubauthVerifier) withCurrentTime(t time.Time) error { + v.AddFact(biscuit.Fact{Predicate: biscuit.Predicate{ + Name: "current_time", + IDs: []biscuit.Atom{ + biscuit.Symbol("ambient"), + biscuit.Date(t), + }, + }}) + + return nil +} diff --git a/pkg/httpapi/http.go b/pkg/httpapi/http.go index 58da911..641212d 100644 --- a/pkg/httpapi/http.go +++ b/pkg/httpapi/http.go @@ -329,11 +329,12 @@ func (a *api) Token(w http.ResponseWriter, req *http.Request) { switch req.Form.Get("grant_type") { case "authorization_code": res, err = a.IdP.ExchangeCode(req.Context(), &hubauth.ExchangeCodeRequest{ - ClientID: req.PostForm.Get("client_id"), - Audience: aud, - RedirectURI: req.PostForm.Get("redirect_uri"), - Code: req.PostForm.Get("code"), - CodeVerifier: req.PostForm.Get("code_verifier"), + ClientID: req.PostForm.Get("client_id"), + Audience: aud, + RedirectURI: req.PostForm.Get("redirect_uri"), + Code: req.PostForm.Get("code"), + CodeVerifier: req.PostForm.Get("code_verifier"), + UserPublicKey: req.PostForm.Get("user_public_key"), }) case "refresh_token": res, err = a.IdP.RefreshToken(req.Context(), &hubauth.RefreshTokenRequest{ diff --git a/pkg/hubauth/idp.go b/pkg/hubauth/idp.go index 66d4722..7b4d132 100644 --- a/pkg/hubauth/idp.go +++ b/pkg/hubauth/idp.go @@ -38,11 +38,12 @@ type AuthorizeResponse struct { } type ExchangeCodeRequest struct { - ClientID string - RedirectURI string - Audience string - Code string - CodeVerifier string + ClientID string + RedirectURI string + Audience string + Code string + CodeVerifier string + UserPublicKey string } type AccessToken struct { @@ -61,9 +62,10 @@ type AccessToken struct { } type RefreshTokenRequest struct { - ClientID string - Audience string - RefreshToken string + ClientID string + Audience string + RefreshToken string + UserPublicKey string } type ListAudiencesRequest struct { diff --git a/pkg/idp/oauth.go b/pkg/idp/oauth.go index a6f44d3..3f44e96 100644 --- a/pkg/idp/oauth.go +++ b/pkg/idp/oauth.go @@ -47,7 +47,7 @@ type idpSteps interface { SignRefreshToken(ctx context.Context, signKey signpb.PrivateKey, t *signedRefreshTokenData) (string, error) RenewRefreshToken(ctx context.Context, clientID, oldTokenID string, oldTokenIssueTime, now time.Time) (*hubauth.RefreshToken, error) VerifyRefreshToken(ctx context.Context, rt *hubauth.RefreshToken, now time.Time) error - SignAccessToken(ctx context.Context, audience string, t *token.AccessTokenData, now time.Time) (string, error) + BuildAccessToken(ctx context.Context, audience string, t *token.AccessTokenData) (string, error) } type idpService struct { @@ -309,11 +309,23 @@ func (s *idpService) ExchangeCode(parentCtx context.Context, req *hubauth.Exchan return nil } - accessToken, err = s.steps.SignAccessToken(ctx, req.Audience, &token.AccessTokenData{ - ClientID: req.ClientID, - UserID: codeInfo.UserId, - UserEmail: codeInfo.UserEmail, - }, now) + var userPublicKey []byte + if len(req.UserPublicKey) > 0 { + var err error + userPublicKey, err = base64Decode(req.UserPublicKey) + if err != nil { + return fmt.Errorf("idp: invalid public key: %v", err) + } + } + + accessToken, err = s.steps.BuildAccessToken(ctx, req.Audience, &token.AccessTokenData{ + ClientID: req.ClientID, + UserID: codeInfo.UserId, + UserEmail: codeInfo.UserEmail, + UserPublicKey: userPublicKey, + IssueTime: now, + ExpireTime: now.Add(accessTokenDuration), + }) return err }) @@ -382,11 +394,24 @@ func (s *idpService) RefreshToken(ctx context.Context, req *hubauth.RefreshToken if req.Audience == "" { return nil } - accessToken, err = s.steps.SignAccessToken(ctx, req.Audience, &token.AccessTokenData{ - ClientID: req.ClientID, - UserID: oldToken.UserID, - UserEmail: oldToken.UserEmail, - }, now) + + var userPublicKey []byte + if len(req.UserPublicKey) > 0 { + var err error + userPublicKey, err = base64Decode(req.UserPublicKey) + if err != nil { + return fmt.Errorf("idp: invalid public key: %v", err) + } + } + + accessToken, err = s.steps.BuildAccessToken(ctx, req.Audience, &token.AccessTokenData{ + ClientID: req.ClientID, + UserID: oldToken.UserID, + UserEmail: oldToken.UserEmail, + UserPublicKey: userPublicKey, + IssueTime: now, + ExpireTime: now.Add(accessTokenDuration), + }) return err }) diff --git a/pkg/idp/oauth_test.go b/pkg/idp/oauth_test.go index 827c2df..d8cdcb6 100644 --- a/pkg/idp/oauth_test.go +++ b/pkg/idp/oauth_test.go @@ -78,8 +78,8 @@ func (m *mockSteps) SignRefreshToken(ctx context.Context, signKey signpb.Private args := m.Called(ctx, signKey, t) return args.String(0), args.Error(1) } -func (m *mockSteps) SignAccessToken(ctx context.Context, audience string, t *token.AccessTokenData, now time.Time) (string, error) { - args := m.Called(ctx, audience, t, now) +func (m *mockSteps) BuildAccessToken(ctx context.Context, audience string, t *token.AccessTokenData) (string, error) { + args := m.Called(ctx, audience, t) return args.String(0), args.Error(1) } func (m *mockSteps) RenewRefreshToken(ctx context.Context, clientID, oldTokenID string, oldTokenIssueTime, now time.Time) (*hubauth.RefreshToken, error) { @@ -665,11 +665,13 @@ func TestExchangeCode(t *testing.T) { }).Return(verifiedCode, nil) idpService.steps.(*mockSteps).On("SaveRefreshToken", mock.Anything, b64CodeID, redirectURI, rtData).Return(client, nil) idpService.steps.(*mockSteps).On("SignRefreshToken", mock.Anything, idpService.refreshKey, signedRTData).Return(refreshToken, nil) - idpService.steps.(*mockSteps).On("SignAccessToken", mock.Anything, audienceURL, &token.AccessTokenData{ - ClientID: clientID, - UserID: userID, - UserEmail: userEmail, - }, now).Return(accessToken, nil) + idpService.steps.(*mockSteps).On("BuildAccessToken", mock.Anything, audienceURL, &token.AccessTokenData{ + ClientID: clientID, + UserID: userID, + UserEmail: userEmail, + IssueTime: now, + ExpireTime: now.Add(accessTokenDuration), + }).Return(accessToken, nil) req := &hubauth.ExchangeCodeRequest{ ClientID: clientID, @@ -783,7 +785,7 @@ func TestExchangeCodeErrors(t *testing.T) { ExpectedErr: expectedErr, }, { - Desc: "SignAccessToken error", + Desc: "BuildAccessToken error", Code: base64Encode(validCode), SignATErr: expectedErr, ExpectedErr: expectedErr, @@ -800,7 +802,7 @@ func TestExchangeCodeErrors(t *testing.T) { idpService.steps.(*mockSteps).On("VerifyAudience", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCase.VerifyAudienceErr) idpService.steps.(*mockSteps).On("SaveRefreshToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&hubauth.Client{}, testCase.SaveErr) idpService.steps.(*mockSteps).On("SignRefreshToken", mock.Anything, mock.Anything, mock.Anything).Return("", testCase.SignRTErr) - idpService.steps.(*mockSteps).On("SignAccessToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", testCase.SignATErr) + idpService.steps.(*mockSteps).On("BuildAccessToken", mock.Anything, mock.Anything, mock.Anything).Return("", testCase.SignATErr) req := &hubauth.ExchangeCodeRequest{ Code: testCase.Code, @@ -894,11 +896,13 @@ func TestRefreshToken(t *testing.T) { }, ExpiryTime: expireTimeProto.AsTime(), }).Return(newRefreshTokenStr, nil) - idpService.steps.(*mockSteps).On("SignAccessToken", mock.Anything, testCase.AudienceURL, &token.AccessTokenData{ - ClientID: b64ClientID, - UserID: userID, - UserEmail: userEmail, - }, now).Return(newAccessTokenStr, nil) + idpService.steps.(*mockSteps).On("BuildAccessToken", mock.Anything, testCase.AudienceURL, &token.AccessTokenData{ + ClientID: b64ClientID, + UserID: userID, + UserEmail: userEmail, + IssueTime: now, + ExpireTime: now.Add(accessTokenDuration), + }).Return(newAccessTokenStr, nil) oldTokenSigned, err := signpb.SignMarshal(context.Background(), idpService.refreshKey, &pb.RefreshToken{ Key: oldTokenID, @@ -974,7 +978,7 @@ func TestRefreshTokenStepErrors(t *testing.T) { ExpectedErr: expectedErr, }, { - Desc: "SignAccessToken error", + Desc: "BuildAccessToken error", SignATErr: expectedErr, ExpectedErr: expectedErr, }, @@ -1001,7 +1005,7 @@ func TestRefreshTokenStepErrors(t *testing.T) { idpService.steps.(*mockSteps).On("VerifyAudience", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCase.VerifyAudienceErr) idpService.steps.(*mockSteps).On("RenewRefreshToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&hubauth.RefreshToken{}, testCase.RenewRTErr) idpService.steps.(*mockSteps).On("SignRefreshToken", mock.Anything, mock.Anything, mock.Anything).Return("", testCase.SignRTErr) - idpService.steps.(*mockSteps).On("SignAccessToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return("", testCase.SignATErr) + idpService.steps.(*mockSteps).On("BuildAccessToken", mock.Anything, mock.Anything, mock.Anything).Return("", testCase.SignATErr) _, err = idpService.RefreshToken(context.Background(), req) require.Equal(t, testCase.ExpectedErr, err) diff --git a/pkg/idp/steps.go b/pkg/idp/steps.go index 440b8ea..2d4369b 100644 --- a/pkg/idp/steps.go +++ b/pkg/idp/steps.go @@ -351,8 +351,8 @@ func (s *steps) VerifyRefreshToken(ctx context.Context, rt *hubauth.RefreshToken return nil } -func (s *steps) SignAccessToken(ctx context.Context, audience string, t *token.AccessTokenData, now time.Time) (token string, err error) { - ctx, span := trace.StartSpan(ctx, "idp.SignAccessToken") +func (s *steps) BuildAccessToken(ctx context.Context, audience string, t *token.AccessTokenData) (token string, err error) { + ctx, span := trace.StartSpan(ctx, "idp.BuildAccessToken") span.AddAttributes( trace.StringAttribute("client_id", t.ClientID), trace.StringAttribute("user_id", t.UserID), @@ -360,7 +360,7 @@ func (s *steps) SignAccessToken(ctx context.Context, audience string, t *token.A ) defer span.End() - tokenBytes, err := s.builder.Build(ctx, audience, t, now, accessTokenDuration) + tokenBytes, err := s.builder.Build(ctx, audience, t) if err != nil { return "", fmt.Errorf("idp: error building access token: %w", err) } diff --git a/pkg/idp/steps_test.go b/pkg/idp/steps_test.go index 8ec7662..4c1a1cd 100644 --- a/pkg/idp/steps_test.go +++ b/pkg/idp/steps_test.go @@ -33,8 +33,8 @@ type mockAccessTokenBuilder struct { var _ token.AccessTokenBuilder = (*mockAccessTokenBuilder)(nil) -func (m *mockAccessTokenBuilder) Build(ctx context.Context, audience string, t *token.AccessTokenData, now time.Time, duration time.Duration) ([]byte, error) { - args := m.Called(ctx, audience, t, now, duration) +func (m *mockAccessTokenBuilder) Build(ctx context.Context, audience string, t *token.AccessTokenData) ([]byte, error) { + args := m.Called(ctx, audience, t) return args.Get(0).([]byte), args.Error(1) } @@ -769,21 +769,23 @@ func TestVerifyRefreshTokenErrors(t *testing.T) { } } -func TestSignAccessToken(t *testing.T) { +func TestBuildAccessToken(t *testing.T) { s := newTestSteps(t) now := time.Now() data := &token.AccessTokenData{ - ClientID: "clientID", - UserID: "userID", - UserEmail: "userEmail", + ClientID: "clientID", + UserID: "userID", + UserEmail: "userEmail", + IssueTime: now, + ExpireTime: now.Add(accessTokenDuration), } expectedAccessToken := []byte("expected-access-token") - s.builder.(*mockAccessTokenBuilder).On("Build", mock.Anything, testAudienceName, data, now, accessTokenDuration).Return(expectedAccessToken, nil) + s.builder.(*mockAccessTokenBuilder).On("Build", mock.Anything, testAudienceName, data).Return(expectedAccessToken, nil) - accessToken, err := s.SignAccessToken(context.Background(), testAudienceName, data, now) + accessToken, err := s.BuildAccessToken(context.Background(), testAudienceName, data) require.NoError(t, err) require.NotEmpty(t, accessToken) diff --git a/pkg/idp/token/biscuit.go b/pkg/idp/token/biscuit.go new file mode 100644 index 0000000..6e9f333 --- /dev/null +++ b/pkg/idp/token/biscuit.go @@ -0,0 +1,41 @@ +package token + +import ( + "context" + "crypto" + "errors" + + "github.com/flynn/biscuit-go/sig" + "github.com/flynn/hubauth/pkg/biscuit" + "github.com/flynn/hubauth/pkg/kmssign" +) + +var ( + ErrPublicKeyRequired = errors.New("token: a public key is required") +) + +type biscuitBuilder struct { + kms kmssign.KMSClient + audienceKey kmssign.AudienceKeyNamer + rootKeyPair sig.Keypair +} + +func NewBiscuitBuilder() AccessTokenBuilder { + return &biscuitBuilder{} +} + +func (b *biscuitBuilder) Build(ctx context.Context, audience string, t *AccessTokenData) ([]byte, error) { + if len(t.UserPublicKey) == 0 { + return nil, ErrPublicKeyRequired + } + audienceKey := kmssign.NewPrivateKey(b.kms, b.audienceKey(audience), crypto.SHA256) + + meta := &biscuit.Metadata{ + ClientID: t.ClientID, + UserID: t.UserID, + UserEmail: t.UserEmail, + IssueTime: t.IssueTime, + } + + return biscuit.GenerateSignable(b.rootKeyPair, audience, audienceKey, t.UserPublicKey, t.ExpireTime, meta) +} diff --git a/pkg/idp/token/builder.go b/pkg/idp/token/builder.go index 15cd05e..5afdd98 100644 --- a/pkg/idp/token/builder.go +++ b/pkg/idp/token/builder.go @@ -6,11 +6,14 @@ import ( ) type AccessTokenData struct { - ClientID string - UserID string - UserEmail string + ClientID string + UserID string + UserEmail string + UserPublicKey []byte + IssueTime time.Time + ExpireTime time.Time } type AccessTokenBuilder interface { - Build(ctx context.Context, audience string, t *AccessTokenData, now time.Time, duration time.Duration) ([]byte, error) + Build(ctx context.Context, audience string, t *AccessTokenData) ([]byte, error) } diff --git a/pkg/idp/token/oauth.go b/pkg/idp/token/oauth.go index 3b96b0c..2ec12f6 100644 --- a/pkg/idp/token/oauth.go +++ b/pkg/idp/token/oauth.go @@ -4,7 +4,6 @@ import ( "context" "crypto" "fmt" - "time" "github.com/flynn/hubauth/pkg/kmssign" "github.com/flynn/hubauth/pkg/pb" @@ -26,11 +25,11 @@ func NewSignedPBBuilder(kms kmssign.KMSClient, audienceKey kmssign.AudienceKeyNa } } -func (b *signedPbBuilder) Build(ctx context.Context, audience string, t *AccessTokenData, now time.Time, duration time.Duration) ([]byte, error) { +func (b *signedPbBuilder) Build(ctx context.Context, audience string, t *AccessTokenData) ([]byte, error) { signKey := kmssign.NewPrivateKey(b.kms, b.audienceKey(audience), crypto.SHA256) - exp, _ := ptypes.TimestampProto(now.Add(duration)) - iss, _ := ptypes.TimestampProto(now) + exp, _ := ptypes.TimestampProto(t.ExpireTime) + iss, _ := ptypes.TimestampProto(t.IssueTime) msg := &pb.AccessToken{ ClientId: t.ClientID, UserId: t.UserID, diff --git a/pkg/idp/token/oauth_test.go b/pkg/idp/token/oauth_test.go index 9785c77..52350e1 100644 --- a/pkg/idp/token/oauth_test.go +++ b/pkg/idp/token/oauth_test.go @@ -31,15 +31,17 @@ func TestSignedPBBuilder(t *testing.T) { now := time.Now() ctx := context.Background() + accessTokenDuration := 5 * time.Minute + data := &AccessTokenData{ - ClientID: "clientID", - UserEmail: "userEmail", - UserID: "userID", + ClientID: "clientID", + UserEmail: "userEmail", + UserID: "userID", + IssueTime: now, + ExpireTime: now.Add(accessTokenDuration), } - accessTokenDuration := 5 * time.Minute - - accessTokenBytes, err := builder.Build(ctx, audienceName, data, now, accessTokenDuration) + accessTokenBytes, err := builder.Build(ctx, audienceName, data) require.NoError(t, err) got := new(pb.AccessToken) From ff94986a15ea2ac2f42bd2318cb61e5a5f1452e5 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Mon, 12 Oct 2020 10:17:47 +0200 Subject: [PATCH 05/17] update returned token type to depend on configured builder --- pkg/idp/oauth.go | 12 +++++++----- pkg/idp/oauth_test.go | 12 ++++++------ pkg/idp/steps.go | 6 +++--- pkg/idp/steps_test.go | 10 +++++++++- pkg/idp/token/biscuit.go | 4 ++++ pkg/idp/token/builder.go | 1 + pkg/idp/token/oauth.go | 4 ++++ 7 files changed, 34 insertions(+), 15 deletions(-) diff --git a/pkg/idp/oauth.go b/pkg/idp/oauth.go index 3f44e96..33a9102 100644 --- a/pkg/idp/oauth.go +++ b/pkg/idp/oauth.go @@ -47,7 +47,7 @@ type idpSteps interface { SignRefreshToken(ctx context.Context, signKey signpb.PrivateKey, t *signedRefreshTokenData) (string, error) RenewRefreshToken(ctx context.Context, clientID, oldTokenID string, oldTokenIssueTime, now time.Time) (*hubauth.RefreshToken, error) VerifyRefreshToken(ctx context.Context, rt *hubauth.RefreshToken, now time.Time) error - BuildAccessToken(ctx context.Context, audience string, t *token.AccessTokenData) (string, error) + BuildAccessToken(ctx context.Context, audience string, t *token.AccessTokenData) (string, string, error) } type idpService struct { @@ -304,6 +304,7 @@ func (s *idpService) ExchangeCode(parentCtx context.Context, req *hubauth.Exchan }) var accessToken string + var tokenType string g.Go(func() (err error) { if req.Audience == "" { return nil @@ -318,7 +319,7 @@ func (s *idpService) ExchangeCode(parentCtx context.Context, req *hubauth.Exchan } } - accessToken, err = s.steps.BuildAccessToken(ctx, req.Audience, &token.AccessTokenData{ + accessToken, tokenType, err = s.steps.BuildAccessToken(ctx, req.Audience, &token.AccessTokenData{ ClientID: req.ClientID, UserID: codeInfo.UserId, UserEmail: codeInfo.UserEmail, @@ -348,7 +349,7 @@ func (s *idpService) ExchangeCode(parentCtx context.Context, req *hubauth.Exchan res.AccessToken = res.RefreshToken res.ExpiresIn = res.RefreshTokenExpiresIn } else { - res.TokenType = "Bearer" + res.TokenType = tokenType res.ExpiresIn = int(accessTokenDuration / time.Second) } return res, nil @@ -390,6 +391,7 @@ func (s *idpService) RefreshToken(ctx context.Context, req *hubauth.RefreshToken }) var accessToken string + var tokenType string g.Go(func() (err error) { if req.Audience == "" { return nil @@ -404,7 +406,7 @@ func (s *idpService) RefreshToken(ctx context.Context, req *hubauth.RefreshToken } } - accessToken, err = s.steps.BuildAccessToken(ctx, req.Audience, &token.AccessTokenData{ + accessToken, tokenType, err = s.steps.BuildAccessToken(ctx, req.Audience, &token.AccessTokenData{ ClientID: req.ClientID, UserID: oldToken.UserID, UserEmail: oldToken.UserEmail, @@ -433,7 +435,7 @@ func (s *idpService) RefreshToken(ctx context.Context, req *hubauth.RefreshToken res.AccessToken = res.RefreshToken res.ExpiresIn = res.RefreshTokenExpiresIn } else { - res.TokenType = "Bearer" + res.TokenType = tokenType res.ExpiresIn = int(accessTokenDuration / time.Second) } return res, nil diff --git a/pkg/idp/oauth_test.go b/pkg/idp/oauth_test.go index d8cdcb6..1722aec 100644 --- a/pkg/idp/oauth_test.go +++ b/pkg/idp/oauth_test.go @@ -78,9 +78,9 @@ func (m *mockSteps) SignRefreshToken(ctx context.Context, signKey signpb.Private args := m.Called(ctx, signKey, t) return args.String(0), args.Error(1) } -func (m *mockSteps) BuildAccessToken(ctx context.Context, audience string, t *token.AccessTokenData) (string, error) { +func (m *mockSteps) BuildAccessToken(ctx context.Context, audience string, t *token.AccessTokenData) (string, string, error) { args := m.Called(ctx, audience, t) - return args.String(0), args.Error(1) + return args.String(0), args.String(1), args.Error(2) } func (m *mockSteps) RenewRefreshToken(ctx context.Context, clientID, oldTokenID string, oldTokenIssueTime, now time.Time) (*hubauth.RefreshToken, error) { args := m.Called(ctx, clientID, oldTokenID, oldTokenIssueTime, now) @@ -671,7 +671,7 @@ func TestExchangeCode(t *testing.T) { UserEmail: userEmail, IssueTime: now, ExpireTime: now.Add(accessTokenDuration), - }).Return(accessToken, nil) + }).Return(accessToken, testCase.Want.TokenType, nil) req := &hubauth.ExchangeCodeRequest{ ClientID: clientID, @@ -802,7 +802,7 @@ func TestExchangeCodeErrors(t *testing.T) { idpService.steps.(*mockSteps).On("VerifyAudience", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCase.VerifyAudienceErr) idpService.steps.(*mockSteps).On("SaveRefreshToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&hubauth.Client{}, testCase.SaveErr) idpService.steps.(*mockSteps).On("SignRefreshToken", mock.Anything, mock.Anything, mock.Anything).Return("", testCase.SignRTErr) - idpService.steps.(*mockSteps).On("BuildAccessToken", mock.Anything, mock.Anything, mock.Anything).Return("", testCase.SignATErr) + idpService.steps.(*mockSteps).On("BuildAccessToken", mock.Anything, mock.Anything, mock.Anything).Return("", "", testCase.SignATErr) req := &hubauth.ExchangeCodeRequest{ Code: testCase.Code, @@ -902,7 +902,7 @@ func TestRefreshToken(t *testing.T) { UserEmail: userEmail, IssueTime: now, ExpireTime: now.Add(accessTokenDuration), - }).Return(newAccessTokenStr, nil) + }).Return(newAccessTokenStr, testCase.Want.TokenType, nil) oldTokenSigned, err := signpb.SignMarshal(context.Background(), idpService.refreshKey, &pb.RefreshToken{ Key: oldTokenID, @@ -1005,7 +1005,7 @@ func TestRefreshTokenStepErrors(t *testing.T) { idpService.steps.(*mockSteps).On("VerifyAudience", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(testCase.VerifyAudienceErr) idpService.steps.(*mockSteps).On("RenewRefreshToken", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything).Return(&hubauth.RefreshToken{}, testCase.RenewRTErr) idpService.steps.(*mockSteps).On("SignRefreshToken", mock.Anything, mock.Anything, mock.Anything).Return("", testCase.SignRTErr) - idpService.steps.(*mockSteps).On("BuildAccessToken", mock.Anything, mock.Anything, mock.Anything).Return("", testCase.SignATErr) + idpService.steps.(*mockSteps).On("BuildAccessToken", mock.Anything, mock.Anything, mock.Anything).Return("", "", testCase.SignATErr) _, err = idpService.RefreshToken(context.Background(), req) require.Equal(t, testCase.ExpectedErr, err) diff --git a/pkg/idp/steps.go b/pkg/idp/steps.go index 2d4369b..db08163 100644 --- a/pkg/idp/steps.go +++ b/pkg/idp/steps.go @@ -351,7 +351,7 @@ func (s *steps) VerifyRefreshToken(ctx context.Context, rt *hubauth.RefreshToken return nil } -func (s *steps) BuildAccessToken(ctx context.Context, audience string, t *token.AccessTokenData) (token string, err error) { +func (s *steps) BuildAccessToken(ctx context.Context, audience string, t *token.AccessTokenData) (token string, tokenType string, err error) { ctx, span := trace.StartSpan(ctx, "idp.BuildAccessToken") span.AddAttributes( trace.StringAttribute("client_id", t.ClientID), @@ -362,7 +362,7 @@ func (s *steps) BuildAccessToken(ctx context.Context, audience string, t *token. tokenBytes, err := s.builder.Build(ctx, audience, t) if err != nil { - return "", fmt.Errorf("idp: error building access token: %w", err) + return "", "", fmt.Errorf("idp: error building access token: %w", err) } idBytes := sha256.Sum256(tokenBytes) @@ -374,5 +374,5 @@ func (s *steps) BuildAccessToken(ctx context.Context, audience string, t *token. zap.Duration("issued_access_token_expires_in", accessTokenDuration), ) - return base64.URLEncoding.EncodeToString(tokenBytes), nil + return base64.URLEncoding.EncodeToString(tokenBytes), s.builder.TokenType(), nil } diff --git a/pkg/idp/steps_test.go b/pkg/idp/steps_test.go index 4c1a1cd..9c02bbf 100644 --- a/pkg/idp/steps_test.go +++ b/pkg/idp/steps_test.go @@ -38,6 +38,11 @@ func (m *mockAccessTokenBuilder) Build(ctx context.Context, audience string, t * return args.Get(0).([]byte), args.Error(1) } +func (m *mockAccessTokenBuilder) TokenType() string { + args := m.Called() + return args.String(0) +} + func newTestSteps(t *testing.T) *steps { dsc, err := gdatastore.NewClient(context.Background(), "test") require.NoError(t, err) @@ -782,13 +787,16 @@ func TestBuildAccessToken(t *testing.T) { } expectedAccessToken := []byte("expected-access-token") + expectedTokenType := "MockBearer" s.builder.(*mockAccessTokenBuilder).On("Build", mock.Anything, testAudienceName, data).Return(expectedAccessToken, nil) + s.builder.(*mockAccessTokenBuilder).On("TokenType").Return(expectedTokenType) - accessToken, err := s.BuildAccessToken(context.Background(), testAudienceName, data) + accessToken, tokenType, err := s.BuildAccessToken(context.Background(), testAudienceName, data) require.NoError(t, err) require.NotEmpty(t, accessToken) + require.Equal(t, expectedTokenType, tokenType) accessTokenBytes, err := base64Decode(accessToken) require.NoError(t, err) diff --git a/pkg/idp/token/biscuit.go b/pkg/idp/token/biscuit.go index 6e9f333..8be1dd8 100644 --- a/pkg/idp/token/biscuit.go +++ b/pkg/idp/token/biscuit.go @@ -39,3 +39,7 @@ func (b *biscuitBuilder) Build(ctx context.Context, audience string, t *AccessTo return biscuit.GenerateSignable(b.rootKeyPair, audience, audienceKey, t.UserPublicKey, t.ExpireTime, meta) } + +func (b *biscuitBuilder) TokenType() string { + return "Biscuit" +} diff --git a/pkg/idp/token/builder.go b/pkg/idp/token/builder.go index 5afdd98..0da8c9a 100644 --- a/pkg/idp/token/builder.go +++ b/pkg/idp/token/builder.go @@ -16,4 +16,5 @@ type AccessTokenData struct { type AccessTokenBuilder interface { Build(ctx context.Context, audience string, t *AccessTokenData) ([]byte, error) + TokenType() string } diff --git a/pkg/idp/token/oauth.go b/pkg/idp/token/oauth.go index 2ec12f6..5039157 100644 --- a/pkg/idp/token/oauth.go +++ b/pkg/idp/token/oauth.go @@ -44,3 +44,7 @@ func (b *signedPbBuilder) Build(ctx context.Context, audience string, t *AccessT return tokenBytes, nil } + +func (b *signedPbBuilder) TokenType() string { + return "Bearer" +} From 05ff6ecef877e6fae3ee0a50752689c461da39b5 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Mon, 12 Oct 2020 11:03:01 +0200 Subject: [PATCH 06/17] add access token builder creation switch from env --- cmd/hubauth-ext/main.go | 26 ++++++++++++++++++++++---- pkg/idp/token/biscuit.go | 24 ++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/cmd/hubauth-ext/main.go b/cmd/hubauth-ext/main.go index ef75f1f..88c7071 100644 --- a/cmd/hubauth-ext/main.go +++ b/cmd/hubauth-ext/main.go @@ -69,6 +69,27 @@ func main() { return result.Payload.String() } + audienceKeyNamer := kmssign.AudienceKeyNameFunc(os.Getenv("PROJECT_ID"), os.Getenv("KMS_LOCATION"), os.Getenv("KMS_KEYRING")) + + var accessTokenBuilder token.AccessTokenBuilder + tokenType, exists := os.LookupEnv("TOKEN_TYPE") + if !exists { + tokenType = "Bearer" + } + switch tokenType { + case "Bearer": + accessTokenBuilder = token.NewSignedPBBuilder(kmsClient, audienceKeyNamer) + case "Biscuit": + biscuitKey, err := token.DecodeB64PrivateKey(secret("BISCUIT_ROOT_PRIVKEY")) + if err != nil { + log.Fatalf("failed to initialize biscuit keypair: %v", err) + } + + accessTokenBuilder = token.NewBiscuitBuilder(kmsClient, audienceKeyNamer, biscuitKey) + default: + log.Fatalf("invalid TOKEN_TYPE, must be one of: Bearer, Biscuit") + } + log.Fatal(http.ListenAndServe(":"+httpPort, &ochttp.Handler{ Propagation: &propagation.HTTPFormat{}, Handler: httpapi.New(httpapi.Config{ @@ -80,10 +101,7 @@ func main() { ), []byte(secret("CODE_KEY_SECRET")), refreshKey, - token.NewSignedPBBuilder( - kmsClient, - kmssign.AudienceKeyNameFunc(os.Getenv("PROJECT_ID"), os.Getenv("KMS_LOCATION"), os.Getenv("KMS_KEYRING")), - ), + accessTokenBuilder, ), CookieKey: []byte(secret("COOKIE_KEY_SECRET")), ProjectID: os.Getenv("PROJECT_ID"), diff --git a/pkg/idp/token/biscuit.go b/pkg/idp/token/biscuit.go index 8be1dd8..7736391 100644 --- a/pkg/idp/token/biscuit.go +++ b/pkg/idp/token/biscuit.go @@ -3,7 +3,9 @@ package token import ( "context" "crypto" + "encoding/base64" "errors" + "fmt" "github.com/flynn/biscuit-go/sig" "github.com/flynn/hubauth/pkg/biscuit" @@ -20,8 +22,12 @@ type biscuitBuilder struct { rootKeyPair sig.Keypair } -func NewBiscuitBuilder() AccessTokenBuilder { - return &biscuitBuilder{} +func NewBiscuitBuilder(kms kmssign.KMSClient, audienceKey kmssign.AudienceKeyNamer, rootKeyPair sig.Keypair) AccessTokenBuilder { + return &biscuitBuilder{ + kms: kms, + audienceKey: audienceKey, + rootKeyPair: rootKeyPair, + } } func (b *biscuitBuilder) Build(ctx context.Context, audience string, t *AccessTokenData) ([]byte, error) { @@ -43,3 +49,17 @@ func (b *biscuitBuilder) Build(ctx context.Context, audience string, t *AccessTo func (b *biscuitBuilder) TokenType() string { return "Biscuit" } + +func DecodeB64PrivateKey(b64key string) (sig.Keypair, error) { + var kp sig.Keypair + privKey, err := base64.StdEncoding.DecodeString(b64key) + if err != nil { + return kp, fmt.Errorf("failed to decode b64 key: %w", err) + } + rootPrivateKey, err := sig.NewPrivateKey(privKey) + if err != nil { + return kp, fmt.Errorf("failed to create biscuit private key: %w", err) + } + kp = sig.NewKeypair(rootPrivateKey) + return kp, nil +} From fd3d9525857b27a9a9ee505cf5d59c1631552282 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Mon, 12 Oct 2020 11:32:03 +0200 Subject: [PATCH 07/17] add /public-key HTTP endpoint --- cmd/hubauth-ext/main.go | 8 +++++++- pkg/httpapi/http.go | 25 +++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/cmd/hubauth-ext/main.go b/cmd/hubauth-ext/main.go index 88c7071..33b3383 100644 --- a/cmd/hubauth-ext/main.go +++ b/cmd/hubauth-ext/main.go @@ -66,12 +66,16 @@ func main() { if err != nil { log.Fatalf("failed to access secret version for %s: %s", name, err) } - return result.Payload.String() + + // Payload.String() would return a json encoded version of the secret: {"data": "..."} + // the actual secret is in Data. + return string(result.Payload.Data) } audienceKeyNamer := kmssign.AudienceKeyNameFunc(os.Getenv("PROJECT_ID"), os.Getenv("KMS_LOCATION"), os.Getenv("KMS_KEYRING")) var accessTokenBuilder token.AccessTokenBuilder + var rootPubKey []byte tokenType, exists := os.LookupEnv("TOKEN_TYPE") if !exists { tokenType = "Bearer" @@ -85,6 +89,7 @@ func main() { log.Fatalf("failed to initialize biscuit keypair: %v", err) } + rootPubKey = biscuitKey.Public().Bytes() accessTokenBuilder = token.NewBiscuitBuilder(kmsClient, audienceKeyNamer, biscuitKey) default: log.Fatalf("invalid TOKEN_TYPE, must be one of: Bearer, Biscuit") @@ -107,6 +112,7 @@ func main() { ProjectID: os.Getenv("PROJECT_ID"), Repository: fmt.Sprintf("https://source.developers.google.com/p/%s/r/%s", os.Getenv("PROJECT_ID"), os.Getenv("BUILD_REPO")), Revision: os.Getenv("BUILD_REV"), + PublicKey: rootPubKey, }), }, )) diff --git a/pkg/httpapi/http.go b/pkg/httpapi/http.go index 641212d..cd0d284 100644 --- a/pkg/httpapi/http.go +++ b/pkg/httpapi/http.go @@ -34,6 +34,7 @@ func (clockImpl) Now() time.Time { type Config struct { IdP hubauth.IdPService CookieKey hmacpb.Key + PublicKey []byte ProjectID string Repository string Revision string @@ -87,6 +88,8 @@ func (a *api) ServeHTTP(rw http.ResponseWriter, req *http.Request) { w.Header().Set("Access-Control-Allow-Methods", "GET") w.Header().Set("Access-Control-Max-Age", "86400") w.WriteHeader(http.StatusOK) + case req.Method == "GET" && req.URL.Path == "/public-key": + a.PublicKey(w, req) case req.Method == "GET" && req.URL.Path == "/": http.Redirect(w, req, "https://flynn.io/", http.StatusFound) case req.Method == "GET" && req.URL.Path == "/privacy": @@ -394,6 +397,28 @@ func (a *api) Audiences(w http.ResponseWriter, req *http.Request) { json.NewEncoder(w).Encode(res) } +func (a *api) PublicKey(w http.ResponseWriter, req *http.Request) { + if len(a.Config.PublicKey) == 0 { + a.handleErr(w, req, &hubauth.OAuthError{ + Code: "unsupported_request", + Description: "no public key configured", + }) + return + } + + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Content-Type", "application/json") + + w.WriteHeader(http.StatusOK) + + type key struct { + PublicKey string `json:"public-key"` + } + json.NewEncoder(w).Encode(&key{ + PublicKey: base64.StdEncoding.EncodeToString(a.Config.PublicKey), + }) +} + func (a *api) handleErr(w http.ResponseWriter, req *http.Request, err error) { oe, ok := err.(*hubauth.OAuthError) if !ok { From f990c69a6e7261bae2577bff07b04f9de8ec0c8e Mon Sep 17 00:00:00 2001 From: daeMOn Date: Mon, 12 Oct 2020 16:53:54 +0200 Subject: [PATCH 08/17] update biscuit verify audience key type to use *ecdsa.PublicKey instead of *kmssign.Key --- pkg/biscuit/biscuit.go | 3 ++- pkg/biscuit/biscuit_test.go | 7 ++++--- pkg/biscuit/signature.go | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pkg/biscuit/biscuit.go b/pkg/biscuit/biscuit.go index e335e60..488d302 100644 --- a/pkg/biscuit/biscuit.go +++ b/pkg/biscuit/biscuit.go @@ -1,6 +1,7 @@ package biscuit import ( + "crypto/ecdsa" "crypto/rand" "fmt" "time" @@ -108,7 +109,7 @@ func Sign(token []byte, rootPubKey sig.PublicKey, userKey *UserKeyPair) ([]byte, // 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 *kmssign.Key) (*Metadata, error) { +func Verify(token []byte, rootPubKey sig.PublicKey, audience string, audienceKey *ecdsa.PublicKey) (*Metadata, error) { b, err := biscuit.Unmarshal(token) if err != nil { return nil, fmt.Errorf("biscuit: failed to unmarshal: %w", err) diff --git a/pkg/biscuit/biscuit_test.go b/pkg/biscuit/biscuit_test.go index bf6199b..5882f32 100644 --- a/pkg/biscuit/biscuit_test.go +++ b/pkg/biscuit/biscuit_test.go @@ -2,6 +2,7 @@ package biscuit import ( "context" + "crypto/ecdsa" "crypto/rand" "testing" "time" @@ -36,7 +37,7 @@ 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) + res, err := Verify(signedBiscuit, rootKey.Public(), audience, audienceKey.Public().(*ecdsa.PublicKey)) require.NoError(t, err) require.Equal(t, metas.ClientID, res.ClientID) require.Equal(t, metas.UserID, res.UserID) @@ -53,7 +54,7 @@ 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) + _, err = Verify(signedBiscuit, rootKey.Public(), "http://another.audience.url", audienceKey.Public().(*ecdsa.PublicKey)) require.Error(t, err) wrongAudience := "http://another.audience.url" @@ -61,7 +62,7 @@ func TestBiscuit(t *testing.T) { wrongAudienceKey, err := kmssign.NewKey(context.Background(), kms, wrongAudience) require.NoError(t, err) - _, err = Verify(signedBiscuit, rootKey.Public(), audience, wrongAudienceKey) + _, err = Verify(signedBiscuit, rootKey.Public(), audience, wrongAudienceKey.Public().(*ecdsa.PublicKey)) require.Error(t, err) }) } diff --git a/pkg/biscuit/signature.go b/pkg/biscuit/signature.go index 078eaa6..86b5545 100644 --- a/pkg/biscuit/signature.go +++ b/pkg/biscuit/signature.go @@ -147,11 +147,12 @@ func audienceSign(audience string, audienceKey *kmssign.Key) (*audienceVerificat }, nil } -func verifyAudienceSignature(audiencePubkey *kmssign.Key, data *audienceVerificationData) error { +func verifyAudienceSignature(audiencePubkey *ecdsa.PublicKey, data *audienceVerificationData) error { signedData := append(signStaticCtx, data.Challenge...) signedData = append(signedData, []byte(data.Audience)...) hash := sha256.Sum256(signedData) - if !audiencePubkey.Verify(hash[:], data.Signature) { + + if !ecdsa.VerifyASN1(audiencePubkey, hash[:], data.Signature) { return errors.New("invalid signature") } return nil From 049c5ed4bd5cc87ef3334ed8026a5a2c58691d70 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Mon, 12 Oct 2020 16:54:13 +0200 Subject: [PATCH 09/17] simplify http public key encoding --- pkg/httpapi/http.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/httpapi/http.go b/pkg/httpapi/http.go index cd0d284..74d7d98 100644 --- a/pkg/httpapi/http.go +++ b/pkg/httpapi/http.go @@ -412,10 +412,10 @@ func (a *api) PublicKey(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) type key struct { - PublicKey string `json:"public-key"` + PublicKey []byte `json:"public-key"` } json.NewEncoder(w).Encode(&key{ - PublicKey: base64.StdEncoding.EncodeToString(a.Config.PublicKey), + PublicKey: a.Config.PublicKey, }) } From a1bac2709b73101a9e32f0f06262709527251ee7 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Mon, 12 Oct 2020 17:38:00 +0200 Subject: [PATCH 10/17] add helper to create UserKeyPair from an ecdsa.PrivateKey --- pkg/biscuit/biscuit.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pkg/biscuit/biscuit.go b/pkg/biscuit/biscuit.go index 488d302..900de10 100644 --- a/pkg/biscuit/biscuit.go +++ b/pkg/biscuit/biscuit.go @@ -3,6 +3,7 @@ package biscuit import ( "crypto/ecdsa" "crypto/rand" + "crypto/x509" "fmt" "time" @@ -23,6 +24,21 @@ type UserKeyPair struct { Private []byte } +func NewECDSAKeyPair(priv *ecdsa.PrivateKey) (*UserKeyPair, error) { + privKeyBytes, err := x509.MarshalECPrivateKey(priv) + if err != nil { + return nil, fmt.Errorf("failed to marshal ecdsa privkey: %v", err) + } + pubKeyBytes, err := x509.MarshalPKIXPublicKey(&priv.PublicKey) + if err != nil { + return nil, fmt.Errorf("failed to marshal ecdsa pubkey: %v", err) + } + return &UserKeyPair{ + Private: privKeyBytes, + Public: pubKeyBytes, + }, nil +} + // GenerateSignable 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 *kmssign.Key, userPublicKey []byte, expireTime time.Time, m *Metadata) ([]byte, error) { From a68931570bd8920665ba95532977736bde6ccc83 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Tue, 13 Oct 2020 11:50:18 +0200 Subject: [PATCH 11/17] cleanup --- go.sum | 6 ++---- pkg/biscuit/biscuit.go | 2 +- pkg/biscuit/signature_test.go | 11 +++-------- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/go.sum b/go.sum index 425d2a8..923df30 100644 --- a/go.sum +++ b/go.sum @@ -77,10 +77,8 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/flynn/biscuit-go v0.0.0-20200907174027-193b7bdbbdca h1:LUZQQzaCT+gltxii4icyPH5oMdAP38JmbvO9aI0E4qM= -github.com/flynn/biscuit-go v0.0.0-20200907174027-193b7bdbbdca/go.mod h1:EMJZ3stAYtwaP763F5HcGjPjCnYu21V2TEsg/iw88I8= -github.com/flynn/biscuit-go v0.0.0-20201009174859-e7eb59a90195 h1:TP3jMHmhjz8XxqqigEd5OQffNAO/6KPvGUYII6TFdmI= -github.com/flynn/biscuit-go v0.0.0-20201009174859-e7eb59a90195/go.mod h1:EMJZ3stAYtwaP763F5HcGjPjCnYu21V2TEsg/iw88I8= +github.com/flynn/biscuit-go v0.0.0-20201013094706-57a856cd63ea h1:QKWFyrvV0SKwUq4wYxdQZxVJASGsrmoGOolGpo/mjAU= +github.com/flynn/biscuit-go v0.0.0-20201013094706-57a856cd63ea/go.mod h1:EMJZ3stAYtwaP763F5HcGjPjCnYu21V2TEsg/iw88I8= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= diff --git a/pkg/biscuit/biscuit.go b/pkg/biscuit/biscuit.go index 900de10..bf7e15b 100644 --- a/pkg/biscuit/biscuit.go +++ b/pkg/biscuit/biscuit.go @@ -43,7 +43,7 @@ func NewECDSAKeyPair(priv *ecdsa.PrivateKey) (*UserKeyPair, error) { // signed with the private key matching the given userPubkey. func GenerateSignable(rootKey sig.Keypair, audience string, audienceKey *kmssign.Key, userPublicKey []byte, expireTime time.Time, m *Metadata) ([]byte, error) { builder := &hubauthBuilder{ - Builder: biscuit.NewBuilder(rand.Reader, rootKey), + biscuit.NewBuilder(rand.Reader, rootKey), } if err := builder.withAudienceSignature(audience, audienceKey); err != nil { diff --git a/pkg/biscuit/signature_test.go b/pkg/biscuit/signature_test.go index d4d4cc2..2351e26 100644 --- a/pkg/biscuit/signature_test.go +++ b/pkg/biscuit/signature_test.go @@ -211,7 +211,6 @@ func TestVerifyUserSignatureFail(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.desc, func(t *testing.T) { - err := verifyUserSignature(testCase.tokenHash, testCase.data) require.Error(t, err) if testCase.expectedErr != nil { @@ -224,12 +223,8 @@ func TestVerifyUserSignatureFail(t *testing.T) { func generateUserKeyPair(t *testing.T) *UserKeyPair { priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) require.NoError(t, err) - privBytes, err := x509.MarshalECPrivateKey(priv) - require.NoError(t, err) - pubBytes, err := x509.MarshalPKIXPublicKey(&priv.PublicKey) + + kp, err := NewECDSAKeyPair(priv) require.NoError(t, err) - return &UserKeyPair{ - Private: privBytes, - Public: pubBytes, - } + return kp } From 1a6f0da7820b7ebbb84280efd84aaf1b247ee573 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Tue, 13 Oct 2020 11:50:44 +0200 Subject: [PATCH 12/17] add biscuit setup instructions in readme --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 13252a1..9f415fc 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,22 @@ Create a new job: - URL: use the hubauth-int URL: `/cron` +## Enabling Biscuit + +To use biscuit tokens instead of bearers, configure the following: + +### In Security > Secret manager + +Create new secret +- HUBAUTH_BISCUIT_ROOT_PRIVKEY: a base64 encoded p256 EC private key + +### In variables + +Add a new variable +- TOKEN_TYPE: `Biscuit` +- BISCUIT_ROOT_PRIVKEY: set to the resource ID from `HUBAUTH_BISCUIT_ROOT_PRIVKEY` + + ## Hubauth CLI Configure gcloud auth application-default with the following command, and follow the browser instructions: From d313f9c9fbeceb87551e7749ced55a3fd19f4de0 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Tue, 13 Oct 2020 13:56:27 +0200 Subject: [PATCH 13/17] renamed signedPbBuilder to bearerBuilder --- cmd/hubauth-ext/main.go | 2 +- pkg/idp/token/{oauth.go => bearer.go} | 12 ++++++------ pkg/idp/token/{oauth_test.go => bearer_test.go} | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) rename pkg/idp/token/{oauth.go => bearer.go} (69%) rename pkg/idp/token/{oauth_test.go => bearer_test.go} (96%) diff --git a/cmd/hubauth-ext/main.go b/cmd/hubauth-ext/main.go index 33b3383..fce743d 100644 --- a/cmd/hubauth-ext/main.go +++ b/cmd/hubauth-ext/main.go @@ -82,7 +82,7 @@ func main() { } switch tokenType { case "Bearer": - accessTokenBuilder = token.NewSignedPBBuilder(kmsClient, audienceKeyNamer) + accessTokenBuilder = token.NewBearerBuilder(kmsClient, audienceKeyNamer) case "Biscuit": biscuitKey, err := token.DecodeB64PrivateKey(secret("BISCUIT_ROOT_PRIVKEY")) if err != nil { diff --git a/pkg/idp/token/oauth.go b/pkg/idp/token/bearer.go similarity index 69% rename from pkg/idp/token/oauth.go rename to pkg/idp/token/bearer.go index 5039157..8ddb234 100644 --- a/pkg/idp/token/oauth.go +++ b/pkg/idp/token/bearer.go @@ -11,21 +11,21 @@ import ( "github.com/golang/protobuf/ptypes" ) -type signedPbBuilder struct { +type bearerBuilder struct { kms kmssign.KMSClient audienceKey kmssign.AudienceKeyNamer } -var _ AccessTokenBuilder = (*signedPbBuilder)(nil) +var _ AccessTokenBuilder = (*bearerBuilder)(nil) -func NewSignedPBBuilder(kms kmssign.KMSClient, audienceKey kmssign.AudienceKeyNamer) AccessTokenBuilder { - return &signedPbBuilder{ +func NewBearerBuilder(kms kmssign.KMSClient, audienceKey kmssign.AudienceKeyNamer) AccessTokenBuilder { + return &bearerBuilder{ kms: kms, audienceKey: audienceKey, } } -func (b *signedPbBuilder) Build(ctx context.Context, audience string, t *AccessTokenData) ([]byte, error) { +func (b *bearerBuilder) Build(ctx context.Context, audience string, t *AccessTokenData) ([]byte, error) { signKey := kmssign.NewPrivateKey(b.kms, b.audienceKey(audience), crypto.SHA256) exp, _ := ptypes.TimestampProto(t.ExpireTime) @@ -45,6 +45,6 @@ func (b *signedPbBuilder) Build(ctx context.Context, audience string, t *AccessT return tokenBytes, nil } -func (b *signedPbBuilder) TokenType() string { +func (b *bearerBuilder) TokenType() string { return "Bearer" } diff --git a/pkg/idp/token/oauth_test.go b/pkg/idp/token/bearer_test.go similarity index 96% rename from pkg/idp/token/oauth_test.go rename to pkg/idp/token/bearer_test.go index 52350e1..0789be4 100644 --- a/pkg/idp/token/oauth_test.go +++ b/pkg/idp/token/bearer_test.go @@ -23,7 +23,7 @@ func TestSignedPBBuilder(t *testing.T) { audienceKeyName := audienceKeyNamer(audienceName) kms := kmssim.NewClient([]string{audienceKeyName}) - builder := NewSignedPBBuilder(kms, audienceKeyNamer) + builder := NewBearerBuilder(kms, audienceKeyNamer) signKey, err := kmssign.NewKey(context.Background(), kms, audienceKeyName) require.NoError(t, err) From a7e14241a8b7cd72454a28914d177a377cd51c73 Mon Sep 17 00:00:00 2001 From: daeMOn Date: Tue, 13 Oct 2020 14:11:42 +0200 Subject: [PATCH 14/17] extracted user signature nonce and timestamp --- pkg/biscuit/biscuit.go | 18 ++++++++++++++++-- pkg/biscuit/biscuit_test.go | 2 ++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/pkg/biscuit/biscuit.go b/pkg/biscuit/biscuit.go index bf7e15b..ebc8958 100644 --- a/pkg/biscuit/biscuit.go +++ b/pkg/biscuit/biscuit.go @@ -123,9 +123,15 @@ func Sign(token []byte, rootPubKey sig.PublicKey, userKey *UserKeyPair) ([]byte, return b.Serialize() } +type VerifiedMetadata 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) (*Metadata, error) { +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) @@ -176,5 +182,13 @@ func Verify(token []byte, rootPubKey sig.PublicKey, audience string, audienceKey return nil, fmt.Errorf("biscuit: failed to verify: %w", err) } - return verifier.getMetadata() + metas, err := verifier.getMetadata() + if err != nil { + return nil, fmt.Errorf("biscuit: failed to get metadata: %v", err) + } + return &VerifiedMetadata{ + Metadata: metas, + UserSignatureNonce: userVerificationData.Nonce, + UserSignatureTimestamp: time.Time(userVerificationData.Timestamp), + }, nil } diff --git a/pkg/biscuit/biscuit_test.go b/pkg/biscuit/biscuit_test.go index 5882f32..560a063 100644 --- a/pkg/biscuit/biscuit_test.go +++ b/pkg/biscuit/biscuit_test.go @@ -43,6 +43,8 @@ func TestBiscuit(t *testing.T) { require.Equal(t, metas.UserID, res.UserID) require.Equal(t, metas.UserEmail, res.UserEmail) require.WithinDuration(t, metas.IssueTime, res.IssueTime, 1*time.Second) + require.NotEmpty(t, res.UserSignatureNonce) + require.NotEmpty(t, res.UserSignatureTimestamp) }) t.Run("user sign with wrong key", func(t *testing.T) { From 00aaddce43f6a29039f41dd37a937251c770d3ae Mon Sep 17 00:00:00 2001 From: daeMOn Date: Tue, 13 Oct 2020 14:35:51 +0200 Subject: [PATCH 15/17] idp/token: add biscuit tests --- pkg/idp/token/biscuit_test.go | 63 +++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 pkg/idp/token/biscuit_test.go diff --git a/pkg/idp/token/biscuit_test.go b/pkg/idp/token/biscuit_test.go new file mode 100644 index 0000000..41b705f --- /dev/null +++ b/pkg/idp/token/biscuit_test.go @@ -0,0 +1,63 @@ +package token + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/base64" + "testing" + "time" + + "github.com/flynn/biscuit-go" + "github.com/flynn/biscuit-go/sig" + "github.com/flynn/hubauth/pkg/kmssign/kmssim" + "github.com/stretchr/testify/require" +) + +func TestBiscuitBuilder(t *testing.T) { + audience := "https://audience.url" + audienceKeyName := audienceKeyNamer(audience) + kmsClient := kmssim.NewClient([]string{audienceKeyName}) + rootKeyPair := sig.GenerateKeypair(rand.Reader) + + builder := NewBiscuitBuilder(kmsClient, audienceKeyNamer, rootKeyPair) + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + userPublicKey, err := x509.MarshalPKIXPublicKey(&priv.PublicKey) + require.NoError(t, err) + + now := time.Now() + accessTokenData := &AccessTokenData{ + ClientID: "clientID", + ExpireTime: now.Add(1 * time.Minute), + IssueTime: now, + UserEmail: "user@email", + UserID: "userID", + } + _, err = builder.Build(context.Background(), audience, accessTokenData) + require.Equal(t, ErrPublicKeyRequired, err) + + accessTokenData.UserPublicKey = userPublicKey + token, err := builder.Build(context.Background(), audience, accessTokenData) + require.NoError(t, err) + require.NotEmpty(t, token) + + b, err := biscuit.Unmarshal(token) + require.NoError(t, err) + + _, err = b.Verify(rootKeyPair.Public()) + require.NoError(t, err) +} + +func TestDecodeB64PrivateKey(t *testing.T) { + expectedKP := sig.GenerateKeypair(rand.Reader) + b64key := base64.StdEncoding.EncodeToString(expectedKP.Private().Bytes()) + + kp, err := DecodeB64PrivateKey(b64key) + require.NoError(t, err) + require.Equal(t, expectedKP.Private().Bytes(), kp.Private().Bytes()) + require.Equal(t, expectedKP.Public().Bytes(), kp.Public().Bytes()) +} From 75f4e5cfd8d3434d212cae35e0093698df30ad86 Mon Sep 17 00:00:00 2001 From: Flavien Binet Date: Thu, 15 Oct 2020 10:38:31 +0200 Subject: [PATCH 16/17] moved biscuit pkg to biscuit-go repo --- go.sum | 5 +- pkg/biscuit/biscuit.go | 194 ---------------- pkg/biscuit/biscuit_test.go | 70 ------ pkg/biscuit/signature.go | 177 -------------- pkg/biscuit/signature_test.go | 230 ------------------ pkg/biscuit/wrapper.go | 426 ---------------------------------- pkg/idp/token/biscuit.go | 6 +- 7 files changed, 6 insertions(+), 1102 deletions(-) delete mode 100644 pkg/biscuit/biscuit.go delete mode 100644 pkg/biscuit/biscuit_test.go delete mode 100644 pkg/biscuit/signature.go delete mode 100644 pkg/biscuit/signature_test.go delete mode 100644 pkg/biscuit/wrapper.go diff --git a/go.sum b/go.sum index 923df30..b397974 100644 --- a/go.sum +++ b/go.sum @@ -77,8 +77,9 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/flynn/biscuit-go v0.0.0-20201013094706-57a856cd63ea h1:QKWFyrvV0SKwUq4wYxdQZxVJASGsrmoGOolGpo/mjAU= -github.com/flynn/biscuit-go v0.0.0-20201013094706-57a856cd63ea/go.mod h1:EMJZ3stAYtwaP763F5HcGjPjCnYu21V2TEsg/iw88I8= +github.com/flynn/biscuit-go v0.0.0-20201015071554-9ea2c863efe8 h1:jz3x+pevBqZiO9m7M58fN/W8K0OyDUfchIsHE8XgZT0= +github.com/flynn/biscuit-go v0.0.0-20201015081742-15d7d351f345 h1:ME6bm5dwn9V2DUlfXJqeN121B5nM7rDFqLFOATALqYE= +github.com/flynn/biscuit-go v0.0.0-20201015081742-15d7d351f345/go.mod h1:Sj4oR2hNkrZH1cf3Cj5DPHc3Xq0o61GWeau6UkZR+3c= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= diff --git a/pkg/biscuit/biscuit.go b/pkg/biscuit/biscuit.go deleted file mode 100644 index ebc8958..0000000 --- a/pkg/biscuit/biscuit.go +++ /dev/null @@ -1,194 +0,0 @@ -package biscuit - -import ( - "crypto/ecdsa" - "crypto/rand" - "crypto/x509" - "fmt" - "time" - - "github.com/flynn/biscuit-go" - "github.com/flynn/biscuit-go/sig" - "github.com/flynn/hubauth/pkg/kmssign" -) - -type Metadata struct { - ClientID string - UserID string - UserEmail string - IssueTime time.Time -} - -type UserKeyPair struct { - Public []byte - Private []byte -} - -func NewECDSAKeyPair(priv *ecdsa.PrivateKey) (*UserKeyPair, error) { - privKeyBytes, err := x509.MarshalECPrivateKey(priv) - if err != nil { - return nil, fmt.Errorf("failed to marshal ecdsa privkey: %v", err) - } - pubKeyBytes, err := x509.MarshalPKIXPublicKey(&priv.PublicKey) - if err != nil { - return nil, fmt.Errorf("failed to marshal ecdsa pubkey: %v", err) - } - return &UserKeyPair{ - Private: privKeyBytes, - Public: pubKeyBytes, - }, nil -} - -// GenerateSignable 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 *kmssign.Key, userPublicKey []byte, expireTime time.Time, m *Metadata) ([]byte, error) { - builder := &hubauthBuilder{ - biscuit.NewBuilder(rand.Reader, rootKey), - } - - if err := builder.withAudienceSignature(audience, audienceKey); err != nil { - return nil, err - } - - if err := builder.withUserToSignFact(userPublicKey); err != nil { - return nil, err - } - - if err := builder.withExpire(expireTime); err != nil { - return nil, err - } - - if err := builder.withMetadata(m); err != nil { - return nil, err - } - - b, err := builder.Build() - if err != nil { - return nil, err - } - - return b.Serialize() -} - -// Sign append a user signature on the given token and return it. -// The UserKeyPair key format to provide depends on the signature algorithm: -// - for ECDSA_P256_SHA256, the private key must be encoded in SEC 1, ASN.1 DER form, -// and the public key in PKIX, ASN.1 DER form. -func Sign(token []byte, rootPubKey sig.PublicKey, userKey *UserKeyPair) ([]byte, 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) - } - verifier := &hubauthVerifier{ - Verifier: v, - } - - toSignData, err := verifier.getUserToSignData(userKey.Public) - if err != nil { - return nil, fmt.Errorf("biscuit: failed to get to_sign data: %w", err) - } - - if err := verifier.ensureNotAlreadyUserSigned(toSignData.DataID, userKey.Public); err != nil { - return nil, fmt.Errorf("biscuit: previous signature check failed: %w", err) - } - - tokenHash, err := b.SHA256Sum(b.BlockCount()) - if err != nil { - return nil, err - } - - signData, err := userSign(tokenHash, userKey, toSignData) - if err != nil { - return nil, fmt.Errorf("biscuit: signature failed: %w", err) - } - - builder := &hubauthBlockBuilder{ - BlockBuilder: b.CreateBlock(), - } - if err := builder.withUserSignature(signData); err != nil { - return nil, fmt.Errorf("biscuit: failed to create signature block: %w", err) - } - - clientKey := sig.GenerateKeypair(rand.Reader) - b, err = b.Append(rand.Reader, clientKey, builder.Build()) - if err != nil { - return nil, fmt.Errorf("biscuit: failed to append signature block: %w", err) - } - - return b.Serialize() -} - -type VerifiedMetadata 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) - } - verifier := &hubauthVerifier{v} - - audienceVerificationData, err := verifier.getAudienceVerificationData(audience) - if err != nil { - return 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) - } - if err := verifier.withValidatedAudienceSignature(audienceVerificationData); err != nil { - return 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) - } - - // 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) - if err != nil { - return 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) - } - if err := verifier.withValidatedUserSignature(userVerificationData); err != nil { - return 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) - } - - metas, err := verifier.getMetadata() - if err != nil { - return nil, fmt.Errorf("biscuit: failed to get metadata: %v", err) - } - return &VerifiedMetadata{ - Metadata: metas, - UserSignatureNonce: userVerificationData.Nonce, - UserSignatureTimestamp: time.Time(userVerificationData.Timestamp), - }, nil -} diff --git a/pkg/biscuit/biscuit_test.go b/pkg/biscuit/biscuit_test.go deleted file mode 100644 index 560a063..0000000 --- a/pkg/biscuit/biscuit_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package biscuit - -import ( - "context" - "crypto/ecdsa" - "crypto/rand" - "testing" - "time" - - "github.com/flynn/biscuit-go/sig" - "github.com/flynn/hubauth/pkg/kmssign" - "github.com/flynn/hubauth/pkg/kmssign/kmssim" - "github.com/stretchr/testify/require" -) - -func TestBiscuit(t *testing.T) { - rootKey := sig.GenerateKeypair(rand.Reader) - audience := "http://random.audience.url" - - kms := kmssim.NewClient([]string{audience}) - audienceKey, err := kmssign.NewKey(context.Background(), kms, audience) - require.NoError(t, err) - - userKey := generateUserKeyPair(t) - metas := &Metadata{ - ClientID: "abcd", - UserEmail: "1234@example.com", - UserID: "1234", - IssueTime: time.Now(), - } - signableBiscuit, err := GenerateSignable(rootKey, audience, audienceKey, userKey.Public, time.Now().Add(5*time.Minute), metas) - require.NoError(t, err) - t.Logf("signable biscuit size: %d", len(signableBiscuit)) - - t.Run("happy path", func(t *testing.T) { - signedBiscuit, err := Sign(signableBiscuit, rootKey.Public(), userKey) - require.NoError(t, err) - t.Logf("signed biscuit size: %d", len(signedBiscuit)) - - res, err := Verify(signedBiscuit, rootKey.Public(), audience, audienceKey.Public().(*ecdsa.PublicKey)) - require.NoError(t, err) - require.Equal(t, metas.ClientID, res.ClientID) - require.Equal(t, metas.UserID, res.UserID) - require.Equal(t, metas.UserEmail, res.UserEmail) - require.WithinDuration(t, metas.IssueTime, res.IssueTime, 1*time.Second) - require.NotEmpty(t, res.UserSignatureNonce) - require.NotEmpty(t, res.UserSignatureTimestamp) - }) - - t.Run("user sign with wrong key", func(t *testing.T) { - _, err := Sign(signableBiscuit, rootKey.Public(), generateUserKeyPair(t)) - require.Error(t, err) - }) - - t.Run("verify wrong audience", func(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)) - require.Error(t, err) - - wrongAudience := "http://another.audience.url" - kms := kmssim.NewClient([]string{wrongAudience}) - wrongAudienceKey, err := kmssign.NewKey(context.Background(), kms, wrongAudience) - require.NoError(t, err) - - _, err = Verify(signedBiscuit, rootKey.Public(), audience, wrongAudienceKey.Public().(*ecdsa.PublicKey)) - require.Error(t, err) - }) -} diff --git a/pkg/biscuit/signature.go b/pkg/biscuit/signature.go deleted file mode 100644 index 86b5545..0000000 --- a/pkg/biscuit/signature.go +++ /dev/null @@ -1,177 +0,0 @@ -package biscuit - -import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/sha256" - "crypto/x509" - "errors" - "fmt" - "time" - - "github.com/flynn/biscuit-go" - "github.com/flynn/hubauth/pkg/kmssign" -) - -var ( - ErrUnsupportedSignatureAlg = errors.New("unsupported signature algorithm") - ErrInvalidSignature = errors.New("invalid signature") -) - -type SignatureAlg biscuit.Symbol - -const ( - ECDSA_P256_SHA256 SignatureAlg = "ECDSA_P256_SHA256" -) - -type userToSignData struct { - DataID biscuit.Integer - Alg biscuit.Symbol - Data biscuit.Bytes -} - -type userSignatureData struct { - DataID biscuit.Integer - UserPubKey biscuit.Bytes - Signature biscuit.Bytes - Nonce biscuit.Bytes - Timestamp biscuit.Date -} - -type userVerificationData struct { - DataID biscuit.Integer - Alg biscuit.Symbol - Data biscuit.Bytes - UserPubKey biscuit.Bytes - Signature biscuit.Bytes - Nonce biscuit.Bytes - Timestamp biscuit.Date -} - -func userSign(tokenHash []byte, userKey *UserKeyPair, toSignData *userToSignData) (*userSignatureData, error) { - if len(tokenHash) == 0 { - return nil, errors.New("invalid tokenHash") - } - - signerTimestamp := time.Now() - signerNonce := make([]byte, nonceSize) - if _, err := rand.Read(signerNonce); err != nil { - return nil, err - } - - var dataToSign []byte - dataToSign = append(dataToSign, toSignData.Data...) - dataToSign = append(dataToSign, tokenHash...) - dataToSign = append(dataToSign, signerNonce...) - dataToSign = append(dataToSign, []byte(signerTimestamp.Format(time.RFC3339))...) - - var signedData biscuit.Bytes - switch SignatureAlg(toSignData.Alg) { - case ECDSA_P256_SHA256: - privKey, err := x509.ParseECPrivateKey(userKey.Private) - if err != nil { - return nil, err - } - hash := sha256.Sum256(dataToSign) - signedData, err = ecdsa.SignASN1(rand.Reader, privKey, hash[:]) - if err != nil { - return nil, err - } - default: - return nil, ErrUnsupportedSignatureAlg - } - - return &userSignatureData{ - DataID: toSignData.DataID, - Nonce: signerNonce, - Signature: signedData, - Timestamp: biscuit.Date(signerTimestamp), - UserPubKey: userKey.Public, - }, nil -} - -func verifyUserSignature(signedTokenHash []byte, data *userVerificationData) error { - var signedData []byte - signedData = append(signedData, data.Data...) - signedData = append(signedData, signedTokenHash...) - signedData = append(signedData, data.Nonce...) - signedData = append(signedData, []byte(time.Time(data.Timestamp).Format(time.RFC3339))...) - - switch SignatureAlg(data.Alg) { - case ECDSA_P256_SHA256: - pk, err := x509.ParsePKIXPublicKey(data.UserPubKey) - if err != nil { - return err - } - pubkey, ok := pk.(*ecdsa.PublicKey) - if !ok { - return errors.New("invalid pubkey, not an *ecdsa.PublicKey") - } - - hash := sha256.Sum256(signedData) - if !ecdsa.VerifyASN1(pubkey, hash[:], data.Signature) { - return ErrInvalidSignature - } - return nil - default: - return ErrUnsupportedSignatureAlg - } -} - -type audienceVerificationData struct { - Audience biscuit.Symbol - Challenge biscuit.Bytes - Signature biscuit.Bytes -} - -func audienceSign(audience string, audienceKey *kmssign.Key) (*audienceVerificationData, error) { - challenge := make([]byte, challengeSize) - if _, err := rand.Reader.Read(challenge); err != nil { - return nil, err - } - - signedData := append(signStaticCtx, challenge...) - signedData = append(signedData, []byte(audience)...) - signedHash := sha256.Sum256(signedData) - signature, err := audienceKey.Sign(rand.Reader, signedHash[:], crypto.SHA256) - if err != nil { - return nil, err - } - - return &audienceVerificationData{ - Audience: biscuit.Symbol(audience), - Challenge: challenge, - Signature: signature, - }, nil -} - -func verifyAudienceSignature(audiencePubkey *ecdsa.PublicKey, data *audienceVerificationData) error { - signedData := append(signStaticCtx, data.Challenge...) - signedData = append(signedData, []byte(data.Audience)...) - hash := sha256.Sum256(signedData) - - if !ecdsa.VerifyASN1(audiencePubkey, hash[:], data.Signature) { - return errors.New("invalid signature") - } - return nil -} - -func validatePKIXP256PublicKey(pubkey []byte) error { - key, err := x509.ParsePKIXPublicKey(pubkey) - if err != nil { - return fmt.Errorf("failed to parse PKIX, ASN.1 DER public key: %v", err) - } - - ecKey, ok := key.(*ecdsa.PublicKey) - if !ok { - return errors.New("public key is not an *ecdsa.PublicKey") - } - - if ecKey.Curve != elliptic.P256() { - return fmt.Errorf("publickey is on wrong curve, expected P256") - } - - return nil -} diff --git a/pkg/biscuit/signature_test.go b/pkg/biscuit/signature_test.go deleted file mode 100644 index 2351e26..0000000 --- a/pkg/biscuit/signature_test.go +++ /dev/null @@ -1,230 +0,0 @@ -package biscuit - -import ( - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "testing" - "time" - - "github.com/flynn/biscuit-go" - "github.com/stretchr/testify/require" -) - -func TestUserSignVerify(t *testing.T) { - tokenHash := make([]byte, 32) - _, err := rand.Read(tokenHash) - require.NoError(t, err) - - challenge := make([]byte, challengeSize) - _, err = rand.Read(challenge) - require.NoError(t, err) - - userKey := generateUserKeyPair(t) - - toSignData := &userToSignData{ - DataID: 1, - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - Data: []byte("challenge"), - } - - signedData, err := userSign(tokenHash, userKey, toSignData) - require.NoError(t, err) - require.NotEmpty(t, signedData.Signature) - require.Equal(t, biscuit.Integer(1), signedData.DataID) - require.Equal(t, biscuit.Bytes(userKey.Public), signedData.UserPubKey) - - require.Len(t, signedData.Nonce, nonceSize) - zeroNonce := make([]byte, nonceSize) - require.NotEqual(t, biscuit.Bytes(zeroNonce), signedData.Nonce) - - require.WithinDuration(t, time.Now(), time.Time(signedData.Timestamp), 1*time.Second) - - require.NoError(t, verifyUserSignature(tokenHash, &userVerificationData{ - DataID: toSignData.DataID, - Alg: toSignData.Alg, - Data: toSignData.Data, - Nonce: signedData.Nonce, - Signature: signedData.Signature, - Timestamp: signedData.Timestamp, - UserPubKey: signedData.UserPubKey, - })) -} - -func TestUserSignFail(t *testing.T) { - validTokenHash := make([]byte, 32) - _, err := rand.Read(validTokenHash) - require.NoError(t, err) - - validChallenge := make([]byte, challengeSize) - _, err = rand.Read(validChallenge) - require.NoError(t, err) - - invalidPrivateKey := &UserKeyPair{ - Private: make([]byte, 32), - } - - testCases := []struct { - desc string - tokenHash []byte - userKey *UserKeyPair - data *userToSignData - expectedErr error - }{ - { - desc: "empty tokenHash", - tokenHash: []byte{}, - }, - { - desc: "unsupported alg", - tokenHash: validTokenHash, - data: &userToSignData{ - Alg: "unsupported", - }, - expectedErr: ErrUnsupportedSignatureAlg, - }, - { - desc: "wrong private key encoding", - tokenHash: validTokenHash, - data: &userToSignData{ - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - }, - userKey: invalidPrivateKey, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.desc, func(t *testing.T) { - _, err := userSign(testCase.tokenHash, testCase.userKey, testCase.data) - require.Error(t, err) - if testCase.expectedErr != nil { - require.Equal(t, testCase.expectedErr, err) - } - }) - } -} - -func TestVerifyUserSignatureFail(t *testing.T) { - tokenHash := []byte("token hash") - toSignData := &userToSignData{ - DataID: 1, - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - Data: []byte("challenge"), - } - - userKey := generateUserKeyPair(t) - invalidKey := generateUserKeyPair(t) - - signedData, err := userSign(tokenHash, userKey, toSignData) - require.NoError(t, err) - - rsaKey, err := rsa.GenerateKey(rand.Reader, 1024) - require.NoError(t, err) - wrongKeyKind, err := x509.MarshalPKIXPublicKey(&rsaKey.PublicKey) - require.NoError(t, err) - - testCases := []struct { - desc string - tokenHash []byte - data *userVerificationData - expectedErr error - }{ - { - desc: "unsupported alg", - expectedErr: ErrUnsupportedSignatureAlg, - data: &userVerificationData{ - Alg: "unknown", - }, - }, - { - desc: "invalid pubkey encoding", - data: &userVerificationData{ - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - UserPubKey: make([]byte, 32), - }, - }, - { - desc: "invalid pubkey kind", - data: &userVerificationData{ - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - UserPubKey: wrongKeyKind, - }, - }, - { - desc: "wrong pubkey", - tokenHash: tokenHash, - data: &userVerificationData{ - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - UserPubKey: invalidKey.Public, - Data: toSignData.Data, - DataID: toSignData.DataID, - Nonce: signedData.Nonce, - Signature: signedData.Signature, - Timestamp: signedData.Timestamp, - }, - }, - { - desc: "tampered token hash", - expectedErr: ErrInvalidSignature, - tokenHash: []byte("wrong"), - data: &userVerificationData{ - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - UserPubKey: userKey.Public, - Data: toSignData.Data, - DataID: toSignData.DataID, - Nonce: signedData.Nonce, - Signature: signedData.Signature, - Timestamp: signedData.Timestamp, - }, - }, - { - desc: "tampered nonce", - expectedErr: ErrInvalidSignature, - tokenHash: tokenHash, - data: &userVerificationData{ - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - UserPubKey: userKey.Public, - Data: toSignData.Data, - DataID: toSignData.DataID, - Nonce: []byte("another nonce"), - Signature: signedData.Signature, - Timestamp: signedData.Timestamp, - }, - }, - { - desc: "tampered timestamp", - expectedErr: ErrInvalidSignature, - tokenHash: tokenHash, - data: &userVerificationData{ - Alg: biscuit.Symbol(ECDSA_P256_SHA256), - UserPubKey: userKey.Public, - Data: toSignData.Data, - DataID: toSignData.DataID, - Nonce: signedData.Nonce, - Signature: signedData.Signature, - Timestamp: biscuit.Date(time.Now().Add(1 * time.Second)), - }, - }, - } - - for _, testCase := range testCases { - t.Run(testCase.desc, func(t *testing.T) { - err := verifyUserSignature(testCase.tokenHash, testCase.data) - require.Error(t, err) - if testCase.expectedErr != nil { - require.Equal(t, testCase.expectedErr, err) - } - }) - } -} - -func generateUserKeyPair(t *testing.T) *UserKeyPair { - priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - require.NoError(t, err) - - kp, err := NewECDSAKeyPair(priv) - require.NoError(t, err) - return kp -} diff --git a/pkg/biscuit/wrapper.go b/pkg/biscuit/wrapper.go deleted file mode 100644 index f39681f..0000000 --- a/pkg/biscuit/wrapper.go +++ /dev/null @@ -1,426 +0,0 @@ -package biscuit - -import ( - "bytes" - "crypto/rand" - "errors" - "fmt" - "time" - - "github.com/flynn/biscuit-go" - "github.com/flynn/biscuit-go/datalog" - "github.com/flynn/hubauth/pkg/kmssign" -) - -var ( - ErrAlreadySigned = errors.New("already signed") - ErrInvalidToSignDataPrefix = errors.New("invalid to_sign data prefix") -) - -var ( - signStaticCtx = []byte("biscuit-pop-v0") - challengeSize = 16 - nonceSize = 16 -) - -type hubauthBuilder struct { - biscuit.Builder -} - -// withUserToSignFact add an authority should_sign fact and associated data to the biscuit -// with an authority caveat requiring the verifier to provide a valid_signature fact. -// the verifier is responsible of ensuring that a valid signature exists over the data. -func (b *hubauthBuilder) withUserToSignFact(userPubkey []byte) error { - dataID := biscuit.Integer(0) - - if err := validatePKIXP256PublicKey(userPubkey); err != nil { - return err - } - - if err := b.AddAuthorityFact(biscuit.Fact{Predicate: biscuit.Predicate{ - Name: "should_sign", - IDs: []biscuit.Atom{ - dataID, - biscuit.Symbol(ECDSA_P256_SHA256), - biscuit.Bytes(userPubkey), - }, - }}); err != nil { - return err - } - - challenge := make([]byte, challengeSize) - if _, err := rand.Reader.Read(challenge); err != nil { - return err - } - - if err := b.AddAuthorityFact(biscuit.Fact{Predicate: biscuit.Predicate{ - Name: "data", - IDs: []biscuit.Atom{ - dataID, - biscuit.Bytes(append(signStaticCtx, challenge...)), - }, - }}); err != nil { - return err - } - - if err := b.AddAuthorityCaveat(biscuit.Rule{ - Head: biscuit.Predicate{Name: "valid", IDs: []biscuit.Atom{biscuit.Variable(0)}}, - Body: []biscuit.Predicate{ - {Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}}, - {Name: "valid_signature", IDs: []biscuit.Atom{biscuit.Symbol("ambient"), biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}}, - }, - }); err != nil { - return err - } - - return nil -} - -// withAudienceSignature add an authority audience_signature fact, containing a challenge and -// a matching signature using the audience key. -// the verifier is responsible of providing a valid_audience_signature fact, after -// verifying the signature using the audience pubkey. -func (b *hubauthBuilder) withAudienceSignature(audience string, audienceKey *kmssign.Key) error { - if len(audience) == 0 { - return errors.New("audience is required") - } - - data, err := audienceSign(audience, audienceKey) - if err != nil { - return err - } - - if err := b.AddAuthorityFact(biscuit.Fact{Predicate: biscuit.Predicate{ - Name: "audience_signature", - IDs: []biscuit.Atom{ - data.Audience, - data.Challenge, - data.Signature, - }, - }}); err != nil { - return err - } - - if err := b.AddAuthorityCaveat(biscuit.Rule{ - Head: biscuit.Predicate{Name: "valid_audience", IDs: []biscuit.Atom{biscuit.Variable(0)}}, - Body: []biscuit.Predicate{ - {Name: "audience_signature", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}}, - {Name: "valid_audience_signature", IDs: []biscuit.Atom{biscuit.Symbol("ambient"), biscuit.Variable(0), biscuit.Variable(2)}}, - }, - }); err != nil { - return err - } - - return nil -} - -func (b *hubauthBuilder) withMetadata(m *Metadata) error { - return b.AddAuthorityFact(biscuit.Fact{Predicate: biscuit.Predicate{ - Name: "metadata", - IDs: []biscuit.Atom{ - biscuit.String(m.ClientID), - biscuit.String(m.UserID), - biscuit.String(m.UserEmail), - biscuit.Date(m.IssueTime), - }, - }}) -} - -func (b *hubauthBuilder) withExpire(exp time.Time) error { - if err := b.AddAuthorityCaveat(biscuit.Rule{ - Head: biscuit.Predicate{Name: "not_expired", IDs: []biscuit.Atom{biscuit.Variable(0)}}, - Body: []biscuit.Predicate{ - {Name: "current_time", IDs: []biscuit.Atom{biscuit.Symbol("ambient"), biscuit.Variable(0)}}, - }, - Constraints: []biscuit.Constraint{{ - Name: biscuit.Variable(0), - Checker: biscuit.DateComparisonChecker{ - Comparison: datalog.DateComparisonBefore, - Date: biscuit.Date(exp), - }, - }}, - }); err != nil { - return err - } - - return nil -} - -type hubauthBlockBuilder struct { - biscuit.BlockBuilder -} - -func (b *hubauthBlockBuilder) withUserSignature(sigData *userSignatureData) error { - return b.AddFact(biscuit.Fact{Predicate: biscuit.Predicate{ - Name: "signature", - IDs: []biscuit.Atom{ - sigData.DataID, - sigData.UserPubKey, - sigData.Signature, - sigData.Nonce, - sigData.Timestamp, - }, - }}) -} - -type hubauthVerifier struct { - biscuit.Verifier -} - -func (v *hubauthVerifier) getUserToSignData(userPubKey biscuit.Bytes) (*userToSignData, error) { - toSign, err := v.Query(biscuit.Rule{ - Head: biscuit.Predicate{ - Name: "to_sign", - IDs: []biscuit.Atom{biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}, - }, - Body: []biscuit.Predicate{ - { - Name: "should_sign", IDs: []biscuit.Atom{ - biscuit.SymbolAuthority, - biscuit.Variable(0), - biscuit.Variable(1), - biscuit.Bytes(userPubKey), - }, - }, { - Name: "data", IDs: []biscuit.Atom{ - biscuit.SymbolAuthority, - biscuit.Variable(0), - biscuit.Variable(2), - }, - }, - }, - }) - if err != nil { - return nil, err - } - - if g, w := len(toSign), 1; g != w { - return nil, fmt.Errorf("invalid to_sign fact count, got %d, want %d", g, w) - } - - toSignFact := toSign[0] - if g, w := len(toSignFact.IDs), 3; g != w { - return nil, fmt.Errorf("invalid to_sign fact, got %d atoms, want %d", g, w) - } - - sigData := &userToSignData{} - var ok bool - sigData.DataID, ok = toSign[0].IDs[0].(biscuit.Integer) - if !ok { - return nil, errors.New("invalid to_sign atom: dataID") - } - sigData.Alg, ok = toSign[0].IDs[1].(biscuit.Symbol) - if !ok { - return nil, errors.New("invalid to_sign atom: alg") - } - sigData.Data, ok = toSign[0].IDs[2].(biscuit.Bytes) - if !ok { - return nil, errors.New("invalid to_sign atom: data") - } - - if !bytes.HasPrefix(sigData.Data, signStaticCtx) { - return nil, ErrInvalidToSignDataPrefix - } - - return sigData, nil -} - -func (v *hubauthVerifier) ensureNotAlreadyUserSigned(dataID biscuit.Integer, userPubKey biscuit.Bytes) error { - alreadySigned, err := v.Query(biscuit.Rule{ - Head: biscuit.Predicate{Name: "already_signed", IDs: []biscuit.Atom{biscuit.Variable(0)}}, - Body: []biscuit.Predicate{ - {Name: "signature", IDs: []biscuit.Atom{dataID, userPubKey, biscuit.Variable(0)}}, - }, - }) - if err != nil { - return err - } - if len(alreadySigned) != 0 { - return ErrAlreadySigned - } - - return nil -} - -func (v *hubauthVerifier) getUserVerificationData() (*userVerificationData, error) { - toValidate, err := v.Query(biscuit.Rule{ - Head: biscuit.Predicate{ - Name: "to_validate", - IDs: []biscuit.Atom{ - biscuit.Variable(0), // dataID - biscuit.Variable(1), // alg - biscuit.Variable(2), // pubkey - biscuit.Variable(3), // data - biscuit.Variable(4), // signature - biscuit.Variable(5), // signerNonce - biscuit.Variable(6), // signerTimestamp - }}, - Body: []biscuit.Predicate{ - {Name: "should_sign", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2)}}, - {Name: "data", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(3)}}, - {Name: "signature", IDs: []biscuit.Atom{biscuit.Variable(0), biscuit.Variable(2), biscuit.Variable(4), biscuit.Variable(5), biscuit.Variable(6)}}, - }, - }) - if err != nil { - return nil, err - } - - if g, w := len(toValidate), 1; g != w { - return nil, fmt.Errorf("invalid to_validate fact count, got %d, want %d", g, w) - } - - toValidateFact := toValidate[0] - if g, w := len(toValidateFact.IDs), 7; g != w { - return nil, fmt.Errorf("invalid to_valid fact atom count, got %d, want %d", g, w) - } - - toVerify := &userVerificationData{} - var ok bool - toVerify.DataID, ok = toValidateFact.IDs[0].(biscuit.Integer) - if !ok { - return nil, errors.New("invalid to_validate atom: dataID") - } - toVerify.Alg, ok = toValidateFact.IDs[1].(biscuit.Symbol) - if !ok { - return nil, errors.New("invalid to_validate atom: alg") - } - toVerify.UserPubKey, ok = toValidateFact.IDs[2].(biscuit.Bytes) - if !ok { - return nil, errors.New("invalid to_validate atom: userPubKey") - } - toVerify.Data, ok = toValidateFact.IDs[3].(biscuit.Bytes) - if !ok { - return nil, errors.New("invalid to_validate atom: data") - } - toVerify.Signature, ok = toValidateFact.IDs[4].(biscuit.Bytes) - if !ok { - return nil, errors.New("invalid to_validate atom: signature") - } - toVerify.Nonce, ok = toValidateFact.IDs[5].(biscuit.Bytes) - if !ok { - return nil, errors.New("invalid to_validate atom: nonce") - } - toVerify.Timestamp, ok = toValidateFact.IDs[6].(biscuit.Date) - if !ok { - return nil, errors.New("invalid to_validate atom: timestamp") - } - - return toVerify, nil -} - -func (v *hubauthVerifier) withValidatedUserSignature(data *userVerificationData) error { - v.AddFact(biscuit.Fact{Predicate: biscuit.Predicate{ - Name: "valid_signature", - IDs: []biscuit.Atom{biscuit.Symbol("ambient"), data.DataID, data.Alg, data.UserPubKey}, - }}) - - return nil -} - -func (v *hubauthVerifier) getAudienceVerificationData(audience string) (*audienceVerificationData, error) { - toValidate, err := v.Query(biscuit.Rule{ - Head: biscuit.Predicate{ - Name: "audience_to_validate", - IDs: []biscuit.Atom{ - biscuit.Variable(0), // challenge - biscuit.Variable(1), // signature - }}, - Body: []biscuit.Predicate{ - {Name: "audience_signature", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Symbol(audience), biscuit.Variable(0), biscuit.Variable(1)}}, - }, - }) - if err != nil { - return nil, err - } - - if g, w := len(toValidate), 1; g != w { - return nil, fmt.Errorf("invalid audience_to_validate fact count, got %d, want %d", g, w) - } - - toValidateFact := toValidate[0] - if g, w := len(toValidateFact.IDs), 2; g != w { - return nil, fmt.Errorf("invalid audience_to_validate fact atom count, got %d, want %d", g, w) - } - - toVerify := &audienceVerificationData{Audience: biscuit.Symbol(audience)} - var ok bool - toVerify.Challenge, ok = toValidateFact.IDs[0].(biscuit.Bytes) - if !ok { - return nil, errors.New("invalid audience_to_validate atom: challenge") - } - toVerify.Signature, ok = toValidateFact.IDs[1].(biscuit.Bytes) - if !ok { - return nil, errors.New("invalid audience_to_validate atom: signature") - } - - return toVerify, nil -} - -func (v *hubauthVerifier) getMetadata() (*Metadata, error) { - metaFacts, err := v.Query(biscuit.Rule{ - Head: biscuit.Predicate{ - Name: "metadata", - IDs: []biscuit.Atom{ - biscuit.Variable(0), // clientID - biscuit.Variable(1), // userID - biscuit.Variable(2), // userEmail - biscuit.Variable(3), // issueTime - }}, - Body: []biscuit.Predicate{ - {Name: "metadata", IDs: []biscuit.Atom{biscuit.SymbolAuthority, biscuit.Variable(0), biscuit.Variable(1), biscuit.Variable(2), biscuit.Variable(3)}}, - }, - }) - if err != nil { - return nil, err - } - - if g, w := len(metaFacts), 1; g != w { - return nil, fmt.Errorf("invalid metadata fact count, got %d, want %d", g, w) - } - - metaFact := metaFacts[0] - - clientID, ok := metaFact.IDs[0].(biscuit.String) - if !ok { - return nil, errors.New("invalid metadata atom: clientID") - } - userID, ok := metaFact.IDs[1].(biscuit.String) - if !ok { - return nil, errors.New("invalid metadata atom: userID") - } - userEmail, ok := metaFact.IDs[2].(biscuit.String) - if !ok { - return nil, errors.New("invalid metadata atom: userEmail") - } - issueTime, ok := metaFact.IDs[3].(biscuit.Date) - if !ok { - return nil, errors.New("invalid metadata atom: issueTime") - } - return &Metadata{ - ClientID: string(clientID), - UserID: string(userID), - UserEmail: string(userEmail), - IssueTime: time.Time(issueTime), - }, nil -} - -func (v *hubauthVerifier) withValidatedAudienceSignature(data *audienceVerificationData) error { - v.AddFact(biscuit.Fact{Predicate: biscuit.Predicate{ - Name: "valid_audience_signature", - IDs: []biscuit.Atom{biscuit.Symbol("ambient"), data.Audience, data.Signature}, - }}) - - return nil -} - -func (v *hubauthVerifier) withCurrentTime(t time.Time) error { - v.AddFact(biscuit.Fact{Predicate: biscuit.Predicate{ - Name: "current_time", - IDs: []biscuit.Atom{ - biscuit.Symbol("ambient"), - biscuit.Date(t), - }, - }}) - - return nil -} diff --git a/pkg/idp/token/biscuit.go b/pkg/idp/token/biscuit.go index 7736391..9c997be 100644 --- a/pkg/idp/token/biscuit.go +++ b/pkg/idp/token/biscuit.go @@ -7,8 +7,8 @@ import ( "errors" "fmt" + "github.com/flynn/biscuit-go/cookbook/signedbiscuit" "github.com/flynn/biscuit-go/sig" - "github.com/flynn/hubauth/pkg/biscuit" "github.com/flynn/hubauth/pkg/kmssign" ) @@ -36,14 +36,14 @@ func (b *biscuitBuilder) Build(ctx context.Context, audience string, t *AccessTo } audienceKey := kmssign.NewPrivateKey(b.kms, b.audienceKey(audience), crypto.SHA256) - meta := &biscuit.Metadata{ + meta := &signedbiscuit.Metadata{ ClientID: t.ClientID, UserID: t.UserID, UserEmail: t.UserEmail, IssueTime: t.IssueTime, } - return biscuit.GenerateSignable(b.rootKeyPair, audience, audienceKey, t.UserPublicKey, t.ExpireTime, meta) + return signedbiscuit.GenerateSignable(b.rootKeyPair, audience, audienceKey, t.UserPublicKey, t.ExpireTime, meta) } func (b *biscuitBuilder) TokenType() string { From f7f02c76e8e4566b5be2e0db4764f0b30e9ad5bb Mon Sep 17 00:00:00 2001 From: Flavien Binet Date: Tue, 20 Oct 2020 13:59:21 +0200 Subject: [PATCH 17/17] go mod tidy --- go.sum | 1 - 1 file changed, 1 deletion(-) diff --git a/go.sum b/go.sum index b397974..287ce43 100644 --- a/go.sum +++ b/go.sum @@ -77,7 +77,6 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/flynn/biscuit-go v0.0.0-20201015071554-9ea2c863efe8 h1:jz3x+pevBqZiO9m7M58fN/W8K0OyDUfchIsHE8XgZT0= github.com/flynn/biscuit-go v0.0.0-20201015081742-15d7d351f345 h1:ME6bm5dwn9V2DUlfXJqeN121B5nM7rDFqLFOATALqYE= github.com/flynn/biscuit-go v0.0.0-20201015081742-15d7d351f345/go.mod h1:Sj4oR2hNkrZH1cf3Cj5DPHc3Xq0o61GWeau6UkZR+3c= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=