summaryrefslogtreecommitdiff
path: root/vendor/gopkg.in/square/go-jose.v2/jwk.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gopkg.in/square/go-jose.v2/jwk.go')
-rw-r--r--vendor/gopkg.in/square/go-jose.v2/jwk.go174
1 files changed, 163 insertions, 11 deletions
diff --git a/vendor/gopkg.in/square/go-jose.v2/jwk.go b/vendor/gopkg.in/square/go-jose.v2/jwk.go
index 6cb8adb84..2dc6aec4b 100644
--- a/vendor/gopkg.in/square/go-jose.v2/jwk.go
+++ b/vendor/gopkg.in/square/go-jose.v2/jwk.go
@@ -17,15 +17,20 @@
package jose
import (
+ "bytes"
"crypto"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rsa"
+ "crypto/sha1"
+ "crypto/sha256"
"crypto/x509"
"encoding/base64"
+ "encoding/hex"
"errors"
"fmt"
"math/big"
+ "net/url"
"reflect"
"strings"
@@ -57,16 +62,31 @@ type rawJSONWebKey struct {
Dq *byteBuffer `json:"dq,omitempty"`
Qi *byteBuffer `json:"qi,omitempty"`
// Certificates
- X5c []string `json:"x5c,omitempty"`
+ X5c []string `json:"x5c,omitempty"`
+ X5u *url.URL `json:"x5u,omitempty"`
+ X5tSHA1 string `json:"x5t,omitempty"`
+ X5tSHA256 string `json:"x5t#S256,omitempty"`
}
// JSONWebKey represents a public or private key in JWK format.
type JSONWebKey struct {
- Key interface{}
+ // Cryptographic key, can be a symmetric or asymmetric key.
+ Key interface{}
+ // Key identifier, parsed from `kid` header.
+ KeyID string
+ // Key algorithm, parsed from `alg` header.
+ Algorithm string
+ // Key use, parsed from `use` header.
+ Use string
+
+ // X.509 certificate chain, parsed from `x5c` header.
Certificates []*x509.Certificate
- KeyID string
- Algorithm string
- Use string
+ // X.509 certificate URL, parsed from `x5u` header.
+ CertificatesURL *url.URL
+ // X.509 certificate thumbprint (SHA-1), parsed from `x5t` header.
+ CertificateThumbprintSHA1 []byte
+ // X.509 certificate thumbprint (SHA-256), parsed from `x5t#S256` header.
+ CertificateThumbprintSHA256 []byte
}
// MarshalJSON serializes the given key to its JSON representation.
@@ -105,6 +125,39 @@ func (k JSONWebKey) MarshalJSON() ([]byte, error) {
raw.X5c = append(raw.X5c, base64.StdEncoding.EncodeToString(cert.Raw))
}
+ x5tSHA1Len := len(k.CertificateThumbprintSHA1)
+ x5tSHA256Len := len(k.CertificateThumbprintSHA256)
+ if x5tSHA1Len > 0 {
+ if x5tSHA1Len != sha1.Size {
+ return nil, fmt.Errorf("square/go-jose: invalid SHA-1 thumbprint (must be %d bytes, not %d)", sha1.Size, x5tSHA1Len)
+ }
+ raw.X5tSHA1 = base64.RawURLEncoding.EncodeToString(k.CertificateThumbprintSHA1)
+ }
+ if x5tSHA256Len > 0 {
+ if x5tSHA256Len != sha256.Size {
+ return nil, fmt.Errorf("square/go-jose: invalid SHA-256 thumbprint (must be %d bytes, not %d)", sha256.Size, x5tSHA256Len)
+ }
+ raw.X5tSHA256 = base64.RawURLEncoding.EncodeToString(k.CertificateThumbprintSHA256)
+ }
+
+ // If cert chain is attached (as opposed to being behind a URL), check the
+ // keys thumbprints to make sure they match what is expected. This is to
+ // ensure we don't accidentally produce a JWK with semantically inconsistent
+ // data in the headers.
+ if len(k.Certificates) > 0 {
+ expectedSHA1 := sha1.Sum(k.Certificates[0].Raw)
+ expectedSHA256 := sha256.Sum256(k.Certificates[0].Raw)
+
+ if len(k.CertificateThumbprintSHA1) > 0 && !bytes.Equal(k.CertificateThumbprintSHA1, expectedSHA1[:]) {
+ return nil, errors.New("square/go-jose: invalid SHA-1 thumbprint, does not match cert chain")
+ }
+ if len(k.CertificateThumbprintSHA256) > 0 && !bytes.Equal(k.CertificateThumbprintSHA256, expectedSHA256[:]) {
+ return nil, errors.New("square/go-jose: invalid or SHA-256 thumbprint, does not match cert chain")
+ }
+ }
+
+ raw.X5u = k.CertificatesURL
+
return json.Marshal(raw)
}
@@ -116,28 +169,61 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
return err
}
+ certs, err := parseCertificateChain(raw.X5c)
+ if err != nil {
+ return fmt.Errorf("square/go-jose: failed to unmarshal x5c field: %s", err)
+ }
+
var key interface{}
+ var certPub interface{}
+ var keyPub interface{}
+
+ if len(certs) > 0 {
+ // We need to check that leaf public key matches the key embedded in this
+ // JWK, as required by the standard (see RFC 7517, Section 4.7). Otherwise
+ // the JWK parsed could be semantically invalid. Technically, should also
+ // check key usage fields and other extensions on the cert here, but the
+ // standard doesn't exactly explain how they're supposed to map from the
+ // JWK representation to the X.509 extensions.
+ certPub = certs[0].PublicKey
+ }
+
switch raw.Kty {
case "EC":
if raw.D != nil {
key, err = raw.ecPrivateKey()
+ if err == nil {
+ keyPub = key.(*ecdsa.PrivateKey).Public()
+ }
} else {
key, err = raw.ecPublicKey()
+ keyPub = key
}
case "RSA":
if raw.D != nil {
key, err = raw.rsaPrivateKey()
+ if err == nil {
+ keyPub = key.(*rsa.PrivateKey).Public()
+ }
} else {
key, err = raw.rsaPublicKey()
+ keyPub = key
}
case "oct":
+ if certPub != nil {
+ return errors.New("square/go-jose: invalid JWK, found 'oct' (symmetric) key with cert chain")
+ }
key, err = raw.symmetricKey()
case "OKP":
if raw.Crv == "Ed25519" && raw.X != nil {
if raw.D != nil {
key, err = raw.edPrivateKey()
+ if err == nil {
+ keyPub = key.(ed25519.PrivateKey).Public()
+ }
} else {
key, err = raw.edPublicKey()
+ keyPub = key
}
} else {
err = fmt.Errorf("square/go-jose: unknown curve %s'", raw.Crv)
@@ -146,12 +232,78 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) {
err = fmt.Errorf("square/go-jose: unknown json web key type '%s'", raw.Kty)
}
- if err == nil {
- *k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use}
+ if err != nil {
+ return
+ }
+
+ if certPub != nil && keyPub != nil {
+ if !reflect.DeepEqual(certPub, keyPub) {
+ return errors.New("square/go-jose: invalid JWK, public keys in key and x5c fields to not match")
+ }
+ }
+
+ *k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use, Certificates: certs}
- k.Certificates, err = parseCertificateChain(raw.X5c)
+ k.CertificatesURL = raw.X5u
+
+ // x5t parameters are base64url-encoded SHA thumbprints
+ // See RFC 7517, Section 4.8, https://tools.ietf.org/html/rfc7517#section-4.8
+ x5tSHA1bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA1)
+ if err != nil {
+ return errors.New("square/go-jose: invalid JWK, x5t header has invalid encoding")
+ }
+
+ // RFC 7517, Section 4.8 is ambiguous as to whether the digest output should be byte or hex,
+ // for this reason, after base64 decoding, if the size is sha1.Size it's likely that the value is a byte encoded
+ // checksum so we skip this. Otherwise if the checksum was hex encoded we expect a 40 byte sized array so we'll
+ // try to hex decode it. When Marshalling this value we'll always use a base64 encoded version of byte format checksum.
+ if len(x5tSHA1bytes) == 2*sha1.Size {
+ hx, err := hex.DecodeString(string(x5tSHA1bytes))
+ if err != nil {
+ return fmt.Errorf("square/go-jose: invalid JWK, unable to hex decode x5t: %v", err)
+
+ }
+ x5tSHA1bytes = hx
+ }
+
+ k.CertificateThumbprintSHA1 = x5tSHA1bytes
+
+ x5tSHA256bytes, err := base64.RawURLEncoding.DecodeString(raw.X5tSHA256)
+ if err != nil {
+ return errors.New("square/go-jose: invalid JWK, x5t#S256 header has invalid encoding")
+ }
+
+ if len(x5tSHA256bytes) == 2*sha256.Size {
+ hx256, err := hex.DecodeString(string(x5tSHA256bytes))
if err != nil {
- return fmt.Errorf("failed to unmarshal x5c field: %s", err)
+ return fmt.Errorf("square/go-jose: invalid JWK, unable to hex decode x5t#S256: %v", err)
+ }
+ x5tSHA256bytes = hx256
+ }
+
+ k.CertificateThumbprintSHA256 = x5tSHA256bytes
+
+ x5tSHA1Len := len(k.CertificateThumbprintSHA1)
+ x5tSHA256Len := len(k.CertificateThumbprintSHA256)
+ if x5tSHA1Len > 0 && x5tSHA1Len != sha1.Size {
+ return errors.New("square/go-jose: invalid JWK, x5t header is of incorrect size")
+ }
+ if x5tSHA256Len > 0 && x5tSHA256Len != sha256.Size {
+ return errors.New("square/go-jose: invalid JWK, x5t#S256 header is of incorrect size")
+ }
+
+ // If certificate chain *and* thumbprints are set, verify correctness.
+ if len(k.Certificates) > 0 {
+ leaf := k.Certificates[0]
+ sha1sum := sha1.Sum(leaf.Raw)
+ sha256sum := sha256.Sum256(leaf.Raw)
+
+ if len(k.CertificateThumbprintSHA1) > 0 && !bytes.Equal(sha1sum[:], k.CertificateThumbprintSHA1) {
+ return errors.New("square/go-jose: invalid JWK, x5c thumbprint does not match x5t value")
+ }
+
+ if len(k.CertificateThumbprintSHA256) > 0 && !bytes.Equal(sha256sum[:], k.CertificateThumbprintSHA256) {
+ return errors.New("square/go-jose: invalid JWK, x5c thumbprint does not match x5t#S256 value")
}
}
@@ -357,11 +509,11 @@ func (key rawJSONWebKey) ecPublicKey() (*ecdsa.PublicKey, error) {
// the curve specified in the "crv" parameter.
// https://tools.ietf.org/html/rfc7518#section-6.2.1.2
if curveSize(curve) != len(key.X.data) {
- return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for x")
+ return nil, fmt.Errorf("square/go-jose: invalid EC public key, wrong length for x")
}
if curveSize(curve) != len(key.Y.data) {
- return nil, fmt.Errorf("square/go-jose: invalid EC private key, wrong length for y")
+ return nil, fmt.Errorf("square/go-jose: invalid EC public key, wrong length for y")
}
x := key.X.bigInt()