Merge pull request #862 from jlhawn/ng_auth_package
Refactor token verification to support x5c headermaster
						commit
						e50fcc0ab9
					
				| 
						 | 
					@ -9,7 +9,6 @@ import (
 | 
				
			||||||
	"io/ioutil"
 | 
						"io/ioutil"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"strconv"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/docker/libtrust"
 | 
						"github.com/docker/libtrust"
 | 
				
			||||||
| 
						 | 
					@ -97,16 +96,16 @@ func (ac *authChallenge) Status() int {
 | 
				
			||||||
// the WWW-Authenticate response challenge header.
 | 
					// the WWW-Authenticate response challenge header.
 | 
				
			||||||
// See https://tools.ietf.org/html/rfc6750#section-3
 | 
					// See https://tools.ietf.org/html/rfc6750#section-3
 | 
				
			||||||
func (ac *authChallenge) challengeParams() string {
 | 
					func (ac *authChallenge) challengeParams() string {
 | 
				
			||||||
	str := fmt.Sprintf("Bearer realm=%s,service=%s", strconv.Quote(ac.realm), strconv.Quote(ac.service))
 | 
						str := fmt.Sprintf("Bearer realm=%q,service=%q", ac.realm, ac.service)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if scope := ac.accessSet.scopeParam(); scope != "" {
 | 
						if scope := ac.accessSet.scopeParam(); scope != "" {
 | 
				
			||||||
		str = fmt.Sprintf("%s,scope=%s", str, strconv.Quote(scope))
 | 
							str = fmt.Sprintf("%s,scope=%q", str, scope)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if ac.err == ErrInvalidToken || ac.err == ErrMalformedToken {
 | 
						if ac.err == ErrInvalidToken || ac.err == ErrMalformedToken {
 | 
				
			||||||
		str = fmt.Sprintf("%s,error=%s", str, strconv.Quote("invalid_token"))
 | 
							str = fmt.Sprintf("%s,error=%q", str, "invalid_token")
 | 
				
			||||||
	} else if ac.err == ErrInsufficientScope {
 | 
						} else if ac.err == ErrInsufficientScope {
 | 
				
			||||||
		str = fmt.Sprintf("%s,error=%s", str, strconv.Quote("insufficient_scope"))
 | 
							str = fmt.Sprintf("%s,error=%q", str, "insufficient_scope")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return str
 | 
						return str
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,25 +53,11 @@ type ClaimSet struct {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Header describes the header section of a JSON Web Token.
 | 
					// Header describes the header section of a JSON Web Token.
 | 
				
			||||||
type Header struct {
 | 
					type Header struct {
 | 
				
			||||||
	Type       string             `json:"typ"`
 | 
						Type       string          `json:"typ"`
 | 
				
			||||||
	SigningAlg string             `json:"alg"`
 | 
						SigningAlg string          `json:"alg"`
 | 
				
			||||||
	KeyID      string             `json:"kid,omitempty"`
 | 
						KeyID      string          `json:"kid,omitempty"`
 | 
				
			||||||
	RawJWK     json.RawMessage    `json:"jwk"`
 | 
						X5c        []string        `json:"x5c,omitempty"`
 | 
				
			||||||
	SigningKey libtrust.PublicKey `json:"-"`
 | 
						RawJWK     json.RawMessage `json:"jwk,omitempty"`
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// CheckSigningKey parses the `jwk` field of a JOSE header and sets the
 | 
					 | 
				
			||||||
// SigningKey field if it is valid.
 | 
					 | 
				
			||||||
func (h *Header) CheckSigningKey() (err error) {
 | 
					 | 
				
			||||||
	if len(h.RawJWK) == 0 {
 | 
					 | 
				
			||||||
		// No signing key was specified.
 | 
					 | 
				
			||||||
		return
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	h.SigningKey, err = libtrust.UnmarshalPublicKeyJWK([]byte(h.RawJWK))
 | 
					 | 
				
			||||||
	h.RawJWK = nil // Don't need this anymore!
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	return
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Token describes a JSON Web Token.
 | 
					// Token describes a JSON Web Token.
 | 
				
			||||||
| 
						 | 
					@ -135,10 +121,6 @@ func NewToken(rawToken string) (*Token, error) {
 | 
				
			||||||
		return nil, ErrMalformedToken
 | 
							return nil, ErrMalformedToken
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = token.Header.CheckSigningKey(); err != nil {
 | 
					 | 
				
			||||||
		return nil, ErrMalformedToken
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if err = json.Unmarshal(claimsJSON, token.Claims); err != nil {
 | 
						if err = json.Unmarshal(claimsJSON, token.Claims); err != nil {
 | 
				
			||||||
		return nil, ErrMalformedToken
 | 
							return nil, ErrMalformedToken
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -174,108 +156,86 @@ func (t *Token) Verify(verifyOpts VerifyOptions) error {
 | 
				
			||||||
		return ErrInvalidToken
 | 
							return ErrInvalidToken
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// If the token header has a SigningKey field, verify the signature
 | 
						// Verify that the signing key is trusted.
 | 
				
			||||||
	// using that key and its included x509 certificate chain if necessary.
 | 
						signingKey, err := t.VerifySigningKey(verifyOpts)
 | 
				
			||||||
	// If the Header's SigningKey field is nil, try using the KeyID field.
 | 
						if err != nil {
 | 
				
			||||||
	signingKey := t.Header.SigningKey
 | 
							log.Error(err)
 | 
				
			||||||
 | 
							return ErrInvalidToken
 | 
				
			||||||
	if signingKey == nil {
 | 
					 | 
				
			||||||
		// Find the key in the given collection of trusted keys.
 | 
					 | 
				
			||||||
		trustedKey, ok := verifyOpts.TrustedKeys[t.Header.KeyID]
 | 
					 | 
				
			||||||
		if !ok {
 | 
					 | 
				
			||||||
			log.Errorf("token signed by untrusted key with ID: %q", t.Header.KeyID)
 | 
					 | 
				
			||||||
			return ErrInvalidToken
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		signingKey = trustedKey
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// First verify the signature of the token using the key which signed it.
 | 
						// Finally, verify the signature of the token using the key which signed it.
 | 
				
			||||||
	if err := signingKey.Verify(strings.NewReader(t.Raw), t.Header.SigningAlg, t.Signature); err != nil {
 | 
						if err := signingKey.Verify(strings.NewReader(t.Raw), t.Header.SigningAlg, t.Signature); err != nil {
 | 
				
			||||||
		log.Errorf("unable to verify token signature: %s", err)
 | 
							log.Errorf("unable to verify token signature: %s", err)
 | 
				
			||||||
		return ErrInvalidToken
 | 
							return ErrInvalidToken
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Next, check if the signing key is one of the trusted keys.
 | 
						return nil
 | 
				
			||||||
	if _, isTrustedKey := verifyOpts.TrustedKeys[signingKey.KeyID()]; isTrustedKey {
 | 
					 | 
				
			||||||
		// We're done! The token was signed by
 | 
					 | 
				
			||||||
		// a trusted key and has been verified!
 | 
					 | 
				
			||||||
		return nil
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Otherwise, we need to check the sigining keys included certificate chain.
 | 
					 | 
				
			||||||
	return t.verifyCertificateChain(signingKey, verifyOpts.Roots)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// verifyCertificateChain attempts to verify the token using the "x5c" field
 | 
					// VerifySigningKey attempts to get the key which was used to sign this token.
 | 
				
			||||||
// of the given leafKey which was used to sign it. Returns a nil error if
 | 
					// The token header should contain either of these 3 fields:
 | 
				
			||||||
// the key's certificate chain is valid and rooted an one of the given roots.
 | 
					//      `x5c` - The x509 certificate chain for the signing key. Needs to be
 | 
				
			||||||
func (t *Token) verifyCertificateChain(leafKey libtrust.PublicKey, roots *x509.CertPool) error {
 | 
					//              verified.
 | 
				
			||||||
	// In this case, the token signature is valid, but the key that signed it
 | 
					//      `jwk` - The JSON Web Key representation of the signing key.
 | 
				
			||||||
	// is not in our set of trusted keys. So, we'll need to check if the
 | 
					//              May contain its own `x5c` field which needs to be verified.
 | 
				
			||||||
	// token's signing key included an x509 certificate chain that can be
 | 
					//      `kid` - The unique identifier for the key. This library interprets it
 | 
				
			||||||
	// verified up to one of our trusted roots.
 | 
					//              as a libtrust fingerprint. The key itself can be looked up in
 | 
				
			||||||
	x5cVal, ok := leafKey.GetExtendedField("x5c").([]interface{})
 | 
					//              the trustedKeys field of the given verify options.
 | 
				
			||||||
	if !ok || x5cVal == nil {
 | 
					// Each of these methods are tried in that order of preference until the
 | 
				
			||||||
		log.Error("unable to verify token signature: signed by untrusted key with no valid certificate chain")
 | 
					// signing key is found or an error is returned.
 | 
				
			||||||
		return ErrInvalidToken
 | 
					func (t *Token) VerifySigningKey(verifyOpts VerifyOptions) (signingKey libtrust.PublicKey, err error) {
 | 
				
			||||||
 | 
						// First attempt to get an x509 certificate chain from the header.
 | 
				
			||||||
 | 
						var (
 | 
				
			||||||
 | 
							x5c    = t.Header.X5c
 | 
				
			||||||
 | 
							rawJWK = t.Header.RawJWK
 | 
				
			||||||
 | 
							keyID  = t.Header.KeyID
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						switch {
 | 
				
			||||||
 | 
						case len(x5c) > 0:
 | 
				
			||||||
 | 
							signingKey, err = parseAndVerifyCertChain(x5c, verifyOpts.Roots)
 | 
				
			||||||
 | 
						case len(rawJWK) > 0:
 | 
				
			||||||
 | 
							signingKey, err = parseAndVerifyRawJWK(rawJWK, verifyOpts)
 | 
				
			||||||
 | 
						case len(keyID) > 0:
 | 
				
			||||||
 | 
							signingKey = verifyOpts.TrustedKeys[keyID]
 | 
				
			||||||
 | 
							if signingKey == nil {
 | 
				
			||||||
 | 
								err = fmt.Errorf("token signed by untrusted key with ID: %q", keyID)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							err = errors.New("unable to get token signing key")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Ensure each item is of the correct type.
 | 
						return
 | 
				
			||||||
	x5c := make([]string, len(x5cVal))
 | 
					}
 | 
				
			||||||
	for i, val := range x5cVal {
 | 
					
 | 
				
			||||||
		certString, ok := val.(string)
 | 
					func parseAndVerifyCertChain(x5c []string, roots *x509.CertPool) (leafKey libtrust.PublicKey, err error) {
 | 
				
			||||||
		if !ok || len(certString) == 0 {
 | 
						if len(x5c) == 0 {
 | 
				
			||||||
			log.Error("unable to verify token signature: signed by untrusted key with malformed certificate chain")
 | 
							return nil, errors.New("empty x509 certificate chain")
 | 
				
			||||||
			return ErrInvalidToken
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		x5c[i] = certString
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Ensure the first element is encoded correctly.
 | 
						// Ensure the first element is encoded correctly.
 | 
				
			||||||
	leafCertDer, err := base64.StdEncoding.DecodeString(x5c[0])
 | 
						leafCertDer, err := base64.StdEncoding.DecodeString(x5c[0])
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Errorf("unable to decode signing key leaf cert: %s", err)
 | 
							return nil, fmt.Errorf("unable to decode leaf certificate: %s", err)
 | 
				
			||||||
		return ErrInvalidToken
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// And that it is a valid x509 certificate.
 | 
						// And that it is a valid x509 certificate.
 | 
				
			||||||
	leafCert, err := x509.ParseCertificate(leafCertDer)
 | 
						leafCert, err := x509.ParseCertificate(leafCertDer)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		log.Errorf("unable to parse signing key leaf cert: %s", err)
 | 
							return nil, fmt.Errorf("unable to parse leaf certificate: %s", err)
 | 
				
			||||||
		return ErrInvalidToken
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Verify that the public key in the leaf cert *is* the signing key.
 | 
						// The rest of the certificate chain are intermediate certificates.
 | 
				
			||||||
	leafCryptoKey, ok := leafCert.PublicKey.(crypto.PublicKey)
 | 
					 | 
				
			||||||
	if !ok {
 | 
					 | 
				
			||||||
		log.Error("unable to get signing key leaf cert public key value")
 | 
					 | 
				
			||||||
		return ErrInvalidToken
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	leafPubKey, err := libtrust.FromCryptoPublicKey(leafCryptoKey)
 | 
					 | 
				
			||||||
	if err != nil {
 | 
					 | 
				
			||||||
		log.Errorf("unable to make libtrust public key from signing key leaf cert: %s", err)
 | 
					 | 
				
			||||||
		return ErrInvalidToken
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	if leafPubKey.KeyID() != leafKey.KeyID() {
 | 
					 | 
				
			||||||
		log.Error("token signing key ID and leaf certificate public key ID do not match")
 | 
					 | 
				
			||||||
		return ErrInvalidToken
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The rest of the x5c array are intermediate certificates.
 | 
					 | 
				
			||||||
	intermediates := x509.NewCertPool()
 | 
						intermediates := x509.NewCertPool()
 | 
				
			||||||
	for i := 1; i < len(x5c); i++ {
 | 
						for i := 1; i < len(x5c); i++ {
 | 
				
			||||||
		intermediateCertDer, err := base64.StdEncoding.DecodeString(x5c[i])
 | 
							intermediateCertDer, err := base64.StdEncoding.DecodeString(x5c[i])
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Errorf("unable to decode signing key intermediate cert: %s", err)
 | 
								return nil, fmt.Errorf("unable to decode intermediate certificate: %s", err)
 | 
				
			||||||
			return ErrInvalidToken
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		intermediateCert, err := x509.ParseCertificate(intermediateCertDer)
 | 
							intermediateCert, err := x509.ParseCertificate(intermediateCertDer)
 | 
				
			||||||
		if err != nil {
 | 
							if err != nil {
 | 
				
			||||||
			log.Errorf("unable to parse signing key intermediate cert: %s", err)
 | 
								return nil, fmt.Errorf("unable to parse intermediate certificate: %s", err)
 | 
				
			||||||
			return ErrInvalidToken
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		intermediates.AddCert(intermediateCert)
 | 
							intermediates.AddCert(intermediateCert)
 | 
				
			||||||
| 
						 | 
					@ -290,12 +250,64 @@ func (t *Token) verifyCertificateChain(leafKey libtrust.PublicKey, roots *x509.C
 | 
				
			||||||
	// TODO: this call returns certificate chains which we ignore for now, but
 | 
						// TODO: this call returns certificate chains which we ignore for now, but
 | 
				
			||||||
	// we should check them for revocations if we have the ability later.
 | 
						// we should check them for revocations if we have the ability later.
 | 
				
			||||||
	if _, err = leafCert.Verify(verifyOpts); err != nil {
 | 
						if _, err = leafCert.Verify(verifyOpts); err != nil {
 | 
				
			||||||
		log.Errorf("unable to verify signing key certificate: %s", err)
 | 
							return nil, fmt.Errorf("unable to verify certificate chain: %s", err)
 | 
				
			||||||
		return ErrInvalidToken
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// The signing key's x509 chain is valid!
 | 
						// Get the public key from the leaf certificate.
 | 
				
			||||||
	return nil
 | 
						leafCryptoKey, ok := leafCert.PublicKey.(crypto.PublicKey)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, errors.New("unable to get leaf cert public key value")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						leafKey, err = libtrust.FromCryptoPublicKey(leafCryptoKey)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("unable to make libtrust public key from leaf certificate: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parseAndVerifyRawJWK(rawJWK json.RawMessage, verifyOpts VerifyOptions) (pubKey libtrust.PublicKey, err error) {
 | 
				
			||||||
 | 
						pubKey, err = libtrust.UnmarshalPublicKeyJWK([]byte(rawJWK))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("unable to decode raw JWK value: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Check to see if the key includes a certificate chain.
 | 
				
			||||||
 | 
						x5cVal, ok := pubKey.GetExtendedField("x5c").([]interface{})
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							// The JWK should be one of the trusted root keys.
 | 
				
			||||||
 | 
							if _, trusted := verifyOpts.TrustedKeys[pubKey.KeyID()]; !trusted {
 | 
				
			||||||
 | 
								return nil, errors.New("untrusted JWK with no certificate chain")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// The JWK is one of the trusted keys.
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Ensure each item in the chain is of the correct type.
 | 
				
			||||||
 | 
						x5c := make([]string, len(x5cVal))
 | 
				
			||||||
 | 
						for i, val := range x5cVal {
 | 
				
			||||||
 | 
							certString, ok := val.(string)
 | 
				
			||||||
 | 
							if !ok || len(certString) == 0 {
 | 
				
			||||||
 | 
								return nil, errors.New("malformed certificate chain")
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							x5c[i] = certString
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Ensure that the x509 certificate chain can
 | 
				
			||||||
 | 
						// be verified up to one of our trusted roots.
 | 
				
			||||||
 | 
						leafKey, err := parseAndVerifyCertChain(x5c, verifyOpts.Roots)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("could not verify JWK certificate chain: %s", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Verify that the public key in the leaf cert *is* the signing key.
 | 
				
			||||||
 | 
						if pubKey.KeyID() != leafKey.KeyID() {
 | 
				
			||||||
 | 
							return nil, errors.New("leaf certificate public key ID does not match JWK key ID")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// accessSet returns a set of actions available for the resource
 | 
					// accessSet returns a set of actions available for the resource
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue