From 9ea39419eb38de247148035c0d2a1592594d1b13 Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Wed, 11 Apr 2018 09:13:33 -0700 Subject: [PATCH 1/2] godeps: bump gopkg.in/square/go-jose.v2 pickup https://github.com/square/go-jose/pull/179 --- Godeps/Godeps.json | 8 +- .../src/k8s.io/apiserver/Godeps/Godeps.json | 6 +- vendor/gopkg.in/square/go-jose.v2/.travis.yml | 11 +-- vendor/gopkg.in/square/go-jose.v2/BUILD | 1 + vendor/gopkg.in/square/go-jose.v2/README.md | 1 + .../gopkg.in/square/go-jose.v2/asymmetric.go | 19 +++-- vendor/gopkg.in/square/go-jose.v2/jwk.go | 40 ++++++--- vendor/gopkg.in/square/go-jose.v2/jws.go | 45 +++++++++- vendor/gopkg.in/square/go-jose.v2/opaque.go | 83 ++++++++++++++++++ vendor/gopkg.in/square/go-jose.v2/shared.go | 57 ++++++++++++- vendor/gopkg.in/square/go-jose.v2/signing.go | 85 ++++++++++++++----- 11 files changed, 293 insertions(+), 63 deletions(-) create mode 100644 vendor/gopkg.in/square/go-jose.v2/opaque.go diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index a6abd865a1..89fc71ed40 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -3669,22 +3669,22 @@ { "ImportPath": "gopkg.in/square/go-jose.v2", "Comment": "v2.1.3", - "Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1" + "Rev": "89060dee6a84df9a4dae49f676f0c755037834f1" }, { "ImportPath": "gopkg.in/square/go-jose.v2/cipher", "Comment": "v2.1.3", - "Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1" + "Rev": "89060dee6a84df9a4dae49f676f0c755037834f1" }, { "ImportPath": "gopkg.in/square/go-jose.v2/json", "Comment": "v2.1.3", - "Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1" + "Rev": "89060dee6a84df9a4dae49f676f0c755037834f1" }, { "ImportPath": "gopkg.in/square/go-jose.v2/jwt", "Comment": "v2.1.3", - "Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1" + "Rev": "89060dee6a84df9a4dae49f676f0c755037834f1" }, { "ImportPath": "gopkg.in/warnings.v0", diff --git a/staging/src/k8s.io/apiserver/Godeps/Godeps.json b/staging/src/k8s.io/apiserver/Godeps/Godeps.json index 9c825b7fff..5881049be2 100644 --- a/staging/src/k8s.io/apiserver/Godeps/Godeps.json +++ b/staging/src/k8s.io/apiserver/Godeps/Godeps.json @@ -840,15 +840,15 @@ }, { "ImportPath": "gopkg.in/square/go-jose.v2", - "Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1" + "Rev": "89060dee6a84df9a4dae49f676f0c755037834f1" }, { "ImportPath": "gopkg.in/square/go-jose.v2/cipher", - "Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1" + "Rev": "89060dee6a84df9a4dae49f676f0c755037834f1" }, { "ImportPath": "gopkg.in/square/go-jose.v2/json", - "Rev": "f8f38de21b4dcd69d0413faf231983f5fd6634b1" + "Rev": "89060dee6a84df9a4dae49f676f0c755037834f1" }, { "ImportPath": "gopkg.in/yaml.v2", diff --git a/vendor/gopkg.in/square/go-jose.v2/.travis.yml b/vendor/gopkg.in/square/go-jose.v2/.travis.yml index c7f8f75ffb..50fb8dd3cf 100644 --- a/vendor/gopkg.in/square/go-jose.v2/.travis.yml +++ b/vendor/gopkg.in/square/go-jose.v2/.travis.yml @@ -8,11 +8,12 @@ matrix: - go: tip go: -- 1.5 -- 1.6 -- 1.7 -- 1.8 -- 1.9 +- '1.5.x' +- '1.6.x' +- '1.7.x' +- '1.8.x' +- '1.9.x' +- '1.10.x' - tip go_import_path: gopkg.in/square/go-jose.v2 diff --git a/vendor/gopkg.in/square/go-jose.v2/BUILD b/vendor/gopkg.in/square/go-jose.v2/BUILD index cf9c9e7993..24459ee8d7 100644 --- a/vendor/gopkg.in/square/go-jose.v2/BUILD +++ b/vendor/gopkg.in/square/go-jose.v2/BUILD @@ -10,6 +10,7 @@ go_library( "jwe.go", "jwk.go", "jws.go", + "opaque.go", "shared.go", "signing.go", "symmetric.go", diff --git a/vendor/gopkg.in/square/go-jose.v2/README.md b/vendor/gopkg.in/square/go-jose.v2/README.md index 43bf1fbea4..d2e989e12d 100644 --- a/vendor/gopkg.in/square/go-jose.v2/README.md +++ b/vendor/gopkg.in/square/go-jose.v2/README.md @@ -84,6 +84,7 @@ standard where possible. The Godoc reference has a list of constants. RSASSA-PSS | PS256, PS384, PS512 HMAC | HS256, HS384, HS512 ECDSA | ES256, ES384, ES512 + Ed25519 | EdDSA Content encryption | Algorithm identifier(s) :------------------------- | :------------------------------ diff --git a/vendor/gopkg.in/square/go-jose.v2/asymmetric.go b/vendor/gopkg.in/square/go-jose.v2/asymmetric.go index 15e9d11a27..5272648faa 100644 --- a/vendor/gopkg.in/square/go-jose.v2/asymmetric.go +++ b/vendor/gopkg.in/square/go-jose.v2/asymmetric.go @@ -104,9 +104,9 @@ func newRSASigner(sigAlg SignatureAlgorithm, privateKey *rsa.PrivateKey) (recipi return recipientSigInfo{ sigAlg: sigAlg, - publicKey: &JSONWebKey{ - Key: &privateKey.PublicKey, - }, + publicKey: staticPublicKey(&JSONWebKey{ + Key: privateKey.Public(), + }), signer: &rsaDecrypterSigner{ privateKey: privateKey, }, @@ -123,9 +123,9 @@ func newEd25519Signer(sigAlg SignatureAlgorithm, privateKey ed25519.PrivateKey) } return recipientSigInfo{ sigAlg: sigAlg, - publicKey: &JSONWebKey{ + publicKey: staticPublicKey(&JSONWebKey{ Key: privateKey.Public(), - }, + }), signer: &edDecrypterSigner{ privateKey: privateKey, }, @@ -168,9 +168,9 @@ func newECDSASigner(sigAlg SignatureAlgorithm, privateKey *ecdsa.PrivateKey) (re return recipientSigInfo{ sigAlg: sigAlg, - publicKey: &JSONWebKey{ - Key: &privateKey.PublicKey, - }, + publicKey: staticPublicKey(&JSONWebKey{ + Key: privateKey.Public(), + }), signer: &ecDecrypterSigner{ privateKey: privateKey, }, @@ -466,6 +466,7 @@ func (ctx ecDecrypterSigner) decryptKey(headers rawHeader, recipient *recipientI return josecipher.KeyUnwrap(block, recipient.encryptedKey) } + func (ctx edDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { if alg != EdDSA { return Signature{}, ErrUnsupportedAlgorithm @@ -531,7 +532,7 @@ func (ctx ecDecrypterSigner) signPayload(payload []byte, alg SignatureAlgorithm) keyBytes++ } - // We serialize the outpus (r and s) into big-endian byte arrays and pad + // We serialize the outputs (r and s) into big-endian byte arrays and pad // them with zeros on the left to make sure the sizes work out. Both arrays // must be keyBytes long, and the output must be 2*keyBytes long. rBytes := r.Bytes() diff --git a/vendor/gopkg.in/square/go-jose.v2/jwk.go b/vendor/gopkg.in/square/go-jose.v2/jwk.go index 8e8f9e7f79..d89046a001 100644 --- a/vendor/gopkg.in/square/go-jose.v2/jwk.go +++ b/vendor/gopkg.in/square/go-jose.v2/jwk.go @@ -148,17 +148,10 @@ func (k *JSONWebKey) UnmarshalJSON(data []byte) (err error) { if err == nil { *k = JSONWebKey{Key: key, KeyID: raw.Kid, Algorithm: raw.Alg, Use: raw.Use} - } - k.Certificates = make([]*x509.Certificate, len(raw.X5c)) - for i, cert := range raw.X5c { - raw, err := base64.StdEncoding.DecodeString(cert) + k.Certificates, err = parseCertificateChain(raw.X5c) if err != nil { - return err - } - k.Certificates[i], err = x509.ParseCertificate(raw) - if err != nil { - return err + return fmt.Errorf("failed to unmarshal x5c field: %s", err) } } @@ -247,13 +240,32 @@ func (k *JSONWebKey) Thumbprint(hash crypto.Hash) ([]byte, error) { // IsPublic returns true if the JWK represents a public key (not symmetric, not private). func (k *JSONWebKey) IsPublic() bool { switch k.Key.(type) { - case *ecdsa.PublicKey, *rsa.PublicKey, *ed25519.PublicKey: + case *ecdsa.PublicKey, *rsa.PublicKey, ed25519.PublicKey: return true default: return false } } +// Public creates JSONWebKey with corresponding publik key if JWK represents asymmetric private key. +func (k *JSONWebKey) Public() JSONWebKey { + if k.IsPublic() { + return *k + } + ret := *k + switch key := k.Key.(type) { + case *ecdsa.PrivateKey: + ret.Key = key.Public() + case *rsa.PrivateKey: + ret.Key = key.Public() + case ed25519.PrivateKey: + ret.Key = key.Public() + default: + return JSONWebKey{} // returning invalid key + } + return ret +} + // Valid checks that the key contains the expected parameters. func (k *JSONWebKey) Valid() bool { if k.Key == nil { @@ -276,12 +288,12 @@ func (k *JSONWebKey) Valid() bool { if key.N == nil || key.E == 0 || key.D == nil || len(key.Primes) < 2 { return false } - case *ed25519.PublicKey: - if len(*key) != 32 { + case ed25519.PublicKey: + if len(key) != 32 { return false } - case *ed25519.PrivateKey: - if len(*key) != 64 { + case ed25519.PrivateKey: + if len(key) != 64 { return false } default: diff --git a/vendor/gopkg.in/square/go-jose.v2/jws.go b/vendor/gopkg.in/square/go-jose.v2/jws.go index 5e23a91b04..8b59b6ab23 100644 --- a/vendor/gopkg.in/square/go-jose.v2/jws.go +++ b/vendor/gopkg.in/square/go-jose.v2/jws.go @@ -52,9 +52,20 @@ type JSONWebSignature struct { // Signature represents a single signature over the JWS payload and protected header. type Signature struct { - // Header fields, such as the signature algorithm + // Merged header fields. Contains both protected and unprotected header + // values. Prefer using Protected and Unprotected fields instead of this. + // Values in this header may or may not have been signed and in general + // should not be trusted. Header Header + // Protected header. Values in this header were signed and + // will be verified as part of the signature verification process. + Protected Header + + // Unprotected header. Values in this header were not signed + // and in general should not be trusted. + Unprotected Header + // The actual signature value Signature []byte @@ -82,7 +93,7 @@ func (sig Signature) mergedHeaders() rawHeader { } // Compute data to be signed -func (obj JSONWebSignature) computeAuthData(signature *Signature) []byte { +func (obj JSONWebSignature) computeAuthData(payload []byte, signature *Signature) []byte { var serializedProtected string if signature.original != nil && signature.original.Protected != nil { @@ -95,7 +106,7 @@ func (obj JSONWebSignature) computeAuthData(signature *Signature) []byte { return []byte(fmt.Sprintf("%s.%s", serializedProtected, - base64.RawURLEncoding.EncodeToString(obj.payload))) + base64.RawURLEncoding.EncodeToString(payload))) } // parseSignedFull parses a message in full format. @@ -159,6 +170,20 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) { return nil, err } + if signature.header != nil { + signature.Unprotected, err = signature.header.sanitized() + if err != nil { + return nil, err + } + } + + if signature.protected != nil { + signature.Protected, err = signature.protected.sanitized() + if err != nil { + return nil, err + } + } + // As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded. jwk := signature.Header.JSONWebKey if jwk != nil && (!jwk.Valid() || !jwk.IsPublic()) { @@ -188,6 +213,20 @@ func (parsed *rawJSONWebSignature) sanitized() (*JSONWebSignature, error) { return nil, err } + if obj.Signatures[i].header != nil { + obj.Signatures[i].Unprotected, err = obj.Signatures[i].header.sanitized() + if err != nil { + return nil, err + } + } + + if obj.Signatures[i].protected != nil { + obj.Signatures[i].Protected, err = obj.Signatures[i].protected.sanitized() + if err != nil { + return nil, err + } + } + obj.Signatures[i].Signature = sig.Signature.bytes() // As per RFC 7515 Section 4.1.3, only public keys are allowed to be embedded. diff --git a/vendor/gopkg.in/square/go-jose.v2/opaque.go b/vendor/gopkg.in/square/go-jose.v2/opaque.go new file mode 100644 index 0000000000..4a8bd8f323 --- /dev/null +++ b/vendor/gopkg.in/square/go-jose.v2/opaque.go @@ -0,0 +1,83 @@ +/*- + * Copyright 2018 Square Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package jose + +// OpaqueSigner is an interface that supports signing payloads with opaque +// private key(s). Private key operations preformed by implementors may, for +// example, occur in a hardware module. An OpaqueSigner may rotate signing keys +// transparently to the user of this interface. +type OpaqueSigner interface { + // Public returns the public key of the current signing key. + Public() *JSONWebKey + // Algs returns a list of supported signing algorithms. + Algs() []SignatureAlgorithm + // SignPayload signs a payload with the current signing key using the given + // algorithm. + SignPayload(payload []byte, alg SignatureAlgorithm) ([]byte, error) +} + +type opaqueSigner struct { + signer OpaqueSigner +} + +func newOpaqueSigner(alg SignatureAlgorithm, signer OpaqueSigner) (recipientSigInfo, error) { + var algSupported bool + for _, salg := range signer.Algs() { + if alg == salg { + algSupported = true + break + } + } + if !algSupported { + return recipientSigInfo{}, ErrUnsupportedAlgorithm + } + + return recipientSigInfo{ + sigAlg: alg, + publicKey: signer.Public, + signer: &opaqueSigner{ + signer: signer, + }, + }, nil +} + +func (o *opaqueSigner) signPayload(payload []byte, alg SignatureAlgorithm) (Signature, error) { + out, err := o.signer.SignPayload(payload, alg) + if err != nil { + return Signature{}, err + } + + return Signature{ + Signature: out, + protected: &rawHeader{}, + }, nil +} + +// OpaqueVerifier is an interface that supports verifying payloads with opaque +// public key(s). An OpaqueSigner may rotate signing keys transparently to the +// user of this interface. +type OpaqueVerifier interface { + VerifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error +} + +type opaqueVerifier struct { + verifier OpaqueVerifier +} + +func (o *opaqueVerifier) verifyPayload(payload []byte, signature []byte, alg SignatureAlgorithm) error { + return o.verifier.VerifyPayload(payload, signature, alg) +} diff --git a/vendor/gopkg.in/square/go-jose.v2/shared.go b/vendor/gopkg.in/square/go-jose.v2/shared.go index 4c19dc382b..e6ab91da10 100644 --- a/vendor/gopkg.in/square/go-jose.v2/shared.go +++ b/vendor/gopkg.in/square/go-jose.v2/shared.go @@ -18,6 +18,8 @@ package jose import ( "crypto/elliptic" + "crypto/x509" + "encoding/base64" "errors" "fmt" @@ -141,6 +143,7 @@ const ( headerEPK = "epk" // *JSONWebKey headerIV = "iv" // *byteBuffer headerTag = "tag" // *byteBuffer + headerX5c = "x5c" // []*x509.Certificate headerJWK = "jwk" // *JSONWebKey headerKeyID = "kid" // string @@ -162,11 +165,34 @@ type Header struct { Algorithm string Nonce string - // Any headers not recognised above get unmarshaled from JSON in a generic - // manner and placed in this map. + // Unverified certificate chain parsed from x5c header. + certificates []*x509.Certificate + + // Any headers not recognised above get unmarshaled + // from JSON in a generic manner and placed in this map. ExtraHeaders map[HeaderKey]interface{} } +// Certificates verifies & returns the certificate chain present +// in the x5c header field of a message, if one was present. Returns +// an error if there was no x5c header present or the chain could +// not be validated with the given verify options. +func (h Header) Certificates(opts x509.VerifyOptions) ([][]*x509.Certificate, error) { + if len(h.certificates) == 0 { + return nil, errors.New("square/go-jose: no x5c header present in message") + } + + leaf := h.certificates[0] + if opts.Intermediates == nil { + opts.Intermediates = x509.NewCertPool() + for _, intermediate := range h.certificates[1:] { + opts.Intermediates.AddCert(intermediate) + } + } + + return leaf.Verify(opts) +} + func (parsed rawHeader) set(k HeaderKey, v interface{}) error { b, err := json.Marshal(v) if err != nil { @@ -333,6 +359,18 @@ func (parsed rawHeader) sanitized() (h Header, err error) { return } h.Nonce = s + case headerX5c: + c := []string{} + err = json.Unmarshal(*v, &c) + if err != nil { + err = fmt.Errorf("failed to unmarshal x5c header: %v: %#v", err, string(*v)) + return + } + h.certificates, err = parseCertificateChain(c) + if err != nil { + err = fmt.Errorf("failed to unmarshal x5c header: %v: %#v", err, string(*v)) + return + } default: if h.ExtraHeaders == nil { h.ExtraHeaders = map[HeaderKey]interface{}{} @@ -349,6 +387,21 @@ func (parsed rawHeader) sanitized() (h Header, err error) { return } +func parseCertificateChain(chain []string) ([]*x509.Certificate, error) { + out := make([]*x509.Certificate, len(chain)) + for i, cert := range chain { + raw, err := base64.StdEncoding.DecodeString(cert) + if err != nil { + return nil, err + } + out[i], err = x509.ParseCertificate(raw) + if err != nil { + return nil, err + } + } + return out, nil +} + func (dst rawHeader) isSet(k HeaderKey) bool { dvr := dst[k] if dvr == nil { diff --git a/vendor/gopkg.in/square/go-jose.v2/signing.go b/vendor/gopkg.in/square/go-jose.v2/signing.go index 13e956d668..1acc6356fa 100644 --- a/vendor/gopkg.in/square/go-jose.v2/signing.go +++ b/vendor/gopkg.in/square/go-jose.v2/signing.go @@ -94,10 +94,16 @@ type genericSigner struct { type recipientSigInfo struct { sigAlg SignatureAlgorithm - publicKey *JSONWebKey + publicKey func() *JSONWebKey signer payloadSigner } +func staticPublicKey(jwk *JSONWebKey) func() *JSONWebKey { + return func() *JSONWebKey { + return jwk + } +} + // NewSigner creates an appropriate signer based on the key type func NewSigner(sig SigningKey, opts *SignerOptions) (Signer, error) { return NewMultiSigner([]SigningKey{sig}, opts) @@ -146,9 +152,11 @@ func newVerifier(verificationKey interface{}) (payloadVerifier, error) { return newVerifier(verificationKey.Key) case *JSONWebKey: return newVerifier(verificationKey.Key) - default: - return nil, ErrUnsupportedKeyType } + if ov, ok := verificationKey.(OpaqueVerifier); ok { + return &opaqueVerifier{verifier: ov}, nil + } + return nil, ErrUnsupportedKeyType } func (ctx *genericSigner) addRecipient(alg SignatureAlgorithm, signingKey interface{}) error { @@ -175,9 +183,11 @@ func makeJWSRecipient(alg SignatureAlgorithm, signingKey interface{}) (recipient return newJWKSigner(alg, signingKey) case *JSONWebKey: return newJWKSigner(alg, *signingKey) - default: - return recipientSigInfo{}, ErrUnsupportedKeyType } + if signer, ok := signingKey.(OpaqueSigner); ok { + return newOpaqueSigner(alg, signer) + } + return recipientSigInfo{}, ErrUnsupportedKeyType } func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigInfo, error) { @@ -185,16 +195,16 @@ func newJWKSigner(alg SignatureAlgorithm, signingKey JSONWebKey) (recipientSigIn if err != nil { return recipientSigInfo{}, err } - if recipient.publicKey != nil { + if recipient.publicKey != nil && recipient.publicKey() != nil { // recipient.publicKey is a JWK synthesized for embedding when recipientSigInfo // was created for the inner key (such as a RSA or ECDSA public key). It contains // the pub key for embedding, but doesn't have extra params like key id. publicKey := signingKey - publicKey.Key = recipient.publicKey.Key - recipient.publicKey = &publicKey + publicKey.Key = recipient.publicKey().Key + recipient.publicKey = staticPublicKey(&publicKey) // This should be impossible, but let's check anyway. - if !recipient.publicKey.IsPublic() { + if !recipient.publicKey().IsPublic() { return recipientSigInfo{}, errors.New("square/go-jose: public key was unexpectedly not public") } } @@ -211,7 +221,7 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) { headerAlgorithm: string(recipient.sigAlg), } - if recipient.publicKey != nil { + if recipient.publicKey != nil && recipient.publicKey() != nil { // We want to embed the JWK or set the kid header, but not both. Having a protected // header that contains an embedded JWK while also simultaneously containing the kid // header is confusing, and at least in ACME the two are considered to be mutually @@ -221,9 +231,9 @@ func (ctx *genericSigner) Sign(payload []byte) (*JSONWebSignature, error) { // // See https://github.com/square/go-jose/issues/157 for more context. if ctx.embedJWK { - protected[headerJWK] = recipient.publicKey + protected[headerJWK] = recipient.publicKey() } else { - protected[headerKeyID] = recipient.publicKey.KeyID + protected[headerKeyID] = recipient.publicKey().KeyID } } @@ -280,34 +290,46 @@ func (ctx *genericSigner) Options() SignerOptions { // payload header. You cannot assume that the key received in a payload is // trusted. func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error) { - verifier, err := newVerifier(verificationKey) + err := obj.DetachedVerify(obj.payload, verificationKey) if err != nil { return nil, err } + return obj.payload, nil +} + +// DetachedVerify validates a detached signature on the given payload. In +// most cases, you will probably want to use Verify instead. DetachedVerify +// is only useful if you have a payload and signature that are separated from +// each other. +func (obj JSONWebSignature) DetachedVerify(payload []byte, verificationKey interface{}) error { + verifier, err := newVerifier(verificationKey) + if err != nil { + return err + } if len(obj.Signatures) > 1 { - return nil, errors.New("square/go-jose: too many signatures in payload; expecting only one") + return errors.New("square/go-jose: too many signatures in payload; expecting only one") } signature := obj.Signatures[0] headers := signature.mergedHeaders() critical, err := headers.getCritical() if err != nil { - return nil, err + return err } if len(critical) > 0 { // Unsupported crit header - return nil, ErrCryptoFailure + return ErrCryptoFailure } - input := obj.computeAuthData(&signature) + input := obj.computeAuthData(payload, &signature) alg := headers.getSignatureAlgorithm() err = verifier.verifyPayload(input, signature.Signature, alg) if err == nil { - return obj.payload, nil + return nil } - return nil, ErrCryptoFailure + return ErrCryptoFailure } // VerifyMulti validates (one of the multiple) signatures on the object and @@ -315,10 +337,27 @@ func (obj JSONWebSignature) Verify(verificationKey interface{}) ([]byte, error) // object and the payload. We return the signature and index to guarantee that // callers are getting the verified value. func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signature, []byte, error) { - verifier, err := newVerifier(verificationKey) + idx, sig, err := obj.DetachedVerifyMulti(obj.payload, verificationKey) if err != nil { return -1, Signature{}, nil, err } + return idx, sig, obj.payload, nil +} + +// DetachedVerifyMulti validates a detached signature on the given payload with +// a signature/object that has potentially multiple signers. This returns the index +// of the signature that was verified, along with the signature object. We return +// the signature and index to guarantee that callers are getting the verified value. +// +// In most cases, you will probably want to use Verify or VerifyMulti instead. +// DetachedVerifyMulti is only useful if you have a payload and signature that are +// separated from each other, and the signature can have multiple signers at the +// same time. +func (obj JSONWebSignature) DetachedVerifyMulti(payload []byte, verificationKey interface{}) (int, Signature, error) { + verifier, err := newVerifier(verificationKey) + if err != nil { + return -1, Signature{}, err + } for i, signature := range obj.Signatures { headers := signature.mergedHeaders() @@ -331,13 +370,13 @@ func (obj JSONWebSignature) VerifyMulti(verificationKey interface{}) (int, Signa continue } - input := obj.computeAuthData(&signature) + input := obj.computeAuthData(payload, &signature) alg := headers.getSignatureAlgorithm() err = verifier.verifyPayload(input, signature.Signature, alg) if err == nil { - return i, signature, obj.payload, nil + return i, signature, nil } } - return -1, Signature{}, nil, ErrCryptoFailure + return -1, Signature{}, ErrCryptoFailure } From e68f14a2495ab4dd63fcc10ab23f29500a8452a7 Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Wed, 11 Apr 2018 11:24:06 -0700 Subject: [PATCH 2/2] jwt: support opaque signer and push errors to token generator creation --- cmd/kube-apiserver/app/server.go | 6 ++- .../app/controllermanager.go | 6 ++- pkg/serviceaccount/jwt.go | 41 ++++++++++--------- pkg/serviceaccount/jwt_test.go | 15 +++++-- test/integration/auth/svcaccttoken_test.go | 6 ++- .../serviceaccount/service_account_test.go | 6 ++- 6 files changed, 53 insertions(+), 27 deletions(-) diff --git a/cmd/kube-apiserver/app/server.go b/cmd/kube-apiserver/app/server.go index 27880351ab..82d47f3d01 100644 --- a/cmd/kube-apiserver/app/server.go +++ b/cmd/kube-apiserver/app/server.go @@ -360,7 +360,11 @@ func CreateKubeAPIServerConfig( } } - issuer = serviceaccount.JWTTokenGenerator(s.Authentication.ServiceAccounts.Issuer, sk) + issuer, err = serviceaccount.JWTTokenGenerator(s.Authentication.ServiceAccounts.Issuer, sk) + if err != nil { + lastErr = fmt.Errorf("failed to build token generator: %v", err) + return + } apiAudiences = s.Authentication.ServiceAccounts.APIAudiences maxExpiration = s.Authentication.ServiceAccounts.MaxExpiration } diff --git a/cmd/kube-controller-manager/app/controllermanager.go b/cmd/kube-controller-manager/app/controllermanager.go index ad74c7ada3..aefbd6dac6 100644 --- a/cmd/kube-controller-manager/app/controllermanager.go +++ b/cmd/kube-controller-manager/app/controllermanager.go @@ -516,12 +516,16 @@ func (c serviceAccountTokenControllerStarter) startServiceAccountTokenController rootCA = c.rootClientBuilder.ConfigOrDie("tokens-controller").CAData } + tokenGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, privateKey) + if err != nil { + return nil, false, fmt.Errorf("failed to build token generator: %v", err) + } controller, err := serviceaccountcontroller.NewTokensController( ctx.InformerFactory.Core().V1().ServiceAccounts(), ctx.InformerFactory.Core().V1().Secrets(), c.rootClientBuilder.ClientOrDie("tokens-controller"), serviceaccountcontroller.TokensControllerOptions{ - TokenGenerator: serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, privateKey), + TokenGenerator: tokenGenerator, RootCA: rootCA, }, ) diff --git a/pkg/serviceaccount/jwt.go b/pkg/serviceaccount/jwt.go index 73960d7f13..3a3c7cb005 100644 --- a/pkg/serviceaccount/jwt.go +++ b/pkg/serviceaccount/jwt.go @@ -54,25 +54,13 @@ type TokenGenerator interface { // JWTTokenGenerator returns a TokenGenerator that generates signed JWT tokens, using the given privateKey. // privateKey is a PEM-encoded byte array of a private RSA key. // JWTTokenAuthenticator() -func JWTTokenGenerator(iss string, privateKey interface{}) TokenGenerator { - return &jwtTokenGenerator{ - iss: iss, - privateKey: privateKey, - } -} - -type jwtTokenGenerator struct { - iss string - privateKey interface{} -} - -func (j *jwtTokenGenerator) GenerateToken(claims *jwt.Claims, privateClaims interface{}) (string, error) { +func JWTTokenGenerator(iss string, privateKey interface{}) (TokenGenerator, error) { var alg jose.SignatureAlgorithm - switch privateKey := j.privateKey.(type) { + switch pk := privateKey.(type) { case *rsa.PrivateKey: alg = jose.RS256 case *ecdsa.PrivateKey: - switch privateKey.Curve { + switch pk.Curve { case elliptic.P256(): alg = jose.ES256 case elliptic.P384(): @@ -80,25 +68,38 @@ func (j *jwtTokenGenerator) GenerateToken(claims *jwt.Claims, privateClaims inte case elliptic.P521(): alg = jose.ES512 default: - return "", fmt.Errorf("unknown private key curve, must be 256, 384, or 521") + return nil, fmt.Errorf("unknown private key curve, must be 256, 384, or 521") } + case jose.OpaqueSigner: + alg = jose.SignatureAlgorithm(pk.Public().Algorithm) default: - return "", fmt.Errorf("unknown private key type %T, must be *rsa.PrivateKey or *ecdsa.PrivateKey", j.privateKey) + return nil, fmt.Errorf("unknown private key type %T, must be *rsa.PrivateKey, *ecdsa.PrivateKey, or jose.OpaqueSigner", privateKey) } signer, err := jose.NewSigner( jose.SigningKey{ Algorithm: alg, - Key: j.privateKey, + Key: privateKey, }, nil, ) if err != nil { - return "", err + return nil, err } + return &jwtTokenGenerator{ + iss: iss, + signer: signer, + }, nil +} +type jwtTokenGenerator struct { + iss string + signer jose.Signer +} + +func (j *jwtTokenGenerator) GenerateToken(claims *jwt.Claims, privateClaims interface{}) (string, error) { // claims are applied in reverse precedence - return jwt.Signed(signer). + return jwt.Signed(j.signer). Claims(privateClaims). Claims(claims). Claims(&jwt.Claims{ diff --git a/pkg/serviceaccount/jwt_test.go b/pkg/serviceaccount/jwt_test.go index 0f0c52f0f1..4f7d82e288 100644 --- a/pkg/serviceaccount/jwt_test.go +++ b/pkg/serviceaccount/jwt_test.go @@ -127,7 +127,10 @@ func TestTokenGenerateAndValidate(t *testing.T) { } // Generate the RSA token - rsaGenerator := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(rsaPrivateKey)) + rsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(rsaPrivateKey)) + if err != nil { + t.Fatalf("error making generator: %v", err) + } rsaToken, err := rsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret)) if err != nil { t.Fatalf("error generating token: %v", err) @@ -140,7 +143,10 @@ func TestTokenGenerateAndValidate(t *testing.T) { } // Generate the ECDSA token - ecdsaGenerator := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(ecdsaPrivateKey)) + ecdsaGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, getPrivateKey(ecdsaPrivateKey)) + if err != nil { + t.Fatalf("error making generator: %v", err) + } ecdsaToken, err := ecdsaGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *ecdsaSecret)) if err != nil { t.Fatalf("error generating token: %v", err) @@ -153,7 +159,10 @@ func TestTokenGenerateAndValidate(t *testing.T) { } // Generate signer with same keys as RSA signer but different issuer - badIssuerGenerator := serviceaccount.JWTTokenGenerator("foo", getPrivateKey(rsaPrivateKey)) + badIssuerGenerator, err := serviceaccount.JWTTokenGenerator("foo", getPrivateKey(rsaPrivateKey)) + if err != nil { + t.Fatalf("error making generator: %v", err) + } badIssuerToken, err := badIssuerGenerator.GenerateToken(serviceaccount.LegacyClaims(*serviceAccount, *rsaSecret)) if err != nil { t.Fatalf("error generating token: %v", err) diff --git a/test/integration/auth/svcaccttoken_test.go b/test/integration/auth/svcaccttoken_test.go index 0978de010b..6ab3d7bf20 100644 --- a/test/integration/auth/svcaccttoken_test.go +++ b/test/integration/auth/svcaccttoken_test.go @@ -81,7 +81,11 @@ func TestServiceAccountTokenCreate(t *testing.T) { serviceaccount.NewValidator(aud, serviceaccountgetter.NewGetterFromClient(gcs)), ), ) - masterConfig.ExtraConfig.ServiceAccountIssuer = serviceaccount.JWTTokenGenerator(iss, sk) + tokenGenerator, err := serviceaccount.JWTTokenGenerator(iss, sk) + if err != nil { + t.Fatalf("err: %v", err) + } + masterConfig.ExtraConfig.ServiceAccountIssuer = tokenGenerator masterConfig.ExtraConfig.ServiceAccountAPIAudiences = aud masterConfig.ExtraConfig.ServiceAccountMaxExpiration = maxExpirationDuration diff --git a/test/integration/serviceaccount/service_account_test.go b/test/integration/serviceaccount/service_account_test.go index 52d957ca11..23a41d5338 100644 --- a/test/integration/serviceaccount/service_account_test.go +++ b/test/integration/serviceaccount/service_account_test.go @@ -436,11 +436,15 @@ func startServiceAccountTestServer(t *testing.T) (*clientset.Clientset, restclie apiServer.Close() } + tokenGenerator, err := serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, serviceAccountKey) + if err != nil { + return rootClientset, clientConfig, stop, err + } tokenController, err := serviceaccountcontroller.NewTokensController( informers.Core().V1().ServiceAccounts(), informers.Core().V1().Secrets(), rootClientset, - serviceaccountcontroller.TokensControllerOptions{TokenGenerator: serviceaccount.JWTTokenGenerator(serviceaccount.LegacyIssuer, serviceAccountKey)}, + serviceaccountcontroller.TokensControllerOptions{TokenGenerator: tokenGenerator}, ) if err != nil { return rootClientset, clientConfig, stop, err