From 2b0327edf60cd8e04d7ee6670b165c9580a42392 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Wed, 22 Jul 2015 13:47:56 -0700 Subject: [PATCH 01/15] added support for none, hopefully in a way you can't use accidentally --- none.go | 54 +++++++++++++++++++++++++++++++++++++++ none_test.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 none.go create mode 100644 none_test.go diff --git a/none.go b/none.go new file mode 100644 index 0000000..d697578 --- /dev/null +++ b/none.go @@ -0,0 +1,54 @@ +package jwt + +// Implements the none signing method. This is required by the spec +// but you probably should never use it. +var SigningMethodNone *signingMethodNone + +const UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed" + +var NoneSignatureTypeDisallowedError error + +type signingMethodNone struct{} +type unsafeNoneMagicConstant string + +func init() { + SigningMethodNone = &signingMethodNone{} + NoneSignatureTypeDisallowedError = &ValidationError{ + "'none' signature type is not allowed", + ValidationErrorSignatureInvalid, + } + RegisterSigningMethod(SigningMethodNone.Alg(), func() SigningMethod { + return SigningMethodNone + }) +} + +func (m *signingMethodNone) Alg() string { + return "none" +} + +// Only allow 'none' alg type if UnsafeAllowNoneSignatureType is specified as the key +func (m *signingMethodNone) Verify(signingString, signature string, key interface{}) (err error) { + // Key must be UnsafeAllowNoneSignatureType to prevent accidentally + // accepting 'none' signing method + if _, ok := key.(unsafeNoneMagicConstant); !ok { + return NoneSignatureTypeDisallowedError + } + // If signing method is none, signature must be an empty string + if signature != "" { + return &ValidationError{ + "'none' signing method with non-empty signature", + ValidationErrorSignatureInvalid, + } + } + + // Accept 'none' signing method. + return nil +} + +// Only allow 'none' signing if UnsafeAllowNoneSignatureType is specified as the key +func (m *signingMethodNone) Sign(signingString string, key interface{}) (string, error) { + if _, ok := key.(unsafeNoneMagicConstant); ok { + return "", nil + } + return "", NoneSignatureTypeDisallowedError +} diff --git a/none_test.go b/none_test.go new file mode 100644 index 0000000..29a69ef --- /dev/null +++ b/none_test.go @@ -0,0 +1,72 @@ +package jwt_test + +import ( + "github.com/dgrijalva/jwt-go" + "strings" + "testing" +) + +var noneTestData = []struct { + name string + tokenString string + alg string + key interface{} + claims map[string]interface{} + valid bool +}{ + { + "Basic", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.", + "none", + jwt.UnsafeAllowNoneSignatureType, + map[string]interface{}{"foo": "bar"}, + true, + }, + { + "Basic - no key", + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.", + "none", + nil, + map[string]interface{}{"foo": "bar"}, + false, + }, + { + "Signed", + "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw", + "none", + jwt.UnsafeAllowNoneSignatureType, + map[string]interface{}{"foo": "bar"}, + false, + }, +} + +func TestNoneVerify(t *testing.T) { + for _, data := range noneTestData { + parts := strings.Split(data.tokenString, ".") + + method := jwt.GetSigningMethod(data.alg) + err := method.Verify(strings.Join(parts[0:2], "."), parts[2], data.key) + if data.valid && err != nil { + t.Errorf("[%v] Error while verifying key: %v", data.name, err) + } + if !data.valid && err == nil { + t.Errorf("[%v] Invalid key passed validation", data.name) + } + } +} + +func TestNoneSign(t *testing.T) { + for _, data := range noneTestData { + if data.valid { + parts := strings.Split(data.tokenString, ".") + method := jwt.GetSigningMethod(data.alg) + sig, err := method.Sign(strings.Join(parts[0:2], "."), data.key) + if err != nil { + t.Errorf("[%v] Error signing token: %v", data.name, err) + } + if sig != parts[2] { + t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2]) + } + } + } +} From b728399c73fa4c9135df5ebddae4177583ab96f0 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 16 Nov 2015 12:42:37 -0800 Subject: [PATCH 02/15] signature should be populated after parsing a valid token --- parser.go | 3 ++- parser_test.go | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/parser.go b/parser.go index 40b02bc..3fc27bf 100644 --- a/parser.go +++ b/parser.go @@ -98,7 +98,8 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { } // Perform validation - if err = token.Method.Verify(strings.Join(parts[0:2], "."), parts[2], key); err != nil { + token.Signature = parts[2] + if err = token.Method.Verify(strings.Join(parts[0:2], "."), token.Signature, key); err != nil { vErr.err = err.Error() vErr.Errors |= ValidationErrorSignatureInvalid } diff --git a/parser_test.go b/parser_test.go index 97d9eee..9115017 100644 --- a/parser_test.go +++ b/parser_test.go @@ -184,9 +184,11 @@ func TestParser_Parse(t *testing.T) { if e := err.(*jwt.ValidationError).Errors; e != data.errors { t.Errorf("[%v] Errors don't match expectation. %v != %v", data.name, e, data.errors) } - } } + if data.valid && token.Signature == "" { + t.Errorf("[%v] Signature is left unpopulated after parsing", data.name) + } } } From f164e17f59b82642a3895ba065c385db6c547344 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 16 Nov 2015 13:12:11 -0800 Subject: [PATCH 03/15] version history --- VERSION_HISTORY.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/VERSION_HISTORY.md b/VERSION_HISTORY.md index 5aa3b13..9eb7ff9 100644 --- a/VERSION_HISTORY.md +++ b/VERSION_HISTORY.md @@ -1,5 +1,13 @@ ## `jwt-go` Version History +#### 2.4.0 + +* Added new type, Parser, to allow for configuration of various parsing parameters + * You can now specify a list of valid signing methods. Anything outside this set will be rejected. + * You can now opt to use the `json.Number` type instead of `float64` when parsing token JSON +* Added support for [Travis CI](https://travis-ci.org/dgrijalva/jwt-go) +* Fixed some bugs with ECDSA parsing + #### 2.3.0 * Added support for ECDSA signing methods From b863883b96690cf7ab0974ab7f48b3ae51524878 Mon Sep 17 00:00:00 2001 From: Snorre lothar von Gohren Edwin Date: Sat, 19 Dec 2015 23:49:37 +0100 Subject: [PATCH 04/15] token.go: did some changes to the checks so that it will give better error feedback for noobs who write the authorization bearer value wrong --- token.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/token.go b/token.go index d35aaa4..4751db7 100644 --- a/token.go +++ b/token.go @@ -84,6 +84,9 @@ func (t *Token) SigningString() (string, error) { // keyFunc will receive the parsed token and should return the key for validating. // If everything is kosher, err will be nil func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { + if strings.Contains(strings.ToLower(tokenString), "bearer") { + return &ValidationError{err: "tokenstring should not contain bearer", Errors: ValidationErrorMalformed} + } return new(Parser).Parse(tokenString, keyFunc) } @@ -94,9 +97,10 @@ func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { func ParseFromRequest(req *http.Request, keyFunc Keyfunc) (token *Token, err error) { // Look for an Authorization header + _ = "breakpoint" if ah := req.Header.Get("Authorization"); ah != "" { // Should be a bearer token - if len(ah) > 6 && strings.ToUpper(ah[0:6]) == "BEARER" { + if len(ah) > 6 && strings.ToUpper(ah[0:7]) == "BEARER " { return Parse(ah[7:], keyFunc) } } From 5d11392aac6e6e2aa29e65a5549c22246e4e63f8 Mon Sep 17 00:00:00 2001 From: Snorre lothar von Gohren Edwin Date: Sat, 19 Dec 2015 23:58:27 +0100 Subject: [PATCH 05/15] no breakpoints --- token.go | 1 - 1 file changed, 1 deletion(-) diff --git a/token.go b/token.go index 4751db7..1bca664 100644 --- a/token.go +++ b/token.go @@ -97,7 +97,6 @@ func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { func ParseFromRequest(req *http.Request, keyFunc Keyfunc) (token *Token, err error) { // Look for an Authorization header - _ = "breakpoint" if ah := req.Header.Get("Authorization"); ah != "" { // Should be a bearer token if len(ah) > 6 && strings.ToUpper(ah[0:7]) == "BEARER " { From 1f970af1f874086e7c4482b7ec92f9866c898280 Mon Sep 17 00:00:00 2001 From: Snorre lothar von Gohren Edwin Date: Sun, 20 Dec 2015 09:25:50 +0100 Subject: [PATCH 06/15] added right amount of return --- token.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/token.go b/token.go index 1bca664..e15ce82 100644 --- a/token.go +++ b/token.go @@ -85,7 +85,7 @@ func (t *Token) SigningString() (string, error) { // If everything is kosher, err will be nil func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { if strings.Contains(strings.ToLower(tokenString), "bearer") { - return &ValidationError{err: "tokenstring should not contain bearer", Errors: ValidationErrorMalformed} + return nil, &ValidationError{err: "tokenstring should not contain bearer", Errors: ValidationErrorMalformed} } return new(Parser).Parse(tokenString, keyFunc) } From 57b1269c416b235474f6481bbe89858279522888 Mon Sep 17 00:00:00 2001 From: Snorre lothar von Gohren Edwin Date: Tue, 22 Dec 2015 15:30:57 +0100 Subject: [PATCH 07/15] modifications on PR. Added a space in the bearer string check so that we unexpectly dont experience an base64url encoding because bearer is technically part of a valid endcoding, we think. Also moved it into a failed decoding to get a better feedback for the developer, but not do unessecary amount of string checks --- parser.go | 3 +++ token.go | 3 --- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/parser.go b/parser.go index 3fc27bf..1659ad2 100644 --- a/parser.go +++ b/parser.go @@ -26,6 +26,9 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { // parse Header var headerBytes []byte if headerBytes, err = DecodeSegment(parts[0]); err != nil { + if strings.Contains(strings.ToLower(tokenString), "bearer ") { + return token, &ValidationError{err: "tokenstring should not contain bearer", Errors: ValidationErrorMalformed} + } return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} } if err = json.Unmarshal(headerBytes, &token.Header); err != nil { diff --git a/token.go b/token.go index e15ce82..1cf267d 100644 --- a/token.go +++ b/token.go @@ -84,9 +84,6 @@ func (t *Token) SigningString() (string, error) { // keyFunc will receive the parsed token and should return the key for validating. // If everything is kosher, err will be nil func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { - if strings.Contains(strings.ToLower(tokenString), "bearer") { - return nil, &ValidationError{err: "tokenstring should not contain bearer", Errors: ValidationErrorMalformed} - } return new(Parser).Parse(tokenString, keyFunc) } From b47bdbc660b24298ab475498ab94f387c60cf373 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Tue, 22 Dec 2015 13:27:59 -0800 Subject: [PATCH 08/15] Added some clarification and (hopefully) helpful documentation --- README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/README.md b/README.md index 001c0a3..8c83780 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,35 @@ This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull While we try to make it obvious when we make breaking changes, there isn't a great mechanism for pushing announcements out to users. You may want to use this alternative package include: `gopkg.in/dgrijalva/jwt-go.v2`. It will do the right thing WRT semantic versioning. +## Usage Tips + +### Signing vs Encryption + +A token is simply a JSON object that is signed by its author. this tells you exactly two things about the data: + +* The author of the token was in the possession of the signing secret +* The data has not been modified since it was signed + +It's important to know that JWT does not provide encryption, which means anyone who has access to the token can read its contents. If you need to protect (encrypt) the data, there is a companion spec, `JWE`, that provides this functionality. JWE is currently outside the scope of this library. + +### Choosing a Signing Method + +There are several signing methods available, and you should probably take the time to learn about the various options before choosing one. The principal design decision is most likely going to be symmetric vs asymmetric. + +Symmetric signing methods, such as HSA, use only a single secret. This is probably the simplest signing method to use since any `[]byte` can be used as a valid secret. They are also slightly computationally faster to use, though this rarely is enough to matter. Symmetric signing methods work the best when both producers and consumers of tokens are trusted, or even the same system. Since the same secret is used to both sign and validate tokens, you can't easily distribute the key for validation. + +Asymmetric signing methods, such as RSA, use different keys for signing and verifying tokens. This makes it possible to produce tokens with a private key, and allow any consumer to access the public key for verification. + +### JWT and OAuth + +It's worth mentioning that OAuth and JWT are not the same thing. A JWT token is simply a signed JSON object. It can be used anywhere such a thing is useful. There is some confusion, though, as JWT is the most common type of bearer token used in OAuth2 authentication. + +Without going too far down the rabbit hole, here's a description of the interaction of these technologies: + +* OAuth is a protocol for allowing an identity provider to be separate from the service a user is logging in to. For example, whenever you use Facebook to log into a different service (Yelp, Spotify, etc), you are using OAuth. +* OAuth defines several options for passing around authentication data. One popular method is called a "bearer token". A bearer token is simply a string that _should_ only be held by an authenticated user. Thus, simply presenting this token proves your identity. You can probably derive from here why a JWT might make a good bearer token. +* Because bearer tokens are used for authentication, it's important they're kept secret. This is why transactions that use bearer tokens typically happen over SSL. + ## More Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go). From 2240de772c17d0e303c9bbc04bca67ffdfef32c4 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Tue, 22 Dec 2015 13:53:19 -0800 Subject: [PATCH 09/15] added supported signing methods --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8c83780..6be8590 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The part in the middle is the interesting bit. It's called the Claims and conta ## What's in the box? -This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are RSA256 and HMAC SHA256, though hooks are present for adding your own. +This library supports the parsing and verification as well as the generation and signing of JWTs. Current supported signing algorithms are HMAC SHA, RSA, RSA-PSS, and ECDSA, though hooks are present for adding your own. ## Parse and Verify From ca46641b15f1c6132c47599e44af5851fd684e4f Mon Sep 17 00:00:00 2001 From: Snorre lothar von Gohren Edwin Date: Wed, 23 Dec 2015 09:43:00 +0100 Subject: [PATCH 10/15] PR updated, faster string method and more reasonable message feedback --- parser.go | 4 ++-- token.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/parser.go b/parser.go index 1659ad2..a078404 100644 --- a/parser.go +++ b/parser.go @@ -26,8 +26,8 @@ func (p *Parser) Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { // parse Header var headerBytes []byte if headerBytes, err = DecodeSegment(parts[0]); err != nil { - if strings.Contains(strings.ToLower(tokenString), "bearer ") { - return token, &ValidationError{err: "tokenstring should not contain bearer", Errors: ValidationErrorMalformed} + if strings.HasPrefix(strings.ToLower(tokenString), "bearer ") { + return token, &ValidationError{err: "tokenstring should not contain 'bearer '", Errors: ValidationErrorMalformed} } return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} } diff --git a/token.go b/token.go index 1cf267d..fde0116 100644 --- a/token.go +++ b/token.go @@ -97,7 +97,7 @@ func ParseFromRequest(req *http.Request, keyFunc Keyfunc) (token *Token, err err if ah := req.Header.Get("Authorization"); ah != "" { // Should be a bearer token if len(ah) > 6 && strings.ToUpper(ah[0:7]) == "BEARER " { - return Parse(ah[7:], keyFunc) + return Parse(ah, keyFunc) } } From fea509ebfec2fd8f0b61fa9a0df05695a830d78b Mon Sep 17 00:00:00 2001 From: Snorre lothar von Gohren Edwin Date: Wed, 23 Dec 2015 09:45:17 +0100 Subject: [PATCH 11/15] pushed a test change --- token.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/token.go b/token.go index fde0116..1cf267d 100644 --- a/token.go +++ b/token.go @@ -97,7 +97,7 @@ func ParseFromRequest(req *http.Request, keyFunc Keyfunc) (token *Token, err err if ah := req.Header.Get("Authorization"); ah != "" { // Should be a bearer token if len(ah) > 6 && strings.ToUpper(ah[0:7]) == "BEARER " { - return Parse(ah, keyFunc) + return Parse(ah[7:], keyFunc) } } From 574cd7c2458b12e41ec4bff5b79b6eb9fa22b914 Mon Sep 17 00:00:00 2001 From: matm Date: Wed, 30 Dec 2015 18:50:00 +0100 Subject: [PATCH 12/15] README: fix return arguments --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6be8590..bf0100f 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ Parsing and verifying tokens is pretty straight forward. You pass in the token if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) } - return myLookupKey(token.Header["kid"]) + return myLookupKey(token.Header["kid"]), nil }) if err == nil && token.Valid { From 9a4b9f2ac1f7685147a5c01544e3b922dbc1f4a0 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Mon, 7 Mar 2016 15:28:38 -0800 Subject: [PATCH 13/15] add 1.6 to travis.yml --- .travis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.travis.yml b/.travis.yml index d608914..5d3b205 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,4 +4,5 @@ go: - 1.3.3 - 1.4.2 - 1.5 + - 1.6 - tip From b7d25eabfbee3d330c9893172d8195e0b76d2939 Mon Sep 17 00:00:00 2001 From: Bo-Yi Wu Date: Tue, 22 Mar 2016 09:07:45 +0800 Subject: [PATCH 14/15] Test latest 1.4.x and 1.3.x version. --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 5d3b205..bde823d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,8 @@ language: go go: - - 1.3.3 - - 1.4.2 + - 1.3 + - 1.4 - 1.5 - 1.6 - tip From b446e078d613dc395bf50b08884acbeafb19ce50 Mon Sep 17 00:00:00 2001 From: Dave Grijalva Date: Thu, 31 Mar 2016 11:13:52 -0700 Subject: [PATCH 15/15] version history update --- VERSION_HISTORY.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/VERSION_HISTORY.md b/VERSION_HISTORY.md index 9eb7ff9..14cf1a8 100644 --- a/VERSION_HISTORY.md +++ b/VERSION_HISTORY.md @@ -1,5 +1,13 @@ ## `jwt-go` Version History +#### 2.5.0 + +This will likely be the last backwards compatible release before 3.0.0. + +* Added support for signing method none. You shouldn't use this. The API tries to make this clear. +* Updated/fixed some documentation +* Added more helpful error message when trying to parse tokens that begin with `BEARER ` + #### 2.4.0 * Added new type, Parser, to allow for configuration of various parsing parameters