...

Source file src/github.com/docker/distribution/registry/auth/token/token.go

Documentation: github.com/docker/distribution/registry/auth/token

     1  package token
     2  
     3  import (
     4  	"crypto"
     5  	"crypto/x509"
     6  	"encoding/base64"
     7  	"encoding/json"
     8  	"errors"
     9  	"fmt"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/docker/libtrust"
    14  	log "github.com/sirupsen/logrus"
    15  
    16  	"github.com/docker/distribution/registry/auth"
    17  )
    18  
    19  const (
    20  	// TokenSeparator is the value which separates the header, claims, and
    21  	// signature in the compact serialization of a JSON Web Token.
    22  	TokenSeparator = "."
    23  	// Leeway is the Duration that will be added to NBF and EXP claim
    24  	// checks to account for clock skew as per https://tools.ietf.org/html/rfc7519#section-4.1.5
    25  	Leeway = 60 * time.Second
    26  )
    27  
    28  // Errors used by token parsing and verification.
    29  var (
    30  	ErrMalformedToken = errors.New("malformed token")
    31  	ErrInvalidToken   = errors.New("invalid token")
    32  )
    33  
    34  // ResourceActions stores allowed actions on a named and typed resource.
    35  type ResourceActions struct {
    36  	Type    string   `json:"type"`
    37  	Class   string   `json:"class,omitempty"`
    38  	Name    string   `json:"name"`
    39  	Actions []string `json:"actions"`
    40  }
    41  
    42  // ClaimSet describes the main section of a JSON Web Token.
    43  type ClaimSet struct {
    44  	// Public claims
    45  	Issuer     string `json:"iss"`
    46  	Subject    string `json:"sub"`
    47  	Audience   string `json:"aud"`
    48  	Expiration int64  `json:"exp"`
    49  	NotBefore  int64  `json:"nbf"`
    50  	IssuedAt   int64  `json:"iat"`
    51  	JWTID      string `json:"jti"`
    52  
    53  	// Private claims
    54  	Access []*ResourceActions `json:"access"`
    55  }
    56  
    57  // Header describes the header section of a JSON Web Token.
    58  type Header struct {
    59  	Type       string           `json:"typ"`
    60  	SigningAlg string           `json:"alg"`
    61  	KeyID      string           `json:"kid,omitempty"`
    62  	X5c        []string         `json:"x5c,omitempty"`
    63  	RawJWK     *json.RawMessage `json:"jwk,omitempty"`
    64  }
    65  
    66  // Token describes a JSON Web Token.
    67  type Token struct {
    68  	Raw       string
    69  	Header    *Header
    70  	Claims    *ClaimSet
    71  	Signature []byte
    72  }
    73  
    74  // VerifyOptions is used to specify
    75  // options when verifying a JSON Web Token.
    76  type VerifyOptions struct {
    77  	TrustedIssuers    []string
    78  	AcceptedAudiences []string
    79  	Roots             *x509.CertPool
    80  	TrustedKeys       map[string]libtrust.PublicKey
    81  }
    82  
    83  // NewToken parses the given raw token string
    84  // and constructs an unverified JSON Web Token.
    85  func NewToken(rawToken string) (*Token, error) {
    86  	parts := strings.Split(rawToken, TokenSeparator)
    87  	if len(parts) != 3 {
    88  		return nil, ErrMalformedToken
    89  	}
    90  
    91  	var (
    92  		rawHeader, rawClaims   = parts[0], parts[1]
    93  		headerJSON, claimsJSON []byte
    94  		err                    error
    95  	)
    96  
    97  	defer func() {
    98  		if err != nil {
    99  			log.Infof("error while unmarshalling raw token: %s", err)
   100  		}
   101  	}()
   102  
   103  	if headerJSON, err = joseBase64UrlDecode(rawHeader); err != nil {
   104  		err = fmt.Errorf("unable to decode header: %s", err)
   105  		return nil, ErrMalformedToken
   106  	}
   107  
   108  	if claimsJSON, err = joseBase64UrlDecode(rawClaims); err != nil {
   109  		err = fmt.Errorf("unable to decode claims: %s", err)
   110  		return nil, ErrMalformedToken
   111  	}
   112  
   113  	token := new(Token)
   114  	token.Header = new(Header)
   115  	token.Claims = new(ClaimSet)
   116  
   117  	token.Raw = strings.Join(parts[:2], TokenSeparator)
   118  	if token.Signature, err = joseBase64UrlDecode(parts[2]); err != nil {
   119  		err = fmt.Errorf("unable to decode signature: %s", err)
   120  		return nil, ErrMalformedToken
   121  	}
   122  
   123  	if err = json.Unmarshal(headerJSON, token.Header); err != nil {
   124  		return nil, ErrMalformedToken
   125  	}
   126  
   127  	if err = json.Unmarshal(claimsJSON, token.Claims); err != nil {
   128  		return nil, ErrMalformedToken
   129  	}
   130  
   131  	return token, nil
   132  }
   133  
   134  // Verify attempts to verify this token using the given options.
   135  // Returns a nil error if the token is valid.
   136  func (t *Token) Verify(verifyOpts VerifyOptions) error {
   137  	// Verify that the Issuer claim is a trusted authority.
   138  	if !contains(verifyOpts.TrustedIssuers, t.Claims.Issuer) {
   139  		log.Infof("token from untrusted issuer: %q", t.Claims.Issuer)
   140  		return ErrInvalidToken
   141  	}
   142  
   143  	// Verify that the Audience claim is allowed.
   144  	if !contains(verifyOpts.AcceptedAudiences, t.Claims.Audience) {
   145  		log.Infof("token intended for another audience: %q", t.Claims.Audience)
   146  		return ErrInvalidToken
   147  	}
   148  
   149  	// Verify that the token is currently usable and not expired.
   150  	currentTime := time.Now()
   151  
   152  	ExpWithLeeway := time.Unix(t.Claims.Expiration, 0).Add(Leeway)
   153  	if currentTime.After(ExpWithLeeway) {
   154  		log.Infof("token not to be used after %s - currently %s", ExpWithLeeway, currentTime)
   155  		return ErrInvalidToken
   156  	}
   157  
   158  	NotBeforeWithLeeway := time.Unix(t.Claims.NotBefore, 0).Add(-Leeway)
   159  	if currentTime.Before(NotBeforeWithLeeway) {
   160  		log.Infof("token not to be used before %s - currently %s", NotBeforeWithLeeway, currentTime)
   161  		return ErrInvalidToken
   162  	}
   163  
   164  	// Verify the token signature.
   165  	if len(t.Signature) == 0 {
   166  		log.Info("token has no signature")
   167  		return ErrInvalidToken
   168  	}
   169  
   170  	// Verify that the signing key is trusted.
   171  	signingKey, err := t.VerifySigningKey(verifyOpts)
   172  	if err != nil {
   173  		log.Info(err)
   174  		return ErrInvalidToken
   175  	}
   176  
   177  	// Finally, verify the signature of the token using the key which signed it.
   178  	if err := signingKey.Verify(strings.NewReader(t.Raw), t.Header.SigningAlg, t.Signature); err != nil {
   179  		log.Infof("unable to verify token signature: %s", err)
   180  		return ErrInvalidToken
   181  	}
   182  
   183  	return nil
   184  }
   185  
   186  // VerifySigningKey attempts to get the key which was used to sign this token.
   187  // The token header should contain either of these 3 fields:
   188  //
   189  //	`x5c` - The x509 certificate chain for the signing key. Needs to be
   190  //	        verified.
   191  //	`jwk` - The JSON Web Key representation of the signing key.
   192  //	        May contain its own `x5c` field which needs to be verified.
   193  //	`kid` - The unique identifier for the key. This library interprets it
   194  //	        as a libtrust fingerprint. The key itself can be looked up in
   195  //	        the trustedKeys field of the given verify options.
   196  //
   197  // Each of these methods are tried in that order of preference until the
   198  // signing key is found or an error is returned.
   199  func (t *Token) VerifySigningKey(verifyOpts VerifyOptions) (signingKey libtrust.PublicKey, err error) {
   200  	// First attempt to get an x509 certificate chain from the header.
   201  	var (
   202  		x5c    = t.Header.X5c
   203  		rawJWK = t.Header.RawJWK
   204  		keyID  = t.Header.KeyID
   205  	)
   206  
   207  	switch {
   208  	case len(x5c) > 0:
   209  		signingKey, err = parseAndVerifyCertChain(x5c, verifyOpts.Roots)
   210  	case rawJWK != nil:
   211  		signingKey, err = parseAndVerifyRawJWK(rawJWK, verifyOpts)
   212  	case len(keyID) > 0:
   213  		signingKey = verifyOpts.TrustedKeys[keyID]
   214  		if signingKey == nil {
   215  			err = fmt.Errorf("token signed by untrusted key with ID: %q", keyID)
   216  		}
   217  	default:
   218  		err = errors.New("unable to get token signing key")
   219  	}
   220  
   221  	return
   222  }
   223  
   224  func parseAndVerifyCertChain(x5c []string, roots *x509.CertPool) (leafKey libtrust.PublicKey, err error) {
   225  	if len(x5c) == 0 {
   226  		return nil, errors.New("empty x509 certificate chain")
   227  	}
   228  
   229  	// Ensure the first element is encoded correctly.
   230  	leafCertDer, err := base64.StdEncoding.DecodeString(x5c[0])
   231  	if err != nil {
   232  		return nil, fmt.Errorf("unable to decode leaf certificate: %s", err)
   233  	}
   234  
   235  	// And that it is a valid x509 certificate.
   236  	leafCert, err := x509.ParseCertificate(leafCertDer)
   237  	if err != nil {
   238  		return nil, fmt.Errorf("unable to parse leaf certificate: %s", err)
   239  	}
   240  
   241  	// The rest of the certificate chain are intermediate certificates.
   242  	intermediates := x509.NewCertPool()
   243  	for i := 1; i < len(x5c); i++ {
   244  		intermediateCertDer, err := base64.StdEncoding.DecodeString(x5c[i])
   245  		if err != nil {
   246  			return nil, fmt.Errorf("unable to decode intermediate certificate: %s", err)
   247  		}
   248  
   249  		intermediateCert, err := x509.ParseCertificate(intermediateCertDer)
   250  		if err != nil {
   251  			return nil, fmt.Errorf("unable to parse intermediate certificate: %s", err)
   252  		}
   253  
   254  		intermediates.AddCert(intermediateCert)
   255  	}
   256  
   257  	verifyOpts := x509.VerifyOptions{
   258  		Intermediates: intermediates,
   259  		Roots:         roots,
   260  		KeyUsages:     []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
   261  	}
   262  
   263  	// TODO: this call returns certificate chains which we ignore for now, but
   264  	// we should check them for revocations if we have the ability later.
   265  	if _, err = leafCert.Verify(verifyOpts); err != nil {
   266  		return nil, fmt.Errorf("unable to verify certificate chain: %s", err)
   267  	}
   268  
   269  	// Get the public key from the leaf certificate.
   270  	leafCryptoKey, ok := leafCert.PublicKey.(crypto.PublicKey)
   271  	if !ok {
   272  		return nil, errors.New("unable to get leaf cert public key value")
   273  	}
   274  
   275  	leafKey, err = libtrust.FromCryptoPublicKey(leafCryptoKey)
   276  	if err != nil {
   277  		return nil, fmt.Errorf("unable to make libtrust public key from leaf certificate: %s", err)
   278  	}
   279  
   280  	return
   281  }
   282  
   283  func parseAndVerifyRawJWK(rawJWK *json.RawMessage, verifyOpts VerifyOptions) (pubKey libtrust.PublicKey, err error) {
   284  	pubKey, err = libtrust.UnmarshalPublicKeyJWK([]byte(*rawJWK))
   285  	if err != nil {
   286  		return nil, fmt.Errorf("unable to decode raw JWK value: %s", err)
   287  	}
   288  
   289  	// Check to see if the key includes a certificate chain.
   290  	x5cVal, ok := pubKey.GetExtendedField("x5c").([]interface{})
   291  	if !ok {
   292  		// The JWK should be one of the trusted root keys.
   293  		if _, trusted := verifyOpts.TrustedKeys[pubKey.KeyID()]; !trusted {
   294  			return nil, errors.New("untrusted JWK with no certificate chain")
   295  		}
   296  
   297  		// The JWK is one of the trusted keys.
   298  		return
   299  	}
   300  
   301  	// Ensure each item in the chain is of the correct type.
   302  	x5c := make([]string, len(x5cVal))
   303  	for i, val := range x5cVal {
   304  		certString, ok := val.(string)
   305  		if !ok || len(certString) == 0 {
   306  			return nil, errors.New("malformed certificate chain")
   307  		}
   308  		x5c[i] = certString
   309  	}
   310  
   311  	// Ensure that the x509 certificate chain can
   312  	// be verified up to one of our trusted roots.
   313  	leafKey, err := parseAndVerifyCertChain(x5c, verifyOpts.Roots)
   314  	if err != nil {
   315  		return nil, fmt.Errorf("could not verify JWK certificate chain: %s", err)
   316  	}
   317  
   318  	// Verify that the public key in the leaf cert *is* the signing key.
   319  	if pubKey.KeyID() != leafKey.KeyID() {
   320  		return nil, errors.New("leaf certificate public key ID does not match JWK key ID")
   321  	}
   322  
   323  	return
   324  }
   325  
   326  // accessSet returns a set of actions available for the resource
   327  // actions listed in the `access` section of this token.
   328  func (t *Token) accessSet() accessSet {
   329  	if t.Claims == nil {
   330  		return nil
   331  	}
   332  
   333  	accessSet := make(accessSet, len(t.Claims.Access))
   334  
   335  	for _, resourceActions := range t.Claims.Access {
   336  		resource := auth.Resource{
   337  			Type: resourceActions.Type,
   338  			Name: resourceActions.Name,
   339  		}
   340  
   341  		set, exists := accessSet[resource]
   342  		if !exists {
   343  			set = newActionSet()
   344  			accessSet[resource] = set
   345  		}
   346  
   347  		for _, action := range resourceActions.Actions {
   348  			set.add(action)
   349  		}
   350  	}
   351  
   352  	return accessSet
   353  }
   354  
   355  func (t *Token) resources() []auth.Resource {
   356  	if t.Claims == nil {
   357  		return nil
   358  	}
   359  
   360  	resourceSet := map[auth.Resource]struct{}{}
   361  	for _, resourceActions := range t.Claims.Access {
   362  		resource := auth.Resource{
   363  			Type:  resourceActions.Type,
   364  			Class: resourceActions.Class,
   365  			Name:  resourceActions.Name,
   366  		}
   367  		resourceSet[resource] = struct{}{}
   368  	}
   369  
   370  	resources := make([]auth.Resource, 0, len(resourceSet))
   371  	for resource := range resourceSet {
   372  		resources = append(resources, resource)
   373  	}
   374  
   375  	return resources
   376  }
   377  
   378  func (t *Token) compactRaw() string {
   379  	return fmt.Sprintf("%s.%s", t.Raw, joseBase64UrlEncode(t.Signature))
   380  }
   381  

View as plain text