-
-
Notifications
You must be signed in to change notification settings - Fork 355
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
enigma: implemented new token generator based on hmac-sha256
Closes #11
- Loading branch information
Aeneas Rekkas
committed
Jan 8, 2016
1 parent
71a9105
commit 01f9ede
Showing
7 changed files
with
230 additions
and
117 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package enigma | ||
|
||
import "strings" | ||
|
||
// Challenge represents an validatable token. | ||
type Challenge struct { | ||
// Key is the messages's key | ||
Key string | ||
|
||
// Signature is the messages's signature | ||
Signature string | ||
} | ||
|
||
// FromString extracts key and signature from "<key>.<signature>". | ||
func (a *Challenge) FromString(data string) { | ||
a.Key = "" | ||
a.Signature = "" | ||
|
||
if data == "" { | ||
return | ||
} | ||
|
||
parts := strings.Split(data, ".") | ||
if len(parts) != 2 { | ||
return | ||
} | ||
|
||
key := strings.TrimSpace(parts[0]) | ||
sig := strings.TrimSpace(parts[1]) | ||
if key == "" || sig == "" { | ||
return | ||
} | ||
|
||
a.Key = key | ||
a.Signature = sig | ||
return | ||
} | ||
|
||
// String will return the Challenge as "<key>.<signature>". | ||
func (a *Challenge) String() string { | ||
return a.Key + "." + a.Signature | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
package enigma | ||
|
||
import ( | ||
"bytes" | ||
"crypto/hmac" | ||
"crypto/sha256" | ||
"encoding/base64" | ||
"github.com/go-errors/errors" | ||
"github.com/ory-am/fosite/rand" | ||
) | ||
|
||
// CryptoEnigma is the default implementation for generating and validating challenges. | ||
type CryptoEnigma struct { | ||
AuthCodeEntropy int | ||
GlobalSecret []byte | ||
} | ||
|
||
// key should be at least 256 bit long, making it | ||
const minimumEntropy = 32 | ||
|
||
// the secrets (client and global) should each have at least 16 characters making it harder to guess them | ||
const minimumSecretLength = 32 | ||
|
||
// GenerateAuthorizeCode generates a new authorize code or returns an error. set secret | ||
// This method implements rfc6819 Section 5.1.4.2.2: Use High Entropy for Secrets. | ||
func (c *CryptoEnigma) GenerateChallenge(secret []byte) (*Challenge, error) { | ||
if len(secret) < minimumSecretLength/2 || len(c.GlobalSecret) < minimumSecretLength/2 { | ||
return nil, errors.New("Secret or GlobalSecret are not strong enough") | ||
} | ||
|
||
if c.AuthCodeEntropy < minimumEntropy { | ||
c.AuthCodeEntropy = minimumEntropy | ||
} | ||
|
||
// When creating secrets not intended for usage by human users (e.g., | ||
// client secrets or token handles), the authorization server should | ||
// include a reasonable level of entropy in order to mitigate the risk | ||
// of guessing attacks. The token value should be >=128 bits long and | ||
// constructed from a cryptographically strong random or pseudo-random | ||
// number sequence (see [RFC4086] for best current practice) generated | ||
// by the authorization server. | ||
randomBytes, err := rand.RandomBytes(c.AuthCodeEntropy, 20) | ||
if err != nil { | ||
return nil, errors.New(err) | ||
} | ||
|
||
if len(randomBytes) < c.AuthCodeEntropy { | ||
return nil, errors.New("Could not read enough random data for key generation") | ||
} | ||
|
||
useSecret := append([]byte{}, c.GlobalSecret...) | ||
mac := hmac.New(sha256.New, append(useSecret, secret...)) | ||
_, err = mac.Write(randomBytes) | ||
if err != nil { | ||
return nil, errors.New(err) | ||
} | ||
signature := mac.Sum([]byte{}) | ||
|
||
encodedKey := make([]byte, base64.StdEncoding.EncodedLen(c.AuthCodeEntropy)) | ||
base64.StdEncoding.Encode(encodedKey, randomBytes) | ||
encodedKey = bytes.Trim(encodedKey, "\x00") | ||
|
||
encodedSignature := make([]byte, base64.StdEncoding.EncodedLen(len(signature))) | ||
base64.StdEncoding.Encode(encodedSignature, signature) | ||
encodedSignature = bytes.Trim(encodedSignature, "\x00") | ||
|
||
return &Challenge{ | ||
Key: string(encodedKey), | ||
Signature: string(encodedSignature), | ||
}, nil | ||
} | ||
|
||
// ValidateAuthorizeCodeSignature returns an AuthorizeCode, if the code argument is a valid authorize code | ||
// and the signature matches the key. | ||
func (c *CryptoEnigma) ValidateChallenge(secret []byte, t *Challenge) (err error) { | ||
if t.Key == "" || t.Signature == "" { | ||
return errors.New("Key and signature must both be not empty") | ||
} | ||
|
||
key := make([]byte, base64.StdEncoding.DecodedLen(len(t.Key))) | ||
if _, err := base64.StdEncoding.Decode(key, []byte(t.Signature)); err != nil { | ||
return err | ||
} | ||
|
||
signature := make([]byte, base64.StdEncoding.DecodedLen(len(t.Signature))) | ||
if _, err := base64.StdEncoding.Decode(signature, []byte(t.Signature)); err != nil { | ||
return err | ||
} | ||
|
||
useSecret := append([]byte{}, c.GlobalSecret...) | ||
mac := hmac.New(sha256.New, append(useSecret, secret...)) | ||
_, err = mac.Write(key) | ||
if err != nil { | ||
return errors.New(err) | ||
} | ||
|
||
if !hmac.Equal(signature, mac.Sum([]byte{})) { | ||
// Hash is invalid | ||
return errors.New("Key and signature do not match") | ||
} | ||
|
||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package enigma | ||
|
||
import ( | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"testing" | ||
) | ||
|
||
func TestGenerateFailsWithShortCredentials(t *testing.T) { | ||
cg := CryptoEnigma{ | ||
GlobalSecret: []byte("foo"), | ||
} | ||
|
||
challenge, err := cg.GenerateChallenge([]byte("bar")) | ||
require.NotNil(t, err, "%s", err) | ||
require.Nil(t, challenge) | ||
|
||
cg.GlobalSecret = []byte("12345678901234567890") | ||
challenge, err = cg.GenerateChallenge([]byte("bar")) | ||
require.NotNil(t, err, "%s", err) | ||
require.Nil(t, challenge) | ||
|
||
cg.GlobalSecret = []byte("bar") | ||
challenge, err = cg.GenerateChallenge([]byte("12345678901234567890")) | ||
require.NotNil(t, err, "%s", err) | ||
require.Nil(t, challenge) | ||
} | ||
|
||
func TestGenerate(t *testing.T) { | ||
cg := CryptoEnigma{ | ||
GlobalSecret: []byte("12345678901234567890"), | ||
} | ||
|
||
challenge, err := cg.GenerateChallenge([]byte("09876543210987654321")) | ||
require.Nil(t, err, "%s", err) | ||
require.NotNil(t, challenge) | ||
t.Logf("%s.%s", challenge.Key, challenge.Signature) | ||
|
||
err = cg.ValidateChallenge([]byte("bar"), challenge) | ||
require.NotNil(t, err, "%s", err) | ||
|
||
err = cg.ValidateChallenge([]byte("baz"), challenge) | ||
require.NotNil(t, err, "%s", err) | ||
|
||
cg.GlobalSecret = []byte("baz") | ||
err = cg.ValidateChallenge([]byte("bar"), challenge) | ||
require.NotNil(t, err, "%s", err) | ||
} | ||
|
||
func TestValidateSignatureRejects(t *testing.T) { | ||
var err error | ||
cg := CryptoEnigma{ | ||
GlobalSecret: []byte("12345678901234567890"), | ||
} | ||
token := new(Challenge) | ||
for _, c := range []string{ | ||
"", | ||
" ", | ||
"foo.bar", | ||
"foo.", | ||
".foo", | ||
} { | ||
token.FromString(c) | ||
err = cg.ValidateChallenge([]byte("09876543210987654321"), token) | ||
assert.NotNil(t, err, "%s", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package enigma | ||
|
||
// Enigma provides a set of methods to create access, refresh and authorize tokens. | ||
type Enigma interface { | ||
|
||
// GenerateChallenge generates a challenge (comparable to an opaque token). | ||
GenerateChallenge(secret []byte) (*Challenge, error) | ||
|
||
// ValidateSignature verifies that the challenge key matches the challenge signature, making the challenge | ||
// valid or returning an error if it is invalid. | ||
ValidateChallenge(secret []byte, challenge *Challenge) error | ||
} |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.