Skip to content

Commit

Permalink
Merge branch 'master' into release_3_0_0
Browse files Browse the repository at this point in the history
  • Loading branch information
dgrijalva committed Nov 16, 2015
2 parents fba4b18 + 2bd555e commit 56c7810
Show file tree
Hide file tree
Showing 11 changed files with 281 additions and 138 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,5 @@ language: go
go:
- 1.3.3
- 1.4.2
- 1.5
- tip
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ Parsing and verifying tokens is pretty straight forward. You pass in the token
tokenString, err := token.SignedString(mySigningKey)
```

## Extensions

This library publishes all the necessary components for adding your own signing methods. Simply implement the `SigningMethod` interface and register a factory method using `RegisterSigningMethod`.

Here's an example of an extension that integrates with the Google App Engine signing tools: https://github.com/someone1/gcp-jwt-go

## Project Status & Versioning

This library is considered production ready. Feedback and feature requests are appreciated. The API should be considered stable. There should be very few backwards-incompatible changes outside of major version updates (and only with good reason).
Expand Down
30 changes: 27 additions & 3 deletions cmd/jwt/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"io/ioutil"
"os"
"regexp"
"strings"

"github.com/dgrijalva/jwt-go"
)
Expand Down Expand Up @@ -116,7 +117,14 @@ func verifyToken() error {

// Parse the token. Load the key from command line option
token, err := jwt.Parse(string(tokData), func(t *jwt.Token) (interface{}, error) {
return loadData(*flagKey)
data, err := loadData(*flagKey)
if err != nil {
return nil, err
}
if isEs() {
return jwt.ParseECPublicKeyFromPEM(data)
}
return data, nil
})

// Print some debug data
Expand Down Expand Up @@ -161,7 +169,8 @@ func signToken() error {
}

// get the key
keyData, err := loadData(*flagKey)
var key interface{}
key, err = loadData(*flagKey)
if err != nil {
return fmt.Errorf("Couldn't read key: %v", err)
}
Expand All @@ -175,11 +184,26 @@ func signToken() error {
// create a new token
token := jwt.NewWithClaims(alg, claims)

if out, err := token.SignedString(keyData); err == nil {
if isEs() {
if k, ok := key.([]byte); !ok {
return fmt.Errorf("Couldn't convert key data to key")
} else {
key, err = jwt.ParseECPrivateKeyFromPEM(k)
if err != nil {
return err
}
}
}

if out, err := token.SignedString(key); err == nil {
fmt.Println(out)
} else {
return fmt.Errorf("Error signing token: %v", err)
}

return nil
}

func isEs() bool {
return strings.HasPrefix(*flagAlg, "ES")
}
63 changes: 37 additions & 26 deletions ecdsa.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"crypto"
"crypto/ecdsa"
"crypto/rand"
"encoding/asn1"
"errors"
"math/big"
)
Expand All @@ -16,14 +15,10 @@ var (

// Implements the ECDSA family of signing methods signing methods
type SigningMethodECDSA struct {
Name string
Hash crypto.Hash
}

// Marshalling structure for r, s EC point
type ECPoint struct {
R *big.Int
S *big.Int
Name string
Hash crypto.Hash
KeySize int
CurveBits int
}

// Specific instances for EC256 and company
Expand All @@ -35,19 +30,19 @@ var (

func init() {
// ES256
SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256}
SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256, 32, 256}
RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod {
return SigningMethodES256
})

// ES384
SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384}
SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384, 48, 384}
RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod {
return SigningMethodES384
})

// ES512
SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512}
SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512, 66, 521}
RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod {
return SigningMethodES512
})
Expand Down Expand Up @@ -77,12 +72,13 @@ func (m *SigningMethodECDSA) Verify(signingString, signature string, key interfa
return ErrInvalidKey
}

// Unmarshal asn1 ECPoint
var ecpoint = new(ECPoint)
if _, err := asn1.Unmarshal(sig, ecpoint); err != nil {
return err
if len(sig) != 2*m.KeySize {
return ErrECDSAVerification
}

r := big.NewInt(0).SetBytes(sig[:m.KeySize])
s := big.NewInt(0).SetBytes(sig[m.KeySize:])

// Create hasher
if !m.Hash.Available() {
return ErrHashUnavailable
Expand All @@ -91,7 +87,7 @@ func (m *SigningMethodECDSA) Verify(signingString, signature string, key interfa
hasher.Write([]byte(signingString))

// Verify the signature
if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), ecpoint.R, ecpoint.S); verifystatus == true {
if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), r, s); verifystatus == true {
return nil
} else {
return ErrECDSAVerification
Expand Down Expand Up @@ -120,16 +116,31 @@ func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string

// Sign the string and return r, s
if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil {
// asn1 marhsal r, s using ecPoint as the structure
var ecpoint = new(ECPoint)
ecpoint.R = r
ecpoint.S = s

if signature, err := asn1.Marshal(*ecpoint); err != nil {
return "", err
} else {
return EncodeSegment(signature), nil
curveBits := ecdsaKey.Curve.Params().BitSize

if m.CurveBits != curveBits {
return "", ErrInvalidKey
}

keyBytes := curveBits / 8
if curveBits%8 > 0 {
keyBytes += 1
}

// We serialize the outpus (r and s) into big-endian byte arrays and pad
// them with zeros on the left to make sure the sizes work out. Both arrays
// must be keyBytes long, and the output must be 2*keyBytes long.
rBytes := r.Bytes()
rBytesPadded := make([]byte, keyBytes)
copy(rBytesPadded[keyBytes-len(rBytes):], rBytes)

sBytes := s.Bytes()
sBytesPadded := make([]byte, keyBytes)
copy(sBytesPadded[keyBytes-len(sBytes):], sBytes)

out := append(rBytesPadded, sBytesPadded...)

return EncodeSegment(out), nil
} else {
return "", err
}
Expand Down
6 changes: 3 additions & 3 deletions ecdsa_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,23 +20,23 @@ var ecdsaTestData = []struct {
{
"Basic ES256",
map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"},
"eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8w",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJmb28iOiJiYXIifQ.feG39E-bn8HXAKhzDZq7yEAPWYDhZlwTn3sePJnU9VrGMmwdXAIEyoOnrjreYlVM_Z4N13eK9-TmMTWyfKJtHQ",
"ES256",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic ES384",
map[string]string{"private": "test/ec384-private.pem", "public": "test/ec384-public.pem"},
"eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MGUCMQCHBr61FXDuFY9xUhyp8iWQAuBIaSgaf1z2j_8XrKcCfzTPzoSa3SZKq-m3L492xe8CMG3kafRMeuaN5Aw8ZJxmOLhkTo4D3-LaGzcaUWINvWvkwFMl7dMC863s0gov6xvXuA",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzM4NCJ9.eyJmb28iOiJiYXIifQ.ngAfKMbJUh0WWubSIYe5GMsA-aHNKwFbJk_wq3lq23aPp8H2anb1rRILIzVR0gUf4a8WzDtrzmiikuPWyCS6CN4-PwdgTk-5nehC7JXqlaBZU05p3toM3nWCwm_LXcld",
"ES384",
map[string]interface{}{"foo": "bar"},
true,
},
{
"Basic ES512",
map[string]string{"private": "test/ec512-private.pem", "public": "test/ec512-public.pem"},
"eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MIGIAkIAmVKjdJE5lG1byOFgZZVTeNDRp6E7SNvUj0UrvpzoBH6nrleWVTcwfHzbwWuooNpPADDSFR_Ql3ze-Vwwi8hBqQsCQgHn-ZooL8zegkOVeEEsqd7WHWdhb8UekFCYw3X8JnNP-D3wvZQ1-tkkHakt5gZ2-xO29TxfSPun4ViGkMYa7Q4N-Q",
"eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzUxMiJ9.eyJmb28iOiJiYXIifQ.AAU0TvGQOcdg2OvrwY73NHKgfk26UDekh9Prz-L_iWuTBIBqOFCWwwLsRiHB1JOddfKAls5do1W0jR_F30JpVd-6AJeTjGKA4C1A1H6gIKwRY0o_tFDIydZCl_lMBMeG5VNFAjO86-WCSKwc3hqaGkq1MugPRq_qrF9AVbuEB4JPLyL5",
"ES512",
map[string]interface{}{"foo": "bar"},
true,
Expand Down
42 changes: 26 additions & 16 deletions hmac.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,26 +44,36 @@ func (m *SigningMethodHMAC) Alg() string {
return m.Name
}

// Verify the signature of HSXXX tokens. Returns nil if the signature is valid.
func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error {
if keyBytes, ok := key.([]byte); ok {
var sig []byte
var err error
if sig, err = DecodeSegment(signature); err == nil {
if !m.Hash.Available() {
return ErrHashUnavailable
}

hasher := hmac.New(m.Hash.New, keyBytes)
hasher.Write([]byte(signingString))

if !hmac.Equal(sig, hasher.Sum(nil)) {
err = ErrSignatureInvalid
}
}
// Verify the key is the right type
keyBytes, ok := key.([]byte)
if !ok {
return ErrInvalidKey
}

// Decode signature, for comparison
sig, err := DecodeSegment(signature)
if err != nil {
return err
}

return ErrInvalidKey
// Can we use the specified hashing method?
if !m.Hash.Available() {
return ErrHashUnavailable
}

// This signing method is symmetric, so we validate the signature
// by reproducing the signature from the signing string and key, then
// comparing that against the provided signature.
hasher := hmac.New(m.Hash.New, keyBytes)
hasher.Write([]byte(signingString))
if !hmac.Equal(sig, hasher.Sum(nil)) {
return ErrSignatureInvalid
}

// No validation errors. Signature is good.
return nil
}

// Implements the Sign method from SigningMethod for this signing method.
Expand Down
118 changes: 118 additions & 0 deletions parser.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package jwt

import (
"bytes"
"encoding/json"
"fmt"
"strings"
)

type Parser struct {
ValidMethods []string // If populated, only these methods will be considered valid
UseJSONNumber bool // Use JSON Number format in JSON decoder
}

// Parse, validate, and return a token.
// keyFunc will receive the parsed token and should return the key for validating.
// If everything is kosher, err will be nil
func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) {
return p.ParseWithClaims(tokenString, keyFunc, &MapClaims{})
}

func (p *Parser) ParseWithClaims(tokenString string, keyFunc Keyfunc, claims Claims) (*Token, error) {
parts := strings.Split(tokenString, ".")
if len(parts) != 3 {
return nil, &ValidationError{err: "token contains an invalid number of segments", Errors: ValidationErrorMalformed}
}

var err error
token := &Token{Raw: tokenString}

// parse Header
var headerBytes []byte
if headerBytes, err = DecodeSegment(parts[0]); err != nil {
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed}
}
if err = json.Unmarshal(headerBytes, &token.Header); err != nil {
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed}
}

// parse Claims
var claimBytes []byte

if claimBytes, err = DecodeSegment(parts[1]); err != nil {
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed}
}
dec := json.NewDecoder(bytes.NewBuffer(claimBytes))
if p.UseJSONNumber {
dec.UseNumber()
}
if err = dec.Decode(&claims); err != nil {
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed}
}

token.Claims = claims

// Lookup signature method
if method, ok := token.Header["alg"].(string); ok {
if token.Method = GetSigningMethod(method); token.Method == nil {
return token, &ValidationError{err: "signing method (alg) is unavailable.", Errors: ValidationErrorUnverifiable}
}
} else {
return token, &ValidationError{err: "signing method (alg) is unspecified.", Errors: ValidationErrorUnverifiable}
}

// Verify signing method is in the required set
if p.ValidMethods != nil {
var signingMethodValid = false
var alg = token.Method.Alg()
for _, m := range p.ValidMethods {
if m == alg {
signingMethodValid = true
break
}
}
if !signingMethodValid {
// signing method is not in the listed set
return token, &ValidationError{err: fmt.Sprintf("signing method %v is invalid", alg), Errors: ValidationErrorSignatureInvalid}
}
}

// Lookup key
var key interface{}
if keyFunc == nil {
// keyFunc was not provided. short circuiting validation
return token, &ValidationError{err: "no Keyfunc was provided.", Errors: ValidationErrorUnverifiable}
}
if key, err = keyFunc(token); err != nil {
// keyFunc returned an error
return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable}
}

vErr := &ValidationError{}

// Validate Claims
if err := token.Claims.Valid(); err != nil {

// If the Claims Valid returned an error, check if it is a validation error,
// If it was another error type, create a ValidationError with a generic ClaimsInvalid flag set
if e, ok := err.(*ValidationError); !ok {
vErr = &ValidationError{err: err.Error(), Errors: ValidationErrorClaimsInvalid}
} else {
vErr = e
}
}

// Perform validation
if err = token.Method.Verify(strings.Join(parts[0:2], "."), parts[2], key); err != nil {
vErr.err = err.Error()
vErr.Errors |= ValidationErrorSignatureInvalid
}

if vErr.valid() {
token.Valid = true
return token, nil
}

return token, vErr
}
Loading

0 comments on commit 56c7810

Please sign in to comment.