1package token 2 3import ( 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 19const ( 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. 29var ( 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. 35type 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. 43type 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. 58type 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. 67type 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. 76type 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. 85func 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. 136func (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// `x5c` - The x509 certificate chain for the signing key. Needs to be 189// verified. 190// `jwk` - The JSON Web Key representation of the signing key. 191// May contain its own `x5c` field which needs to be verified. 192// `kid` - The unique identifier for the key. This library interprets it 193// as a libtrust fingerprint. The key itself can be looked up in 194// the trustedKeys field of the given verify options. 195// Each of these methods are tried in that order of preference until the 196// signing key is found or an error is returned. 197func (t *Token) VerifySigningKey(verifyOpts VerifyOptions) (signingKey libtrust.PublicKey, err error) { 198 // First attempt to get an x509 certificate chain from the header. 199 var ( 200 x5c = t.Header.X5c 201 rawJWK = t.Header.RawJWK 202 keyID = t.Header.KeyID 203 ) 204 205 switch { 206 case len(x5c) > 0: 207 signingKey, err = parseAndVerifyCertChain(x5c, verifyOpts.Roots) 208 case rawJWK != nil: 209 signingKey, err = parseAndVerifyRawJWK(rawJWK, verifyOpts) 210 case len(keyID) > 0: 211 signingKey = verifyOpts.TrustedKeys[keyID] 212 if signingKey == nil { 213 err = fmt.Errorf("token signed by untrusted key with ID: %q", keyID) 214 } 215 default: 216 err = errors.New("unable to get token signing key") 217 } 218 219 return 220} 221 222func parseAndVerifyCertChain(x5c []string, roots *x509.CertPool) (leafKey libtrust.PublicKey, err error) { 223 if len(x5c) == 0 { 224 return nil, errors.New("empty x509 certificate chain") 225 } 226 227 // Ensure the first element is encoded correctly. 228 leafCertDer, err := base64.StdEncoding.DecodeString(x5c[0]) 229 if err != nil { 230 return nil, fmt.Errorf("unable to decode leaf certificate: %s", err) 231 } 232 233 // And that it is a valid x509 certificate. 234 leafCert, err := x509.ParseCertificate(leafCertDer) 235 if err != nil { 236 return nil, fmt.Errorf("unable to parse leaf certificate: %s", err) 237 } 238 239 // The rest of the certificate chain are intermediate certificates. 240 intermediates := x509.NewCertPool() 241 for i := 1; i < len(x5c); i++ { 242 intermediateCertDer, err := base64.StdEncoding.DecodeString(x5c[i]) 243 if err != nil { 244 return nil, fmt.Errorf("unable to decode intermediate certificate: %s", err) 245 } 246 247 intermediateCert, err := x509.ParseCertificate(intermediateCertDer) 248 if err != nil { 249 return nil, fmt.Errorf("unable to parse intermediate certificate: %s", err) 250 } 251 252 intermediates.AddCert(intermediateCert) 253 } 254 255 verifyOpts := x509.VerifyOptions{ 256 Intermediates: intermediates, 257 Roots: roots, 258 KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny}, 259 } 260 261 // TODO: this call returns certificate chains which we ignore for now, but 262 // we should check them for revocations if we have the ability later. 263 if _, err = leafCert.Verify(verifyOpts); err != nil { 264 return nil, fmt.Errorf("unable to verify certificate chain: %s", err) 265 } 266 267 // Get the public key from the leaf certificate. 268 leafCryptoKey, ok := leafCert.PublicKey.(crypto.PublicKey) 269 if !ok { 270 return nil, errors.New("unable to get leaf cert public key value") 271 } 272 273 leafKey, err = libtrust.FromCryptoPublicKey(leafCryptoKey) 274 if err != nil { 275 return nil, fmt.Errorf("unable to make libtrust public key from leaf certificate: %s", err) 276 } 277 278 return 279} 280 281func parseAndVerifyRawJWK(rawJWK *json.RawMessage, verifyOpts VerifyOptions) (pubKey libtrust.PublicKey, err error) { 282 pubKey, err = libtrust.UnmarshalPublicKeyJWK([]byte(*rawJWK)) 283 if err != nil { 284 return nil, fmt.Errorf("unable to decode raw JWK value: %s", err) 285 } 286 287 // Check to see if the key includes a certificate chain. 288 x5cVal, ok := pubKey.GetExtendedField("x5c").([]interface{}) 289 if !ok { 290 // The JWK should be one of the trusted root keys. 291 if _, trusted := verifyOpts.TrustedKeys[pubKey.KeyID()]; !trusted { 292 return nil, errors.New("untrusted JWK with no certificate chain") 293 } 294 295 // The JWK is one of the trusted keys. 296 return 297 } 298 299 // Ensure each item in the chain is of the correct type. 300 x5c := make([]string, len(x5cVal)) 301 for i, val := range x5cVal { 302 certString, ok := val.(string) 303 if !ok || len(certString) == 0 { 304 return nil, errors.New("malformed certificate chain") 305 } 306 x5c[i] = certString 307 } 308 309 // Ensure that the x509 certificate chain can 310 // be verified up to one of our trusted roots. 311 leafKey, err := parseAndVerifyCertChain(x5c, verifyOpts.Roots) 312 if err != nil { 313 return nil, fmt.Errorf("could not verify JWK certificate chain: %s", err) 314 } 315 316 // Verify that the public key in the leaf cert *is* the signing key. 317 if pubKey.KeyID() != leafKey.KeyID() { 318 return nil, errors.New("leaf certificate public key ID does not match JWK key ID") 319 } 320 321 return 322} 323 324// accessSet returns a set of actions available for the resource 325// actions listed in the `access` section of this token. 326func (t *Token) accessSet() accessSet { 327 if t.Claims == nil { 328 return nil 329 } 330 331 accessSet := make(accessSet, len(t.Claims.Access)) 332 333 for _, resourceActions := range t.Claims.Access { 334 resource := auth.Resource{ 335 Type: resourceActions.Type, 336 Name: resourceActions.Name, 337 } 338 339 set, exists := accessSet[resource] 340 if !exists { 341 set = newActionSet() 342 accessSet[resource] = set 343 } 344 345 for _, action := range resourceActions.Actions { 346 set.add(action) 347 } 348 } 349 350 return accessSet 351} 352 353func (t *Token) resources() []auth.Resource { 354 if t.Claims == nil { 355 return nil 356 } 357 358 resourceSet := map[auth.Resource]struct{}{} 359 for _, resourceActions := range t.Claims.Access { 360 resource := auth.Resource{ 361 Type: resourceActions.Type, 362 Class: resourceActions.Class, 363 Name: resourceActions.Name, 364 } 365 resourceSet[resource] = struct{}{} 366 } 367 368 resources := make([]auth.Resource, 0, len(resourceSet)) 369 for resource := range resourceSet { 370 resources = append(resources, resource) 371 } 372 373 return resources 374} 375 376func (t *Token) compactRaw() string { 377 return fmt.Sprintf("%s.%s", t.Raw, joseBase64UrlEncode(t.Signature)) 378} 379