diff --git a/vendor/github.com/briankassouf/jose/LICENSE b/vendor/github.com/briankassouf/jose/LICENSE new file mode 100644 index 0000000000..d2d35b66cb --- /dev/null +++ b/vendor/github.com/briankassouf/jose/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2015 Sermo Digital LLC + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. + diff --git a/vendor/github.com/briankassouf/jose/README.md b/vendor/github.com/briankassouf/jose/README.md new file mode 100644 index 0000000000..621862ef91 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/README.md @@ -0,0 +1,40 @@ +JOSE +============ +[![Build Status](https://travis-ci.org/SermoDigital/jose.svg?branch=master)](https://travis-ci.org/SermoDigital/jose) +[![GoDoc](https://godoc.org/github.com/SermoDigital/jose?status.svg)](https://godoc.org/github.com/SermoDigital/jose) + +JOSE is a comprehensive set of JWT, JWS, and JWE libraries. + +## Why + +The only other JWS/JWE/JWT implementations are specific to JWT, and none +were particularly pleasant to work with. + +These libraries should provide an easy, straightforward way to securely +create, parse, and validate JWS, JWE, and JWTs. + +## Notes: +JWE is currently unimplemented. + +## Version 0.9: + +## Documentation + +The docs can be found at [godoc.org] [docs], as usual. + +A gopkg.in mirror can be found at https://gopkg.in/jose.v1, thanks to +@zia-newversion. (For context, see issue #30.) + +### [JWS RFC][jws] +### [JWE RFC][jwe] +### [JWT RFC][jwt] + +## License + +[MIT] [license]. + +[docs]: https://godoc.org/github.com/SermoDigital/jose +[license]: https://github.com/SermoDigital/jose/blob/master/LICENSE.md +[jws]: https://tools.ietf.org/html/rfc7515 +[jwe]: https://tools.ietf.org/html/rfc7516 +[jwt]: https://tools.ietf.org/html/rfc7519 diff --git a/vendor/github.com/briankassouf/jose/_test.sh b/vendor/github.com/briankassouf/jose/_test.sh new file mode 100755 index 0000000000..a36a4709b8 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/_test.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash + +set -euo pipefail + +go build ./... +go test ./... +golint ./... +go vet ./... \ No newline at end of file diff --git a/vendor/github.com/briankassouf/jose/base64.go b/vendor/github.com/briankassouf/jose/base64.go new file mode 100644 index 0000000000..f7275fb2e9 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/base64.go @@ -0,0 +1,44 @@ +package jose + +import "encoding/base64" + +// Encoder is satisfied if the type can marshal itself into a valid +// structure for a JWS. +type Encoder interface { + // Base64 implies T -> JSON -> RawURLEncodingBase64 + Base64() ([]byte, error) +} + +// Base64Decode decodes a base64-encoded byte slice. +func Base64Decode(b []byte) ([]byte, error) { + buf := make([]byte, base64.RawURLEncoding.DecodedLen(len(b))) + n, err := base64.RawURLEncoding.Decode(buf, b) + return buf[:n], err +} + +// Base64Encode encodes a byte slice. +func Base64Encode(b []byte) []byte { + buf := make([]byte, base64.RawURLEncoding.EncodedLen(len(b))) + base64.RawURLEncoding.Encode(buf, b) + return buf +} + +// EncodeEscape base64-encodes a byte slice but escapes it for JSON. +// It'll return the format: `"base64"` +func EncodeEscape(b []byte) []byte { + buf := make([]byte, base64.RawURLEncoding.EncodedLen(len(b))+2) + buf[0] = '"' + base64.RawURLEncoding.Encode(buf[1:], b) + buf[len(buf)-1] = '"' + return buf +} + +// DecodeEscaped decodes a base64-encoded byte slice straight from a JSON +// structure. It assumes it's in the format: `"base64"`, but can handle +// cases where it's not. +func DecodeEscaped(b []byte) ([]byte, error) { + if len(b) > 1 && b[0] == '"' && b[len(b)-1] == '"' { + b = b[1 : len(b)-1] + } + return Base64Decode(b) +} diff --git a/vendor/github.com/briankassouf/jose/crypto/doc.go b/vendor/github.com/briankassouf/jose/crypto/doc.go new file mode 100644 index 0000000000..16cf476ba0 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/crypto/doc.go @@ -0,0 +1,4 @@ +// Package crypto implements "SigningMethods" and "EncryptionMethods"; +// that is, ways to sign and encrypt JWS and JWEs, respectively, as well +// as JWTs. +package crypto diff --git a/vendor/github.com/briankassouf/jose/crypto/ecdsa.go b/vendor/github.com/briankassouf/jose/crypto/ecdsa.go new file mode 100644 index 0000000000..fe9fd5e249 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/crypto/ecdsa.go @@ -0,0 +1,138 @@ +package crypto + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rand" + "encoding/asn1" + "encoding/json" + "errors" + "math/big" +) + +// ErrECDSAVerification is missing from crypto/ecdsa compared to crypto/rsa +var ErrECDSAVerification = errors.New("crypto/ecdsa: verification error") + +// SigningMethodECDSA implements the ECDSA family of signing methods signing +// methods +type SigningMethodECDSA struct { + Name string + Hash crypto.Hash + _ struct{} +} + +// ECPoint is a marshalling structure for the EC points R and S. +type ECPoint struct { + R *big.Int + S *big.Int +} + +// Specific instances of EC SigningMethods. +var ( + // SigningMethodES256 implements ES256. + SigningMethodES256 = &SigningMethodECDSA{ + Name: "ES256", + Hash: crypto.SHA256, + } + + // SigningMethodES384 implements ES384. + SigningMethodES384 = &SigningMethodECDSA{ + Name: "ES384", + Hash: crypto.SHA384, + } + + // SigningMethodES512 implements ES512. + SigningMethodES512 = &SigningMethodECDSA{ + Name: "ES512", + Hash: crypto.SHA512, + } +) + +// Alg returns the name of the SigningMethodECDSA instance. +func (m *SigningMethodECDSA) Alg() string { return m.Name } + +// Verify implements the Verify method from SigningMethod. +// For this verify method, key must be an *ecdsa.PublicKey. +func (m *SigningMethodECDSA) Verify(raw []byte, signature Signature, key interface{}) error { + ecdsaKey, ok := key.(*ecdsa.PublicKey) + if !ok { + return ErrInvalidKey + } + + var keySize int + switch m.Name { + case "ES256": + keySize = 32 + case "ES384": + keySize = 48 + case "ES512": + keySize = 66 + } + + if len(signature) == 2*keySize { + r := big.NewInt(0).SetBytes(signature[:keySize]) + s := big.NewInt(0).SetBytes(signature[keySize:]) + + // If verification succeeds return + if ecdsa.Verify(ecdsaKey, m.sum(raw), r, s) { + return nil + } + } + + // Fall back to the old method + // Unmarshal asn1 ECPoint + var ecpoint ECPoint + if _, err := asn1.Unmarshal(signature, &ecpoint); err != nil { + return err + } + + // Verify the signature + if !ecdsa.Verify(ecdsaKey, m.sum(raw), ecpoint.R, ecpoint.S) { + return ErrECDSAVerification + } + + return nil +} + +// Sign implements the Sign method from SigningMethod. +// For this signing method, key must be an *ecdsa.PrivateKey. +func (m *SigningMethodECDSA) Sign(data []byte, key interface{}) (Signature, error) { + + ecdsaKey, ok := key.(*ecdsa.PrivateKey) + if !ok { + return nil, ErrInvalidKey + } + + r, s, err := ecdsa.Sign(rand.Reader, ecdsaKey, m.sum(data)) + if err != nil { + return nil, err + } + + signature, err := asn1.Marshal(ECPoint{R: r, S: s}) + if err != nil { + return nil, err + } + return Signature(signature), nil +} + +func (m *SigningMethodECDSA) sum(b []byte) []byte { + h := m.Hash.New() + h.Write(b) + return h.Sum(nil) +} + +// Hasher implements the Hasher method from SigningMethod. +func (m *SigningMethodECDSA) Hasher() crypto.Hash { + return m.Hash +} + +// MarshalJSON is in case somebody decides to place SigningMethodECDSA +// inside the Header, presumably because they (wrongly) decided it was a good +// idea to use the SigningMethod itself instead of the SigningMethod's Alg +// method. In order to keep things sane, marshalling this will simply +// return the JSON-compatible representation of m.Alg(). +func (m *SigningMethodECDSA) MarshalJSON() ([]byte, error) { + return []byte(`"` + m.Alg() + `"`), nil +} + +var _ json.Marshaler = (*SigningMethodECDSA)(nil) diff --git a/vendor/github.com/briankassouf/jose/crypto/ecdsa_utils.go b/vendor/github.com/briankassouf/jose/crypto/ecdsa_utils.go new file mode 100644 index 0000000000..4bd75d2e5b --- /dev/null +++ b/vendor/github.com/briankassouf/jose/crypto/ecdsa_utils.go @@ -0,0 +1,48 @@ +package crypto + +import ( + "crypto/ecdsa" + "crypto/x509" + "encoding/pem" + "errors" +) + +// ECDSA parsing errors. +var ( + ErrNotECPublicKey = errors.New("Key is not a valid ECDSA public key") + ErrNotECPrivateKey = errors.New("Key is not a valid ECDSA private key") +) + +// ParseECPrivateKeyFromPEM will parse a PEM encoded EC Private +// Key Structure. +func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error) { + block, _ := pem.Decode(key) + if block == nil { + return nil, ErrKeyMustBePEMEncoded + } + return x509.ParseECPrivateKey(block.Bytes) +} + +// ParseECPublicKeyFromPEM will parse a PEM encoded PKCS1 or PKCS8 public key +func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error) { + + block, _ := pem.Decode(key) + if block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + parsedKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + cert, err := x509.ParseCertificate(block.Bytes) + if err != nil { + return nil, err + } + parsedKey = cert.PublicKey + } + + pkey, ok := parsedKey.(*ecdsa.PublicKey) + if !ok { + return nil, ErrNotECPublicKey + } + return pkey, nil +} diff --git a/vendor/github.com/briankassouf/jose/crypto/errors.go b/vendor/github.com/briankassouf/jose/crypto/errors.go new file mode 100644 index 0000000000..34fbd25ff7 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/crypto/errors.go @@ -0,0 +1,9 @@ +package crypto + +import "errors" + +var ( + // ErrInvalidKey means the key argument passed to SigningMethod.Verify + // was not the correct type. + ErrInvalidKey = errors.New("key is invalid") +) diff --git a/vendor/github.com/briankassouf/jose/crypto/hmac.go b/vendor/github.com/briankassouf/jose/crypto/hmac.go new file mode 100644 index 0000000000..1cb7f6e09c --- /dev/null +++ b/vendor/github.com/briankassouf/jose/crypto/hmac.go @@ -0,0 +1,81 @@ +package crypto + +import ( + "crypto" + "crypto/hmac" + "encoding/json" + "errors" +) + +// SigningMethodHMAC implements the HMAC-SHA family of SigningMethods. +type SigningMethodHMAC struct { + Name string + Hash crypto.Hash + _ struct{} +} + +// Specific instances of HMAC-SHA SigningMethods. +var ( + // SigningMethodHS256 implements HS256. + SigningMethodHS256 = &SigningMethodHMAC{ + Name: "HS256", + Hash: crypto.SHA256, + } + + // SigningMethodHS384 implements HS384. + SigningMethodHS384 = &SigningMethodHMAC{ + Name: "HS384", + Hash: crypto.SHA384, + } + + // SigningMethodHS512 implements HS512. + SigningMethodHS512 = &SigningMethodHMAC{ + Name: "HS512", + Hash: crypto.SHA512, + } + + // ErrSignatureInvalid is returned when the provided signature is found + // to be invalid. + ErrSignatureInvalid = errors.New("signature is invalid") +) + +// Alg implements the SigningMethod interface. +func (m *SigningMethodHMAC) Alg() string { return m.Name } + +// Verify implements the Verify method from SigningMethod. +// For this signing method, must be a []byte. +func (m *SigningMethodHMAC) Verify(raw []byte, signature Signature, key interface{}) error { + keyBytes, ok := key.([]byte) + if !ok { + return ErrInvalidKey + } + hasher := hmac.New(m.Hash.New, keyBytes) + hasher.Write(raw) + if hmac.Equal(signature, hasher.Sum(nil)) { + return nil + } + return ErrSignatureInvalid +} + +// Sign implements the Sign method from SigningMethod for this signing method. +// Key must be a []byte. +func (m *SigningMethodHMAC) Sign(data []byte, key interface{}) (Signature, error) { + keyBytes, ok := key.([]byte) + if !ok { + return nil, ErrInvalidKey + } + hasher := hmac.New(m.Hash.New, keyBytes) + hasher.Write(data) + return Signature(hasher.Sum(nil)), nil +} + +// Hasher implements the SigningMethod interface. +func (m *SigningMethodHMAC) Hasher() crypto.Hash { return m.Hash } + +// MarshalJSON implements json.Marshaler. +// See SigningMethodECDSA.MarshalJSON() for information. +func (m *SigningMethodHMAC) MarshalJSON() ([]byte, error) { + return []byte(`"` + m.Alg() + `"`), nil +} + +var _ json.Marshaler = (*SigningMethodHMAC)(nil) diff --git a/vendor/github.com/briankassouf/jose/crypto/none.go b/vendor/github.com/briankassouf/jose/crypto/none.go new file mode 100644 index 0000000000..db3d139e96 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/crypto/none.go @@ -0,0 +1,72 @@ +package crypto + +import ( + "crypto" + "encoding/json" + "hash" + "io" +) + +func init() { + crypto.RegisterHash(crypto.Hash(0), h) +} + +// h is passed to crypto.RegisterHash. +func h() hash.Hash { + return &f{Writer: nil} +} + +type f struct{ io.Writer } + +// Sum helps implement the hash.Hash interface. +func (_ *f) Sum(b []byte) []byte { return nil } + +// Reset helps implement the hash.Hash interface. +func (_ *f) Reset() {} + +// Size helps implement the hash.Hash interface. +func (_ *f) Size() int { return -1 } + +// BlockSize helps implement the hash.Hash interface. +func (_ *f) BlockSize() int { return -1 } + +// Unsecured is the default "none" algorithm. +var Unsecured = &SigningMethodNone{ + Name: "none", + Hash: crypto.Hash(0), +} + +// SigningMethodNone is the default "none" algorithm. +type SigningMethodNone struct { + Name string + Hash crypto.Hash + _ struct{} +} + +// Verify helps implement the SigningMethod interface. +func (_ *SigningMethodNone) Verify(_ []byte, _ Signature, _ interface{}) error { + return nil +} + +// Sign helps implement the SigningMethod interface. +func (_ *SigningMethodNone) Sign(_ []byte, _ interface{}) (Signature, error) { + return nil, nil +} + +// Alg helps implement the SigningMethod interface. +func (m *SigningMethodNone) Alg() string { + return m.Name +} + +// Hasher helps implement the SigningMethod interface. +func (m *SigningMethodNone) Hasher() crypto.Hash { + return m.Hash +} + +// MarshalJSON implements json.Marshaler. +// See SigningMethodECDSA.MarshalJSON() for information. +func (m *SigningMethodNone) MarshalJSON() ([]byte, error) { + return []byte(`"` + m.Alg() + `"`), nil +} + +var _ json.Marshaler = (*SigningMethodNone)(nil) diff --git a/vendor/github.com/briankassouf/jose/crypto/rsa.go b/vendor/github.com/briankassouf/jose/crypto/rsa.go new file mode 100644 index 0000000000..80596df33b --- /dev/null +++ b/vendor/github.com/briankassouf/jose/crypto/rsa.go @@ -0,0 +1,80 @@ +package crypto + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "encoding/json" +) + +// SigningMethodRSA implements the RSA family of SigningMethods. +type SigningMethodRSA struct { + Name string + Hash crypto.Hash + _ struct{} +} + +// Specific instances of RSA SigningMethods. +var ( + // SigningMethodRS256 implements RS256. + SigningMethodRS256 = &SigningMethodRSA{ + Name: "RS256", + Hash: crypto.SHA256, + } + + // SigningMethodRS384 implements RS384. + SigningMethodRS384 = &SigningMethodRSA{ + Name: "RS384", + Hash: crypto.SHA384, + } + + // SigningMethodRS512 implements RS512. + SigningMethodRS512 = &SigningMethodRSA{ + Name: "RS512", + Hash: crypto.SHA512, + } +) + +// Alg implements the SigningMethod interface. +func (m *SigningMethodRSA) Alg() string { return m.Name } + +// Verify implements the Verify method from SigningMethod. +// For this signing method, must be an *rsa.PublicKey. +func (m *SigningMethodRSA) Verify(raw []byte, sig Signature, key interface{}) error { + rsaKey, ok := key.(*rsa.PublicKey) + if !ok { + return ErrInvalidKey + } + return rsa.VerifyPKCS1v15(rsaKey, m.Hash, m.sum(raw), sig) +} + +// Sign implements the Sign method from SigningMethod. +// For this signing method, must be an *rsa.PrivateKey structure. +func (m *SigningMethodRSA) Sign(data []byte, key interface{}) (Signature, error) { + rsaKey, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, ErrInvalidKey + } + sigBytes, err := rsa.SignPKCS1v15(rand.Reader, rsaKey, m.Hash, m.sum(data)) + if err != nil { + return nil, err + } + return Signature(sigBytes), nil +} + +func (m *SigningMethodRSA) sum(b []byte) []byte { + h := m.Hash.New() + h.Write(b) + return h.Sum(nil) +} + +// Hasher implements the SigningMethod interface. +func (m *SigningMethodRSA) Hasher() crypto.Hash { return m.Hash } + +// MarshalJSON implements json.Marshaler. +// See SigningMethodECDSA.MarshalJSON() for information. +func (m *SigningMethodRSA) MarshalJSON() ([]byte, error) { + return []byte(`"` + m.Alg() + `"`), nil +} + +var _ json.Marshaler = (*SigningMethodRSA)(nil) diff --git a/vendor/github.com/briankassouf/jose/crypto/rsa_pss.go b/vendor/github.com/briankassouf/jose/crypto/rsa_pss.go new file mode 100644 index 0000000000..3847ae2d27 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/crypto/rsa_pss.go @@ -0,0 +1,96 @@ +// +build go1.4 + +package crypto + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "encoding/json" +) + +// SigningMethodRSAPSS implements the RSAPSS family of SigningMethods. +type SigningMethodRSAPSS struct { + *SigningMethodRSA + Options *rsa.PSSOptions +} + +// Specific instances for RS/PS SigningMethods. +var ( + // SigningMethodPS256 implements PS256. + SigningMethodPS256 = &SigningMethodRSAPSS{ + &SigningMethodRSA{ + Name: "PS256", + Hash: crypto.SHA256, + }, + &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthAuto, + Hash: crypto.SHA256, + }, + } + + // SigningMethodPS384 implements PS384. + SigningMethodPS384 = &SigningMethodRSAPSS{ + &SigningMethodRSA{ + Name: "PS384", + Hash: crypto.SHA384, + }, + &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthAuto, + Hash: crypto.SHA384, + }, + } + + // SigningMethodPS512 implements PS512. + SigningMethodPS512 = &SigningMethodRSAPSS{ + &SigningMethodRSA{ + Name: "PS512", + Hash: crypto.SHA512, + }, + &rsa.PSSOptions{ + SaltLength: rsa.PSSSaltLengthAuto, + Hash: crypto.SHA512, + }, + } +) + +// Verify implements the Verify method from SigningMethod. +// For this verify method, key must be an *rsa.PublicKey. +func (m *SigningMethodRSAPSS) Verify(raw []byte, signature Signature, key interface{}) error { + rsaKey, ok := key.(*rsa.PublicKey) + if !ok { + return ErrInvalidKey + } + return rsa.VerifyPSS(rsaKey, m.Hash, m.sum(raw), signature, m.Options) +} + +// Sign implements the Sign method from SigningMethod. +// For this signing method, key must be an *rsa.PrivateKey. +func (m *SigningMethodRSAPSS) Sign(raw []byte, key interface{}) (Signature, error) { + rsaKey, ok := key.(*rsa.PrivateKey) + if !ok { + return nil, ErrInvalidKey + } + sigBytes, err := rsa.SignPSS(rand.Reader, rsaKey, m.Hash, m.sum(raw), m.Options) + if err != nil { + return nil, err + } + return Signature(sigBytes), nil +} + +func (m *SigningMethodRSAPSS) sum(b []byte) []byte { + h := m.Hash.New() + h.Write(b) + return h.Sum(nil) +} + +// Hasher implements the Hasher method from SigningMethod. +func (m *SigningMethodRSAPSS) Hasher() crypto.Hash { return m.Hash } + +// MarshalJSON implements json.Marshaler. +// See SigningMethodECDSA.MarshalJSON() for information. +func (m *SigningMethodRSAPSS) MarshalJSON() ([]byte, error) { + return []byte(`"` + m.Alg() + `"`), nil +} + +var _ json.Marshaler = (*SigningMethodRSAPSS)(nil) diff --git a/vendor/github.com/briankassouf/jose/crypto/rsa_utils.go b/vendor/github.com/briankassouf/jose/crypto/rsa_utils.go new file mode 100644 index 0000000000..43aeff3756 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/crypto/rsa_utils.go @@ -0,0 +1,70 @@ +package crypto + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "errors" +) + +// Errors specific to rsa_utils. +var ( + ErrKeyMustBePEMEncoded = errors.New("invalid key: Key must be PEM encoded PKCS1 or PKCS8 private key") + ErrNotRSAPrivateKey = errors.New("key is not a valid RSA private key") + ErrNotRSAPublicKey = errors.New("key is not a valid RSA public key") +) + +// ParseRSAPrivateKeyFromPEM parses a PEM encoded PKCS1 or PKCS8 private key. +func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + var parsedKey interface{} + if parsedKey, err = x509.ParsePKCS1PrivateKey(block.Bytes); err != nil { + if parsedKey, err = x509.ParsePKCS8PrivateKey(block.Bytes); err != nil { + return nil, err + } + } + + var pkey *rsa.PrivateKey + var ok bool + if pkey, ok = parsedKey.(*rsa.PrivateKey); !ok { + return nil, ErrNotRSAPrivateKey + } + + return pkey, nil +} + +// ParseRSAPublicKeyFromPEM parses PEM encoded PKCS1 or PKCS8 public key. +func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error) { + var err error + + // Parse PEM block + var block *pem.Block + if block, _ = pem.Decode(key); block == nil { + return nil, ErrKeyMustBePEMEncoded + } + + // Parse the key + var parsedKey interface{} + if parsedKey, err = x509.ParsePKIXPublicKey(block.Bytes); err != nil { + if cert, err := x509.ParseCertificate(block.Bytes); err == nil { + parsedKey = cert.PublicKey + } else { + return nil, err + } + } + + var pkey *rsa.PublicKey + var ok bool + if pkey, ok = parsedKey.(*rsa.PublicKey); !ok { + return nil, ErrNotRSAPublicKey + } + + return pkey, nil +} diff --git a/vendor/github.com/briankassouf/jose/crypto/signature.go b/vendor/github.com/briankassouf/jose/crypto/signature.go new file mode 100644 index 0000000000..1f79f86118 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/crypto/signature.go @@ -0,0 +1,36 @@ +package crypto + +import ( + "encoding/json" + + "github.com/briankassouf/jose" +) + +// Signature is a JWS signature. +type Signature []byte + +// MarshalJSON implements json.Marshaler for a signature. +func (s Signature) MarshalJSON() ([]byte, error) { + return jose.EncodeEscape(s), nil +} + +// Base64 helps implements jose.Encoder for Signature. +func (s Signature) Base64() ([]byte, error) { + return jose.Base64Encode(s), nil +} + +// UnmarshalJSON implements json.Unmarshaler for signature. +func (s *Signature) UnmarshalJSON(b []byte) error { + dec, err := jose.DecodeEscaped(b) + if err != nil { + return err + } + *s = Signature(dec) + return nil +} + +var ( + _ json.Marshaler = (Signature)(nil) + _ json.Unmarshaler = (*Signature)(nil) + _ jose.Encoder = (Signature)(nil) +) diff --git a/vendor/github.com/briankassouf/jose/crypto/signing_method.go b/vendor/github.com/briankassouf/jose/crypto/signing_method.go new file mode 100644 index 0000000000..c8b8874b2a --- /dev/null +++ b/vendor/github.com/briankassouf/jose/crypto/signing_method.go @@ -0,0 +1,24 @@ +package crypto + +import "crypto" + +// SigningMethod is an interface that provides a way to sign JWS tokens. +type SigningMethod interface { + // Alg describes the signing algorithm, and is used to uniquely + // describe the specific crypto.SigningMethod. + Alg() string + + // Verify accepts the raw content, the signature, and the key used + // to sign the raw content, and returns any errors found while validating + // the signature and content. + Verify(raw []byte, sig Signature, key interface{}) error + + // Sign returns a Signature for the raw bytes, as well as any errors + // that occurred during the signing. + Sign(raw []byte, key interface{}) (Signature, error) + + // Used to cause quick panics when a crypto.SigningMethod whose form of hashing + // isn't linked in the binary when you register a crypto.SigningMethod. + // To spoof this, see "crypto.SigningMethodNone". + Hasher() crypto.Hash +} diff --git a/vendor/github.com/briankassouf/jose/doc.go b/vendor/github.com/briankassouf/jose/doc.go new file mode 100644 index 0000000000..7abb7bf141 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/doc.go @@ -0,0 +1,3 @@ +// Package jose implements some helper functions and types for the children +// packages, jws, jwt, and jwe. +package jose diff --git a/vendor/github.com/briankassouf/jose/header.go b/vendor/github.com/briankassouf/jose/header.go new file mode 100644 index 0000000000..4499a7696a --- /dev/null +++ b/vendor/github.com/briankassouf/jose/header.go @@ -0,0 +1,124 @@ +package jose + +import "encoding/json" + +// Header implements a JOSE Header with the addition of some helper +// methods, similar to net/url.Values. +type Header map[string]interface{} + +// Get retrieves the value corresponding with key from the Header. +func (h Header) Get(key string) interface{} { + if h == nil { + return nil + } + return h[key] +} + +// Set sets Claims[key] = val. It'll overwrite without warning. +func (h Header) Set(key string, val interface{}) { + h[key] = val +} + +// Del removes the value that corresponds with key from the Header. +func (h Header) Del(key string) { + delete(h, key) +} + +// Has returns true if a value for the given key exists inside the Header. +func (h Header) Has(key string) bool { + _, ok := h[key] + return ok +} + +// MarshalJSON implements json.Marshaler for Header. +func (h Header) MarshalJSON() ([]byte, error) { + if len(h) == 0 { + return nil, nil + } + b, err := json.Marshal(map[string]interface{}(h)) + if err != nil { + return nil, err + } + return EncodeEscape(b), nil +} + +// Base64 implements the Encoder interface. +func (h Header) Base64() ([]byte, error) { + return h.MarshalJSON() +} + +// UnmarshalJSON implements json.Unmarshaler for Header. +func (h *Header) UnmarshalJSON(b []byte) error { + if b == nil { + return nil + } + b, err := DecodeEscaped(b) + if err != nil { + return err + } + return json.Unmarshal(b, (*map[string]interface{})(h)) +} + +// Protected Headers are base64-encoded after they're marshaled into +// JSON. +type Protected Header + +// Get retrieves the value corresponding with key from the Protected Header. +func (p Protected) Get(key string) interface{} { + if p == nil { + return nil + } + return p[key] +} + +// Set sets Protected[key] = val. It'll overwrite without warning. +func (p Protected) Set(key string, val interface{}) { + p[key] = val +} + +// Del removes the value that corresponds with key from the Protected Header. +func (p Protected) Del(key string) { + delete(p, key) +} + +// Has returns true if a value for the given key exists inside the Protected +// Header. +func (p Protected) Has(key string) bool { + _, ok := p[key] + return ok +} + +// MarshalJSON implements json.Marshaler for Protected. +func (p Protected) MarshalJSON() ([]byte, error) { + b, err := json.Marshal(map[string]interface{}(p)) + if err != nil { + return nil, err + } + return EncodeEscape(b), nil +} + +// Base64 implements the Encoder interface. +func (p Protected) Base64() ([]byte, error) { + b, err := json.Marshal(map[string]interface{}(p)) + if err != nil { + return nil, err + } + return Base64Encode(b), nil +} + +// UnmarshalJSON implements json.Unmarshaler for Protected. +func (p *Protected) UnmarshalJSON(b []byte) error { + var h Header + if err := h.UnmarshalJSON(b); err != nil { + return err + } + *p = Protected(h) + return nil +} + +var ( + _ json.Marshaler = (Protected)(nil) + _ json.Unmarshaler = (*Protected)(nil) + _ json.Marshaler = (Header)(nil) + _ json.Unmarshaler = (*Header)(nil) +) diff --git a/vendor/github.com/briankassouf/jose/jws/claims.go b/vendor/github.com/briankassouf/jose/jws/claims.go new file mode 100644 index 0000000000..56a5b91e3e --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jws/claims.go @@ -0,0 +1,190 @@ +package jws + +import ( + "encoding/json" + "time" + + "github.com/briankassouf/jose" + "github.com/briankassouf/jose/jwt" +) + +// Claims represents a set of JOSE Claims. +type Claims jwt.Claims + +// Get retrieves the value corresponding with key from the Claims. +func (c Claims) Get(key string) interface{} { + return jwt.Claims(c).Get(key) +} + +// Set sets Claims[key] = val. It'll overwrite without warning. +func (c Claims) Set(key string, val interface{}) { + jwt.Claims(c).Set(key, val) +} + +// Del removes the value that corresponds with key from the Claims. +func (c Claims) Del(key string) { + jwt.Claims(c).Del(key) +} + +// Has returns true if a value for the given key exists inside the Claims. +func (c Claims) Has(key string) bool { + return jwt.Claims(c).Has(key) +} + +// MarshalJSON implements json.Marshaler for Claims. +func (c Claims) MarshalJSON() ([]byte, error) { + return jwt.Claims(c).MarshalJSON() +} + +// Base64 implements the Encoder interface. +func (c Claims) Base64() ([]byte, error) { + return jwt.Claims(c).Base64() +} + +// UnmarshalJSON implements json.Unmarshaler for Claims. +func (c *Claims) UnmarshalJSON(b []byte) error { + if b == nil { + return nil + } + + b, err := jose.DecodeEscaped(b) + if err != nil { + return err + } + + // Since json.Unmarshal calls UnmarshalJSON, + // calling json.Unmarshal on *p would be infinitely recursive + // A temp variable is needed because &map[string]interface{}(*p) is + // invalid Go. + + tmp := map[string]interface{}(*c) + if err = json.Unmarshal(b, &tmp); err != nil { + return err + } + *c = Claims(tmp) + return nil +} + +// Issuer retrieves claim "iss" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.1 +func (c Claims) Issuer() (string, bool) { + return jwt.Claims(c).Issuer() +} + +// Subject retrieves claim "sub" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.2 +func (c Claims) Subject() (string, bool) { + return jwt.Claims(c).Subject() +} + +// Audience retrieves claim "aud" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.3 +func (c Claims) Audience() ([]string, bool) { + return jwt.Claims(c).Audience() +} + +// Expiration retrieves claim "exp" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.4 +func (c Claims) Expiration() (time.Time, bool) { + return jwt.Claims(c).Expiration() +} + +// NotBefore retrieves claim "nbf" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.5 +func (c Claims) NotBefore() (time.Time, bool) { + return jwt.Claims(c).NotBefore() +} + +// IssuedAt retrieves claim "iat" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.6 +func (c Claims) IssuedAt() (time.Time, bool) { + return jwt.Claims(c).IssuedAt() +} + +// JWTID retrieves claim "jti" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.7 +func (c Claims) JWTID() (string, bool) { + return jwt.Claims(c).JWTID() +} + +// RemoveIssuer deletes claim "iss" from c. +func (c Claims) RemoveIssuer() { + jwt.Claims(c).RemoveIssuer() +} + +// RemoveSubject deletes claim "sub" from c. +func (c Claims) RemoveSubject() { + jwt.Claims(c).RemoveIssuer() +} + +// RemoveAudience deletes claim "aud" from c. +func (c Claims) RemoveAudience() { + jwt.Claims(c).Audience() +} + +// RemoveExpiration deletes claim "exp" from c. +func (c Claims) RemoveExpiration() { + jwt.Claims(c).RemoveExpiration() +} + +// RemoveNotBefore deletes claim "nbf" from c. +func (c Claims) RemoveNotBefore() { + jwt.Claims(c).NotBefore() +} + +// RemoveIssuedAt deletes claim "iat" from c. +func (c Claims) RemoveIssuedAt() { + jwt.Claims(c).IssuedAt() +} + +// RemoveJWTID deletes claim "jti" from c. +func (c Claims) RemoveJWTID() { + jwt.Claims(c).RemoveJWTID() +} + +// SetIssuer sets claim "iss" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.1 +func (c Claims) SetIssuer(issuer string) { + jwt.Claims(c).SetIssuer(issuer) +} + +// SetSubject sets claim "iss" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.2 +func (c Claims) SetSubject(subject string) { + jwt.Claims(c).SetSubject(subject) +} + +// SetAudience sets claim "aud" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.3 +func (c Claims) SetAudience(audience ...string) { + jwt.Claims(c).SetAudience(audience...) +} + +// SetExpiration sets claim "exp" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.4 +func (c Claims) SetExpiration(expiration time.Time) { + jwt.Claims(c).SetExpiration(expiration) +} + +// SetNotBefore sets claim "nbf" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.5 +func (c Claims) SetNotBefore(notBefore time.Time) { + jwt.Claims(c).SetNotBefore(notBefore) +} + +// SetIssuedAt sets claim "iat" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.6 +func (c Claims) SetIssuedAt(issuedAt time.Time) { + jwt.Claims(c).SetIssuedAt(issuedAt) +} + +// SetJWTID sets claim "jti" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.7 +func (c Claims) SetJWTID(uniqueID string) { + jwt.Claims(c).SetJWTID(uniqueID) +} + +var ( + _ json.Marshaler = (Claims)(nil) + _ json.Unmarshaler = (*Claims)(nil) +) diff --git a/vendor/github.com/briankassouf/jose/jws/doc.go b/vendor/github.com/briankassouf/jose/jws/doc.go new file mode 100644 index 0000000000..165836d57e --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jws/doc.go @@ -0,0 +1,2 @@ +// Package jws implements JWSs per RFC 7515 +package jws diff --git a/vendor/github.com/briankassouf/jose/jws/errors.go b/vendor/github.com/briankassouf/jose/jws/errors.go new file mode 100644 index 0000000000..0512a0e408 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jws/errors.go @@ -0,0 +1,62 @@ +package jws + +import "errors" + +var ( + + // ErrNotEnoughMethods is returned if New was called _or_ the Flat/Compact + // methods were called with 0 SigningMethods. + ErrNotEnoughMethods = errors.New("not enough methods provided") + + // ErrCouldNotUnmarshal is returned when Parse's json.Unmarshaler + // parameter returns an error. + ErrCouldNotUnmarshal = errors.New("custom unmarshal failed") + + // ErrNotCompact signals that the provided potential JWS is not + // in its compact representation. + ErrNotCompact = errors.New("not a compact JWS") + + // ErrDuplicateHeaderParameter signals that there are duplicate parameters + // in the provided Headers. + ErrDuplicateHeaderParameter = errors.New("duplicate parameters in the JOSE Header") + + // ErrTwoEmptyHeaders is returned if both Headers are empty. + ErrTwoEmptyHeaders = errors.New("both headers cannot be empty") + + // ErrNotEnoughKeys is returned when not enough keys are provided for + // the given SigningMethods. + ErrNotEnoughKeys = errors.New("not enough keys (for given methods)") + + // ErrDidNotValidate means the given JWT did not properly validate + ErrDidNotValidate = errors.New("did not validate") + + // ErrNoAlgorithm means no algorithm ("alg") was found in the Protected + // Header. + ErrNoAlgorithm = errors.New("no algorithm found") + + // ErrAlgorithmDoesntExist means the algorithm asked for cannot be + // found inside the signingMethod cache. + ErrAlgorithmDoesntExist = errors.New("algorithm doesn't exist") + + // ErrMismatchedAlgorithms means the algorithm inside the JWT was + // different than the algorithm the caller wanted to use. + ErrMismatchedAlgorithms = errors.New("mismatched algorithms") + + // ErrCannotValidate means the JWS cannot be validated for various + // reasons. For example, if there aren't any signatures/payloads/headers + // to actually validate. + ErrCannotValidate = errors.New("cannot validate") + + // ErrIsNotJWT means the given JWS is not a JWT. + ErrIsNotJWT = errors.New("JWS is not a JWT") + + // ErrHoldsJWE means the given JWS holds a JWE inside its payload. + ErrHoldsJWE = errors.New("JWS holds JWE") + + // ErrNotEnoughValidSignatures means the JWS did not meet the required + // number of signatures. + ErrNotEnoughValidSignatures = errors.New("not enough valid signatures in the JWS") + + // ErrNoTokenInRequest means there's no token present inside the *http.Request. + ErrNoTokenInRequest = errors.New("no token present in request") +) diff --git a/vendor/github.com/briankassouf/jose/jws/jws.go b/vendor/github.com/briankassouf/jose/jws/jws.go new file mode 100644 index 0000000000..9cf3adff07 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jws/jws.go @@ -0,0 +1,490 @@ +package jws + +import ( + "bytes" + "encoding/json" + "net/http" + "strings" + + "github.com/briankassouf/jose" + "github.com/briankassouf/jose/crypto" +) + +// JWS implements a JWS per RFC 7515. +type JWS interface { + // Payload Returns the payload. + Payload() interface{} + + // SetPayload sets the payload with the given value. + SetPayload(p interface{}) + + // Protected returns the JWS' Protected Header. + Protected() jose.Protected + + // ProtectedAt returns the JWS' Protected Header. + // i represents the index of the Protected Header. + ProtectedAt(i int) jose.Protected + + // Header returns the JWS' unprotected Header. + Header() jose.Header + + // HeaderAt returns the JWS' unprotected Header. + // i represents the index of the unprotected Header. + HeaderAt(i int) jose.Header + + // Verify validates the current JWS' signature as-is. Refer to + // ValidateMulti for more information. + Verify(key interface{}, method crypto.SigningMethod) error + + // ValidateMulti validates the current JWS' signature as-is. Since it's + // meant to be called after parsing a stream of bytes into a JWS, it + // shouldn't do any internal parsing like the Sign, Flat, Compact, or + // General methods do. + VerifyMulti(keys []interface{}, methods []crypto.SigningMethod, o *SigningOpts) error + + // VerifyCallback validates the current JWS' signature as-is. It + // accepts a callback function that can be used to access header + // parameters to lookup needed information. For example, looking + // up the "kid" parameter. + // The return slice must be a slice of keys used in the verification + // of the JWS. + VerifyCallback(fn VerifyCallback, methods []crypto.SigningMethod, o *SigningOpts) error + + // General serializes the JWS into its "general" form per + // https://tools.ietf.org/html/rfc7515#section-7.2.1 + General(keys ...interface{}) ([]byte, error) + + // Flat serializes the JWS to its "flattened" form per + // https://tools.ietf.org/html/rfc7515#section-7.2.2 + Flat(key interface{}) ([]byte, error) + + // Compact serializes the JWS into its "compact" form per + // https://tools.ietf.org/html/rfc7515#section-7.1 + Compact(key interface{}) ([]byte, error) + + // IsJWT returns true if the JWS is a JWT. + IsJWT() bool +} + +// jws represents a specific jws. +type jws struct { + payload *payload + plcache rawBase64 + clean bool + + sb []sigHead + + isJWT bool +} + +// Payload returns the jws' payload. +func (j *jws) Payload() interface{} { + return j.payload.v +} + +// SetPayload sets the jws' raw, unexported payload. +func (j *jws) SetPayload(val interface{}) { + j.payload.v = val +} + +// Protected returns the JWS' Protected Header. +func (j *jws) Protected() jose.Protected { + return j.sb[0].protected +} + +// Protected returns the JWS' Protected Header. +// i represents the index of the Protected Header. +// Left empty, it defaults to 0. +func (j *jws) ProtectedAt(i int) jose.Protected { + return j.sb[i].protected +} + +// Header returns the JWS' unprotected Header. +func (j *jws) Header() jose.Header { + return j.sb[0].unprotected +} + +// HeaderAt returns the JWS' unprotected Header. +// |i| is the index of the unprotected Header. +func (j *jws) HeaderAt(i int) jose.Header { + return j.sb[i].unprotected +} + +// sigHead represents the 'signatures' member of the jws' "general" +// serialization form per +// https://tools.ietf.org/html/rfc7515#section-7.2.1 +// +// It's embedded inside the "flat" structure in order to properly +// create the "flat" jws. +type sigHead struct { + Protected rawBase64 `json:"protected,omitempty"` + Unprotected rawBase64 `json:"header,omitempty"` + Signature crypto.Signature `json:"signature"` + + protected jose.Protected + unprotected jose.Header + clean bool + + method crypto.SigningMethod +} + +func (s *sigHead) unmarshal() error { + if err := s.protected.UnmarshalJSON(s.Protected); err != nil { + return err + } + return s.unprotected.UnmarshalJSON(s.Unprotected) +} + +// New creates a JWS with the provided crypto.SigningMethods. +func New(content interface{}, methods ...crypto.SigningMethod) JWS { + sb := make([]sigHead, len(methods)) + for i := range methods { + sb[i] = sigHead{ + protected: jose.Protected{ + "alg": methods[i].Alg(), + }, + unprotected: jose.Header{}, + method: methods[i], + } + } + return &jws{ + payload: &payload{v: content}, + sb: sb, + } +} + +func (s *sigHead) assignMethod(p jose.Protected) error { + alg, ok := p.Get("alg").(string) + if !ok { + return ErrNoAlgorithm + } + + sm := GetSigningMethod(alg) + if sm == nil { + return ErrNoAlgorithm + } + s.method = sm + return nil +} + +type generic struct { + Payload rawBase64 `json:"payload"` + sigHead + Signatures []sigHead `json:"signatures,omitempty"` +} + +// Parse parses any of the three serialized jws forms into a physical +// jws per https://tools.ietf.org/html/rfc7515#section-5.2 +// +// It accepts a json.Unmarshaler in order to properly parse +// the payload. In order to keep the caller from having to do extra +// parsing of the payload, a json.Unmarshaler can be passed +// which will be then to unmarshal the payload however the caller +// wishes. Do note that if json.Unmarshal returns an error the +// original payload will be used as if no json.Unmarshaler was +// passed. +// +// Internally, Parse applies some heuristics and then calls either +// ParseGeneral, ParseFlat, or ParseCompact. +// It should only be called if, for whatever reason, you do not +// know which form the serialized JWT is in. +// +// It cannot parse a JWT. +func Parse(encoded []byte, u ...json.Unmarshaler) (JWS, error) { + // Try and unmarshal into a generic struct that'll + // hopefully hold either of the two JSON serialization + // formats. + var g generic + + // Not valid JSON. Let's try compact. + if err := json.Unmarshal(encoded, &g); err != nil { + return ParseCompact(encoded, u...) + } + + if g.Signatures == nil { + return g.parseFlat(u...) + } + return g.parseGeneral(u...) +} + +// ParseGeneral parses a jws serialized into its "general" form per +// https://tools.ietf.org/html/rfc7515#section-7.2.1 +// into a physical jws per +// https://tools.ietf.org/html/rfc7515#section-5.2 +// +// For information on the json.Unmarshaler parameter, see Parse. +func ParseGeneral(encoded []byte, u ...json.Unmarshaler) (JWS, error) { + var g generic + if err := json.Unmarshal(encoded, &g); err != nil { + return nil, err + } + return g.parseGeneral(u...) +} + +func (g *generic) parseGeneral(u ...json.Unmarshaler) (JWS, error) { + + var p payload + if len(u) > 0 { + p.u = u[0] + } + + if err := p.UnmarshalJSON(g.Payload); err != nil { + return nil, err + } + + for i := range g.Signatures { + if err := g.Signatures[i].unmarshal(); err != nil { + return nil, err + } + if err := checkHeaders(jose.Header(g.Signatures[i].protected), g.Signatures[i].unprotected); err != nil { + return nil, err + } + + if err := g.Signatures[i].assignMethod(g.Signatures[i].protected); err != nil { + return nil, err + } + } + + g.clean = len(g.Signatures) != 0 + + return &jws{ + payload: &p, + plcache: g.Payload, + clean: true, + sb: g.Signatures, + }, nil +} + +// ParseFlat parses a jws serialized into its "flat" form per +// https://tools.ietf.org/html/rfc7515#section-7.2.2 +// into a physical jws per +// https://tools.ietf.org/html/rfc7515#section-5.2 +// +// For information on the json.Unmarshaler parameter, see Parse. +func ParseFlat(encoded []byte, u ...json.Unmarshaler) (JWS, error) { + var g generic + if err := json.Unmarshal(encoded, &g); err != nil { + return nil, err + } + return g.parseFlat(u...) +} + +func (g *generic) parseFlat(u ...json.Unmarshaler) (JWS, error) { + + var p payload + if len(u) > 0 { + p.u = u[0] + } + + if err := p.UnmarshalJSON(g.Payload); err != nil { + return nil, err + } + + if err := g.sigHead.unmarshal(); err != nil { + return nil, err + } + g.sigHead.clean = true + + if err := checkHeaders(jose.Header(g.sigHead.protected), g.sigHead.unprotected); err != nil { + return nil, err + } + + if err := g.sigHead.assignMethod(g.sigHead.protected); err != nil { + return nil, err + } + + return &jws{ + payload: &p, + plcache: g.Payload, + clean: true, + sb: []sigHead{g.sigHead}, + }, nil +} + +// ParseCompact parses a jws serialized into its "compact" form per +// https://tools.ietf.org/html/rfc7515#section-7.1 +// into a physical jws per +// https://tools.ietf.org/html/rfc7515#section-5.2 +// +// For information on the json.Unmarshaler parameter, see Parse. +func ParseCompact(encoded []byte, u ...json.Unmarshaler) (JWS, error) { + return parseCompact(encoded, false, u...) +} + +func parseCompact(encoded []byte, jwt bool, u ...json.Unmarshaler) (*jws, error) { + + // This section loosely follows + // https://tools.ietf.org/html/rfc7519#section-7.2 + // because it's used to parse _both_ jws and JWTs. + + parts := bytes.Split(encoded, []byte{'.'}) + if len(parts) != 3 { + return nil, ErrNotCompact + } + + var p jose.Protected + if err := p.UnmarshalJSON(parts[0]); err != nil { + return nil, err + } + + s := sigHead{ + Protected: parts[0], + protected: p, + Signature: parts[2], + clean: true, + } + + if err := s.assignMethod(p); err != nil { + return nil, err + } + + var pl payload + if len(u) > 0 { + pl.u = u[0] + } + + j := jws{ + payload: &pl, + plcache: parts[1], + sb: []sigHead{s}, + isJWT: jwt, + } + + if err := j.payload.UnmarshalJSON(parts[1]); err != nil { + return nil, err + } + + j.clean = true + + if err := j.sb[0].Signature.UnmarshalJSON(parts[2]); err != nil { + return nil, err + } + + // https://tools.ietf.org/html/rfc7519#section-7.2.8 + cty, ok := p.Get("cty").(string) + if ok && cty == "JWT" { + return &j, ErrHoldsJWE + } + return &j, nil +} + +var ( + // JWSFormKey is the form "key" which should be used inside + // ParseFromRequest if the request is a multipart.Form. + JWSFormKey = "access_token" + + // MaxMemory is maximum amount of memory which should be used + // inside ParseFromRequest while parsing the multipart.Form + // if the request is a multipart.Form. + MaxMemory int64 = 10e6 +) + +// Format specifies which "format" the JWS is in -- Flat, General, +// or compact. Additionally, constants for JWT/Unknown are added. +type Format uint8 + +const ( + // Unknown format. + Unknown Format = iota + + // Flat format. + Flat + + // General format. + General + + // Compact format. + Compact +) + +var parseJumpTable = [...]func([]byte, ...json.Unmarshaler) (JWS, error){ + Unknown: Parse, + Flat: ParseFlat, + General: ParseGeneral, + Compact: ParseCompact, + 1<<8 - 1: Parse, // Max uint8. +} + +func init() { + for i := range parseJumpTable { + if parseJumpTable[i] == nil { + parseJumpTable[i] = Parse + } + } +} + +func fromHeader(req *http.Request) ([]byte, bool) { + if ah := req.Header.Get("Authorization"); len(ah) > 7 && strings.EqualFold(ah[0:7], "BEARER ") { + return []byte(ah[7:]), true + } + return nil, false +} + +func fromForm(req *http.Request) ([]byte, bool) { + if err := req.ParseMultipartForm(MaxMemory); err != nil { + return nil, false + } + if tokStr := req.Form.Get(JWSFormKey); tokStr != "" { + return []byte(tokStr), true + } + return nil, false +} + +// ParseFromHeader tries to find the JWS in an http.Request header. +func ParseFromHeader(req *http.Request, format Format, u ...json.Unmarshaler) (JWS, error) { + if b, ok := fromHeader(req); ok { + return parseJumpTable[format](b, u...) + } + return nil, ErrNoTokenInRequest +} + +// ParseFromForm tries to find the JWS in an http.Request form request. +func ParseFromForm(req *http.Request, format Format, u ...json.Unmarshaler) (JWS, error) { + if b, ok := fromForm(req); ok { + return parseJumpTable[format](b, u...) + } + return nil, ErrNoTokenInRequest +} + +// ParseFromRequest tries to find the JWS in an http.Request. +// This method will call ParseMultipartForm if there's no token in the header. +func ParseFromRequest(req *http.Request, format Format, u ...json.Unmarshaler) (JWS, error) { + token, err := ParseFromHeader(req, format, u...) + if err == nil { + return token, nil + } + + token, err = ParseFromForm(req, format, u...) + if err == nil { + return token, nil + } + + return nil, err +} + +// IgnoreDupes should be set to true if the internal duplicate header key check +// should ignore duplicate Header keys instead of reporting an error when +// duplicate Header keys are found. +// +// Note: +// Duplicate Header keys are defined in +// https://tools.ietf.org/html/rfc7515#section-5.2 +// meaning keys that both the protected and unprotected +// Headers possess. +var IgnoreDupes bool + +// checkHeaders returns an error per the constraints described in +// IgnoreDupes' comment. +func checkHeaders(a, b jose.Header) error { + if len(a)+len(b) == 0 { + return ErrTwoEmptyHeaders + } + for key := range a { + if b.Has(key) && !IgnoreDupes { + return ErrDuplicateHeaderParameter + } + } + return nil +} + +var _ JWS = (*jws)(nil) diff --git a/vendor/github.com/briankassouf/jose/jws/jws_serialize.go b/vendor/github.com/briankassouf/jose/jws/jws_serialize.go new file mode 100644 index 0000000000..923fdc224b --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jws/jws_serialize.go @@ -0,0 +1,132 @@ +package jws + +import ( + "bytes" + "encoding/json" +) + +// Flat serializes the JWS to its "flattened" form per +// https://tools.ietf.org/html/rfc7515#section-7.2.2 +func (j *jws) Flat(key interface{}) ([]byte, error) { + if len(j.sb) < 1 { + return nil, ErrNotEnoughMethods + } + if err := j.sign(key); err != nil { + return nil, err + } + return json.Marshal(struct { + Payload rawBase64 `json:"payload"` + sigHead + }{ + Payload: j.plcache, + sigHead: j.sb[0], + }) +} + +// General serializes the JWS into its "general" form per +// https://tools.ietf.org/html/rfc7515#section-7.2.1 +// +// If only one key is passed it's used for all the provided +// crypto.SigningMethods. Otherwise, len(keys) must equal the number +// of crypto.SigningMethods added. +func (j *jws) General(keys ...interface{}) ([]byte, error) { + if err := j.sign(keys...); err != nil { + return nil, err + } + return json.Marshal(struct { + Payload rawBase64 `json:"payload"` + Signatures []sigHead `json:"signatures"` + }{ + Payload: j.plcache, + Signatures: j.sb, + }) +} + +// Compact serializes the JWS into its "compact" form per +// https://tools.ietf.org/html/rfc7515#section-7.1 +func (j *jws) Compact(key interface{}) ([]byte, error) { + if len(j.sb) < 1 { + return nil, ErrNotEnoughMethods + } + + if err := j.sign(key); err != nil { + return nil, err + } + + sig, err := j.sb[0].Signature.Base64() + if err != nil { + return nil, err + } + return format( + j.sb[0].Protected, + j.plcache, + sig, + ), nil +} + +// sign signs each index of j's sb member. +func (j *jws) sign(keys ...interface{}) error { + if err := j.cache(); err != nil { + return err + } + + if len(keys) < 1 || + len(keys) > 1 && len(keys) != len(j.sb) { + return ErrNotEnoughKeys + } + + if len(keys) == 1 { + k := keys[0] + keys = make([]interface{}, len(j.sb)) + for i := range keys { + keys[i] = k + } + } + + for i := range j.sb { + if err := j.sb[i].cache(); err != nil { + return err + } + + raw := format(j.sb[i].Protected, j.plcache) + sig, err := j.sb[i].method.Sign(raw, keys[i]) + if err != nil { + return err + } + j.sb[i].Signature = sig + } + + return nil +} + +// cache marshals the payload, but only if it's changed since the last cache. +func (j *jws) cache() (err error) { + if !j.clean { + j.plcache, err = j.payload.Base64() + j.clean = err == nil + } + return err +} + +// cache marshals the protected and unprotected headers, but only if +// they've changed since their last cache. +func (s *sigHead) cache() (err error) { + if !s.clean { + s.Protected, err = s.protected.Base64() + if err != nil { + return err + } + s.Unprotected, err = s.unprotected.Base64() + if err != nil { + return err + } + } + s.clean = true + return nil +} + +// format formats a slice of bytes in the order given, joining +// them with a period. +func format(a ...[]byte) []byte { + return bytes.Join(a, []byte{'.'}) +} diff --git a/vendor/github.com/briankassouf/jose/jws/jws_validate.go b/vendor/github.com/briankassouf/jose/jws/jws_validate.go new file mode 100644 index 0000000000..2904047408 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jws/jws_validate.go @@ -0,0 +1,203 @@ +package jws + +import ( + "fmt" + + "github.com/briankassouf/jose/crypto" +) + +// VerifyCallback is a callback function that can be used to access header +// parameters to lookup needed information. For example, looking +// up the "kid" parameter. +// The return slice must be a slice of keys used in the verification +// of the JWS. +type VerifyCallback func(JWS) ([]interface{}, error) + +// VerifyCallback validates the current JWS' signature as-is. It +// accepts a callback function that can be used to access header +// parameters to lookup needed information. For example, looking +// up the "kid" parameter. +// The return slice must be a slice of keys used in the verification +// of the JWS. +func (j *jws) VerifyCallback(fn VerifyCallback, methods []crypto.SigningMethod, o *SigningOpts) error { + keys, err := fn(j) + if err != nil { + return err + } + return j.VerifyMulti(keys, methods, o) +} + +// IsMultiError returns true if the given error is type *MultiError. +func IsMultiError(err error) bool { + _, ok := err.(*MultiError) + return ok +} + +// MultiError is a slice of errors. +type MultiError []error + +// Errors implements the error interface. +func (m *MultiError) Error() string { + var s string + var n int + for _, err := range *m { + if err != nil { + if n == 0 { + s = err.Error() + } + n++ + } + } + switch n { + case 0: + return "" + case 1: + return s + case 2: + return s + " and 1 other error" + } + return fmt.Sprintf("%s (and %d other errors)", s, n-1) +} + +// Any means any of the JWS signatures need to verify. +// Refer to verifyMulti for more information. +const Any int = 0 + +// VerifyMulti verifies the current JWS as-is. Since it's meant to be +// called after parsing a stream of bytes into a JWS, it doesn't do any +// internal parsing like the Sign, Flat, Compact, or General methods do. +func (j *jws) VerifyMulti(keys []interface{}, methods []crypto.SigningMethod, o *SigningOpts) error { + + // Catch a simple mistake. Parameter o is irrelevant in this scenario. + if len(keys) == 1 && + len(methods) == 1 && + len(j.sb) == 1 { + return j.Verify(keys[0], methods[0]) + } + + if len(j.sb) != len(methods) { + return ErrNotEnoughMethods + } + + if len(keys) < 1 || + len(keys) > 1 && len(keys) != len(j.sb) { + return ErrNotEnoughKeys + } + + // TODO do this better. + if len(keys) == 1 { + k := keys[0] + keys = make([]interface{}, len(methods)) + for i := range keys { + keys[i] = k + } + } + + var o2 SigningOpts + if o == nil { + o = new(SigningOpts) + } + + var m MultiError + for i := range j.sb { + err := j.sb[i].verify(j.plcache, keys[i], methods[i]) + if err != nil { + m = append(m, err) + } else { + o2.Inc() + if o.Needs(i) { + o.ptr++ + o2.Append(i) + } + } + } + + err := o.Validate(&o2) + if err != nil { + m = append(m, err) + } + if len(m) == 0 { + return nil + } + return &m +} + +// SigningOpts is a struct which holds options for validating +// JWS signatures. +// Number represents the cumulative which signatures need to verify +// in order for the JWS to be considered valid. +// Leave 'Number' empty or set it to the constant 'Any' if any number of +// valid signatures (greater than one) should verify the JWS. +// +// Use the indices of the signatures that need to verify in order +// for the JWS to be considered valid if specific signatures need +// to verify in order for the JWS to be considered valid. +// +// Note: +// The JWS spec requires *at least* one +// signature to verify in order for the JWS to be considered valid. +type SigningOpts struct { + // Minimum of signatures which need to verify. + Number int + + // Indices of specific signatures which need to verify. + Indices []int + ptr int + + _ struct{} +} + +// Append appends x to s' Indices member. +func (s *SigningOpts) Append(x int) { + s.Indices = append(s.Indices, x) +} + +// Needs returns true if x resides inside s' Indices member +// for the given index. It's used to match two SigningOpts Indices members. +func (s *SigningOpts) Needs(x int) bool { + return s.ptr < len(s.Indices) && s.Indices[s.ptr] == x +} + +// Inc increments s' Number member by one. +func (s *SigningOpts) Inc() { s.Number++ } + +// Validate returns any errors found while validating the +// provided SigningOpts. The receiver validates |have|. +// It'll return an error if the passed SigningOpts' Number member is less +// than s' or if the passed SigningOpts' Indices slice isn't equal to s'. +func (s *SigningOpts) Validate(have *SigningOpts) error { + if have.Number < s.Number || + (s.Indices != nil && + !eq(s.Indices, have.Indices)) { + return ErrNotEnoughValidSignatures + } + return nil +} + +func eq(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i := range a { + if a[i] != b[i] { + return false + } + } + return true +} + +// Verify verifies the current JWS as-is. Refer to verifyMulti +// for more information. +func (j *jws) Verify(key interface{}, method crypto.SigningMethod) error { + if len(j.sb) < 1 { + return ErrCannotValidate + } + return j.sb[0].verify(j.plcache, key, method) +} + +func (s *sigHead) verify(pl []byte, key interface{}, method crypto.SigningMethod) error { + if s.method.Alg() != method.Alg() || s.method.Hasher() != method.Hasher() { + return ErrMismatchedAlgorithms + } + return method.Verify(format(s.Protected, pl), s.Signature, key) +} diff --git a/vendor/github.com/briankassouf/jose/jws/jwt.go b/vendor/github.com/briankassouf/jose/jws/jwt.go new file mode 100644 index 0000000000..c09fb83ea9 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jws/jwt.go @@ -0,0 +1,115 @@ +package jws + +import ( + "net/http" + "time" + + "github.com/briankassouf/jose" + "github.com/briankassouf/jose/crypto" + "github.com/briankassouf/jose/jwt" +) + +// NewJWT creates a new JWT with the given claims. +func NewJWT(claims Claims, method crypto.SigningMethod) jwt.JWT { + j, ok := New(claims, method).(*jws) + if !ok { + panic("jws.NewJWT: runtime panic: New(...).(*jws) != true") + } + j.sb[0].protected.Set("typ", "JWT") + j.isJWT = true + return j +} + +// Serialize helps implements jwt.JWT. +func (j *jws) Serialize(key interface{}) ([]byte, error) { + if j.isJWT { + return j.Compact(key) + } + return nil, ErrIsNotJWT +} + +// Claims helps implements jwt.JWT. +func (j *jws) Claims() jwt.Claims { + if j.isJWT { + if c, ok := j.payload.v.(Claims); ok { + return jwt.Claims(c) + } + } + return nil +} + +// ParseJWTFromRequest tries to find the JWT in an http.Request. +// This method will call ParseMultipartForm if there's no token in the header. +func ParseJWTFromRequest(req *http.Request) (jwt.JWT, error) { + if b, ok := fromHeader(req); ok { + return ParseJWT(b) + } + if b, ok := fromForm(req); ok { + return ParseJWT(b) + } + return nil, ErrNoTokenInRequest +} + +// ParseJWT parses a serialized jwt.JWT into a physical jwt.JWT. +// If its payload isn't a set of claims (or able to be coerced into +// a set of claims) it'll return an error stating the +// JWT isn't a JWT. +func ParseJWT(encoded []byte) (jwt.JWT, error) { + t, err := parseCompact(encoded, true) + if err != nil { + return nil, err + } + c, ok := t.Payload().(map[string]interface{}) + if !ok { + return nil, ErrIsNotJWT + } + t.SetPayload(Claims(c)) + return t, nil +} + +// IsJWT returns true if the JWS is a JWT. +func (j *jws) IsJWT() bool { + return j.isJWT +} + +func (j *jws) Validate(key interface{}, m crypto.SigningMethod, v ...*jwt.Validator) error { + if j.isJWT { + if err := j.Verify(key, m); err != nil { + return err + } + var v1 jwt.Validator + if len(v) > 0 { + v1 = *v[0] + } + c, ok := j.payload.v.(Claims) + if ok { + if err := v1.Validate(j); err != nil { + return err + } + return jwt.Claims(c).Validate(jose.Now(), v1.EXP, v1.NBF) + } + } + return ErrIsNotJWT +} + +// Conv converts a func(Claims) error to type jwt.ValidateFunc. +func Conv(fn func(Claims) error) jwt.ValidateFunc { + if fn == nil { + return nil + } + return func(c jwt.Claims) error { + return fn(Claims(c)) + } +} + +// NewValidator returns a jwt.Validator. +func NewValidator(c Claims, exp, nbf time.Duration, fn func(Claims) error) *jwt.Validator { + return &jwt.Validator{ + Expected: jwt.Claims(c), + EXP: exp, + NBF: nbf, + Fn: Conv(fn), + } +} + +var _ jwt.JWT = (*jws)(nil) diff --git a/vendor/github.com/briankassouf/jose/jws/payload.go b/vendor/github.com/briankassouf/jose/jws/payload.go new file mode 100644 index 0000000000..0a326b5ce5 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jws/payload.go @@ -0,0 +1,52 @@ +package jws + +import ( + "encoding/json" + + "github.com/briankassouf/jose" +) + +// payload represents the payload of a JWS. +type payload struct { + v interface{} + u json.Unmarshaler + _ struct{} +} + +// MarshalJSON implements json.Marshaler for payload. +func (p *payload) MarshalJSON() ([]byte, error) { + b, err := json.Marshal(p.v) + if err != nil { + return nil, err + } + return jose.EncodeEscape(b), nil +} + +// Base64 implements jose.Encoder. +func (p *payload) Base64() ([]byte, error) { + b, err := json.Marshal(p.v) + if err != nil { + return nil, err + } + return jose.Base64Encode(b), nil +} + +// MarshalJSON implements json.Unmarshaler for payload. +func (p *payload) UnmarshalJSON(b []byte) error { + b2, err := jose.DecodeEscaped(b) + if err != nil { + return err + } + if p.u != nil { + err := p.u.UnmarshalJSON(b2) + p.v = p.u + return err + } + return json.Unmarshal(b2, &p.v) +} + +var ( + _ json.Marshaler = (*payload)(nil) + _ json.Unmarshaler = (*payload)(nil) + _ jose.Encoder = (*payload)(nil) +) diff --git a/vendor/github.com/briankassouf/jose/jws/rawbase64.go b/vendor/github.com/briankassouf/jose/jws/rawbase64.go new file mode 100644 index 0000000000..f2c4060481 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jws/rawbase64.go @@ -0,0 +1,28 @@ +package jws + +import "encoding/json" + +type rawBase64 []byte + +// MarshalJSON implements json.Marshaler for rawBase64. +func (r rawBase64) MarshalJSON() ([]byte, error) { + buf := make([]byte, len(r)+2) + buf[0] = '"' + copy(buf[1:], r) + buf[len(buf)-1] = '"' + return buf, nil +} + +// MarshalJSON implements json.Unmarshaler for rawBase64. +func (r *rawBase64) UnmarshalJSON(b []byte) error { + if len(b) > 1 && b[0] == '"' && b[len(b)-1] == '"' { + b = b[1 : len(b)-1] + } + *r = rawBase64(b) + return nil +} + +var ( + _ json.Marshaler = (rawBase64)(nil) + _ json.Unmarshaler = (*rawBase64)(nil) +) diff --git a/vendor/github.com/briankassouf/jose/jws/signing_methods.go b/vendor/github.com/briankassouf/jose/jws/signing_methods.go new file mode 100644 index 0000000000..9fc04c7e9b --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jws/signing_methods.go @@ -0,0 +1,63 @@ +package jws + +import ( + "sync" + + "github.com/briankassouf/jose/crypto" +) + +var ( + mu sync.RWMutex + + signingMethods = map[string]crypto.SigningMethod{ + crypto.SigningMethodES256.Alg(): crypto.SigningMethodES256, + crypto.SigningMethodES384.Alg(): crypto.SigningMethodES384, + crypto.SigningMethodES512.Alg(): crypto.SigningMethodES512, + + crypto.SigningMethodPS256.Alg(): crypto.SigningMethodPS256, + crypto.SigningMethodPS384.Alg(): crypto.SigningMethodPS384, + crypto.SigningMethodPS512.Alg(): crypto.SigningMethodPS512, + + crypto.SigningMethodRS256.Alg(): crypto.SigningMethodRS256, + crypto.SigningMethodRS384.Alg(): crypto.SigningMethodRS384, + crypto.SigningMethodRS512.Alg(): crypto.SigningMethodRS512, + + crypto.SigningMethodHS256.Alg(): crypto.SigningMethodHS256, + crypto.SigningMethodHS384.Alg(): crypto.SigningMethodHS384, + crypto.SigningMethodHS512.Alg(): crypto.SigningMethodHS512, + + crypto.Unsecured.Alg(): crypto.Unsecured, + } +) + +// RegisterSigningMethod registers the crypto.SigningMethod in the global map. +// This is typically done inside the caller's init function. +func RegisterSigningMethod(sm crypto.SigningMethod) { + alg := sm.Alg() + if GetSigningMethod(alg) != nil { + panic("jose/jws: cannot duplicate signing methods") + } + + if !sm.Hasher().Available() { + panic("jose/jws: specific hash is unavailable") + } + + mu.Lock() + signingMethods[alg] = sm + mu.Unlock() +} + +// RemoveSigningMethod removes the crypto.SigningMethod from the global map. +func RemoveSigningMethod(sm crypto.SigningMethod) { + mu.Lock() + delete(signingMethods, sm.Alg()) + mu.Unlock() +} + +// GetSigningMethod retrieves a crypto.SigningMethod from the global map. +func GetSigningMethod(alg string) (method crypto.SigningMethod) { + mu.RLock() + method = signingMethods[alg] + mu.RUnlock() + return method +} diff --git a/vendor/github.com/briankassouf/jose/jwt/claims.go b/vendor/github.com/briankassouf/jose/jwt/claims.go new file mode 100644 index 0000000000..9a0311305e --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jwt/claims.go @@ -0,0 +1,274 @@ +package jwt + +import ( + "encoding/json" + "time" + + "github.com/briankassouf/jose" +) + +// Claims implements a set of JOSE Claims with the addition of some helper +// methods, similar to net/url.Values. +type Claims map[string]interface{} + +// Validate validates the Claims per the claims found in +// https://tools.ietf.org/html/rfc7519#section-4.1 +func (c Claims) Validate(now time.Time, expLeeway, nbfLeeway time.Duration) error { + if exp, ok := c.Expiration(); ok { + if now.After(exp.Add(expLeeway)) { + return ErrTokenIsExpired + } + } + + if nbf, ok := c.NotBefore(); ok { + if !now.After(nbf.Add(-nbfLeeway)) { + return ErrTokenNotYetValid + } + } + return nil +} + +// Get retrieves the value corresponding with key from the Claims. +func (c Claims) Get(key string) interface{} { + if c == nil { + return nil + } + return c[key] +} + +// Set sets Claims[key] = val. It'll overwrite without warning. +func (c Claims) Set(key string, val interface{}) { + c[key] = val +} + +// Del removes the value that corresponds with key from the Claims. +func (c Claims) Del(key string) { + delete(c, key) +} + +// Has returns true if a value for the given key exists inside the Claims. +func (c Claims) Has(key string) bool { + _, ok := c[key] + return ok +} + +// MarshalJSON implements json.Marshaler for Claims. +func (c Claims) MarshalJSON() ([]byte, error) { + if c == nil || len(c) == 0 { + return nil, nil + } + return json.Marshal(map[string]interface{}(c)) +} + +// Base64 implements the jose.Encoder interface. +func (c Claims) Base64() ([]byte, error) { + b, err := c.MarshalJSON() + if err != nil { + return nil, err + } + return jose.Base64Encode(b), nil +} + +// UnmarshalJSON implements json.Unmarshaler for Claims. +func (c *Claims) UnmarshalJSON(b []byte) error { + if b == nil { + return nil + } + + b, err := jose.DecodeEscaped(b) + if err != nil { + return err + } + + // Since json.Unmarshal calls UnmarshalJSON, + // calling json.Unmarshal on *p would be infinitely recursive + // A temp variable is needed because &map[string]interface{}(*p) is + // invalid Go. (Address of unaddressable object and all that...) + + tmp := map[string]interface{}(*c) + if err = json.Unmarshal(b, &tmp); err != nil { + return err + } + *c = Claims(tmp) + return nil +} + +// Issuer retrieves claim "iss" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.1 +func (c Claims) Issuer() (string, bool) { + v, ok := c.Get("iss").(string) + return v, ok +} + +// Subject retrieves claim "sub" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.2 +func (c Claims) Subject() (string, bool) { + v, ok := c.Get("sub").(string) + return v, ok +} + +// Audience retrieves claim "aud" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.3 +func (c Claims) Audience() ([]string, bool) { + // Audience claim must be stringy. That is, it may be one string + // or multiple strings but it should not be anything else. E.g. an int. + switch t := c.Get("aud").(type) { + case string: + return []string{t}, true + case []string: + return t, true + case []interface{}: + return stringify(t...) + case interface{}: + return stringify(t) + } + return nil, false +} + +func stringify(a ...interface{}) ([]string, bool) { + if len(a) == 0 { + return nil, false + } + + s := make([]string, len(a)) + for i := range a { + str, ok := a[i].(string) + if !ok { + return nil, false + } + s[i] = str + } + return s, true +} + +// Expiration retrieves claim "exp" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.4 +func (c Claims) Expiration() (time.Time, bool) { + return c.GetTime("exp") +} + +// NotBefore retrieves claim "nbf" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.5 +func (c Claims) NotBefore() (time.Time, bool) { + return c.GetTime("nbf") +} + +// IssuedAt retrieves claim "iat" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.6 +func (c Claims) IssuedAt() (time.Time, bool) { + return c.GetTime("iat") +} + +// JWTID retrieves claim "jti" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.7 +func (c Claims) JWTID() (string, bool) { + v, ok := c.Get("jti").(string) + return v, ok +} + +// RemoveIssuer deletes claim "iss" from c. +func (c Claims) RemoveIssuer() { c.Del("iss") } + +// RemoveSubject deletes claim "sub" from c. +func (c Claims) RemoveSubject() { c.Del("sub") } + +// RemoveAudience deletes claim "aud" from c. +func (c Claims) RemoveAudience() { c.Del("aud") } + +// RemoveExpiration deletes claim "exp" from c. +func (c Claims) RemoveExpiration() { c.Del("exp") } + +// RemoveNotBefore deletes claim "nbf" from c. +func (c Claims) RemoveNotBefore() { c.Del("nbf") } + +// RemoveIssuedAt deletes claim "iat" from c. +func (c Claims) RemoveIssuedAt() { c.Del("iat") } + +// RemoveJWTID deletes claim "jti" from c. +func (c Claims) RemoveJWTID() { c.Del("jti") } + +// SetIssuer sets claim "iss" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.1 +func (c Claims) SetIssuer(issuer string) { + c.Set("iss", issuer) +} + +// SetSubject sets claim "iss" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.2 +func (c Claims) SetSubject(subject string) { + c.Set("sub", subject) +} + +// SetAudience sets claim "aud" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.3 +func (c Claims) SetAudience(audience ...string) { + if len(audience) == 1 { + c.Set("aud", audience[0]) + } else { + c.Set("aud", audience) + } +} + +// SetExpiration sets claim "exp" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.4 +func (c Claims) SetExpiration(expiration time.Time) { + c.SetTime("exp", expiration) +} + +// SetNotBefore sets claim "nbf" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.5 +func (c Claims) SetNotBefore(notBefore time.Time) { + c.SetTime("nbf", notBefore) +} + +// SetIssuedAt sets claim "iat" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.6 +func (c Claims) SetIssuedAt(issuedAt time.Time) { + c.SetTime("iat", issuedAt) +} + +// SetJWTID sets claim "jti" per its type in +// https://tools.ietf.org/html/rfc7519#section-4.1.7 +func (c Claims) SetJWTID(uniqueID string) { + c.Set("jti", uniqueID) +} + +// GetTime returns a Unix timestamp for the given key. +// +// It converts an int, int32, int64, uint, uint32, uint64 or float64 into a Unix +// timestamp (epoch seconds). float32 does not have sufficient precision to +// store a Unix timestamp. +// +// Numeric values parsed from JSON will always be stored as float64 since +// Claims is a map[string]interface{}. However, the values may be stored directly +// in the claims as a different type. +func (c Claims) GetTime(key string) (time.Time, bool) { + switch t := c.Get(key).(type) { + case int: + return time.Unix(int64(t), 0), true + case int32: + return time.Unix(int64(t), 0), true + case int64: + return time.Unix(int64(t), 0), true + case uint: + return time.Unix(int64(t), 0), true + case uint32: + return time.Unix(int64(t), 0), true + case uint64: + return time.Unix(int64(t), 0), true + case float64: + return time.Unix(int64(t), 0), true + default: + return time.Time{}, false + } +} + +// SetTime stores a UNIX time for the given key. +func (c Claims) SetTime(key string, t time.Time) { + c.Set(key, t.Unix()) +} + +var ( + _ json.Marshaler = (Claims)(nil) + _ json.Unmarshaler = (*Claims)(nil) +) diff --git a/vendor/github.com/briankassouf/jose/jwt/doc.go b/vendor/github.com/briankassouf/jose/jwt/doc.go new file mode 100644 index 0000000000..6004d0fa93 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jwt/doc.go @@ -0,0 +1,2 @@ +// Package jwt implements JWTs per RFC 7519 +package jwt diff --git a/vendor/github.com/briankassouf/jose/jwt/eq.go b/vendor/github.com/briankassouf/jose/jwt/eq.go new file mode 100644 index 0000000000..3113269fb0 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jwt/eq.go @@ -0,0 +1,47 @@ +package jwt + +func verifyPrincipals(pcpls, auds []string) bool { + // "Each principal intended to process the JWT MUST + // identify itself with a value in the audience claim." + // - https://tools.ietf.org/html/rfc7519#section-4.1.3 + + found := -1 + for i, p := range pcpls { + for _, v := range auds { + if p == v { + found++ + break + } + } + if found != i { + return false + } + } + return true +} + +// ValidAudience returns true iff: +// - a and b are strings and a == b +// - a is string, b is []string and a is in b +// - a is []string, b is []string and all of a is in b +// - a is []string, b is string and len(a) == 1 and a[0] == b +func ValidAudience(a, b interface{}) bool { + s1, ok := a.(string) + if ok { + if s2, ok := b.(string); ok { + return s1 == s2 + } + a2, ok := b.([]string) + return ok && verifyPrincipals([]string{s1}, a2) + } + + a1, ok := a.([]string) + if !ok { + return false + } + if a2, ok := b.([]string); ok { + return verifyPrincipals(a1, a2) + } + s2, ok := b.(string) + return ok && len(a1) == 1 && a1[0] == s2 +} diff --git a/vendor/github.com/briankassouf/jose/jwt/errors.go b/vendor/github.com/briankassouf/jose/jwt/errors.go new file mode 100644 index 0000000000..96b240d547 --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jwt/errors.go @@ -0,0 +1,28 @@ +package jwt + +import "errors" + +var ( + // ErrTokenIsExpired is return when time.Now().Unix() is after + // the token's "exp" claim. + ErrTokenIsExpired = errors.New("token is expired") + + // ErrTokenNotYetValid is return when time.Now().Unix() is before + // the token's "nbf" claim. + ErrTokenNotYetValid = errors.New("token is not yet valid") + + // ErrInvalidISSClaim means the "iss" claim is invalid. + ErrInvalidISSClaim = errors.New("claim \"iss\" is invalid") + + // ErrInvalidSUBClaim means the "sub" claim is invalid. + ErrInvalidSUBClaim = errors.New("claim \"sub\" is invalid") + + // ErrInvalidIATClaim means the "iat" claim is invalid. + ErrInvalidIATClaim = errors.New("claim \"iat\" is invalid") + + // ErrInvalidJTIClaim means the "jti" claim is invalid. + ErrInvalidJTIClaim = errors.New("claim \"jti\" is invalid") + + // ErrInvalidAUDClaim means the "aud" claim is invalid. + ErrInvalidAUDClaim = errors.New("claim \"aud\" is invalid") +) diff --git a/vendor/github.com/briankassouf/jose/jwt/jwt.go b/vendor/github.com/briankassouf/jose/jwt/jwt.go new file mode 100644 index 0000000000..691df58aca --- /dev/null +++ b/vendor/github.com/briankassouf/jose/jwt/jwt.go @@ -0,0 +1,144 @@ +package jwt + +import ( + "time" + + "github.com/briankassouf/jose/crypto" +) + +// JWT represents a JWT per RFC 7519. +// It's described as an interface instead of a physical structure +// because both JWS and JWEs can be JWTs. So, in order to use either, +// import one of those two packages and use their "NewJWT" (and other) +// functions. +type JWT interface { + // Claims returns the set of Claims. + Claims() Claims + + // Validate returns an error describing any issues found while + // validating the JWT. For info on the fn parameter, see the + // comment on ValidateFunc. + Validate(key interface{}, method crypto.SigningMethod, v ...*Validator) error + + // Serialize serializes the JWT into its on-the-wire + // representation. + Serialize(key interface{}) ([]byte, error) +} + +// ValidateFunc is a function that provides access to the JWT +// and allows for custom validation. Keep in mind that the Verify +// methods in the JWS/JWE sibling packages call ValidateFunc *after* +// validating the JWS/JWE, but *before* any validation per the JWT +// RFC. Therefore, the ValidateFunc can be used to short-circuit +// verification, but cannot be used to circumvent the RFC. +// Custom JWT implementations are free to abuse this, but it is +// not recommended. +type ValidateFunc func(Claims) error + +// Validator represents some of the validation options. +type Validator struct { + Expected Claims // If non-nil, these are required to match. + EXP time.Duration // EXPLeeway + NBF time.Duration // NBFLeeway + Fn ValidateFunc // See ValidateFunc for more information. + + _ struct{} // Require explicitly-named struct fields. +} + +// Validate validates the JWT based on the expected claims in v. +// Note: it only validates the registered claims per +// https://tools.ietf.org/html/rfc7519#section-4.1 +// +// Custom claims should be validated using v's Fn member. +func (v *Validator) Validate(j JWT) error { + if iss, ok := v.Expected.Issuer(); ok && + j.Claims().Get("iss") != iss { + return ErrInvalidISSClaim + } + if sub, ok := v.Expected.Subject(); ok && + j.Claims().Get("sub") != sub { + return ErrInvalidSUBClaim + } + if iat, ok := v.Expected.IssuedAt(); ok { + if t, ok := j.Claims().GetTime("iat"); !t.Equal(iat) || !ok { + return ErrInvalidIATClaim + } + } + if jti, ok := v.Expected.JWTID(); ok && + j.Claims().Get("jti") != jti { + return ErrInvalidJTIClaim + } + + if aud, ok := v.Expected.Audience(); ok { + aud2, ok := j.Claims().Audience() + if !ok || !ValidAudience(aud, aud2) { + return ErrInvalidAUDClaim + } + } + + if v.Fn != nil { + return v.Fn(j.Claims()) + } + return nil +} + +// SetClaim sets the claim with the given val. +func (v *Validator) SetClaim(claim string, val interface{}) { + v.expect() + v.Expected.Set(claim, val) +} + +// SetIssuer sets the "iss" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.1 +func (v *Validator) SetIssuer(iss string) { + v.expect() + v.Expected.Set("iss", iss) +} + +// SetSubject sets the "sub" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.2 +func (v *Validator) SetSubject(sub string) { + v.expect() + v.Expected.Set("sub", sub) +} + +// SetAudience sets the "aud" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.3 +func (v *Validator) SetAudience(aud string) { + v.expect() + v.Expected.Set("aud", aud) +} + +// SetExpiration sets the "exp" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.4 +func (v *Validator) SetExpiration(exp time.Time) { + v.expect() + v.Expected.Set("exp", exp) +} + +// SetNotBefore sets the "nbf" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.5 +func (v *Validator) SetNotBefore(nbf time.Time) { + v.expect() + v.Expected.Set("nbf", nbf) +} + +// SetIssuedAt sets the "iat" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.6 +func (v *Validator) SetIssuedAt(iat time.Time) { + v.expect() + v.Expected.Set("iat", iat) +} + +// SetJWTID sets the "jti" claim per +// https://tools.ietf.org/html/rfc7519#section-4.1.7 +func (v *Validator) SetJWTID(jti string) { + v.expect() + v.Expected.Set("jti", jti) +} + +func (v *Validator) expect() { + if v.Expected == nil { + v.Expected = make(Claims) + } +} diff --git a/vendor/github.com/briankassouf/jose/time.go b/vendor/github.com/briankassouf/jose/time.go new file mode 100644 index 0000000000..f366a7a67f --- /dev/null +++ b/vendor/github.com/briankassouf/jose/time.go @@ -0,0 +1,6 @@ +package jose + +import "time" + +// Now returns the current time in UTC. +func Now() time.Time { return time.Now().UTC() } diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-kubernetes/Gopkg.lock b/vendor/github.com/hashicorp/vault-plugin-auth-kubernetes/Gopkg.lock index e405172698..d48b8a263a 100644 --- a/vendor/github.com/hashicorp/vault-plugin-auth-kubernetes/Gopkg.lock +++ b/vendor/github.com/hashicorp/vault-plugin-auth-kubernetes/Gopkg.lock @@ -18,6 +18,17 @@ packages = ["."] revision = "1fca145dffbcaa8fe914309b1ec0cfc67500fe61" +[[projects]] + branch = "master" + name = "github.com/briankassouf/jose" + packages = [ + ".", + "crypto", + "jws", + "jwt" + ] + revision = "d2569464773f2b9de32e57a79d87318bca5b56c0" + [[projects]] name = "github.com/gogo/protobuf" packages = [ @@ -345,6 +356,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a4f5c4784a693e2d63df96593815f130a8511739b284e67037efc44321d48a36" + inputs-digest = "1150b789dee30cbbfadac50cfc7d783841ea49e18043d0046bcc8de22f150e44" solver-name = "gps-cdcl" solver-version = 1 diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-kubernetes/Gopkg.toml b/vendor/github.com/hashicorp/vault-plugin-auth-kubernetes/Gopkg.toml index e4940edf74..feb3739104 100644 --- a/vendor/github.com/hashicorp/vault-plugin-auth-kubernetes/Gopkg.toml +++ b/vendor/github.com/hashicorp/vault-plugin-auth-kubernetes/Gopkg.toml @@ -64,3 +64,7 @@ [prune] go-tests = true unused-packages = true + +[[constraint]] + name = "github.com/briankassouf/jose" + branch = "master" diff --git a/vendor/github.com/hashicorp/vault-plugin-auth-kubernetes/path_login.go b/vendor/github.com/hashicorp/vault-plugin-auth-kubernetes/path_login.go index b4cb568d76..d28e40dbe2 100644 --- a/vendor/github.com/hashicorp/vault-plugin-auth-kubernetes/path_login.go +++ b/vendor/github.com/hashicorp/vault-plugin-auth-kubernetes/path_login.go @@ -7,9 +7,9 @@ import ( "errors" "fmt" - "github.com/SermoDigital/jose/crypto" - "github.com/SermoDigital/jose/jws" - "github.com/SermoDigital/jose/jwt" + "github.com/briankassouf/jose/crypto" + "github.com/briankassouf/jose/jws" + "github.com/briankassouf/jose/jwt" "github.com/hashicorp/errwrap" multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/vault/helper/cidrutil" @@ -250,7 +250,7 @@ func (b *kubeAuthBackend) parseAndValidateJWT(jwtStr string, role *roleStorageEn // validates the signature and then runs the claim validation if err := parsedJWT.Validate(cert, signingMethod); err != nil { - return errwrap.Wrapf("failed to validate JWT: {{err}}", err) + return err } return nil @@ -267,7 +267,7 @@ func (b *kubeAuthBackend) parseAndValidateJWT(jwtStr string, role *roleStorageEn // if the error is a failure to verify or a signing method mismatch // continue onto the next cert, storing the error to be returned if // this is the last cert. - validationErr = multierror.Append(validationErr, err) + validationErr = multierror.Append(validationErr, errwrap.Wrapf("failed to validate JWT: {{err}}", err)) continue default: return nil, err diff --git a/vendor/vendor.json b/vendor/vendor.json index 7bc13c74eb..cd64083023 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -498,6 +498,30 @@ "revision": "5a7395f62784d17e29056e861ebd448ac1096261", "revisionTime": "2018-02-03T08:01:48Z" }, + { + "checksumSHA1": "wCaMkrXyTIFhss/mCRnbSTjqKoI=", + "path": "github.com/briankassouf/jose", + "revision": "d2569464773f2b9de32e57a79d87318bca5b56c0", + "revisionTime": "2018-06-19T21:45:49Z" + }, + { + "checksumSHA1": "trdC2+st1v9tFgU3/vIYW238fgk=", + "path": "github.com/briankassouf/jose/crypto", + "revision": "d2569464773f2b9de32e57a79d87318bca5b56c0", + "revisionTime": "2018-06-19T21:45:49Z" + }, + { + "checksumSHA1": "MS5CbPD4yQKfyN5+8wwoUlNYoRY=", + "path": "github.com/briankassouf/jose/jws", + "revision": "d2569464773f2b9de32e57a79d87318bca5b56c0", + "revisionTime": "2018-06-19T21:45:49Z" + }, + { + "checksumSHA1": "xSZ/hDO7yll2NzZlFR7V9XpjZlw=", + "path": "github.com/briankassouf/jose/jwt", + "revision": "d2569464773f2b9de32e57a79d87318bca5b56c0", + "revisionTime": "2018-06-19T21:45:49Z" + }, { "checksumSHA1": "Zlk5zPiw+3u9pyy4kNThpKDaYmE=", "path": "github.com/cenkalti/backoff", @@ -1321,10 +1345,10 @@ "revisionTime": "2018-04-08T01:06:05Z" }, { - "checksumSHA1": "7YzPANZR2eqdB+XWgN+QtvGR9aI=", + "checksumSHA1": "L5pDwOw2/MLLUSykrwxXbXQI7zI=", "path": "github.com/hashicorp/vault-plugin-auth-kubernetes", - "revision": "dabfb5747165f7226d282959b77c17e3c2c29a2f", - "revisionTime": "2018-06-18T16:24:03Z" + "revision": "7ca24869a5b9c9e308c475a21c9ce1331734120e", + "revisionTime": "2018-06-19T23:09:39Z" }, { "checksumSHA1": "kGEaf6lxGWkJibW4Dto2wFDXrfY=",