├── .gitignore ├── test ├── hmacTestKey ├── ec256-public.pem ├── ec384-public.pem ├── ec256-private.pem ├── ec512-public.pem ├── ec384-private.pem ├── ec512-private.pem ├── sample_key.pub └── sample_key ├── .travis.yml ├── doc.go ├── signing_method.go ├── LICENSE ├── errors.go ├── ecdsa_utils.go ├── example_test.go ├── rsa_utils.go ├── hmac.go ├── rsa.go ├── hmac_test.go ├── VERSION_HISTORY.md ├── rsa_pss.go ├── ecdsa_test.go ├── ecdsa.go ├── rsa_pss_test.go ├── README.md ├── cmd └── jwt │ └── app.go ├── rsa_test.go ├── jwt_test.go └── jwt.go /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | bin 3 | 4 | 5 | -------------------------------------------------------------------------------- /test/hmacTestKey: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pl/jwt-go/master/test/hmacTestKey -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.3.3 5 | - 1.4.2 6 | - 1.5 7 | - tip 8 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Package jwt is a Go implementation of JSON Web Tokens: http://self-issued.info/docs/draft-jones-json-web-token.html 2 | // 3 | // See README.md for more info. 4 | package jwt 5 | -------------------------------------------------------------------------------- /test/ec256-public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYD54V/vp+54P9DXarYqx4MPcm+HK 3 | RIQzNasYSoRQHQ/6S6Ps8tpMcT+KvIIC8W/e9k0W7Cm72M1P9jU7SLf/vg== 4 | -----END PUBLIC KEY----- 5 | -------------------------------------------------------------------------------- /test/ec384-public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE2Gu6yWlO33izKqwMemnWRTUDqWlB/1Mp 3 | W2S64jizjeWBVcLVVKTtRXxF2VCEKabUT7XOYsSD2OufMoQUm+oq3yCVEj/WmE35 4 | SRipP5g5CuLfJlgc4Yg+Qeo4PXBBoRoA 5 | -----END PUBLIC KEY----- 6 | -------------------------------------------------------------------------------- /test/ec256-private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MHcCAQEEIAh5qA3rmqQQuu0vbKV/+zouz/y/Iy2pLpIcWUSyImSwoAoGCCqGSM49 3 | AwEHoUQDQgAEYD54V/vp+54P9DXarYqx4MPcm+HKRIQzNasYSoRQHQ/6S6Ps8tpM 4 | cT+KvIIC8W/e9k0W7Cm72M1P9jU7SLf/vg== 5 | -----END EC PRIVATE KEY----- 6 | -------------------------------------------------------------------------------- /test/ec512-public.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQACVyLsNdjFM6R4IImvTzgRWF0sWjh 3 | ihmzIyMgyPuqu8IuyzMNx4G2jpoCKhRu9qPCQUMGDeCG1x3/n/OgkWNQANsB82x7 4 | 7eiIZAl0zcQRH32tcjFILvJ/XCihdoi4MkCnCqlt9/HxjsP590ZtmHfxAeertq5w 5 | 9vakvpzjPXhkvoMt/Tk= 6 | -----END PUBLIC KEY----- 7 | -------------------------------------------------------------------------------- /test/ec384-private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MIGkAgEBBDCaCvMHKhcG/qT7xsNLYnDT7sE/D+TtWIol1ROdaK1a564vx5pHbsRy 3 | SEKcIxISi1igBwYFK4EEACKhZANiAATYa7rJaU7feLMqrAx6adZFNQOpaUH/Uylb 4 | ZLriOLON5YFVwtVUpO1FfEXZUIQpptRPtc5ixIPY658yhBSb6irfIJUSP9aYTflJ 5 | GKk/mDkK4t8mWBzhiD5B6jg9cEGhGgA= 6 | -----END EC PRIVATE KEY----- 7 | -------------------------------------------------------------------------------- /test/ec512-private.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PRIVATE KEY----- 2 | MIHcAgEBBEIB0pE4uFaWRx7t03BsYlYvF1YvKaBGyvoakxnodm9ou0R9wC+sJAjH 3 | QZZJikOg4SwNqgQ/hyrOuDK2oAVHhgVGcYmgBwYFK4EEACOhgYkDgYYABAAJXIuw 4 | 12MUzpHggia9POBFYXSxaOGKGbMjIyDI+6q7wi7LMw3HgbaOmgIqFG72o8JBQwYN 5 | 4IbXHf+f86CRY1AA2wHzbHvt6IhkCXTNxBEffa1yMUgu8n9cKKF2iLgyQKcKqW33 6 | 8fGOw/n3Rm2Yd/EB56u2rnD29qS+nOM9eGS+gy39OQ== 7 | -----END EC PRIVATE KEY----- 8 | -------------------------------------------------------------------------------- /test/sample_key.pub: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4f5wg5l2hKsTeNem/V41 3 | fGnJm6gOdrj8ym3rFkEU/wT8RDtnSgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7 4 | mCpz9Er5qLaMXJwZxzHzAahlfA0icqabvJOMvQtzD6uQv6wPEyZtDTWiQi9AXwBp 5 | HssPnpYGIn20ZZuNlX2BrClciHhCPUIIZOQn/MmqTD31jSyjoQoV7MhhMTATKJx2 6 | XrHhR+1DcKJzQBSTAGnpYVaqpsARap+nwRipr3nUTuxyGohBTSmjJ2usSeQXHI3b 7 | ODIRe1AuTyHceAbewn8b462yEWKARdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy 8 | 7wIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /signing_method.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | var signingMethods = map[string]func() SigningMethod{} 4 | 5 | // Signing method 6 | type SigningMethod interface { 7 | Verify(signingString, signature string, key interface{}) error 8 | Sign(signingString string, key interface{}) (string, error) 9 | Alg() string 10 | } 11 | 12 | // Register the "alg" name and a factory function for signing method. 13 | // This is typically done during init() in the method's implementation 14 | func RegisterSigningMethod(alg string, f func() SigningMethod) { 15 | signingMethods[alg] = f 16 | } 17 | 18 | // Get a signing method from an "alg" string 19 | func GetSigningMethod(alg string) (method SigningMethod) { 20 | if methodF, ok := signingMethods[alg]; ok { 21 | method = methodF() 22 | } 23 | return 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 Dave Grijalva 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | 9 | -------------------------------------------------------------------------------- /errors.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // Error constants 8 | var ( 9 | ErrInvalidKey = errors.New("key is invalid or of invalid type") 10 | ErrHashUnavailable = errors.New("the requested hash function is unavailable") 11 | ErrNoTokenInRequest = errors.New("no token present in request") 12 | ) 13 | 14 | // The errors that might occur when parsing and validating a token 15 | const ( 16 | ValidationErrorMalformed uint32 = 1 << iota // Token is malformed 17 | ValidationErrorUnverifiable // Token could not be verified because of signing problems 18 | ValidationErrorSignatureInvalid // Signature validation failed 19 | ValidationErrorExpired // Exp validation failed 20 | ValidationErrorNotValidYet // NBF validation failed 21 | ) 22 | 23 | // The error from Parse if token is not valid 24 | type ValidationError struct { 25 | err string 26 | Errors uint32 // bitfield. see ValidationError... constants 27 | } 28 | 29 | // Validation error is an error type 30 | func (e ValidationError) Error() string { 31 | if e.err == "" { 32 | return "token is invalid" 33 | } 34 | return e.err 35 | } 36 | 37 | // No errors 38 | func (e *ValidationError) valid() bool { 39 | if e.Errors > 0 { 40 | return false 41 | } 42 | return true 43 | } 44 | -------------------------------------------------------------------------------- /ecdsa_utils.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "crypto/x509" 6 | "encoding/pem" 7 | "errors" 8 | ) 9 | 10 | var ( 11 | ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key") 12 | ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key") 13 | ) 14 | 15 | // Parse PEM encoded Elliptic Curve Private Key Structure 16 | func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) { 17 | var err error 18 | 19 | // Parse PEM block 20 | var block *pem.Block 21 | if block, _ = pem.Decode(key); block == nil { 22 | return nil, ErrKeyMustBePEMEncoded 23 | } 24 | 25 | // Parse the key 26 | var parsedKey interface{} 27 | if parsedKey, err = x509.ParseECPrivateKey(block.Bytes); err != nil { 28 | return nil, err 29 | } 30 | 31 | var pkey *ecdsa.PrivateKey 32 | var ok bool 33 | if pkey, ok = parsedKey.(*ecdsa.PrivateKey); !ok { 34 | return nil, ErrNotECPrivateKey 35 | } 36 | 37 | return pkey, nil 38 | } 39 | 40 | // Parse PEM encoded PKCS1 or PKCS8 public key 41 | func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) { 42 | var err error 43 | 44 | // Parse PEM block 45 | var block *pem.Block 46 | if block, _ = pem.Decode(key); block == nil { 47 | return nil, ErrKeyMustBePEMEncoded 48 | } 49 | 50 | // Parse the key 51 | var parsedKey interface{} 52 | if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { 53 | if cert, err := x509.ParseCertificate(block.Bytes); err == nil { 54 | parsedKey = cert.PublicKey 55 | } else { 56 | return nil, err 57 | } 58 | } 59 | 60 | var pkey *ecdsa.PublicKey 61 | var ok bool 62 | if pkey, ok = parsedKey.(*ecdsa.PublicKey); !ok { 63 | return nil, ErrNotECPublicKey 64 | } 65 | 66 | return pkey, nil 67 | } 68 | -------------------------------------------------------------------------------- /test/sample_key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA4f5wg5l2hKsTeNem/V41fGnJm6gOdrj8ym3rFkEU/wT8RDtn 3 | SgFEZOQpHEgQ7JL38xUfU0Y3g6aYw9QT0hJ7mCpz9Er5qLaMXJwZxzHzAahlfA0i 4 | cqabvJOMvQtzD6uQv6wPEyZtDTWiQi9AXwBpHssPnpYGIn20ZZuNlX2BrClciHhC 5 | PUIIZOQn/MmqTD31jSyjoQoV7MhhMTATKJx2XrHhR+1DcKJzQBSTAGnpYVaqpsAR 6 | ap+nwRipr3nUTuxyGohBTSmjJ2usSeQXHI3bODIRe1AuTyHceAbewn8b462yEWKA 7 | Rdpd9AjQW5SIVPfdsz5B6GlYQ5LdYKtznTuy7wIDAQABAoIBAQCwia1k7+2oZ2d3 8 | n6agCAbqIE1QXfCmh41ZqJHbOY3oRQG3X1wpcGH4Gk+O+zDVTV2JszdcOt7E5dAy 9 | MaomETAhRxB7hlIOnEN7WKm+dGNrKRvV0wDU5ReFMRHg31/Lnu8c+5BvGjZX+ky9 10 | POIhFFYJqwCRlopGSUIxmVj5rSgtzk3iWOQXr+ah1bjEXvlxDOWkHN6YfpV5ThdE 11 | KdBIPGEVqa63r9n2h+qazKrtiRqJqGnOrHzOECYbRFYhexsNFz7YT02xdfSHn7gM 12 | IvabDDP/Qp0PjE1jdouiMaFHYnLBbgvlnZW9yuVf/rpXTUq/njxIXMmvmEyyvSDn 13 | FcFikB8pAoGBAPF77hK4m3/rdGT7X8a/gwvZ2R121aBcdPwEaUhvj/36dx596zvY 14 | mEOjrWfZhF083/nYWE2kVquj2wjs+otCLfifEEgXcVPTnEOPO9Zg3uNSL0nNQghj 15 | FuD3iGLTUBCtM66oTe0jLSslHe8gLGEQqyMzHOzYxNqibxcOZIe8Qt0NAoGBAO+U 16 | I5+XWjWEgDmvyC3TrOSf/KCGjtu0TSv30ipv27bDLMrpvPmD/5lpptTFwcxvVhCs 17 | 2b+chCjlghFSWFbBULBrfci2FtliClOVMYrlNBdUSJhf3aYSG2Doe6Bgt1n2CpNn 18 | /iu37Y3NfemZBJA7hNl4dYe+f+uzM87cdQ214+jrAoGAXA0XxX8ll2+ToOLJsaNT 19 | OvNB9h9Uc5qK5X5w+7G7O998BN2PC/MWp8H+2fVqpXgNENpNXttkRm1hk1dych86 20 | EunfdPuqsX+as44oCyJGFHVBnWpm33eWQw9YqANRI+pCJzP08I5WK3osnPiwshd+ 21 | hR54yjgfYhBFNI7B95PmEQkCgYBzFSz7h1+s34Ycr8SvxsOBWxymG5zaCsUbPsL0 22 | 4aCgLScCHb9J+E86aVbbVFdglYa5Id7DPTL61ixhl7WZjujspeXZGSbmq0Kcnckb 23 | mDgqkLECiOJW2NHP/j0McAkDLL4tysF8TLDO8gvuvzNC+WQ6drO2ThrypLVZQ+ry 24 | eBIPmwKBgEZxhqa0gVvHQG/7Od69KWj4eJP28kq13RhKay8JOoN0vPmspXJo1HY3 25 | CKuHRG+AP579dncdUnOMvfXOtkdM4vk0+hWASBQzM9xzVcztCa+koAugjVaLS9A+ 26 | 9uQoqEeVNTckxx0S2bYevRy7hGQmUJTyQm3j1zEUR5jpdbL83Fbq 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /example_test.go: -------------------------------------------------------------------------------- 1 | package jwt_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dgrijalva/jwt-go" 6 | "time" 7 | ) 8 | 9 | func ExampleParse(myToken string, myLookupKey func(interface{}) (interface{}, error)) { 10 | token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) { 11 | return myLookupKey(token.Header["kid"]) 12 | }) 13 | 14 | if err == nil && token.Valid { 15 | fmt.Println("Your token is valid. I like your style.") 16 | } else { 17 | fmt.Println("This token is terrible! I cannot accept this.") 18 | } 19 | } 20 | 21 | func ExampleNew(mySigningKey []byte) (string, error) { 22 | // Create the token 23 | token := jwt.New(jwt.SigningMethodHS256) 24 | // Set some claims 25 | token.Claims["foo"] = "bar" 26 | token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix() 27 | // Sign and get the complete encoded token as a string 28 | tokenString, err := token.SignedString(mySigningKey) 29 | return tokenString, err 30 | } 31 | 32 | func ExampleParse_errorChecking(myToken string, myLookupKey func(interface{}) (interface{}, error)) { 33 | token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) { 34 | return myLookupKey(token.Header["kid"]) 35 | }) 36 | 37 | if token.Valid { 38 | fmt.Println("You look nice today") 39 | } else if ve, ok := err.(*jwt.ValidationError); ok { 40 | if ve.Errors&jwt.ValidationErrorMalformed != 0 { 41 | fmt.Println("That's not even a token") 42 | } else if ve.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 { 43 | // Token is either expired or not active yet 44 | fmt.Println("Timing is everything") 45 | } else { 46 | fmt.Println("Couldn't handle this token:", err) 47 | } 48 | } else { 49 | fmt.Println("Couldn't handle this token:", err) 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /rsa_utils.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "crypto/rsa" 5 | "crypto/x509" 6 | "encoding/pem" 7 | "errors" 8 | ) 9 | 10 | var ( 11 | ErrKeyMustBePEMEncoded = errors.New("Invalid Key: Key must be PEM encoded PKCS1 or PKCS8 private key") 12 | ErrNotRSAPrivateKey = errors.New("Key is not a valid RSA private key") 13 | ) 14 | 15 | // Parse PEM encoded PKCS1 or PKCS8 private key 16 | func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) { 17 | var err error 18 | 19 | // Parse PEM block 20 | var block *pem.Block 21 | if block, _ = pem.Decode(key); block == nil { 22 | return nil, ErrKeyMustBePEMEncoded 23 | } 24 | 25 | var parsedKey interface{} 26 | if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil { 27 | if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { 28 | return nil, err 29 | } 30 | } 31 | 32 | var pkey *rsa.PrivateKey 33 | var ok bool 34 | if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok { 35 | return nil, ErrNotRSAPrivateKey 36 | } 37 | 38 | return pkey, nil 39 | } 40 | 41 | // Parse PEM encoded PKCS1 or PKCS8 public key 42 | func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) { 43 | var err error 44 | 45 | // Parse PEM block 46 | var block *pem.Block 47 | if block, _ = pem.Decode(key); block == nil { 48 | return nil, ErrKeyMustBePEMEncoded 49 | } 50 | 51 | // Parse the key 52 | var parsedKey interface{} 53 | if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { 54 | if cert, err := x509.ParseCertificate(block.Bytes); err == nil { 55 | parsedKey = cert.PublicKey 56 | } else { 57 | return nil, err 58 | } 59 | } 60 | 61 | var pkey *rsa.PublicKey 62 | var ok bool 63 | if pkey, ok = parsedKey.(*rsa.PublicKey); !ok { 64 | return nil, ErrNotRSAPrivateKey 65 | } 66 | 67 | return pkey, nil 68 | } 69 | -------------------------------------------------------------------------------- /hmac.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "crypto" 5 | "crypto/hmac" 6 | "errors" 7 | ) 8 | 9 | // Implements the HMAC-SHA family of signing methods signing methods 10 | type SigningMethodHMAC struct { 11 | Name string 12 | Hash crypto.Hash 13 | } 14 | 15 | // Specific instances for HS256 and company 16 | var ( 17 | SigningMethodHS256 *SigningMethodHMAC 18 | SigningMethodHS384 *SigningMethodHMAC 19 | SigningMethodHS512 *SigningMethodHMAC 20 | ErrSignatureInvalid = errors.New("signature is invalid") 21 | ) 22 | 23 | func init() { 24 | // HS256 25 | SigningMethodHS256 = &SigningMethodHMAC{"HS256", crypto.SHA256} 26 | RegisterSigningMethod(SigningMethodHS256.Alg(), func() SigningMethod { 27 | return SigningMethodHS256 28 | }) 29 | 30 | // HS384 31 | SigningMethodHS384 = &SigningMethodHMAC{"HS384", crypto.SHA384} 32 | RegisterSigningMethod(SigningMethodHS384.Alg(), func() SigningMethod { 33 | return SigningMethodHS384 34 | }) 35 | 36 | // HS512 37 | SigningMethodHS512 = &SigningMethodHMAC{"HS512", crypto.SHA512} 38 | RegisterSigningMethod(SigningMethodHS512.Alg(), func() SigningMethod { 39 | return SigningMethodHS512 40 | }) 41 | } 42 | 43 | func (m *SigningMethodHMAC) Alg() string { 44 | return m.Name 45 | } 46 | 47 | func (m *SigningMethodHMAC) Verify(signingString, signature string, key interface{}) error { 48 | if keyBytes, ok := key.([]byte); ok { 49 | var sig []byte 50 | var err error 51 | if sig, err = DecodeSegment(signature); err == nil { 52 | if !m.Hash.Available() { 53 | return ErrHashUnavailable 54 | } 55 | 56 | hasher := hmac.New(m.Hash.New, keyBytes) 57 | hasher.Write([]byte(signingString)) 58 | 59 | if !hmac.Equal(sig, hasher.Sum(nil)) { 60 | err = ErrSignatureInvalid 61 | } 62 | } 63 | return err 64 | } 65 | 66 | return ErrInvalidKey 67 | } 68 | 69 | // Implements the Sign method from SigningMethod for this signing method. 70 | // Key must be []byte 71 | func (m *SigningMethodHMAC) Sign(signingString string, key interface{}) (string, error) { 72 | if keyBytes, ok := key.([]byte); ok { 73 | if !m.Hash.Available() { 74 | return "", ErrHashUnavailable 75 | } 76 | 77 | hasher := hmac.New(m.Hash.New, keyBytes) 78 | hasher.Write([]byte(signingString)) 79 | 80 | return EncodeSegment(hasher.Sum(nil)), nil 81 | } 82 | 83 | return "", ErrInvalidKey 84 | } 85 | -------------------------------------------------------------------------------- /rsa.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "crypto" 5 | "crypto/rand" 6 | "crypto/rsa" 7 | ) 8 | 9 | // Implements the RSA family of signing methods signing methods 10 | type SigningMethodRSA struct { 11 | Name string 12 | Hash crypto.Hash 13 | } 14 | 15 | // Specific instances for RS256 and company 16 | var ( 17 | SigningMethodRS256 *SigningMethodRSA 18 | SigningMethodRS384 *SigningMethodRSA 19 | SigningMethodRS512 *SigningMethodRSA 20 | ) 21 | 22 | func init() { 23 | // RS256 24 | SigningMethodRS256 = &SigningMethodRSA{"RS256", crypto.SHA256} 25 | RegisterSigningMethod(SigningMethodRS256.Alg(), func() SigningMethod { 26 | return SigningMethodRS256 27 | }) 28 | 29 | // RS384 30 | SigningMethodRS384 = &SigningMethodRSA{"RS384", crypto.SHA384} 31 | RegisterSigningMethod(SigningMethodRS384.Alg(), func() SigningMethod { 32 | return SigningMethodRS384 33 | }) 34 | 35 | // RS512 36 | SigningMethodRS512 = &SigningMethodRSA{"RS512", crypto.SHA512} 37 | RegisterSigningMethod(SigningMethodRS512.Alg(), func() SigningMethod { 38 | return SigningMethodRS512 39 | }) 40 | } 41 | 42 | func (m *SigningMethodRSA) Alg() string { 43 | return m.Name 44 | } 45 | 46 | // Implements the Verify method from SigningMethod 47 | // For this signing method, must be either a PEM encoded PKCS1 or PKCS8 RSA public key as 48 | // []byte, or an rsa.PublicKey structure. 49 | func (m *SigningMethodRSA) Verify(signingString, signature string, key interface{}) error { 50 | var err error 51 | 52 | // Decode the signature 53 | var sig []byte 54 | if sig, err = DecodeSegment(signature); err != nil { 55 | return err 56 | } 57 | 58 | var rsaKey *rsa.PublicKey 59 | 60 | switch k := key.(type) { 61 | case []byte: 62 | if rsaKey, err = ParseRSAPublicKeyFromPEM(k); err != nil { 63 | return err 64 | } 65 | case *rsa.PublicKey: 66 | rsaKey = k 67 | default: 68 | return ErrInvalidKey 69 | } 70 | 71 | // Create hasher 72 | if !m.Hash.Available() { 73 | return ErrHashUnavailable 74 | } 75 | hasher := m.Hash.New() 76 | hasher.Write([]byte(signingString)) 77 | 78 | // Verify the signature 79 | return rsa.VerifyPKCS1v15(rsaKey, m.Hash, hasher.Sum(nil), sig) 80 | } 81 | 82 | // Implements the Sign method from SigningMethod 83 | // For this signing method, must be either a PEM encoded PKCS1 or PKCS8 RSA private key as 84 | // []byte, or an rsa.PrivateKey structure. 85 | func (m *SigningMethodRSA) Sign(signingString string, key interface{}) (string, error) { 86 | var err error 87 | var rsaKey *rsa.PrivateKey 88 | 89 | switch k := key.(type) { 90 | case []byte: 91 | if rsaKey, err = ParseRSAPrivateKeyFromPEM(k); err != nil { 92 | return "", err 93 | } 94 | case *rsa.PrivateKey: 95 | rsaKey = k 96 | default: 97 | return "", ErrInvalidKey 98 | } 99 | 100 | // Create the hasher 101 | if !m.Hash.Available() { 102 | return "", ErrHashUnavailable 103 | } 104 | 105 | hasher := m.Hash.New() 106 | hasher.Write([]byte(signingString)) 107 | 108 | // Sign the string and return the encoded bytes 109 | if sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil)); err == nil { 110 | return EncodeSegment(sigBytes), nil 111 | } else { 112 | return "", err 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /hmac_test.go: -------------------------------------------------------------------------------- 1 | package jwt_test 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | "io/ioutil" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | var hmacTestData = []struct { 11 | name string 12 | tokenString string 13 | alg string 14 | claims map[string]interface{} 15 | valid bool 16 | }{ 17 | { 18 | "web sample", 19 | "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk", 20 | "HS256", 21 | map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, 22 | true, 23 | }, 24 | { 25 | "HS384", 26 | "eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.KWZEuOD5lbBxZ34g7F-SlVLAQ_r5KApWNWlZIIMyQVz5Zs58a7XdNzj5_0EcNoOy", 27 | "HS384", 28 | map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, 29 | true, 30 | }, 31 | { 32 | "HS512", 33 | "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEuMzAwODE5MzhlKzA5LCJodHRwOi8vZXhhbXBsZS5jb20vaXNfcm9vdCI6dHJ1ZSwiaXNzIjoiam9lIn0.CN7YijRX6Aw1n2jyI2Id1w90ja-DEMYiWixhYCyHnrZ1VfJRaFQz1bEbjjA5Fn4CLYaUG432dEYmSbS4Saokmw", 34 | "HS512", 35 | map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, 36 | true, 37 | }, 38 | { 39 | "web sample: invalid", 40 | "eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ.dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXo", 41 | "HS256", 42 | map[string]interface{}{"iss": "joe", "exp": 1300819380, "http://example.com/is_root": true}, 43 | false, 44 | }, 45 | } 46 | 47 | // Sample data from http://tools.ietf.org/html/draft-jones-json-web-signature-04#appendix-A.1 48 | var hmacTestKey, _ = ioutil.ReadFile("test/hmacTestKey") 49 | 50 | func TestHMACVerify(t *testing.T) { 51 | for _, data := range hmacTestData { 52 | parts := strings.Split(data.tokenString, ".") 53 | 54 | method := jwt.GetSigningMethod(data.alg) 55 | err := method.Verify(strings.Join(parts[0:2], "."), parts[2], hmacTestKey) 56 | if data.valid && err != nil { 57 | t.Errorf("[%v] Error while verifying key: %v", data.name, err) 58 | } 59 | if !data.valid && err == nil { 60 | t.Errorf("[%v] Invalid key passed validation", data.name) 61 | } 62 | } 63 | } 64 | 65 | func TestHMACSign(t *testing.T) { 66 | for _, data := range hmacTestData { 67 | if data.valid { 68 | parts := strings.Split(data.tokenString, ".") 69 | method := jwt.GetSigningMethod(data.alg) 70 | sig, err := method.Sign(strings.Join(parts[0:2], "."), hmacTestKey) 71 | if err != nil { 72 | t.Errorf("[%v] Error signing token: %v", data.name, err) 73 | } 74 | if sig != parts[2] { 75 | t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2]) 76 | } 77 | } 78 | } 79 | } 80 | 81 | func BenchmarkHS256Signing(b *testing.B) { 82 | benchmarkSigning(b, jwt.SigningMethodHS256, hmacTestKey) 83 | } 84 | 85 | func BenchmarkHS384Signing(b *testing.B) { 86 | benchmarkSigning(b, jwt.SigningMethodHS384, hmacTestKey) 87 | } 88 | 89 | func BenchmarkHS512Signing(b *testing.B) { 90 | benchmarkSigning(b, jwt.SigningMethodHS512, hmacTestKey) 91 | } 92 | -------------------------------------------------------------------------------- /VERSION_HISTORY.md: -------------------------------------------------------------------------------- 1 | ## `jwt-go` Version History 2 | 3 | #### 2.3.0 4 | 5 | * Added support for ECDSA signing methods 6 | * Added support for RSA PSS signing methods (requires go v1.4) 7 | 8 | #### 2.2.0 9 | 10 | * Gracefully handle a `nil` `Keyfunc` being passed to `Parse`. Result will now be the parsed token and an error, instead of a panic. 11 | 12 | #### 2.1.0 13 | 14 | Backwards compatible API change that was missed in 2.0.0. 15 | 16 | * The `SignedString` method on `Token` now takes `interface{}` instead of `[]byte` 17 | 18 | #### 2.0.0 19 | 20 | There were two major reasons for breaking backwards compatibility with this update. The first was a refactor required to expand the width of the RSA and HMAC-SHA signing implementations. There will likely be no required code changes to support this change. 21 | 22 | The second update, while unfortunately requiring a small change in integration, is required to open up this library to other signing methods. Not all keys used for all signing methods have a single standard on-disk representation. Requiring `[]byte` as the type for all keys proved too limiting. Additionally, this implementation allows for pre-parsed tokens to be reused, which might matter in an application that parses a high volume of tokens with a small set of keys. Backwards compatibilty has been maintained for passing `[]byte` to the RSA signing methods, but they will also accept `*rsa.PublicKey` and `*rsa.PrivateKey`. 23 | 24 | It is likely the only integration change required here will be to change `func(t *jwt.Token) ([]byte, error)` to `func(t *jwt.Token) (interface{}, error)` when calling `Parse`. 25 | 26 | * **Compatibility Breaking Changes** 27 | * `SigningMethodHS256` is now `*SigningMethodHMAC` instead of `type struct` 28 | * `SigningMethodRS256` is now `*SigningMethodRSA` instead of `type struct` 29 | * `KeyFunc` now returns `interface{}` instead of `[]byte` 30 | * `SigningMethod.Sign` now takes `interface{}` instead of `[]byte` for the key 31 | * `SigningMethod.Verify` now takes `interface{}` instead of `[]byte` for the key 32 | * Renamed type `SigningMethodHS256` to `SigningMethodHMAC`. Specific sizes are now just instances of this type. 33 | * Added public package global `SigningMethodHS256` 34 | * Added public package global `SigningMethodHS384` 35 | * Added public package global `SigningMethodHS512` 36 | * Renamed type `SigningMethodRS256` to `SigningMethodRSA`. Specific sizes are now just instances of this type. 37 | * Added public package global `SigningMethodRS256` 38 | * Added public package global `SigningMethodRS384` 39 | * Added public package global `SigningMethodRS512` 40 | * Moved sample private key for HMAC tests from an inline value to a file on disk. Value is unchanged. 41 | * Refactored the RSA implementation to be easier to read 42 | * Exposed helper methods `ParseRSAPrivateKeyFromPEM` and `ParseRSAPublicKeyFromPEM` 43 | 44 | #### 1.0.2 45 | 46 | * Fixed bug in parsing public keys from certificates 47 | * Added more tests around the parsing of keys for RS256 48 | * Code refactoring in RS256 implementation. No functional changes 49 | 50 | #### 1.0.1 51 | 52 | * Fixed panic if RS256 signing method was passed an invalid key 53 | 54 | #### 1.0.0 55 | 56 | * First versioned release 57 | * API stabilized 58 | * Supports creating, signing, parsing, and validating JWT tokens 59 | * Supports RS256 and HS256 signing methods -------------------------------------------------------------------------------- /rsa_pss.go: -------------------------------------------------------------------------------- 1 | // +build go1.4 2 | 3 | package jwt 4 | 5 | import ( 6 | "crypto" 7 | "crypto/rand" 8 | "crypto/rsa" 9 | ) 10 | 11 | // Implements the RSAPSS family of signing methods signing methods 12 | type SigningMethodRSAPSS struct { 13 | *SigningMethodRSA 14 | Options *rsa.PSSOptions 15 | } 16 | 17 | // Specific instances for RS/PS and company 18 | var ( 19 | SigningMethodPS256 *SigningMethodRSAPSS 20 | SigningMethodPS384 *SigningMethodRSAPSS 21 | SigningMethodPS512 *SigningMethodRSAPSS 22 | ) 23 | 24 | func init() { 25 | // PS256 26 | SigningMethodPS256 = &SigningMethodRSAPSS{ 27 | &SigningMethodRSA{ 28 | Name: "PS256", 29 | Hash: crypto.SHA256, 30 | }, 31 | &rsa.PSSOptions{ 32 | SaltLength: rsa.PSSSaltLengthAuto, 33 | Hash: crypto.SHA256, 34 | }, 35 | } 36 | RegisterSigningMethod(SigningMethodPS256.Alg(), func() SigningMethod { 37 | return SigningMethodPS256 38 | }) 39 | 40 | // PS384 41 | SigningMethodPS384 = &SigningMethodRSAPSS{ 42 | &SigningMethodRSA{ 43 | Name: "PS384", 44 | Hash: crypto.SHA384, 45 | }, 46 | &rsa.PSSOptions{ 47 | SaltLength: rsa.PSSSaltLengthAuto, 48 | Hash: crypto.SHA384, 49 | }, 50 | } 51 | RegisterSigningMethod(SigningMethodPS384.Alg(), func() SigningMethod { 52 | return SigningMethodPS384 53 | }) 54 | 55 | // PS512 56 | SigningMethodPS512 = &SigningMethodRSAPSS{ 57 | &SigningMethodRSA{ 58 | Name: "PS512", 59 | Hash: crypto.SHA512, 60 | }, 61 | &rsa.PSSOptions{ 62 | SaltLength: rsa.PSSSaltLengthAuto, 63 | Hash: crypto.SHA512, 64 | }, 65 | } 66 | RegisterSigningMethod(SigningMethodPS512.Alg(), func() SigningMethod { 67 | return SigningMethodPS512 68 | }) 69 | } 70 | 71 | // Implements the Verify method from SigningMethod 72 | // For this verify method, key must be an rsa.PrivateKey struct 73 | func (m *SigningMethodRSAPSS) Verify(signingString, signature string, key interface{}) error { 74 | var err error 75 | 76 | // Decode the signature 77 | var sig []byte 78 | if sig, err = DecodeSegment(signature); err != nil { 79 | return err 80 | } 81 | 82 | var rsaKey *rsa.PublicKey 83 | switch k := key.(type) { 84 | case *rsa.PublicKey: 85 | rsaKey = k 86 | default: 87 | return ErrInvalidKey 88 | } 89 | 90 | // Create hasher 91 | if !m.Hash.Available() { 92 | return ErrHashUnavailable 93 | } 94 | hasher := m.Hash.New() 95 | hasher.Write([]byte(signingString)) 96 | 97 | return rsa.VerifyPSS(rsaKey, m.Hash, hasher.Sum(nil), sig, m.Options) 98 | } 99 | 100 | // Implements the Sign method from SigningMethod 101 | // For this signing method, key must be an rsa.PublicKey struct 102 | func (m *SigningMethodRSAPSS) Sign(signingString string, key interface{}) (string, error) { 103 | var rsaKey *rsa.PrivateKey 104 | 105 | switch k := key.(type) { 106 | case *rsa.PrivateKey: 107 | rsaKey = k 108 | default: 109 | return "", ErrInvalidKey 110 | } 111 | 112 | // Create the hasher 113 | if !m.Hash.Available() { 114 | return "", ErrHashUnavailable 115 | } 116 | 117 | hasher := m.Hash.New() 118 | hasher.Write([]byte(signingString)) 119 | 120 | // Sign the string and return the encoded bytes 121 | if sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, hasher.Sum(nil), m.Options); err == nil { 122 | return EncodeSegment(sigBytes), nil 123 | } else { 124 | return "", err 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /ecdsa_test.go: -------------------------------------------------------------------------------- 1 | package jwt_test 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "io/ioutil" 6 | "strings" 7 | "testing" 8 | 9 | "github.com/dgrijalva/jwt-go" 10 | ) 11 | 12 | var ecdsaTestData = []struct { 13 | name string 14 | keys map[string]string 15 | tokenString string 16 | alg string 17 | claims map[string]interface{} 18 | valid bool 19 | }{ 20 | { 21 | "Basic ES256", 22 | map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"}, 23 | "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8w", 24 | "ES256", 25 | map[string]interface{}{"foo": "bar"}, 26 | true, 27 | }, 28 | { 29 | "Basic ES384", 30 | map[string]string{"private": "test/ec384-private.pem", "public": "test/ec384-public.pem"}, 31 | "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MGUCMQCHBr61FXDuFY9xUhyp8iWQAuBIaSgaf1z2j_8XrKcCfzTPzoSa3SZKq-m3L492xe8CMG3kafRMeuaN5Aw8ZJxmOLhkTo4D3-LaGzcaUWINvWvkwFMl7dMC863s0gov6xvXuA", 32 | "ES384", 33 | map[string]interface{}{"foo": "bar"}, 34 | true, 35 | }, 36 | { 37 | "Basic ES512", 38 | map[string]string{"private": "test/ec512-private.pem", "public": "test/ec512-public.pem"}, 39 | "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MIGIAkIAmVKjdJE5lG1byOFgZZVTeNDRp6E7SNvUj0UrvpzoBH6nrleWVTcwfHzbwWuooNpPADDSFR_Ql3ze-Vwwi8hBqQsCQgHn-ZooL8zegkOVeEEsqd7WHWdhb8UekFCYw3X8JnNP-D3wvZQ1-tkkHakt5gZ2-xO29TxfSPun4ViGkMYa7Q4N-Q", 40 | "ES512", 41 | map[string]interface{}{"foo": "bar"}, 42 | true, 43 | }, 44 | { 45 | "basic ES256 invalid: foo => bar", 46 | map[string]string{"private": "test/ec256-private.pem", "public": "test/ec256-public.pem"}, 47 | "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.MEQCIHoSJnmGlPaVQDqacx_2XlXEhhqtWceVopjomc2PJLtdAiAUTeGPoNYxZw0z8mgOnnIcjoxRuNDVZvybRZF3wR1l8W", 48 | "ES256", 49 | map[string]interface{}{"foo": "bar"}, 50 | false, 51 | }, 52 | } 53 | 54 | func TestECDSAVerify(t *testing.T) { 55 | for _, data := range ecdsaTestData { 56 | var err error 57 | 58 | key, _ := ioutil.ReadFile(data.keys["public"]) 59 | 60 | var ecdsaKey *ecdsa.PublicKey 61 | if ecdsaKey, err = jwt.ParseECPublicKeyFromPEM(key); err != nil { 62 | t.Errorf("Unable to parse ECDSA public key: %v", err) 63 | } 64 | 65 | parts := strings.Split(data.tokenString, ".") 66 | 67 | method := jwt.GetSigningMethod(data.alg) 68 | err = method.Verify(strings.Join(parts[0:2], "."), parts[2], ecdsaKey) 69 | if data.valid && err != nil { 70 | t.Errorf("[%v] Error while verifying key: %v", data.name, err) 71 | } 72 | if !data.valid && err == nil { 73 | t.Errorf("[%v] Invalid key passed validation", data.name) 74 | } 75 | } 76 | } 77 | 78 | func TestECDSASign(t *testing.T) { 79 | for _, data := range ecdsaTestData { 80 | var err error 81 | key, _ := ioutil.ReadFile(data.keys["private"]) 82 | 83 | var ecdsaKey *ecdsa.PrivateKey 84 | if ecdsaKey, err = jwt.ParseECPrivateKeyFromPEM(key); err != nil { 85 | t.Errorf("Unable to parse ECDSA private key: %v", err) 86 | } 87 | 88 | if data.valid { 89 | parts := strings.Split(data.tokenString, ".") 90 | method := jwt.GetSigningMethod(data.alg) 91 | sig, err := method.Sign(strings.Join(parts[0:2], "."), ecdsaKey) 92 | if err != nil { 93 | t.Errorf("[%v] Error signing token: %v", data.name, err) 94 | } 95 | if sig == parts[2] { 96 | t.Errorf("[%v] Identical signatures\nbefore:\n%v\nafter:\n%v", data.name, parts[2], sig) 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ecdsa.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "crypto" 5 | "crypto/ecdsa" 6 | "crypto/rand" 7 | "encoding/asn1" 8 | "errors" 9 | "math/big" 10 | ) 11 | 12 | var ( 13 | // Sadly this is missing from crypto/ecdsa compared to crypto/rsa 14 | ErrECDSAVerification = errors.New("crypto/ecdsa: verification error") 15 | ) 16 | 17 | // Implements the ECDSA family of signing methods signing methods 18 | type SigningMethodECDSA struct { 19 | Name string 20 | Hash crypto.Hash 21 | } 22 | 23 | // Marshalling structure for r, s EC point 24 | type ECPoint struct { 25 | R *big.Int 26 | S *big.Int 27 | } 28 | 29 | // Specific instances for EC256 and company 30 | var ( 31 | SigningMethodES256 *SigningMethodECDSA 32 | SigningMethodES384 *SigningMethodECDSA 33 | SigningMethodES512 *SigningMethodECDSA 34 | ) 35 | 36 | func init() { 37 | // ES256 38 | SigningMethodES256 = &SigningMethodECDSA{"ES256", crypto.SHA256} 39 | RegisterSigningMethod(SigningMethodES256.Alg(), func() SigningMethod { 40 | return SigningMethodES256 41 | }) 42 | 43 | // ES384 44 | SigningMethodES384 = &SigningMethodECDSA{"ES384", crypto.SHA384} 45 | RegisterSigningMethod(SigningMethodES384.Alg(), func() SigningMethod { 46 | return SigningMethodES384 47 | }) 48 | 49 | // ES512 50 | SigningMethodES512 = &SigningMethodECDSA{"ES512", crypto.SHA512} 51 | RegisterSigningMethod(SigningMethodES512.Alg(), func() SigningMethod { 52 | return SigningMethodES512 53 | }) 54 | } 55 | 56 | func (m *SigningMethodECDSA) Alg() string { 57 | return m.Name 58 | } 59 | 60 | // Implements the Verify method from SigningMethod 61 | // For this verify method, key must be an ecdsa.PublicKey struct 62 | func (m *SigningMethodECDSA) Verify(signingString, signature string, key interface{}) error { 63 | var err error 64 | 65 | // Decode the signature 66 | var sig []byte 67 | if sig, err = DecodeSegment(signature); err != nil { 68 | return err 69 | } 70 | 71 | // Get the key 72 | var ecdsaKey *ecdsa.PublicKey 73 | switch k := key.(type) { 74 | case *ecdsa.PublicKey: 75 | ecdsaKey = k 76 | default: 77 | return ErrInvalidKey 78 | } 79 | 80 | // Unmarshal asn1 ECPoint 81 | var ecpoint = new(ECPoint) 82 | if _, err := asn1.Unmarshal(sig, ecpoint); err != nil { 83 | return err 84 | } 85 | 86 | // Create hasher 87 | if !m.Hash.Available() { 88 | return ErrHashUnavailable 89 | } 90 | hasher := m.Hash.New() 91 | hasher.Write([]byte(signingString)) 92 | 93 | // Verify the signature 94 | if verifystatus := ecdsa.Verify(ecdsaKey, hasher.Sum(nil), ecpoint.R, ecpoint.S); verifystatus == true { 95 | return nil 96 | } else { 97 | return ErrECDSAVerification 98 | } 99 | } 100 | 101 | // Implements the Sign method from SigningMethod 102 | // For this signing method, key must be an ecdsa.PrivateKey struct 103 | func (m *SigningMethodECDSA) Sign(signingString string, key interface{}) (string, error) { 104 | // Get the key 105 | var ecdsaKey *ecdsa.PrivateKey 106 | switch k := key.(type) { 107 | case *ecdsa.PrivateKey: 108 | ecdsaKey = k 109 | default: 110 | return "", ErrInvalidKey 111 | } 112 | 113 | // Create the hasher 114 | if !m.Hash.Available() { 115 | return "", ErrHashUnavailable 116 | } 117 | 118 | hasher := m.Hash.New() 119 | hasher.Write([]byte(signingString)) 120 | 121 | // Sign the string and return r, s 122 | if r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, hasher.Sum(nil)); err == nil { 123 | // asn1 marhsal r, s using ecPoint as the structure 124 | var ecpoint = new(ECPoint) 125 | ecpoint.R = r 126 | ecpoint.S = s 127 | 128 | if signature, err := asn1.Marshal(*ecpoint); err != nil { 129 | return "", err 130 | } else { 131 | return EncodeSegment(signature), nil 132 | } 133 | } else { 134 | return "", err 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /rsa_pss_test.go: -------------------------------------------------------------------------------- 1 | // +build go1.4 2 | 3 | package jwt_test 4 | 5 | import ( 6 | "crypto/rsa" 7 | "io/ioutil" 8 | "strings" 9 | "testing" 10 | 11 | "github.com/dgrijalva/jwt-go" 12 | ) 13 | 14 | var rsaPSSTestData = []struct { 15 | name string 16 | tokenString string 17 | alg string 18 | claims map[string]interface{} 19 | valid bool 20 | }{ 21 | { 22 | "Basic PS256", 23 | "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9w", 24 | "PS256", 25 | map[string]interface{}{"foo": "bar"}, 26 | true, 27 | }, 28 | { 29 | "Basic PS384", 30 | "eyJhbGciOiJQUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.w7-qqgj97gK4fJsq_DCqdYQiylJjzWONvD0qWWWhqEOFk2P1eDULPnqHRnjgTXoO4HAw4YIWCsZPet7nR3Xxq4ZhMqvKW8b7KlfRTb9cH8zqFvzMmybQ4jv2hKc3bXYqVow3AoR7hN_CWXI3Dv6Kd2X5xhtxRHI6IL39oTVDUQ74LACe-9t4c3QRPuj6Pq1H4FAT2E2kW_0KOc6EQhCLWEhm2Z2__OZskDC8AiPpP8Kv4k2vB7l0IKQu8Pr4RcNBlqJdq8dA5D3hk5TLxP8V5nG1Ib80MOMMqoS3FQvSLyolFX-R_jZ3-zfq6Ebsqr0yEb0AH2CfsECF7935Pa0FKQ", 31 | "PS384", 32 | map[string]interface{}{"foo": "bar"}, 33 | true, 34 | }, 35 | { 36 | "Basic PS512", 37 | "eyJhbGciOiJQUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.GX1HWGzFaJevuSLavqqFYaW8_TpvcjQ8KfC5fXiSDzSiT9UD9nB_ikSmDNyDILNdtjZLSvVKfXxZJqCfefxAtiozEDDdJthZ-F0uO4SPFHlGiXszvKeodh7BuTWRI2wL9-ZO4mFa8nq3GMeQAfo9cx11i7nfN8n2YNQ9SHGovG7_T_AvaMZB_jT6jkDHpwGR9mz7x1sycckEo6teLdHRnH_ZdlHlxqknmyTu8Odr5Xh0sJFOL8BepWbbvIIn-P161rRHHiDWFv6nhlHwZnVzjx7HQrWSGb6-s2cdLie9QL_8XaMcUpjLkfOMKkDOfHo6AvpL7Jbwi83Z2ZTHjJWB-A", 38 | "PS512", 39 | map[string]interface{}{"foo": "bar"}, 40 | true, 41 | }, 42 | { 43 | "basic PS256 invalid: foo => bar", 44 | "eyJhbGciOiJQUzI1NiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.PPG4xyDVY8ffp4CcxofNmsTDXsrVG2npdQuibLhJbv4ClyPTUtR5giNSvuxo03kB6I8VXVr0Y9X7UxhJVEoJOmULAwRWaUsDnIewQa101cVhMa6iR8X37kfFoiZ6NkS-c7henVkkQWu2HtotkEtQvN5hFlk8IevXXPmvZlhQhwzB1sGzGYnoi1zOfuL98d3BIjUjtlwii5w6gYG2AEEzp7HnHCsb3jIwUPdq86Oe6hIFjtBwduIK90ca4UqzARpcfwxHwVLMpatKask00AgGVI0ysdk0BLMjmLutquD03XbThHScC2C2_Pp4cHWgMzvbgLU2RYYZcZRKr46QeNgz9W", 45 | "PS256", 46 | map[string]interface{}{"foo": "bar"}, 47 | false, 48 | }, 49 | } 50 | 51 | func TestRSAPSSVerify(t *testing.T) { 52 | var err error 53 | 54 | key, _ := ioutil.ReadFile("test/sample_key.pub") 55 | var rsaPSSKey *rsa.PublicKey 56 | if rsaPSSKey, err = jwt.ParseRSAPublicKeyFromPEM(key); err != nil { 57 | t.Errorf("Unable to parse RSA public key: %v", err) 58 | } 59 | 60 | for _, data := range rsaPSSTestData { 61 | parts := strings.Split(data.tokenString, ".") 62 | 63 | method := jwt.GetSigningMethod(data.alg) 64 | err := method.Verify(strings.Join(parts[0:2], "."), parts[2], rsaPSSKey) 65 | if data.valid && err != nil { 66 | t.Errorf("[%v] Error while verifying key: %v", data.name, err) 67 | } 68 | if !data.valid && err == nil { 69 | t.Errorf("[%v] Invalid key passed validation", data.name) 70 | } 71 | } 72 | } 73 | 74 | func TestRSAPSSSign(t *testing.T) { 75 | var err error 76 | 77 | key, _ := ioutil.ReadFile("test/sample_key") 78 | var rsaPSSKey *rsa.PrivateKey 79 | if rsaPSSKey, err = jwt.ParseRSAPrivateKeyFromPEM(key); err != nil { 80 | t.Errorf("Unable to parse RSA private key: %v", err) 81 | } 82 | 83 | for _, data := range rsaPSSTestData { 84 | if data.valid { 85 | parts := strings.Split(data.tokenString, ".") 86 | method := jwt.GetSigningMethod(data.alg) 87 | sig, err := method.Sign(strings.Join(parts[0:2], "."), rsaPSSKey) 88 | if err != nil { 89 | t.Errorf("[%v] Error signing token: %v", data.name, err) 90 | } 91 | if sig == parts[2] { 92 | t.Errorf("[%v] Signatures shouldn't match\nnew:\n%v\noriginal:\n%v", data.name, sig, parts[2]) 93 | } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A [go](http://www.golang.org) (or 'golang' for search engine friendliness) implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-jones-json-web-token.html) 2 | 3 | [![Build Status](https://travis-ci.org/dgrijalva/jwt-go.svg?branch=master)](https://travis-ci.org/dgrijalva/jwt-go) 4 | 5 | **NOTICE:** A vulnerability in JWT was [recently published](https://auth0.com/blog/2015/03/31/critical-vulnerabilities-in-json-web-token-libraries/). As this library doesn't force users to validate the `alg` is what they expected, it's possible your usage is effected. There will be an update soon to remedy this, and it will likey require backwards-incompatible changes to the API. In the short term, please make sure your implementation verifies the `alg` is what you expect. 6 | 7 | ## What the heck is a JWT? 8 | 9 | In short, it's a signed JSON object that does something useful (for example, authentication). It's commonly used for `Bearer` tokens in Oauth 2. A token is made of three parts, separated by `.`'s. The first two parts are JSON objects, that have been [base64url](http://tools.ietf.org/html/rfc4648) encoded. The last part is the signature, encoded the same way. 10 | 11 | The first part is called the header. It contains the necessary information for verifying the last part, the signature. For example, which encryption method was used for signing and what key was used. 12 | 13 | The part in the middle is the interesting bit. It's called the Claims and contains the actual stuff you care about. Refer to [the RFC](http://self-issued.info/docs/draft-jones-json-web-token.html) for information about reserved keys and the proper way to add your own. 14 | 15 | ## What's in the box? 16 | 17 | 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. 18 | 19 | ## Parse and Verify 20 | 21 | Parsing and verifying tokens is pretty straight forward. You pass in the token and a function for looking up the key. This is done as a callback since you may need to parse the token to find out what signing method and key was used. 22 | 23 | ```go 24 | token, err := jwt.Parse(myToken, func(token *jwt.Token) (interface{}, error) { 25 | // Don't forget to validate the alg is what you expect: 26 | if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok { 27 | return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) 28 | } 29 | return myLookupKey(token.Header["kid"]) 30 | }) 31 | 32 | if err == nil && token.Valid { 33 | deliverGoodness("!") 34 | } else { 35 | deliverUtterRejection(":(") 36 | } 37 | ``` 38 | 39 | ## Create a token 40 | 41 | ```go 42 | // Create the token 43 | token := jwt.New(jwt.SigningMethodHS256) 44 | // Set some claims 45 | token.Claims["foo"] = "bar" 46 | token.Claims["exp"] = time.Now().Add(time.Hour * 72).Unix() 47 | // Sign and get the complete encoded token as a string 48 | tokenString, err := token.SignedString(mySigningKey) 49 | ``` 50 | 51 | ## Extensions 52 | 53 | 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`. 54 | 55 | Here's an example of an extension that integrates with the Google App Engine signing tools: https://github.com/someone1/gcp-jwt-go 56 | 57 | ## Project Status & Versioning 58 | 59 | 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). 60 | 61 | This project uses [Semantic Versioning 2.0.0](http://semver.org). Accepted pull requests will land on `master`. Periodically, versions will be tagged from `master`. You can find all the releases on [the project releases page](https://github.com/dgrijalva/jwt-go/releases). 62 | 63 | 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. 64 | 65 | ## More 66 | 67 | Documentation can be found [on godoc.org](http://godoc.org/github.com/dgrijalva/jwt-go). 68 | 69 | The command line utility included in this project (cmd/jwt) provides a straightforward example of token creation and parsing as well as a useful tool for debugging your own integration. For a more http centric example, see [this gist](https://gist.github.com/cryptix/45c33ecf0ae54828e63b). 70 | -------------------------------------------------------------------------------- /cmd/jwt/app.go: -------------------------------------------------------------------------------- 1 | // A useful example app. You can use this to debug your tokens on the command line. 2 | // This is also a great place to look at how you might use this library. 3 | // 4 | // Example usage: 5 | // The following will create and sign a token, then verify it and output the original claims. 6 | // echo {\"foo\":\"bar\"} | bin/jwt -key test/sample_key -alg RS256 -sign - | bin/jwt -key test/sample_key.pub -verify - 7 | package main 8 | 9 | import ( 10 | "encoding/json" 11 | "flag" 12 | "fmt" 13 | "io" 14 | "io/ioutil" 15 | "os" 16 | "regexp" 17 | 18 | "github.com/dgrijalva/jwt-go" 19 | ) 20 | 21 | var ( 22 | // Options 23 | flagAlg = flag.String("alg", "", "signing algorithm identifier") 24 | flagKey = flag.String("key", "", "path to key file or '-' to read from stdin") 25 | flagCompact = flag.Bool("compact", false, "output compact JSON") 26 | flagDebug = flag.Bool("debug", false, "print out all kinds of debug data") 27 | 28 | // Modes - exactly one of these is required 29 | flagSign = flag.String("sign", "", "path to claims object to sign or '-' to read from stdin") 30 | flagVerify = flag.String("verify", "", "path to JWT token to verify or '-' to read from stdin") 31 | ) 32 | 33 | func main() { 34 | // Usage message if you ask for -help or if you mess up inputs. 35 | flag.Usage = func() { 36 | fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) 37 | fmt.Fprintf(os.Stderr, " One of the following flags is required: sign, verify\n") 38 | flag.PrintDefaults() 39 | } 40 | 41 | // Parse command line options 42 | flag.Parse() 43 | 44 | // Do the thing. If something goes wrong, print error to stderr 45 | // and exit with a non-zero status code 46 | if err := start(); err != nil { 47 | fmt.Fprintf(os.Stderr, "Error: %v\n", err) 48 | os.Exit(1) 49 | } 50 | } 51 | 52 | // Figure out which thing to do and then do that 53 | func start() error { 54 | if *flagSign != "" { 55 | return signToken() 56 | } else if *flagVerify != "" { 57 | return verifyToken() 58 | } else { 59 | flag.Usage() 60 | return fmt.Errorf("None of the required flags are present. What do you want me to do?") 61 | } 62 | } 63 | 64 | // Helper func: Read input from specified file or stdin 65 | func loadData(p string) ([]byte, error) { 66 | if p == "" { 67 | return nil, fmt.Errorf("No path specified") 68 | } 69 | 70 | var rdr io.Reader 71 | if p == "-" { 72 | rdr = os.Stdin 73 | } else { 74 | if f, err := os.Open(p); err == nil { 75 | rdr = f 76 | defer f.Close() 77 | } else { 78 | return nil, err 79 | } 80 | } 81 | return ioutil.ReadAll(rdr) 82 | } 83 | 84 | // Print a json object in accordance with the prophecy (or the command line options) 85 | func printJSON(j interface{}) error { 86 | var out []byte 87 | var err error 88 | 89 | if *flagCompact == false { 90 | out, err = json.MarshalIndent(j, "", " ") 91 | } else { 92 | out, err = json.Marshal(j) 93 | } 94 | 95 | if err == nil { 96 | fmt.Println(string(out)) 97 | } 98 | 99 | return err 100 | } 101 | 102 | // Verify a token and output the claims. This is a great example 103 | // of how to verify and view a token. 104 | func verifyToken() error { 105 | // get the token 106 | tokData, err := loadData(*flagVerify) 107 | if err != nil { 108 | return fmt.Errorf("Couldn't read token: %v", err) 109 | } 110 | 111 | // trim possible whitespace from token 112 | tokData = regexp.MustCompile(`\s*$`).ReplaceAll(tokData, []byte{}) 113 | if *flagDebug { 114 | fmt.Fprintf(os.Stderr, "Token len: %v bytes\n", len(tokData)) 115 | } 116 | 117 | // Parse the token. Load the key from command line option 118 | token, err := jwt.Parse(string(tokData), func(t *jwt.Token) (interface{}, error) { 119 | return loadData(*flagKey) 120 | }) 121 | 122 | // Print some debug data 123 | if *flagDebug && token != nil { 124 | fmt.Fprintf(os.Stderr, "Header:\n%v\n", token.Header) 125 | fmt.Fprintf(os.Stderr, "Claims:\n%v\n", token.Claims) 126 | } 127 | 128 | // Print an error if we can't parse for some reason 129 | if err != nil { 130 | return fmt.Errorf("Couldn't parse token: %v", err) 131 | } 132 | 133 | // Is token invalid? 134 | if !token.Valid { 135 | return fmt.Errorf("Token is invalid") 136 | } 137 | 138 | // Print the token details 139 | if err := printJSON(token.Claims); err != nil { 140 | return fmt.Errorf("Failed to output claims: %v", err) 141 | } 142 | 143 | return nil 144 | } 145 | 146 | // Create, sign, and output a token. This is a great, simple example of 147 | // how to use this library to create and sign a token. 148 | func signToken() error { 149 | // get the token data from command line arguments 150 | tokData, err := loadData(*flagSign) 151 | if err != nil { 152 | return fmt.Errorf("Couldn't read token: %v", err) 153 | } else if *flagDebug { 154 | fmt.Fprintf(os.Stderr, "Token: %v bytes", len(tokData)) 155 | } 156 | 157 | // parse the JSON of the claims 158 | var claims map[string]interface{} 159 | if err := json.Unmarshal(tokData, &claims); err != nil { 160 | return fmt.Errorf("Couldn't parse claims JSON: %v", err) 161 | } 162 | 163 | // get the key 164 | keyData, err := loadData(*flagKey) 165 | if err != nil { 166 | return fmt.Errorf("Couldn't read key: %v", err) 167 | } 168 | 169 | // get the signing alg 170 | alg := jwt.GetSigningMethod(*flagAlg) 171 | if alg == nil { 172 | return fmt.Errorf("Couldn't find signing method: %v", *flagAlg) 173 | } 174 | 175 | // create a new token 176 | token := jwt.New(alg) 177 | token.Claims = claims 178 | 179 | if out, err := token.SignedString(keyData); err == nil { 180 | fmt.Println(out) 181 | } else { 182 | return fmt.Errorf("Error signing token: %v", err) 183 | } 184 | 185 | return nil 186 | } 187 | -------------------------------------------------------------------------------- /rsa_test.go: -------------------------------------------------------------------------------- 1 | package jwt_test 2 | 3 | import ( 4 | "github.com/dgrijalva/jwt-go" 5 | "io/ioutil" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | var rsaTestData = []struct { 11 | name string 12 | tokenString string 13 | alg string 14 | claims map[string]interface{} 15 | valid bool 16 | }{ 17 | { 18 | "Basic RS256", 19 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", 20 | "RS256", 21 | map[string]interface{}{"foo": "bar"}, 22 | true, 23 | }, 24 | { 25 | "Basic RS384", 26 | "eyJhbGciOiJSUzM4NCIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.W-jEzRfBigtCWsinvVVuldiuilzVdU5ty0MvpLaSaqK9PlAWWlDQ1VIQ_qSKzwL5IXaZkvZFJXT3yL3n7OUVu7zCNJzdwznbC8Z-b0z2lYvcklJYi2VOFRcGbJtXUqgjk2oGsiqUMUMOLP70TTefkpsgqDxbRh9CDUfpOJgW-dU7cmgaoswe3wjUAUi6B6G2YEaiuXC0XScQYSYVKIzgKXJV8Zw-7AN_DBUI4GkTpsvQ9fVVjZM9csQiEXhYekyrKu1nu_POpQonGd8yqkIyXPECNmmqH5jH4sFiF67XhD7_JpkvLziBpI-uh86evBUadmHhb9Otqw3uV3NTaXLzJw", 27 | "RS384", 28 | map[string]interface{}{"foo": "bar"}, 29 | true, 30 | }, 31 | { 32 | "Basic RS512", 33 | "eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJmb28iOiJiYXIifQ.zBlLlmRrUxx4SJPUbV37Q1joRcI9EW13grnKduK3wtYKmDXbgDpF1cZ6B-2Jsm5RB8REmMiLpGms-EjXhgnyh2TSHE-9W2gA_jvshegLWtwRVDX40ODSkTb7OVuaWgiy9y7llvcknFBTIg-FnVPVpXMmeV_pvwQyhaz1SSwSPrDyxEmksz1hq7YONXhXPpGaNbMMeDTNP_1oj8DZaqTIL9TwV8_1wb2Odt_Fy58Ke2RVFijsOLdnyEAjt2n9Mxihu9i3PhNBkkxa2GbnXBfq3kzvZ_xxGGopLdHhJjcGWXO-NiwI9_tiu14NRv4L2xC0ItD9Yz68v2ZIZEp_DuzwRQ", 34 | "RS512", 35 | map[string]interface{}{"foo": "bar"}, 36 | true, 37 | }, 38 | { 39 | "basic invalid: foo => bar", 40 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", 41 | "RS256", 42 | map[string]interface{}{"foo": "bar"}, 43 | false, 44 | }, 45 | } 46 | 47 | func TestRSAVerify(t *testing.T) { 48 | key, _ := ioutil.ReadFile("test/sample_key.pub") 49 | 50 | for _, data := range rsaTestData { 51 | parts := strings.Split(data.tokenString, ".") 52 | 53 | method := jwt.GetSigningMethod(data.alg) 54 | err := method.Verify(strings.Join(parts[0:2], "."), parts[2], key) 55 | if data.valid && err != nil { 56 | t.Errorf("[%v] Error while verifying key: %v", data.name, err) 57 | } 58 | if !data.valid && err == nil { 59 | t.Errorf("[%v] Invalid key passed validation", data.name) 60 | } 61 | } 62 | } 63 | 64 | func TestRSASign(t *testing.T) { 65 | key, _ := ioutil.ReadFile("test/sample_key") 66 | 67 | for _, data := range rsaTestData { 68 | if data.valid { 69 | parts := strings.Split(data.tokenString, ".") 70 | method := jwt.GetSigningMethod(data.alg) 71 | sig, err := method.Sign(strings.Join(parts[0:2], "."), key) 72 | if err != nil { 73 | t.Errorf("[%v] Error signing token: %v", data.name, err) 74 | } 75 | if sig != parts[2] { 76 | t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", data.name, sig, parts[2]) 77 | } 78 | } 79 | } 80 | } 81 | 82 | func TestRSAVerifyWithPreParsedPrivateKey(t *testing.T) { 83 | key, _ := ioutil.ReadFile("test/sample_key.pub") 84 | parsedKey, err := jwt.ParseRSAPublicKeyFromPEM(key) 85 | if err != nil { 86 | t.Fatal(err) 87 | } 88 | testData := rsaTestData[0] 89 | parts := strings.Split(testData.tokenString, ".") 90 | err = jwt.SigningMethodRS256.Verify(strings.Join(parts[0:2], "."), parts[2], parsedKey) 91 | if err != nil { 92 | t.Errorf("[%v] Error while verifying key: %v", testData.name, err) 93 | } 94 | } 95 | 96 | func TestRSAWithPreParsedPrivateKey(t *testing.T) { 97 | key, _ := ioutil.ReadFile("test/sample_key") 98 | parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) 99 | if err != nil { 100 | t.Fatal(err) 101 | } 102 | testData := rsaTestData[0] 103 | parts := strings.Split(testData.tokenString, ".") 104 | sig, err := jwt.SigningMethodRS256.Sign(strings.Join(parts[0:2], "."), parsedKey) 105 | if err != nil { 106 | t.Errorf("[%v] Error signing token: %v", testData.name, err) 107 | } 108 | if sig != parts[2] { 109 | t.Errorf("[%v] Incorrect signature.\nwas:\n%v\nexpecting:\n%v", testData.name, sig, parts[2]) 110 | } 111 | } 112 | 113 | func TestRSAKeyParsing(t *testing.T) { 114 | key, _ := ioutil.ReadFile("test/sample_key") 115 | pubKey, _ := ioutil.ReadFile("test/sample_key.pub") 116 | badKey := []byte("All your base are belong to key") 117 | 118 | // Test parsePrivateKey 119 | if _, e := jwt.ParseRSAPrivateKeyFromPEM(key); e != nil { 120 | t.Errorf("Failed to parse valid private key: %v", e) 121 | } 122 | 123 | if k, e := jwt.ParseRSAPrivateKeyFromPEM(pubKey); e == nil { 124 | t.Errorf("Parsed public key as valid private key: %v", k) 125 | } 126 | 127 | if k, e := jwt.ParseRSAPrivateKeyFromPEM(badKey); e == nil { 128 | t.Errorf("Parsed invalid key as valid private key: %v", k) 129 | } 130 | 131 | // Test parsePublicKey 132 | if _, e := jwt.ParseRSAPublicKeyFromPEM(pubKey); e != nil { 133 | t.Errorf("Failed to parse valid public key: %v", e) 134 | } 135 | 136 | if k, e := jwt.ParseRSAPublicKeyFromPEM(key); e == nil { 137 | t.Errorf("Parsed private key as valid public key: %v", k) 138 | } 139 | 140 | if k, e := jwt.ParseRSAPublicKeyFromPEM(badKey); e == nil { 141 | t.Errorf("Parsed invalid key as valid private key: %v", k) 142 | } 143 | 144 | } 145 | 146 | func BenchmarkRS256Signing(b *testing.B) { 147 | key, _ := ioutil.ReadFile("test/sample_key") 148 | parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) 149 | if err != nil { 150 | b.Fatal(err) 151 | } 152 | 153 | benchmarkSigning(b, jwt.SigningMethodRS256, parsedKey) 154 | } 155 | 156 | func BenchmarkRS384Signing(b *testing.B) { 157 | key, _ := ioutil.ReadFile("test/sample_key") 158 | parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) 159 | if err != nil { 160 | b.Fatal(err) 161 | } 162 | 163 | benchmarkSigning(b, jwt.SigningMethodRS384, parsedKey) 164 | } 165 | 166 | func BenchmarkRS512Signing(b *testing.B) { 167 | key, _ := ioutil.ReadFile("test/sample_key") 168 | parsedKey, err := jwt.ParseRSAPrivateKeyFromPEM(key) 169 | if err != nil { 170 | b.Fatal(err) 171 | } 172 | 173 | benchmarkSigning(b, jwt.SigningMethodRS512, parsedKey) 174 | } 175 | -------------------------------------------------------------------------------- /jwt_test.go: -------------------------------------------------------------------------------- 1 | package jwt_test 2 | 3 | import ( 4 | "fmt" 5 | "github.com/dgrijalva/jwt-go" 6 | "io/ioutil" 7 | "net/http" 8 | "reflect" 9 | "testing" 10 | "time" 11 | ) 12 | 13 | var ( 14 | jwtTestDefaultKey []byte 15 | defaultKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return jwtTestDefaultKey, nil } 16 | emptyKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, nil } 17 | errorKeyFunc jwt.Keyfunc = func(t *jwt.Token) (interface{}, error) { return nil, fmt.Errorf("error loading key") } 18 | nilKeyFunc jwt.Keyfunc = nil 19 | ) 20 | 21 | var jwtTestData = []struct { 22 | name string 23 | tokenString string 24 | keyfunc jwt.Keyfunc 25 | claims map[string]interface{} 26 | valid bool 27 | errors uint32 28 | }{ 29 | { 30 | "basic", 31 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", 32 | defaultKeyFunc, 33 | map[string]interface{}{"foo": "bar"}, 34 | true, 35 | 0, 36 | }, 37 | { 38 | "basic expired", 39 | "", // autogen 40 | defaultKeyFunc, 41 | map[string]interface{}{"foo": "bar", "exp": float64(time.Now().Unix() - 100)}, 42 | false, 43 | jwt.ValidationErrorExpired, 44 | }, 45 | { 46 | "basic nbf", 47 | "", // autogen 48 | defaultKeyFunc, 49 | map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100)}, 50 | false, 51 | jwt.ValidationErrorNotValidYet, 52 | }, 53 | { 54 | "expired and nbf", 55 | "", // autogen 56 | defaultKeyFunc, 57 | map[string]interface{}{"foo": "bar", "nbf": float64(time.Now().Unix() + 100), "exp": float64(time.Now().Unix() - 100)}, 58 | false, 59 | jwt.ValidationErrorNotValidYet | jwt.ValidationErrorExpired, 60 | }, 61 | { 62 | "basic invalid", 63 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.EhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", 64 | defaultKeyFunc, 65 | map[string]interface{}{"foo": "bar"}, 66 | false, 67 | jwt.ValidationErrorSignatureInvalid, 68 | }, 69 | { 70 | "basic nokeyfunc", 71 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", 72 | nilKeyFunc, 73 | map[string]interface{}{"foo": "bar"}, 74 | false, 75 | jwt.ValidationErrorUnverifiable, 76 | }, 77 | { 78 | "basic nokey", 79 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", 80 | emptyKeyFunc, 81 | map[string]interface{}{"foo": "bar"}, 82 | false, 83 | jwt.ValidationErrorSignatureInvalid, 84 | }, 85 | { 86 | "basic errorkey", 87 | "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJmb28iOiJiYXIifQ.FhkiHkoESI_cG3NPigFrxEk9Z60_oXrOT2vGm9Pn6RDgYNovYORQmmA0zs1AoAOf09ly2Nx2YAg6ABqAYga1AcMFkJljwxTT5fYphTuqpWdy4BELeSYJx5Ty2gmr8e7RonuUztrdD5WfPqLKMm1Ozp_T6zALpRmwTIW0QPnaBXaQD90FplAg46Iy1UlDKr-Eupy0i5SLch5Q-p2ZpaL_5fnTIUDlxC3pWhJTyx_71qDI-mAA_5lE_VdroOeflG56sSmDxopPEG3bFlSu1eowyBfxtu0_CuVd-M42RU75Zc4Gsj6uV77MBtbMrf4_7M_NUTSgoIF3fRqxrj0NzihIBg", 88 | errorKeyFunc, 89 | map[string]interface{}{"foo": "bar"}, 90 | false, 91 | jwt.ValidationErrorUnverifiable, 92 | }, 93 | } 94 | 95 | func init() { 96 | var e error 97 | if jwtTestDefaultKey, e = ioutil.ReadFile("test/sample_key.pub"); e != nil { 98 | panic(e) 99 | } 100 | } 101 | 102 | func makeSample(c map[string]interface{}) string { 103 | key, e := ioutil.ReadFile("test/sample_key") 104 | if e != nil { 105 | panic(e.Error()) 106 | } 107 | 108 | token := jwt.New(jwt.SigningMethodRS256) 109 | token.Claims = c 110 | s, e := token.SignedString(key) 111 | 112 | if e != nil { 113 | panic(e.Error()) 114 | } 115 | 116 | return s 117 | } 118 | 119 | func TestJWT(t *testing.T) { 120 | for _, data := range jwtTestData { 121 | if data.tokenString == "" { 122 | data.tokenString = makeSample(data.claims) 123 | } 124 | token, err := jwt.Parse(data.tokenString, data.keyfunc) 125 | 126 | if !reflect.DeepEqual(data.claims, token.Claims) { 127 | t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims) 128 | } 129 | if data.valid && err != nil { 130 | t.Errorf("[%v] Error while verifying token: %T:%v", data.name, err, err) 131 | } 132 | if !data.valid && err == nil { 133 | t.Errorf("[%v] Invalid token passed validation", data.name) 134 | } 135 | if data.errors != 0 { 136 | if err == nil { 137 | t.Errorf("[%v] Expecting error. Didn't get one.", data.name) 138 | } else { 139 | // compare the bitfield part of the error 140 | if err.(*jwt.ValidationError).Errors != data.errors { 141 | t.Errorf("[%v] Errors don't match expectation", data.name) 142 | } 143 | 144 | } 145 | } 146 | } 147 | } 148 | 149 | func TestParseRequest(t *testing.T) { 150 | // Bearer token request 151 | for _, data := range jwtTestData { 152 | if data.tokenString == "" { 153 | data.tokenString = makeSample(data.claims) 154 | } 155 | 156 | r, _ := http.NewRequest("GET", "/", nil) 157 | r.Header.Set("Authorization", fmt.Sprintf("Bearer %v", data.tokenString)) 158 | token, err := jwt.ParseFromRequest(r, data.keyfunc) 159 | 160 | if token == nil { 161 | t.Errorf("[%v] Token was not found: %v", data.name, err) 162 | continue 163 | } 164 | if !reflect.DeepEqual(data.claims, token.Claims) { 165 | t.Errorf("[%v] Claims mismatch. Expecting: %v Got: %v", data.name, data.claims, token.Claims) 166 | } 167 | if data.valid && err != nil { 168 | t.Errorf("[%v] Error while verifying token: %v", data.name, err) 169 | } 170 | if !data.valid && err == nil { 171 | t.Errorf("[%v] Invalid token passed validation", data.name) 172 | } 173 | } 174 | } 175 | 176 | // Helper method for benchmarking various methods 177 | func benchmarkSigning(b *testing.B, method jwt.SigningMethod, key interface{}) { 178 | t := jwt.New(method) 179 | b.RunParallel(func(pb *testing.PB) { 180 | for pb.Next() { 181 | if _, err := t.SignedString(key); err != nil { 182 | b.Fatal(err) 183 | } 184 | } 185 | }) 186 | 187 | } 188 | -------------------------------------------------------------------------------- /jwt.go: -------------------------------------------------------------------------------- 1 | package jwt 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "net/http" 7 | "strings" 8 | "time" 9 | ) 10 | 11 | // TimeFunc provides the current time when parsing token to validate "exp" claim (expiration time). 12 | // You can override it to use another time value. This is useful for testing or if your 13 | // server uses a different time zone than your tokens. 14 | var TimeFunc = time.Now 15 | 16 | // Parse methods use this callback function to supply 17 | // the key for verification. The function receives the parsed, 18 | // but unverified Token. This allows you to use propries in the 19 | // Header of the token (such as `kid`) to identify which key to use. 20 | type Keyfunc func(*Token) (interface{}, error) 21 | 22 | // A JWT Token. Different fields will be used depending on whether you're 23 | // creating or parsing/verifying a token. 24 | type Token struct { 25 | Raw string // The raw token. Populated when you Parse a token 26 | Method SigningMethod // The signing method used or to be used 27 | Header map[string]interface{} // The first segment of the token 28 | Claims map[string]interface{} // The second segment of the token 29 | Signature string // The third segment of the token. Populated when you Parse a token 30 | Valid bool // Is the token valid? Populated when you Parse/Verify a token 31 | } 32 | 33 | // Create a new Token. Takes a signing method 34 | func New(method SigningMethod) *Token { 35 | return &Token{ 36 | Header: map[string]interface{}{ 37 | "typ": "JWT", 38 | "alg": method.Alg(), 39 | }, 40 | Claims: make(map[string]interface{}), 41 | Method: method, 42 | } 43 | } 44 | 45 | // Get the complete, signed token 46 | func (t *Token) SignedString(key interface{}) (string, error) { 47 | var sig, sstr string 48 | var err error 49 | if sstr, err = t.SigningString(); err != nil { 50 | return "", err 51 | } 52 | if sig, err = t.Method.Sign(sstr, key); err != nil { 53 | return "", err 54 | } 55 | return strings.Join([]string{sstr, sig}, "."), nil 56 | } 57 | 58 | // Generate the signing string. This is the 59 | // most expensive part of the whole deal. Unless you 60 | // need this for something special, just go straight for 61 | // the SignedString. 62 | func (t *Token) SigningString() (string, error) { 63 | var err error 64 | parts := make([]string, 2) 65 | for i, _ := range parts { 66 | var source map[string]interface{} 67 | if i == 0 { 68 | source = t.Header 69 | } else { 70 | source = t.Claims 71 | } 72 | 73 | var jsonValue []byte 74 | if jsonValue, err = json.Marshal(source); err != nil { 75 | return "", err 76 | } 77 | 78 | parts[i] = EncodeSegment(jsonValue) 79 | } 80 | return strings.Join(parts, "."), nil 81 | } 82 | 83 | // Parse and return a token without signature verification. 84 | func ParseUnverified(tokenString string) (*Token, error) { 85 | parts := strings.Split(tokenString, ".") 86 | if len(parts) != 3 { 87 | return nil, &ValidationError{err: "token contains an invalid number of segments", Errors: ValidationErrorMalformed} 88 | } 89 | 90 | var err error 91 | token := &Token{Raw: tokenString} 92 | // parse Header 93 | var headerBytes []byte 94 | if headerBytes, err = DecodeSegment(parts[0]); err != nil { 95 | return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} 96 | } 97 | if err = json.Unmarshal(headerBytes, &token.Header); err != nil { 98 | return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} 99 | } 100 | 101 | // parse Claims 102 | var claimBytes []byte 103 | if claimBytes, err = DecodeSegment(parts[1]); err != nil { 104 | return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} 105 | } 106 | if err = json.Unmarshal(claimBytes, &token.Claims); err != nil { 107 | return token, &ValidationError{err: err.Error(), Errors: ValidationErrorMalformed} 108 | } 109 | 110 | return token, nil 111 | } 112 | 113 | // Parse, validate, and return a token. 114 | // keyFunc will receive the parsed token and should return the key for validating. 115 | // If everything is kosher, err will be nil 116 | func Parse(tokenString string, keyFunc Keyfunc) (*Token, error) { 117 | token, encodingErr := ParseUnverified(tokenString) 118 | if encodingErr != nil { 119 | return token, encodingErr 120 | } 121 | 122 | // Lookup signature method 123 | if method, ok := token.Header["alg"].(string); ok { 124 | if token.Method = GetSigningMethod(method); token.Method == nil { 125 | return token, &ValidationError{err: "signing method (alg) is unavailable.", Errors: ValidationErrorUnverifiable} 126 | } 127 | } else { 128 | return token, &ValidationError{err: "signing method (alg) is unspecified.", Errors: ValidationErrorUnverifiable} 129 | } 130 | 131 | var err error 132 | 133 | // Lookup key 134 | var key interface{} 135 | if keyFunc == nil { 136 | // keyFunc was not provided. short circuiting validation 137 | return token, &ValidationError{err: "no Keyfunc was provided.", Errors: ValidationErrorUnverifiable} 138 | } 139 | if key, err = keyFunc(token); err != nil { 140 | // keyFunc returned an error 141 | return token, &ValidationError{err: err.Error(), Errors: ValidationErrorUnverifiable} 142 | } 143 | 144 | // Check expiration times 145 | vErr := &ValidationError{} 146 | now := TimeFunc().Unix() 147 | if exp, ok := token.Claims["exp"].(float64); ok { 148 | if now > int64(exp) { 149 | vErr.err = "token is expired" 150 | vErr.Errors |= ValidationErrorExpired 151 | } 152 | } 153 | if nbf, ok := token.Claims["nbf"].(float64); ok { 154 | if now < int64(nbf) { 155 | vErr.err = "token is not valid yet" 156 | vErr.Errors |= ValidationErrorNotValidYet 157 | } 158 | } 159 | 160 | // Perform validation 161 | parts := strings.Split(tokenString, ".") // FIXME splitting twice 162 | if err = token.Method.Verify(strings.Join(parts[0:2], "."), parts[2], key); err != nil { 163 | vErr.err = err.Error() 164 | vErr.Errors |= ValidationErrorSignatureInvalid 165 | } 166 | 167 | if vErr.valid() { 168 | token.Valid = true 169 | return token, nil 170 | } 171 | 172 | return token, vErr 173 | } 174 | 175 | // Try to find the token in an http.Request. 176 | // This method will call ParseMultipartForm if there's no token in the header. 177 | // Currently, it looks in the Authorization header as well as 178 | // looking for an 'access_token' request parameter in req.Form. 179 | func ParseFromRequest(req *http.Request, keyFunc Keyfunc) (token *Token, err error) { 180 | 181 | // Look for an Authorization header 182 | if ah := req.Header.Get("Authorization"); ah != "" { 183 | // Should be a bearer token 184 | if len(ah) > 6 && strings.ToUpper(ah[0:6]) == "BEARER" { 185 | return Parse(ah[7:], keyFunc) 186 | } 187 | } 188 | 189 | // Look for "access_token" parameter 190 | req.ParseMultipartForm(10e6) 191 | if tokStr := req.Form.Get("access_token"); tokStr != "" { 192 | return Parse(tokStr, keyFunc) 193 | } 194 | 195 | return nil, ErrNoTokenInRequest 196 | 197 | } 198 | 199 | // Encode JWT specific base64url encoding with padding stripped 200 | func EncodeSegment(seg []byte) string { 201 | return strings.TrimRight(base64.URLEncoding.EncodeToString(seg), "=") 202 | } 203 | 204 | // Decode JWT specific base64url encoding with padding stripped 205 | func DecodeSegment(seg string) ([]byte, error) { 206 | if l := len(seg) % 4; l > 0 { 207 | seg += strings.Repeat("=", 4-l) 208 | } 209 | 210 | return base64.URLEncoding.DecodeString(seg) 211 | } 212 | --------------------------------------------------------------------------------