From fd218d076da60784e09d91a47b2be332fff224a5 Mon Sep 17 00:00:00 2001 From: arekkas Date: Sun, 26 Aug 2018 15:16:40 +0200 Subject: [PATCH] token/hmac: Add ability to rotate HMAC keys Signed-off-by: arekkas --- compose/compose.go | 2 +- compose/compose_strategy.go | 5 +++-- token/hmac/hmacsha.go | 27 +++++++++++++++++------ token/hmac/hmacsha_test.go | 43 +++++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 9 deletions(-) diff --git a/compose/compose.go b/compose/compose.go index 16738abb8..84e65d972 100644 --- a/compose/compose.go +++ b/compose/compose.go @@ -95,7 +95,7 @@ func ComposeAllEnabled(config *Config, storage interface{}, secret []byte, key * config, storage, &CommonStrategy{ - CoreStrategy: NewOAuth2HMACStrategy(config, secret), + CoreStrategy: NewOAuth2HMACStrategy(config, secret, nil), OpenIDConnectTokenStrategy: NewOpenIDConnectStrategy(config, key), JWTStrategy: &jwt.RS256JWTStrategy{ PrivateKey: key, diff --git a/compose/compose_strategy.go b/compose/compose_strategy.go index c5ac411ce..f394abc4a 100644 --- a/compose/compose_strategy.go +++ b/compose/compose_strategy.go @@ -36,10 +36,11 @@ type CommonStrategy struct { jwt.JWTStrategy } -func NewOAuth2HMACStrategy(config *Config, secret []byte) *oauth2.HMACSHAStrategy { +func NewOAuth2HMACStrategy(config *Config, secret []byte, rotatedSecrets [][]byte) *oauth2.HMACSHAStrategy { return &oauth2.HMACSHAStrategy{ Enigma: &hmac.HMACStrategy{ - GlobalSecret: secret, + GlobalSecret: secret, + RotatedGlobalSecrets: rotatedSecrets, }, AccessTokenLifespan: config.GetAccessTokenLifespan(), AuthorizeCodeLifespan: config.GetAuthorizeCodeLifespan(), diff --git a/token/hmac/hmacsha.go b/token/hmac/hmacsha.go index f02883ced..5c4989b3a 100644 --- a/token/hmac/hmacsha.go +++ b/token/hmac/hmacsha.go @@ -37,8 +37,9 @@ import ( // HMACStrategy is responsible for generating and validating challenges. type HMACStrategy struct { - AuthCodeEntropy int - GlobalSecret []byte + AuthCodeEntropy int + GlobalSecret []byte + RotatedGlobalSecrets [][]byte sync.Mutex } @@ -59,7 +60,7 @@ func (c *HMACStrategy) Generate() (string, string, error) { defer c.Unlock() if len(c.GlobalSecret) < minimumSecretLength { - return "", "", errors.Errorf("Secret for signing HMAC-SHA256 is expected to be 32 byte long, got %d byte", len(c.GlobalSecret)) + return "", "", errors.Errorf("secret for signing HMAC-SHA256 is expected to be 32 byte long, got %d byte", len(c.GlobalSecret)) } var signingKey [32]byte @@ -90,12 +91,26 @@ func (c *HMACStrategy) Generate() (string, string, error) { // Validate validates a token and returns its signature or an error if the token is not valid. func (c *HMACStrategy) Validate(token string) error { - if len(c.GlobalSecret) < minimumSecretLength { - return errors.Errorf("Secret for signing HMAC-SHA256 is expected to be 32 byte long, got %d byte", len(c.GlobalSecret)) + keys := append([][]byte{c.GlobalSecret}, c.RotatedGlobalSecrets...) + for _, key := range keys { + if err := c.validate(key, token); err == nil { + return nil + } else if errors.Cause(err) == fosite.ErrTokenSignatureMismatch { + } else { + return err + } + } + + return errors.New("a secret for signing HMAC-SHA256 is expected to be defined, but none were") +} + +func (c *HMACStrategy) validate(secret []byte, token string) error { + if len(secret) < minimumSecretLength { + return errors.Errorf("secret for signing HMAC-SHA256 is expected to be 32 byte long, got %d byte", len(secret)) } var signingKey [32]byte - copy(signingKey[:], c.GlobalSecret) + copy(signingKey[:], secret) split := strings.Split(token, ".") if len(split) != 2 { diff --git a/token/hmac/hmacsha_test.go b/token/hmac/hmacsha_test.go index 3b764f6ff..019424c97 100644 --- a/token/hmac/hmacsha_test.go +++ b/token/hmac/hmacsha_test.go @@ -75,3 +75,46 @@ func TestValidateSignatureRejects(t *testing.T) { t.Logf("Passed test case %d", k) } } + +func TestValidateWithRotatedKey(t *testing.T) { + old := HMACStrategy{ + GlobalSecret: []byte("1234567890123456789012345678901234567890"), + } + now := HMACStrategy{ + GlobalSecret: []byte("0000000090123456789012345678901234567890"), + RotatedGlobalSecrets: [][]byte{ + []byte("abcdefgh90123456789012345678901234567890"), + []byte("1234567890123456789012345678901234567890"), + }, + } + + token, _, err := old.Generate() + require.NoError(t, err) + + require.NoError(t, now.Validate(token)) +} + +func TestValidateWithRotatedKeyInvalid(t *testing.T) { + old := HMACStrategy{ + GlobalSecret: []byte("1234567890123456789012345678901234567890"), + } + now := HMACStrategy{ + GlobalSecret: []byte("0000000090123456789012345678901234567890"), + RotatedGlobalSecrets: [][]byte{ + []byte("abcdefgh90123456789012345678901"), + []byte("1234567890123456789012345678901234567890"), + }, + } + + token, _, err := old.Generate() + require.NoError(t, err) + + require.Error(t, now.Validate(token)) + + now = HMACStrategy{ + GlobalSecret: nil, + RotatedGlobalSecrets: [][]byte{}, + } + + require.Error(t, now.Validate(token)) +}