1# Copyright 2016 Google Inc. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14 15"""JSON Web Tokens 16 17Provides support for creating (encoding) and verifying (decoding) JWTs, 18especially JWTs generated and consumed by Google infrastructure. 19 20See `rfc7519`_ for more details on JWTs. 21 22To encode a JWT use :func:`encode`:: 23 24 from google.auth import crypt 25 from google.auth import jwt 26 27 signer = crypt.Signer(private_key) 28 payload = {'some': 'payload'} 29 encoded = jwt.encode(signer, payload) 30 31To decode a JWT and verify claims use :func:`decode`:: 32 33 claims = jwt.decode(encoded, certs=public_certs) 34 35You can also skip verification:: 36 37 claims = jwt.decode(encoded, verify=False) 38 39.. _rfc7519: https://tools.ietf.org/html/rfc7519 40 41""" 42 43import collections 44import copy 45import datetime 46import json 47 48import cachetools 49import six 50from six.moves import urllib 51 52from google.auth import _helpers 53from google.auth import _service_account_info 54from google.auth import crypt 55from google.auth import exceptions 56import google.auth.credentials 57 58_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in seconds 59_DEFAULT_MAX_CACHE_SIZE = 10 60 61 62def encode(signer, payload, header=None, key_id=None): 63 """Make a signed JWT. 64 65 Args: 66 signer (google.auth.crypt.Signer): The signer used to sign the JWT. 67 payload (Mapping[str, str]): The JWT payload. 68 header (Mapping[str, str]): Additional JWT header payload. 69 key_id (str): The key id to add to the JWT header. If the 70 signer has a key id it will be used as the default. If this is 71 specified it will override the signer's key id. 72 73 Returns: 74 bytes: The encoded JWT. 75 """ 76 if header is None: 77 header = {} 78 79 if key_id is None: 80 key_id = signer.key_id 81 82 header.update({'typ': 'JWT', 'alg': 'RS256'}) 83 84 if key_id is not None: 85 header['kid'] = key_id 86 87 segments = [ 88 _helpers.unpadded_urlsafe_b64encode( 89 json.dumps(header).encode('utf-8') 90 ), 91 _helpers.unpadded_urlsafe_b64encode( 92 json.dumps(payload).encode('utf-8') 93 ), 94 ] 95 96 signing_input = b'.'.join(segments) 97 signature = signer.sign(signing_input) 98 segments.append( 99 _helpers.unpadded_urlsafe_b64encode(signature) 100 ) 101 102 return b'.'.join(segments) 103 104 105def _decode_jwt_segment(encoded_section): 106 """Decodes a single JWT segment.""" 107 section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section) 108 try: 109 return json.loads(section_bytes.decode('utf-8')) 110 except ValueError as caught_exc: 111 new_exc = ValueError('Can\'t parse segment: {0}'.format(section_bytes)) 112 six.raise_from(new_exc, caught_exc) 113 114 115def _unverified_decode(token): 116 """Decodes a token and does no verification. 117 118 Args: 119 token (Union[str, bytes]): The encoded JWT. 120 121 Returns: 122 Tuple[str, str, str, str]: header, payload, signed_section, and 123 signature. 124 125 Raises: 126 ValueError: if there are an incorrect amount of segments in the token. 127 """ 128 token = _helpers.to_bytes(token) 129 130 if token.count(b'.') != 2: 131 raise ValueError( 132 'Wrong number of segments in token: {0}'.format(token)) 133 134 encoded_header, encoded_payload, signature = token.split(b'.') 135 signed_section = encoded_header + b'.' + encoded_payload 136 signature = _helpers.padded_urlsafe_b64decode(signature) 137 138 # Parse segments 139 header = _decode_jwt_segment(encoded_header) 140 payload = _decode_jwt_segment(encoded_payload) 141 142 return header, payload, signed_section, signature 143 144 145def decode_header(token): 146 """Return the decoded header of a token. 147 148 No verification is done. This is useful to extract the key id from 149 the header in order to acquire the appropriate certificate to verify 150 the token. 151 152 Args: 153 token (Union[str, bytes]): the encoded JWT. 154 155 Returns: 156 Mapping: The decoded JWT header. 157 """ 158 header, _, _, _ = _unverified_decode(token) 159 return header 160 161 162def _verify_iat_and_exp(payload): 163 """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token 164 payload. 165 166 Args: 167 payload (Mapping[str, str]): The JWT payload. 168 169 Raises: 170 ValueError: if any checks failed. 171 """ 172 now = _helpers.datetime_to_secs(_helpers.utcnow()) 173 174 # Make sure the iat and exp claims are present. 175 for key in ('iat', 'exp'): 176 if key not in payload: 177 raise ValueError( 178 'Token does not contain required claim {}'.format(key)) 179 180 # Make sure the token wasn't issued in the future. 181 iat = payload['iat'] 182 # Err on the side of accepting a token that is slightly early to account 183 # for clock skew. 184 earliest = iat - _helpers.CLOCK_SKEW_SECS 185 if now < earliest: 186 raise ValueError('Token used too early, {} < {}'.format(now, iat)) 187 188 # Make sure the token wasn't issued in the past. 189 exp = payload['exp'] 190 # Err on the side of accepting a token that is slightly out of date 191 # to account for clow skew. 192 latest = exp + _helpers.CLOCK_SKEW_SECS 193 if latest < now: 194 raise ValueError('Token expired, {} < {}'.format(latest, now)) 195 196 197def decode(token, certs=None, verify=True, audience=None): 198 """Decode and verify a JWT. 199 200 Args: 201 token (str): The encoded JWT. 202 certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The 203 certificate used to validate the JWT signatyre. If bytes or string, 204 it must the the public key certificate in PEM format. If a mapping, 205 it must be a mapping of key IDs to public key certificates in PEM 206 format. The mapping must contain the same key ID that's specified 207 in the token's header. 208 verify (bool): Whether to perform signature and claim validation. 209 Verification is done by default. 210 audience (str): The audience claim, 'aud', that this JWT should 211 contain. If None then the JWT's 'aud' parameter is not verified. 212 213 Returns: 214 Mapping[str, str]: The deserialized JSON payload in the JWT. 215 216 Raises: 217 ValueError: if any verification checks failed. 218 """ 219 header, payload, signed_section, signature = _unverified_decode(token) 220 221 if not verify: 222 return payload 223 224 # If certs is specified as a dictionary of key IDs to certificates, then 225 # use the certificate identified by the key ID in the token header. 226 if isinstance(certs, collections.Mapping): 227 key_id = header.get('kid') 228 if key_id: 229 if key_id not in certs: 230 raise ValueError( 231 'Certificate for key id {} not found.'.format(key_id)) 232 certs_to_check = [certs[key_id]] 233 # If there's no key id in the header, check against all of the certs. 234 else: 235 certs_to_check = certs.values() 236 else: 237 certs_to_check = certs 238 239 # Verify that the signature matches the message. 240 if not crypt.verify_signature(signed_section, signature, certs_to_check): 241 raise ValueError('Could not verify token signature.') 242 243 # Verify the issued at and created times in the payload. 244 _verify_iat_and_exp(payload) 245 246 # Check audience. 247 if audience is not None: 248 claim_audience = payload.get('aud') 249 if audience != claim_audience: 250 raise ValueError( 251 'Token has wrong audience {}, expected {}'.format( 252 claim_audience, audience)) 253 254 return payload 255 256 257class Credentials(google.auth.credentials.Signing, 258 google.auth.credentials.Credentials): 259 """Credentials that use a JWT as the bearer token. 260 261 These credentials require an "audience" claim. This claim identifies the 262 intended recipient of the bearer token. 263 264 The constructor arguments determine the claims for the JWT that is 265 sent with requests. Usually, you'll construct these credentials with 266 one of the helper constructors as shown in the next section. 267 268 To create JWT credentials using a Google service account private key 269 JSON file:: 270 271 audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher' 272 credentials = jwt.Credentials.from_service_account_file( 273 'service-account.json', 274 audience=audience) 275 276 If you already have the service account file loaded and parsed:: 277 278 service_account_info = json.load(open('service_account.json')) 279 credentials = jwt.Credentials.from_service_account_info( 280 service_account_info, 281 audience=audience) 282 283 Both helper methods pass on arguments to the constructor, so you can 284 specify the JWT claims:: 285 286 credentials = jwt.Credentials.from_service_account_file( 287 'service-account.json', 288 audience=audience, 289 additional_claims={'meta': 'data'}) 290 291 You can also construct the credentials directly if you have a 292 :class:`~google.auth.crypt.Signer` instance:: 293 294 credentials = jwt.Credentials( 295 signer, 296 issuer='your-issuer', 297 subject='your-subject', 298 audience=audience) 299 300 The claims are considered immutable. If you want to modify the claims, 301 you can easily create another instance using :meth:`with_claims`:: 302 303 new_audience = ( 304 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber') 305 new_credentials = credentials.with_claims(audience=new_audience) 306 """ 307 308 def __init__(self, signer, issuer, subject, audience, 309 additional_claims=None, 310 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS): 311 """ 312 Args: 313 signer (google.auth.crypt.Signer): The signer used to sign JWTs. 314 issuer (str): The `iss` claim. 315 subject (str): The `sub` claim. 316 audience (str): the `aud` claim. The intended audience for the 317 credentials. 318 additional_claims (Mapping[str, str]): Any additional claims for 319 the JWT payload. 320 token_lifetime (int): The amount of time in seconds for 321 which the token is valid. Defaults to 1 hour. 322 """ 323 super(Credentials, self).__init__() 324 self._signer = signer 325 self._issuer = issuer 326 self._subject = subject 327 self._audience = audience 328 self._token_lifetime = token_lifetime 329 330 if additional_claims is None: 331 additional_claims = {} 332 333 self._additional_claims = additional_claims 334 335 @classmethod 336 def _from_signer_and_info(cls, signer, info, **kwargs): 337 """Creates a Credentials instance from a signer and service account 338 info. 339 340 Args: 341 signer (google.auth.crypt.Signer): The signer used to sign JWTs. 342 info (Mapping[str, str]): The service account info. 343 kwargs: Additional arguments to pass to the constructor. 344 345 Returns: 346 google.auth.jwt.Credentials: The constructed credentials. 347 348 Raises: 349 ValueError: If the info is not in the expected format. 350 """ 351 kwargs.setdefault('subject', info['client_email']) 352 kwargs.setdefault('issuer', info['client_email']) 353 return cls(signer, **kwargs) 354 355 @classmethod 356 def from_service_account_info(cls, info, **kwargs): 357 """Creates an Credentials instance from a dictionary. 358 359 Args: 360 info (Mapping[str, str]): The service account info in Google 361 format. 362 kwargs: Additional arguments to pass to the constructor. 363 364 Returns: 365 google.auth.jwt.Credentials: The constructed credentials. 366 367 Raises: 368 ValueError: If the info is not in the expected format. 369 """ 370 signer = _service_account_info.from_dict( 371 info, require=['client_email']) 372 return cls._from_signer_and_info(signer, info, **kwargs) 373 374 @classmethod 375 def from_service_account_file(cls, filename, **kwargs): 376 """Creates a Credentials instance from a service account .json file 377 in Google format. 378 379 Args: 380 filename (str): The path to the service account .json file. 381 kwargs: Additional arguments to pass to the constructor. 382 383 Returns: 384 google.auth.jwt.Credentials: The constructed credentials. 385 """ 386 info, signer = _service_account_info.from_filename( 387 filename, require=['client_email']) 388 return cls._from_signer_and_info(signer, info, **kwargs) 389 390 @classmethod 391 def from_signing_credentials(cls, credentials, audience, **kwargs): 392 """Creates a new :class:`google.auth.jwt.Credentials` instance from an 393 existing :class:`google.auth.credentials.Signing` instance. 394 395 The new instance will use the same signer as the existing instance and 396 will use the existing instance's signer email as the issuer and 397 subject by default. 398 399 Example:: 400 401 svc_creds = service_account.Credentials.from_service_account_file( 402 'service_account.json') 403 audience = ( 404 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher') 405 jwt_creds = jwt.Credentials.from_signing_credentials( 406 svc_creds, audience=audience) 407 408 Args: 409 credentials (google.auth.credentials.Signing): The credentials to 410 use to construct the new credentials. 411 audience (str): the `aud` claim. The intended audience for the 412 credentials. 413 kwargs: Additional arguments to pass to the constructor. 414 415 Returns: 416 google.auth.jwt.Credentials: A new Credentials instance. 417 """ 418 kwargs.setdefault('issuer', credentials.signer_email) 419 kwargs.setdefault('subject', credentials.signer_email) 420 return cls( 421 credentials.signer, 422 audience=audience, 423 **kwargs) 424 425 def with_claims(self, issuer=None, subject=None, audience=None, 426 additional_claims=None): 427 """Returns a copy of these credentials with modified claims. 428 429 Args: 430 issuer (str): The `iss` claim. If unspecified the current issuer 431 claim will be used. 432 subject (str): The `sub` claim. If unspecified the current subject 433 claim will be used. 434 audience (str): the `aud` claim. If unspecified the current 435 audience claim will be used. 436 additional_claims (Mapping[str, str]): Any additional claims for 437 the JWT payload. This will be merged with the current 438 additional claims. 439 440 Returns: 441 google.auth.jwt.Credentials: A new credentials instance. 442 """ 443 new_additional_claims = copy.deepcopy(self._additional_claims) 444 new_additional_claims.update(additional_claims or {}) 445 446 return self.__class__( 447 self._signer, 448 issuer=issuer if issuer is not None else self._issuer, 449 subject=subject if subject is not None else self._subject, 450 audience=audience if audience is not None else self._audience, 451 additional_claims=new_additional_claims) 452 453 def _make_jwt(self): 454 """Make a signed JWT. 455 456 Returns: 457 Tuple[bytes, datetime]: The encoded JWT and the expiration. 458 """ 459 now = _helpers.utcnow() 460 lifetime = datetime.timedelta(seconds=self._token_lifetime) 461 expiry = now + lifetime 462 463 payload = { 464 'iss': self._issuer, 465 'sub': self._subject, 466 'iat': _helpers.datetime_to_secs(now), 467 'exp': _helpers.datetime_to_secs(expiry), 468 'aud': self._audience, 469 } 470 471 payload.update(self._additional_claims) 472 473 jwt = encode(self._signer, payload) 474 475 return jwt, expiry 476 477 def refresh(self, request): 478 """Refreshes the access token. 479 480 Args: 481 request (Any): Unused. 482 """ 483 # pylint: disable=unused-argument 484 # (pylint doesn't correctly recognize overridden methods.) 485 self.token, self.expiry = self._make_jwt() 486 487 @_helpers.copy_docstring(google.auth.credentials.Signing) 488 def sign_bytes(self, message): 489 return self._signer.sign(message) 490 491 @property 492 @_helpers.copy_docstring(google.auth.credentials.Signing) 493 def signer_email(self): 494 return self._issuer 495 496 @property 497 @_helpers.copy_docstring(google.auth.credentials.Signing) 498 def signer(self): 499 return self._signer 500 501 502class OnDemandCredentials( 503 google.auth.credentials.Signing, 504 google.auth.credentials.Credentials): 505 """On-demand JWT credentials. 506 507 Like :class:`Credentials`, this class uses a JWT as the bearer token for 508 authentication. However, this class does not require the audience at 509 construction time. Instead, it will generate a new token on-demand for 510 each request using the request URI as the audience. It caches tokens 511 so that multiple requests to the same URI do not incur the overhead 512 of generating a new token every time. 513 514 This behavior is especially useful for `gRPC`_ clients. A gRPC service may 515 have multiple audience and gRPC clients may not know all of the audiences 516 required for accessing a particular service. With these credentials, 517 no knowledge of the audiences is required ahead of time. 518 519 .. _grpc: http://www.grpc.io/ 520 """ 521 522 def __init__(self, signer, issuer, subject, 523 additional_claims=None, 524 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS, 525 max_cache_size=_DEFAULT_MAX_CACHE_SIZE): 526 """ 527 Args: 528 signer (google.auth.crypt.Signer): The signer used to sign JWTs. 529 issuer (str): The `iss` claim. 530 subject (str): The `sub` claim. 531 additional_claims (Mapping[str, str]): Any additional claims for 532 the JWT payload. 533 token_lifetime (int): The amount of time in seconds for 534 which the token is valid. Defaults to 1 hour. 535 max_cache_size (int): The maximum number of JWT tokens to keep in 536 cache. Tokens are cached using :class:`cachetools.LRUCache`. 537 """ 538 super(OnDemandCredentials, self).__init__() 539 self._signer = signer 540 self._issuer = issuer 541 self._subject = subject 542 self._token_lifetime = token_lifetime 543 544 if additional_claims is None: 545 additional_claims = {} 546 547 self._additional_claims = additional_claims 548 self._cache = cachetools.LRUCache(maxsize=max_cache_size) 549 550 @classmethod 551 def _from_signer_and_info(cls, signer, info, **kwargs): 552 """Creates an OnDemandCredentials instance from a signer and service 553 account info. 554 555 Args: 556 signer (google.auth.crypt.Signer): The signer used to sign JWTs. 557 info (Mapping[str, str]): The service account info. 558 kwargs: Additional arguments to pass to the constructor. 559 560 Returns: 561 google.auth.jwt.OnDemandCredentials: The constructed credentials. 562 563 Raises: 564 ValueError: If the info is not in the expected format. 565 """ 566 kwargs.setdefault('subject', info['client_email']) 567 kwargs.setdefault('issuer', info['client_email']) 568 return cls(signer, **kwargs) 569 570 @classmethod 571 def from_service_account_info(cls, info, **kwargs): 572 """Creates an OnDemandCredentials instance from a dictionary. 573 574 Args: 575 info (Mapping[str, str]): The service account info in Google 576 format. 577 kwargs: Additional arguments to pass to the constructor. 578 579 Returns: 580 google.auth.jwt.OnDemandCredentials: The constructed credentials. 581 582 Raises: 583 ValueError: If the info is not in the expected format. 584 """ 585 signer = _service_account_info.from_dict( 586 info, require=['client_email']) 587 return cls._from_signer_and_info(signer, info, **kwargs) 588 589 @classmethod 590 def from_service_account_file(cls, filename, **kwargs): 591 """Creates an OnDemandCredentials instance from a service account .json 592 file in Google format. 593 594 Args: 595 filename (str): The path to the service account .json file. 596 kwargs: Additional arguments to pass to the constructor. 597 598 Returns: 599 google.auth.jwt.OnDemandCredentials: The constructed credentials. 600 """ 601 info, signer = _service_account_info.from_filename( 602 filename, require=['client_email']) 603 return cls._from_signer_and_info(signer, info, **kwargs) 604 605 @classmethod 606 def from_signing_credentials(cls, credentials, **kwargs): 607 """Creates a new :class:`google.auth.jwt.OnDemandCredentials` instance 608 from an existing :class:`google.auth.credentials.Signing` instance. 609 610 The new instance will use the same signer as the existing instance and 611 will use the existing instance's signer email as the issuer and 612 subject by default. 613 614 Example:: 615 616 svc_creds = service_account.Credentials.from_service_account_file( 617 'service_account.json') 618 jwt_creds = jwt.OnDemandCredentials.from_signing_credentials( 619 svc_creds) 620 621 Args: 622 credentials (google.auth.credentials.Signing): The credentials to 623 use to construct the new credentials. 624 kwargs: Additional arguments to pass to the constructor. 625 626 Returns: 627 google.auth.jwt.Credentials: A new Credentials instance. 628 """ 629 kwargs.setdefault('issuer', credentials.signer_email) 630 kwargs.setdefault('subject', credentials.signer_email) 631 return cls(credentials.signer, **kwargs) 632 633 def with_claims(self, issuer=None, subject=None, additional_claims=None): 634 """Returns a copy of these credentials with modified claims. 635 636 Args: 637 issuer (str): The `iss` claim. If unspecified the current issuer 638 claim will be used. 639 subject (str): The `sub` claim. If unspecified the current subject 640 claim will be used. 641 additional_claims (Mapping[str, str]): Any additional claims for 642 the JWT payload. This will be merged with the current 643 additional claims. 644 645 Returns: 646 google.auth.jwt.OnDemandCredentials: A new credentials instance. 647 """ 648 new_additional_claims = copy.deepcopy(self._additional_claims) 649 new_additional_claims.update(additional_claims or {}) 650 651 return self.__class__( 652 self._signer, 653 issuer=issuer if issuer is not None else self._issuer, 654 subject=subject if subject is not None else self._subject, 655 additional_claims=new_additional_claims, 656 max_cache_size=self._cache.maxsize) 657 658 @property 659 def valid(self): 660 """Checks the validity of the credentials. 661 662 These credentials are always valid because it generates tokens on 663 demand. 664 """ 665 return True 666 667 def _make_jwt_for_audience(self, audience): 668 """Make a new JWT for the given audience. 669 670 Args: 671 audience (str): The intended audience. 672 673 Returns: 674 Tuple[bytes, datetime]: The encoded JWT and the expiration. 675 """ 676 now = _helpers.utcnow() 677 lifetime = datetime.timedelta(seconds=self._token_lifetime) 678 expiry = now + lifetime 679 680 payload = { 681 'iss': self._issuer, 682 'sub': self._subject, 683 'iat': _helpers.datetime_to_secs(now), 684 'exp': _helpers.datetime_to_secs(expiry), 685 'aud': audience, 686 } 687 688 payload.update(self._additional_claims) 689 690 jwt = encode(self._signer, payload) 691 692 return jwt, expiry 693 694 def _get_jwt_for_audience(self, audience): 695 """Get a JWT For a given audience. 696 697 If there is already an existing, non-expired token in the cache for 698 the audience, that token is used. Otherwise, a new token will be 699 created. 700 701 Args: 702 audience (str): The intended audience. 703 704 Returns: 705 bytes: The encoded JWT. 706 """ 707 token, expiry = self._cache.get(audience, (None, None)) 708 709 if token is None or expiry < _helpers.utcnow(): 710 token, expiry = self._make_jwt_for_audience(audience) 711 self._cache[audience] = token, expiry 712 713 return token 714 715 def refresh(self, request): 716 """Raises an exception, these credentials can not be directly 717 refreshed. 718 719 Args: 720 request (Any): Unused. 721 722 Raises: 723 google.auth.RefreshError 724 """ 725 # pylint: disable=unused-argument 726 # (pylint doesn't correctly recognize overridden methods.) 727 raise exceptions.RefreshError( 728 'OnDemandCredentials can not be directly refreshed.') 729 730 def before_request(self, request, method, url, headers): 731 """Performs credential-specific before request logic. 732 733 Args: 734 request (Any): Unused. JWT credentials do not need to make an 735 HTTP request to refresh. 736 method (str): The request's HTTP method. 737 url (str): The request's URI. This is used as the audience claim 738 when generating the JWT. 739 headers (Mapping): The request's headers. 740 """ 741 # pylint: disable=unused-argument 742 # (pylint doesn't correctly recognize overridden methods.) 743 parts = urllib.parse.urlsplit(url) 744 # Strip query string and fragment 745 audience = urllib.parse.urlunsplit( 746 (parts.scheme, parts.netloc, parts.path, "", "")) 747 token = self._get_jwt_for_audience(audience) 748 self.apply(headers, token=token) 749 750 @_helpers.copy_docstring(google.auth.credentials.Signing) 751 def sign_bytes(self, message): 752 return self._signer.sign(message) 753 754 @property 755 @_helpers.copy_docstring(google.auth.credentials.Signing) 756 def signer_email(self): 757 return self._issuer 758 759 @property 760 @_helpers.copy_docstring(google.auth.credentials.Signing) 761 def signer(self): 762 return self._signer 763