diff --git a/vendor.conf b/vendor.conf index c0e7a087f..99a837aab 100644 --- a/vendor.conf +++ b/vendor.conf @@ -8,7 +8,7 @@ github.com/docker/distribution edc3ab29cdff8694dd6feb85cfeb4b5f1b38ed9c github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb github.com/docker/go-connections 7395e3f8aa162843a74ed6d48e79627d9792ac55 github.com/docker/go d30aec9fd63c35133f8f79c3412ad91a3b08be06 -github.com/dvsekhvalnov/jose2go 6387d3c1f5abd8443b223577d5a7e0f4e0e5731f # v1.2 +github.com/dvsekhvalnov/jose2go f21a8cedbbae609f623613ec8f81125c243212e6 # v1.3 github.com/go-sql-driver/mysql a0583e0143b1624142adab07e0e97fe106d99561 # v1.3 github.com/gorilla/mux e444e69cbd2e2e3e0749a2f3c717cec491552bbf github.com/jinzhu/gorm 5409931a1bb87e484d68d649af9367c207713ea2 diff --git a/vendor/github.com/dvsekhvalnov/jose2go/README.md b/vendor/github.com/dvsekhvalnov/jose2go/README.md index 2a75db640..9c265e0af 100644 --- a/vendor/github.com/dvsekhvalnov/jose2go/README.md +++ b/vendor/github.com/dvsekhvalnov/jose2go/README.md @@ -6,13 +6,16 @@ on standard library. Supports full suite of signing, encryption and compression algorithms defined by [JSON Web Algorithms](https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-31) as of July 4, 2014 version. Extensively unit tested and cross tested (100+ tests) for compatibility with [jose.4.j](https://bitbucket.org/b_c/jose4j/wiki/Home), [Nimbus-JOSE-JWT](https://bitbucket.org/nimbusds/nimbus-jose-jwt/wiki/Home), [json-jwt](https://github.com/nov/json-jwt) and -[jose-jwt](https://github.com/dvsekhvalnov/jose-jwt) libraries. +[jose-jwt](https://github.com/dvsekhvalnov/jose-jwt) libraries. ## Status -Used in production. GA ready. Current version is 1.2 +Used in production. GA ready. Current version is 1.3. ## Important +v1.3 fixed potential Invalid Curve Attack on NIST curves within ECDH key management. +Upgrade strongly recommended. + v1.2 breaks `jose.Decode` interface by returning 3 values instead of 2. v1.2 deprecates `jose.Compress` method in favor of using configuration options to `jose.Encrypt`, @@ -78,7 +81,7 @@ import ( ``` ## Usage -#### Creating Plaintext (unprotected) Tokens +#### Creating Plaintext (unprotected) Tokens ```Go package main @@ -127,7 +130,7 @@ func main() { } } ``` - + #### RS-256, RS-384 and RS-512, PS-256, PS-384 and PS-512 Signing with RS256, RS384, RS512, PS256, PS384, PS512 expecting `*rsa.PrivateKey` private key of corresponding length. **jose2go** [provides convinient utils](#dealing-with-keys) to construct `*rsa.PrivateKey` instance from PEM encoded PKCS1 or PKCS8 data: `Rsa.ReadPrivate([]byte)` under `jose2go/keys/rsa` package. @@ -164,7 +167,7 @@ func main() { fmt.Printf("\nRS256 = %v\n",token) } } -``` +``` #### ES-256, ES-384 and ES-512 ES256, ES384, ES512 ECDSA signatures expecting `*ecdsa.PrivateKey` private elliptic curve key of corresponding length. **jose2go** [provides convinient utils](#dealing-with-keys) to construct `*ecdsa.PrivateKey` instance from PEM encoded PKCS1 or PKCS8 data: `ecc.ReadPrivate([]byte)` or directly from `X,Y,D` parameters: `ecc.NewPrivate(x,y,d []byte)` under `jose2go/keys/ecc` package. @@ -192,8 +195,8 @@ func main() { //go use token fmt.Printf("\ntoken = %v\n",token) } -} -``` +} +``` ### Creating encrypted tokens #### RSA-OAEP-256, RSA-OAEP and RSA1\_5 key management algorithm @@ -317,7 +320,7 @@ func main() { ``` #### PBES2 using HMAC SHA with AES Key Wrap key management family of algorithms -PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW key management requires `string` passphrase from which actual key will be derived +PBES2-HS256+A128KW, PBES2-HS384+A192KW, PBES2-HS512+A256KW key management requires `string` passphrase from which actual key will be derived ```Go package main @@ -366,8 +369,8 @@ func main() { fmt.Printf("\nDIR A128GCM = %v\n",token) } } -``` - +``` + ### Creating compressed & encrypted tokens #### DEFLATE compression **jose2go** supports optional DEFLATE compression of payload before encrypting, can be used with all supported encryption and key management algorithms: @@ -394,7 +397,7 @@ func main() { } } ``` - + ### Verifying, Decoding and Decompressing tokens Decoding json web tokens is fully symmetric to creating signed or encrypted tokens (with respect to public/private cryptography), decompressing deflated payloads is handled automatically: @@ -421,8 +424,8 @@ func main() { if(err==nil) { //go use token fmt.Printf("\npayload = %v\n",payload) - - //and/or use headers + + //and/or use headers fmt.Printf("\nheaders = %v\n",headers) } } @@ -461,8 +464,8 @@ func main() { if(err==nil) { //go use token fmt.Printf("\npayload = %v\n",payload) - - //and/or use headers + + //and/or use headers fmt.Printf("\nheaders = %v\n",headers) } } @@ -501,8 +504,8 @@ func main() { if(err==nil) { //go use payload fmt.Printf("\npayload = %v\n",payload) - - //and/or use headers + + //and/or use headers fmt.Printf("\nheaders = %v\n",headers) } } @@ -529,8 +532,8 @@ func main() { if(err==nil) { //go use token fmt.Printf("\npayload = %v\n",payload) - - //and/or use headers + + //and/or use headers fmt.Printf("\nheaders = %v\n",headers) } } @@ -559,13 +562,13 @@ func main() { if(err==nil) { //go use token fmt.Printf("\npayload = %v\n",payload) - - //and/or use headers + + //and/or use headers fmt.Printf("\nheaders = %v\n",headers) } } ``` - + **ECDH-ES** and **ECDH-ES+A128KW**, **ECDH-ES+A192KW**, **ECDH-ES+A256KW** key management expecting `*ecdsa.PrivateKey` private elliptic curve key of corresponding length. **jose2go** [provides convinient utils](#dealing-with-keys) to construct `*ecdsa.PrivateKey` instance from PEM encoded PKCS1 or PKCS8 data: `ecc.ReadPrivate([]byte)` or directly from `X,Y,D` parameters: `ecc.NewPrivate(x,y,d []byte)` under `jose2go/keys/ecc` package: ```Go @@ -590,15 +593,15 @@ func main() { if(err==nil) { //go use token fmt.Printf("\npayload = %v\n",payload) - - //and/or use headers + + //and/or use headers fmt.Printf("\nheaders = %v\n",headers) } -} +} ``` - + ### Adding extra headers -It's possible to pass additional headers while encoding token. **jose2go** provides convenience configuration helpers: `Header(name string, value interface{})` and `Headers(headers map[string]interface{})` that can be passed to `Sign(..)` and `Encrypt(..)` calls. +It's possible to pass additional headers while encoding token. **jose2go** provides convenience configuration helpers: `Header(name string, value interface{})` and `Headers(headers map[string]interface{})` that can be passed to `Sign(..)` and `Encrypt(..)` calls. Note: **jose2go** do not allow to override `alg`, `enc` and `zip` headers. @@ -614,11 +617,11 @@ Encryption with extra headers: token, err := jose.Encrypt(payload, jose.DIR, jose.A128GCM, sharedKey, jose.Headers(map[string]interface{}{"keyid": "111-22-33", "cty": "text/plain"})) ``` - + ### Two phase validation In some cases validation (decoding) key can be unknown prior to examining token content. For instance one can use different keys per token issuer or rely on headers information to determine which key to use, do logging or other things. -**jose2go** allows to pass `func(headers map[string]interface{}, payload string) key interface{}` callback instead of key to `jose.Decode(..)`. Callback will be executed prior to decoding and integrity validation and will recieve parsed headers and payload as is (for encrypted tokens it will be cipher text). Callback should return key to be used for actual decoding process. +**jose2go** allows to pass `func(headers map[string]interface{}, payload string) key interface{}` callback instead of key to `jose.Decode(..)`. Callback will be executed prior to decoding and integrity validation and will recieve parsed headers and payload as is (for encrypted tokens it will be cipher text). Callback should return key to be used for actual decoding process or `error` if decoding should be stopped, given error object will be returned from `jose.Decode(..)` call. Example of decoding token with callback: @@ -631,6 +634,7 @@ import ( "github.com/dvsekhvalnov/jose2go" "github.com/dvsekhvalnov/jose2go/keys/rsa" "io/ioutil" + "errors" ) func main() { @@ -644,8 +648,14 @@ func main() { fmt.Printf("\nPayload before decoding: %v\n", payload) //lookup key based on keyid header as en example - //or lookup based on something from payload, e.g. 'iss' claim for instance - return FindKey(headers['keyid']) + //or lookup based on something from payload, e.g. 'iss' claim for instance + key := FindKey(headers['keyid']) + + if(key==nil) { + return errors.New("Key not found") + } + + return key; }) if err == nil { @@ -654,9 +664,108 @@ func main() { } } ``` - -### Dealing with keys -**jose2go** provides several helper methods to simplify loading & importing of elliptic and rsa keys. Import `jose2go/keys/rsa` or `jose2go/keys/ecc` respectively: + +### Working with binary payload +In addition to work with string payloads (typical use-case) `jose2go` supports +encoding and decoding of raw binary data. `jose.DecodeBytes`, `jose.SignBytes` +and `jose.EncryptBytes` functions provides similar interface but accepting +`[]byte` payloads. + +Examples: + +```Go +package main + +import ( + "github.com/dvsekhvalnov/jose2go" +) + +func main() { + + token := `eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJjIjo4MTkyLCJwMnMiOiJlZWpFZTF0YmJVbU5XV2s2In0.J2HTgltxH3p7A2zDgQWpZPgA2CHTSnDmMhlZWeSOMoZ0YvhphCeg-w.FzYG5AOptknu7jsG.L8jAxfxZhDNIqb0T96YWoznQ.yNeOfQWUbm8KuDGZ_5lL_g` + + passphrase := `top secret` + + payload, headers, err := jose.DecodeBytes(token,passphrase) + + if(err==nil) { + //go use token + //payload = []byte{....} + } +} +``` + +```Go +package main + +import ( + "fmt" + "io/ioutil" + "github.com/dvsekhvalnov/jose2go/keys/rsa" + "github.com/dvsekhvalnov/jose2go" +) + +func main() { + + payload := []byte {0x01, 0x02, 0x03, 0x04} + + keyBytes,err := ioutil.ReadFile("private.key") + + if(err!=nil) { + panic("invalid key file") + } + + privateKey,e:=Rsa.ReadPrivate(keyBytes) + + if(e!=nil) { + panic("invalid key format") + } + + token,err := jose.SignBytes(payload,jose.RS256, privateKey) + + if(err==nil) { + //go use token + fmt.Printf("\nRS256 = %v\n",token) + } +} +``` + +```Go +package main + +import ( + "fmt" + "io/ioutil" + "github.com/dvsekhvalnov/jose2go/keys/rsa" + "github.com/dvsekhvalnov/jose2go" +) + +func main() { + + payload := []byte {0x01, 0x02, 0x03, 0x04} + + keyBytes,err := ioutil.ReadFile("public.key") + + if(err!=nil) { + panic("invalid key file") + } + + publicKey,e:=Rsa.ReadPublic(keyBytes) + + if(e!=nil) { + panic("invalid key format") + } + + token,err := jose.EncryptBytes(payload, jose.RSA_OAEP, jose.A256GCM, publicKey) + + if(err==nil) { + //go use token + fmt.Printf("\ntoken = %v\n",token) + } +} +``` +### Dealing with keys +**jose2go** provides several helper methods to simplify loading & importing of elliptic and rsa keys. Import `jose2go/keys/rsa` or `jose2go/keys/ecc` respectively: #### RSA keys 1. `Rsa.ReadPrivate(raw []byte) (key *rsa.PrivateKey,err error)` attempts to parse RSA private key from PKCS1 or PKCS8 format (`BEGIN RSA PRIVATE KEY` and `BEGIN PRIVATE KEY` headers) @@ -686,7 +795,7 @@ func main() { 2. `Rsa.ReadPublic(raw []byte) (key *rsa.PublicKey,err error)` attempts to parse RSA public key from PKIX key format or PKCS1 X509 certificate (`BEGIN PUBLIC KEY` and `BEGIN CERTIFICATE` headers) -```Go +```Go package main import ( @@ -708,7 +817,7 @@ func main() { fmt.Printf("publicKey = %v\n",publicKey) } ``` - + #### ECC keys 1. `ecc.ReadPrivate(raw []byte) (key *ecdsa.PrivateKey,err error)` attemps to parse elliptic curve private key from PKCS1 or PKCS8 format (`BEGIN EC PRIVATE KEY` and `BEGIN PRIVATE KEY` headers) @@ -800,16 +909,16 @@ func main() { ``` ### More examples -Checkout `jose_test.go` for more examples. +Checkout `jose_test.go` for more examples. ##Changelog ### 1.2 -- interface to access token headers after decoding +- interface to access token headers after decoding - interface to provide extra headers for token encoding - two-phase validation support - + ### 1.1 - security and bug fixes ### 1.0 -- initial stable version with full suite JOSE spec support \ No newline at end of file +- initial stable version with full suite JOSE spec support diff --git a/vendor/github.com/dvsekhvalnov/jose2go/ecdh.go b/vendor/github.com/dvsekhvalnov/jose2go/ecdh.go index 61160e39e..c3512fb34 100644 --- a/vendor/github.com/dvsekhvalnov/jose2go/ecdh.go +++ b/vendor/github.com/dvsekhvalnov/jose2go/ecdh.go @@ -1,26 +1,26 @@ package jose import ( - "errors" - "crypto/ecdsa" + "crypto/ecdsa" "crypto/elliptic" "crypto/rand" "crypto/sha256" + "errors" "fmt" - "math/big" - "github.com/dvsekhvalnov/jose2go/base64url" - "github.com/dvsekhvalnov/jose2go/arrays" - "github.com/dvsekhvalnov/jose2go/keys/ecc" - "github.com/dvsekhvalnov/jose2go/kdf" + "github.com/dvsekhvalnov/jose2go/arrays" + "github.com/dvsekhvalnov/jose2go/base64url" + "github.com/dvsekhvalnov/jose2go/kdf" + "github.com/dvsekhvalnov/jose2go/keys/ecc" "github.com/dvsekhvalnov/jose2go/padding" + "math/big" ) func init() { - RegisterJwa(&Ecdh{directAgreement:true}) + RegisterJwa(&Ecdh{directAgreement: true}) } // Elliptic curve Diffie–Hellman key management (key agreement) algorithm implementation -type Ecdh struct{ +type Ecdh struct { directAgreement bool } @@ -29,123 +29,129 @@ func (alg *Ecdh) Name() string { } func (alg *Ecdh) WrapNewKey(cekSizeBits int, key interface{}, header map[string]interface{}) (cek []byte, encryptedCek []byte, err error) { - - if pubKey,ok := key.(*ecdsa.PublicKey);ok { - if _,ok := header[alg.idHeader()].(string);!ok { - return nil, nil, errors.New(fmt.Sprintf("Ecdh.WrapNewKey(): expected '%v' param in JWT header, but was not found.",alg.idHeader())) + if pubKey, ok := key.(*ecdsa.PublicKey); ok { + + if _, ok := header[alg.idHeader()].(string); !ok { + return nil, nil, errors.New(fmt.Sprintf("Ecdh.WrapNewKey(): expected '%v' param in JWT header, but was not found.", alg.idHeader())) } var d []byte var x, y *big.Int - if d, x, y, err=elliptic.GenerateKey(pubKey.Curve,rand.Reader); err!=nil { - return nil,nil,err + if d, x, y, err = elliptic.GenerateKey(pubKey.Curve, rand.Reader); err != nil { + return nil, nil, err } - ephemeral:=ecc.NewPrivate(x.Bytes(),y.Bytes(),d) + ephemeral := ecc.NewPrivate(x.Bytes(), y.Bytes(), d) - xBytes:=padding.Align(x.Bytes(), pubKey.Curve.Params().BitSize) - yBytes:=padding.Align(y.Bytes(), pubKey.Curve.Params().BitSize) + xBytes := padding.Align(x.Bytes(), pubKey.Curve.Params().BitSize) + yBytes := padding.Align(y.Bytes(), pubKey.Curve.Params().BitSize) - epk:= map[string]string { + epk := map[string]string{ "kty": "EC", - "x": base64url.Encode(xBytes), - "y": base64url.Encode(yBytes), + "x": base64url.Encode(xBytes), + "y": base64url.Encode(yBytes), "crv": name(pubKey.Curve), } - header["epk"]=epk + header["epk"] = epk - return alg.deriveKey(pubKey,ephemeral,cekSizeBits,header),nil,nil + return alg.deriveKey(pubKey, ephemeral, cekSizeBits, header), nil, nil } - return nil,nil,errors.New("Ecdh.WrapNewKey(): expected key to be '*ecdsa.PublicKey'") + return nil, nil, errors.New("Ecdh.WrapNewKey(): expected key to be '*ecdsa.PublicKey'") } func (alg *Ecdh) Unwrap(encryptedCek []byte, key interface{}, cekSizeBits int, header map[string]interface{}) (cek []byte, err error) { - if privKey,ok := key.(*ecdsa.PrivateKey);ok { - + if privKey, ok := key.(*ecdsa.PrivateKey); ok { + var epk map[string]interface{} - - if epk,ok = header["epk"].(map[string]interface{});!ok { - return nil,errors.New("Ecdh.Unwrap(): expected 'epk' param in JWT header, but was not found.") + + if epk, ok = header["epk"].(map[string]interface{}); !ok { + return nil, errors.New("Ecdh.Unwrap(): expected 'epk' param in JWT header, but was not found.") } - - if _,ok := header[alg.idHeader()].(string);!ok { - return nil,errors.New(fmt.Sprintf("Ecdh.Unwrap(): expected '%v' param in JWT header, but was not found.",alg.idHeader())) + + if _, ok := header[alg.idHeader()].(string); !ok { + return nil, errors.New(fmt.Sprintf("Ecdh.Unwrap(): expected '%v' param in JWT header, but was not found.", alg.idHeader())) } - - var x,y,crv string + + var x, y, crv string var xBytes, yBytes []byte - - if x,ok=epk["x"].(string);!ok { - return nil,errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'x' was not found.") + + if x, ok = epk["x"].(string); !ok { + return nil, errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'x' was not found.") } - - if y,ok=epk["y"].(string);!ok { - return nil,errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'y' was not found.") + + if y, ok = epk["y"].(string); !ok { + return nil, errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'y' was not found.") } - - if crv,ok=epk["crv"].(string);!ok { - return nil,errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'crv' was not found.") + + if crv, ok = epk["crv"].(string); !ok { + return nil, errors.New("Ecdh.Unwrap(): expects 'epk' key to contain 'x','y' and 'crv' fields, but 'crv' was not found.") } - - if crv!="P-256" && crv!="P-384" && crv!="P-521" { - return nil,errors.New(fmt.Sprintf("Ecdh.Unwrap(): unknown or unsupported curve %v",crv)) + + if crv != "P-256" && crv != "P-384" && crv != "P-521" { + return nil, errors.New(fmt.Sprintf("Ecdh.Unwrap(): unknown or unsupported curve %v", crv)) } - - if xBytes,err=base64url.Decode(x);err!=nil { - return nil,err + + if xBytes, err = base64url.Decode(x); err != nil { + return nil, err } - if yBytes,err=base64url.Decode(y);err!=nil { - return nil,err - } - - pubKey := ecc.NewPublic(xBytes,yBytes) - - return alg.deriveKey(pubKey,privKey,cekSizeBits,header),nil + if yBytes, err = base64url.Decode(y); err != nil { + return nil, err + } + + pubKey := ecc.NewPublic(xBytes, yBytes) + + if !privKey.Curve.IsOnCurve(pubKey.X, pubKey.Y) { + return nil, errors.New(fmt.Sprintf("Ephemeral public key received in header is invalid for reciever's private key.")) + } + + return alg.deriveKey(pubKey, privKey, cekSizeBits, header), nil } - return nil,errors.New("Ecdh.Unwrap(): expected key to be '*ecdsa.PrivateKey'") + return nil, errors.New("Ecdh.Unwrap(): expected key to be '*ecdsa.PrivateKey'") } func (alg *Ecdh) deriveKey(pubKey *ecdsa.PublicKey, privKey *ecdsa.PrivateKey, keySizeBits int, header map[string]interface{}) []byte { - - var enc,apv,apu []byte + + var enc, apv, apu []byte var err error - - enc=[]byte(header[alg.idHeader()].(string)) - - if a,ok:=header["apv"].(string);!ok { - if apv,err=base64url.Decode(a);err!=nil { + + enc = []byte(header[alg.idHeader()].(string)) + + if a, ok := header["apv"].(string); !ok { + if apv, err = base64url.Decode(a); err != nil { apv = nil } } - if a,ok:=header["apu"].(string);!ok { - if apu,err=base64url.Decode(a);err!=nil { + if a, ok := header["apu"].(string); !ok { + if apu, err = base64url.Decode(a); err != nil { apu = nil } - } - - z, _ := pubKey.Curve.ScalarMult(pubKey.X, pubKey.Y, privKey.D.Bytes()) - zBytes := padding.Align(z.Bytes(), privKey.Curve.Params().BitSize) - - return kdf.DeriveConcatKDF(keySizeBits,zBytes, prependDatalen(enc), prependDatalen(apu), prependDatalen(apv),arrays.UInt32ToBytes(uint32(keySizeBits)),nil,sha256.New()) + } + + z, _ := pubKey.Curve.ScalarMult(pubKey.X, pubKey.Y, privKey.D.Bytes()) + zBytes := padding.Align(z.Bytes(), privKey.Curve.Params().BitSize) + + return kdf.DeriveConcatKDF(keySizeBits, zBytes, prependDatalen(enc), prependDatalen(apu), prependDatalen(apv), arrays.UInt32ToBytes(uint32(keySizeBits)), nil, sha256.New()) } -func(alg *Ecdh) idHeader() string { - if alg.directAgreement { return "enc" } - +func (alg *Ecdh) idHeader() string { + if alg.directAgreement { + return "enc" + } + return "alg" } func name(curve elliptic.Curve) string { - return fmt.Sprintf("P-%v",curve.Params().BitSize) + return fmt.Sprintf("P-%v", curve.Params().BitSize) } func prependDatalen(bytes []byte) []byte { - return arrays.Concat(arrays.UInt32ToBytes(uint32(len(bytes))),bytes) + return arrays.Concat(arrays.UInt32ToBytes(uint32(len(bytes))), bytes) } diff --git a/vendor/github.com/dvsekhvalnov/jose2go/jose.go b/vendor/github.com/dvsekhvalnov/jose2go/jose.go index 633f9b4a7..2739df926 100644 --- a/vendor/github.com/dvsekhvalnov/jose2go/jose.go +++ b/vendor/github.com/dvsekhvalnov/jose2go/jose.go @@ -132,12 +132,21 @@ type joseConfig struct { headers map[string]interface{} } -// Sign produces signed JWT token given arbitrary payload, signature algorithm to use (see constants for list of supported algs), signing key and extra options (see option functions) +// Sign produces signed JWT token given arbitrary string payload, signature algorithm to use (see constants for list of supported algs), signing key and extra options (see option functions) // Signing key is of different type for different signing alg, see specific // signing alg implementation documentation. // // It returns 3 parts signed JWT token as string and not nil error if something went wrong. func Sign(payload string, signingAlg string, key interface{}, options ...func(*joseConfig)) (token string, err error) { + return SignBytes([]byte(payload), signingAlg, key, options...) +} + +// Sign produces signed JWT token given arbitrary binary payload, signature algorithm to use (see constants for list of supported algs), signing key and extra options (see option functions) +// Signing key is of different type for different signing alg, see specific +// signing alg implementation documentation. +// +// It returns 3 parts signed JWT token as string and not nil error if something went wrong. +func SignBytes(payload []byte, signingAlg string, key interface{}, options ...func(*joseConfig)) (token string, err error) { if signer, ok := jwsHashers[signingAlg]; ok { cfg := &joseConfig{compressionAlg: "", headers: make(map[string]interface{})} @@ -154,7 +163,7 @@ func Sign(payload string, signingAlg string, key interface{}, options ...func(*j cfg.headers["typ"] = "JWT" } - paloadBytes := []byte(payload) + paloadBytes := payload var header []byte var signature []byte @@ -172,12 +181,21 @@ func Sign(payload string, signingAlg string, key interface{}, options ...func(*j return "", errors.New(fmt.Sprintf("jwt.Sign(): unknown algorithm: '%v'", signingAlg)) } -// Encrypt produces encrypted JWT token given arbitrary payload, key management and encryption algorithms to use (see constants for list of supported algs) and management key. +// Encrypt produces encrypted JWT token given arbitrary string payload, key management and encryption algorithms to use (see constants for list of supported algs) and management key. // Management key is of different type for different key management alg, see specific // key management alg implementation documentation. // // It returns 5 parts encrypted JWT token as string and not nil error if something went wrong. func Encrypt(payload string, alg string, enc string, key interface{}, options ...func(*joseConfig)) (token string, err error) { + return EncryptBytes([]byte(payload), alg, enc, key, options...) +} + +// Encrypt produces encrypted JWT token given arbitrary binary payload, key management and encryption algorithms to use (see constants for list of supported algs) and management key. +// Management key is of different type for different key management alg, see specific +// key management alg implementation documentation. +// +// It returns 5 parts encrypted JWT token as string and not nil error if something went wrong. +func EncryptBytes(payload []byte, alg string, enc string, key interface{}, options ...func(*joseConfig)) (token string, err error) { cfg := &joseConfig{compressionAlg: "", headers: make(map[string]interface{})} @@ -190,7 +208,7 @@ func Encrypt(payload string, alg string, enc string, key interface{}, options .. cfg.headers["alg"] = alg cfg.headers["enc"] = enc - byteContent := []byte(payload) + byteContent := payload if cfg.compressionAlg != "" { if zipAlg, ok := jwcCompressors[cfg.compressionAlg]; ok { @@ -235,15 +253,29 @@ func Compress(payload string, alg string, enc string, zip string, key interface{ // Decode verifies, decrypts and decompresses given JWT token using management key. // Management key is of different type for different key management or signing algorithms, see specific alg implementation documentation. // -// Returns decoded payload as a string and not nil error if something went wrong. +// Returns decoded payload as a string, headers and not nil error if something went wrong. func Decode(token string, key interface{}) (string, map[string]interface{}, error) { - parts, err := compact.Parse(token) + payload, headers, err := DecodeBytes(token, key) if err != nil { return "", nil, err } + return string(payload), headers, nil +} + +// Decode verifies, decrypts and decompresses given JWT token using management key. +// Management key is of different type for different key management or signing algorithms, see specific alg implementation documentation. +// +// Returns decoded payload as a raw bytes, headers and not nil error if something went wrong. +func DecodeBytes(token string, key interface{}) ([]byte, map[string]interface{}, error) { + parts, err := compact.Parse(token) + + if err != nil { + return nil, nil, err + } + if len(parts) == 3 { return verify(parts, key) } @@ -252,7 +284,7 @@ func Decode(token string, key interface{}) (string, map[string]interface{}, erro return decrypt(parts, key) } - return "", nil, errors.New(fmt.Sprintf("jwt.Decode() expects token of 3 or 5 parts, but was given: %v parts", len(parts))) + return nil, nil, errors.New(fmt.Sprintf("jwt.DecodeBytes() expects token of 3 or 5 parts, but was given: %v parts", len(parts))) } func encrypt(payload []byte, jwtHeader map[string]interface{}, key interface{}) (token string, err error) { @@ -288,7 +320,7 @@ func encrypt(payload []byte, jwtHeader map[string]interface{}, key interface{}) return compact.Serialize(header, encryptedCek, iv, cipherText, authTag), nil } -func verify(parts [][]byte, key interface{}) (plainText string, headers map[string]interface{}, err error) { +func verify(parts [][]byte, key interface{}) (plainText []byte, headers map[string]interface{}, err error) { header, payload, signature := parts[0], parts[1], parts[2] @@ -297,50 +329,61 @@ func verify(parts [][]byte, key interface{}) (plainText string, headers map[stri var jwtHeader map[string]interface{} if err = json.Unmarshal(header, &jwtHeader); err != nil { - return "", nil, err + return nil, nil, err } - alg := jwtHeader["alg"].(string) - - if verifier, ok := jwsHashers[alg]; ok { + if alg, ok := jwtHeader["alg"].(string); ok { + if verifier, ok := jwsHashers[alg]; ok { + if key, err = retrieveActualKey(jwtHeader, string(payload), key); err != nil { + return nil, nil, err + } - key = retrieveActualKey(jwtHeader, string(payload), key) + if err = verifier.Verify(secured, signature, key); err == nil { + return payload, jwtHeader, nil + } - if err = verifier.Verify(secured, signature, key); err == nil { - return string(payload), jwtHeader, nil + return nil, nil, err } - return "", nil, err + return nil, nil, errors.New(fmt.Sprintf("jwt.Decode(): Unknown algorithm: '%v'", alg)) } - return "", nil, errors.New(fmt.Sprintf("jwt.Decode(): Unknown algorithm: '%v'", alg)) + return nil, nil, errors.New(fmt.Sprint("jwt.Decode(): required 'alg' header is missing or of invalid type")) } -func decrypt(parts [][]byte, key interface{}) (plainText string, headers map[string]interface{}, err error) { +func decrypt(parts [][]byte, key interface{}) (plainText []byte, headers map[string]interface{}, err error) { header, encryptedCek, iv, cipherText, authTag := parts[0], parts[1], parts[2], parts[3], parts[4] var jwtHeader map[string]interface{} if e := json.Unmarshal(header, &jwtHeader); e != nil { - return "", nil, e + return nil, nil, e } - alg := jwtHeader["alg"].(string) - enc := jwtHeader["enc"].(string) - - aad := []byte(compact.Serialize(header)) - var keyMgmtAlg JwaAlgorithm var encAlg JweEncryption var zipAlg JwcAlgorithm var cek, plainBytes []byte var ok bool + var alg, enc string + + if alg, ok = jwtHeader["alg"].(string); !ok { + return nil, nil, errors.New(fmt.Sprint("jwt.Decode(): required 'alg' header is missing or of invalid type")) + } + + if enc, ok = jwtHeader["enc"].(string); !ok { + return nil, nil, errors.New(fmt.Sprint("jwt.Decode(): required 'enc' header is missing or of invalid type")) + } + + aad := []byte(compact.Serialize(header)) if keyMgmtAlg, ok = jwaAlgorithms[alg]; ok { if encAlg, ok = jweEncryptors[enc]; ok { - key = retrieveActualKey(jwtHeader, string(cipherText), key) + if key, err = retrieveActualKey(jwtHeader, string(cipherText), key); err != nil { + return nil, nil, err + } if cek, err = keyMgmtAlg.Unwrap(encryptedCek, key, encAlg.KeySizeBits(), jwtHeader); err == nil { if plainBytes, err = encAlg.Decrypt(aad, cek, iv, cipherText, authTag); err == nil { @@ -348,31 +391,37 @@ func decrypt(parts [][]byte, key interface{}) (plainText string, headers map[str if zip, compressed := jwtHeader["zip"].(string); compressed { if zipAlg, ok = jwcCompressors[zip]; !ok { - return "", nil, errors.New(fmt.Sprintf("jwt.decrypt(): Unknown compression algorithm '%v'", zip)) + return nil, nil, errors.New(fmt.Sprintf("jwt.decrypt(): Unknown compression algorithm '%v'", zip)) } plainBytes = zipAlg.Decompress(plainBytes) } - return string(plainBytes), jwtHeader, nil + return plainBytes, jwtHeader, nil } - return "", nil, err + return nil, nil, err } - return "", nil, err + return nil, nil, err } - return "", nil, errors.New(fmt.Sprintf("jwt.decrypt(): Unknown encryption algorithm '%v'", enc)) + return nil, nil, errors.New(fmt.Sprintf("jwt.decrypt(): Unknown encryption algorithm '%v'", enc)) } - return "", nil, errors.New(fmt.Sprintf("jwt.decrypt(): Unknown key management algorithm '%v'", alg)) + return nil, nil, errors.New(fmt.Sprintf("jwt.decrypt(): Unknown key management algorithm '%v'", alg)) } -func retrieveActualKey(headers map[string]interface{}, payload string, key interface{}) interface{} { +func retrieveActualKey(headers map[string]interface{}, payload string, key interface{}) (interface{}, error) { if keyCallback, ok := key.(func(headers map[string]interface{}, payload string) interface{}); ok { - return keyCallback(headers, payload) + result := keyCallback(headers, payload) + + if err, ok := result.(error); ok { + return nil, err + } + + return result, nil } - return key + return key, nil }