blob: 7a9bdd59a93c9a27de8e980f36e4d66c8717fb73 [file] [log] [blame]
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -07001# 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
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -070022To encode a JWT use :func:`encode`::
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070023
24 from google.auth import crypto
25 from google.auth import jwt
26
27 signer = crypt.Signer(private_key)
28 payload = {'some': 'payload'}
29 encoded = jwt.encode(signer, payload)
30
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -070031To decode a JWT and verify claims use :func:`decode`::
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070032
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 base64
44import collections
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -070045import datetime
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070046import json
47
Jon Wayne Parrott54a85172016-10-17 11:27:37 -070048from google.auth import _helpers
Jon Wayne Parrott807032c2016-10-18 09:38:26 -070049from google.auth import _service_account_info
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070050from google.auth import crypt
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -080051import google.auth.credentials
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070052
53
54_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections
55_CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
56
57
58def encode(signer, payload, header=None, key_id=None):
59 """Make a signed JWT.
60
61 Args:
62 signer (google.auth.crypt.Signer): The signer used to sign the JWT.
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -070063 payload (Mapping[str, str]): The JWT payload.
64 header (Mapping[str, str]): Additional JWT header payload.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070065 key_id (str): The key id to add to the JWT header. If the
66 signer has a key id it will be used as the default. If this is
67 specified it will override the signer's key id.
68
69 Returns:
70 bytes: The encoded JWT.
71 """
72 if header is None:
73 header = {}
74
75 if key_id is None:
76 key_id = signer.key_id
77
78 header.update({'typ': 'JWT', 'alg': 'RS256'})
79
80 if key_id is not None:
81 header['kid'] = key_id
82
83 segments = [
84 base64.urlsafe_b64encode(json.dumps(header).encode('utf-8')),
85 base64.urlsafe_b64encode(json.dumps(payload).encode('utf-8')),
86 ]
87
88 signing_input = b'.'.join(segments)
89 signature = signer.sign(signing_input)
90 segments.append(base64.urlsafe_b64encode(signature))
91
92 return b'.'.join(segments)
93
94
95def _decode_jwt_segment(encoded_section):
96 """Decodes a single JWT segment."""
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -080097 section_bytes = _helpers.padded_urlsafe_b64decode(encoded_section)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -070098 try:
99 return json.loads(section_bytes.decode('utf-8'))
100 except ValueError:
101 raise ValueError('Can\'t parse segment: {0}'.format(section_bytes))
102
103
104def _unverified_decode(token):
105 """Decodes a token and does no verification.
106
107 Args:
108 token (Union[str, bytes]): The encoded JWT.
109
110 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800111 Tuple[str, str, str, str]: header, payload, signed_section, and
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700112 signature.
113
114 Raises:
115 ValueError: if there are an incorrect amount of segments in the token.
116 """
117 token = _helpers.to_bytes(token)
118
119 if token.count(b'.') != 2:
120 raise ValueError(
121 'Wrong number of segments in token: {0}'.format(token))
122
123 encoded_header, encoded_payload, signature = token.split(b'.')
124 signed_section = encoded_header + b'.' + encoded_payload
Jon Wayne Parrott97eb8702016-11-17 09:43:16 -0800125 signature = _helpers.padded_urlsafe_b64decode(signature)
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700126
127 # Parse segments
128 header = _decode_jwt_segment(encoded_header)
129 payload = _decode_jwt_segment(encoded_payload)
130
131 return header, payload, signed_section, signature
132
133
134def decode_header(token):
135 """Return the decoded header of a token.
136
137 No verification is done. This is useful to extract the key id from
138 the header in order to acquire the appropriate certificate to verify
139 the token.
140
141 Args:
142 token (Union[str, bytes]): the encoded JWT.
143
144 Returns:
145 Mapping: The decoded JWT header.
146 """
147 header, _, _, _ = _unverified_decode(token)
148 return header
149
150
151def _verify_iat_and_exp(payload):
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700152 """Verifies the ``iat`` (Issued At) and ``exp`` (Expires) claims in a token
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700153 payload.
154
155 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700156 payload (Mapping[str, str]): The JWT payload.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700157
158 Raises:
159 ValueError: if any checks failed.
160 """
161 now = _helpers.datetime_to_secs(_helpers.utcnow())
162
163 # Make sure the iat and exp claims are present
164 for key in ('iat', 'exp'):
165 if key not in payload:
166 raise ValueError(
167 'Token does not contain required claim {}'.format(key))
168
169 # Make sure the token wasn't issued in the future
170 iat = payload['iat']
171 earliest = iat - _CLOCK_SKEW_SECS
172 if now < earliest:
173 raise ValueError('Token used too early, {} < {}'.format(now, iat))
174
175 # Make sure the token wasn't issue in the past
176 exp = payload['exp']
177 latest = exp + _CLOCK_SKEW_SECS
178 if latest < now:
179 raise ValueError('Token expired, {} < {}'.format(latest, now))
180
181
182def decode(token, certs=None, verify=True, audience=None):
183 """Decode and verify a JWT.
184
185 Args:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700186 token (str): The encoded JWT.
187 certs (Union[str, bytes, Mapping[str, Union[str, bytes]]]): The
188 certificate used to validate the JWT signatyre. If bytes or string,
189 it must the the public key certificate in PEM format. If a mapping,
190 it must be a mapping of key IDs to public key certificates in PEM
191 format. The mapping must contain the same key ID that's specified
192 in the token's header.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700193 verify (bool): Whether to perform signature and claim validation.
194 Verification is done by default.
195 audience (str): The audience claim, 'aud', that this JWT should
196 contain. If None then the JWT's 'aud' parameter is not verified.
197
198 Returns:
Jon Wayne Parrott7eeab7d2016-10-12 15:02:37 -0700199 Mapping[str, str]: The deserialized JSON payload in the JWT.
Jon Wayne Parrott5824ad82016-10-06 09:27:44 -0700200
201 Raises:
202 ValueError: if any verification checks failed.
203 """
204 header, payload, signed_section, signature = _unverified_decode(token)
205
206 if not verify:
207 return payload
208
209 # If certs is specified as a dictionary of key IDs to certificates, then
210 # use the certificate identified by the key ID in the token header.
211 if isinstance(certs, collections.Mapping):
212 key_id = header.get('kid')
213 if key_id:
214 if key_id not in certs:
215 raise ValueError(
216 'Certificate for key id {} not found.'.format(key_id))
217 certs_to_check = [certs[key_id]]
218 # If there's no key id in the header, check against all of the certs.
219 else:
220 certs_to_check = certs.values()
221 else:
222 certs_to_check = certs
223
224 # Verify that the signature matches the message.
225 if not crypt.verify_signature(signed_section, signature, certs_to_check):
226 raise ValueError('Could not verify token signature.')
227
228 # Verify the issued at and created times in the payload.
229 _verify_iat_and_exp(payload)
230
231 # Check audience.
232 if audience is not None:
233 claim_audience = payload.get('aud')
234 if audience != claim_audience:
235 raise ValueError(
236 'Token has wrong audience {}, expected {}'.format(
237 claim_audience, audience))
238
239 return payload
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700240
241
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800242class Credentials(google.auth.credentials.Signing,
243 google.auth.credentials.Credentials):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700244 """Credentials that use a JWT as the bearer token.
245
246 These credentials require an "audience" claim. This claim identifies the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800247 intended recipient of the bearer token.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700248
249 The constructor arguments determine the claims for the JWT that is
250 sent with requests. Usually, you'll construct these credentials with
251 one of the helper constructors as shown in the next section.
252
253 To create JWT credentials using a Google service account private key
254 JSON file::
255
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800256 audience = 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher'
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700257 credentials = jwt.Credentials.from_service_account_file(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800258 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800259 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700260
261 If you already have the service account file loaded and parsed::
262
263 service_account_info = json.load(open('service_account.json'))
264 credentials = jwt.Credentials.from_service_account_info(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800265 service_account_info,
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800266 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700267
268 Both helper methods pass on arguments to the constructor, so you can
269 specify the JWT claims::
270
271 credentials = jwt.Credentials.from_service_account_file(
272 'service-account.json',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800273 audience=audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700274 additional_claims={'meta': 'data'})
275
276 You can also construct the credentials directly if you have a
277 :class:`~google.auth.crypt.Signer` instance::
278
279 credentials = jwt.Credentials(
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800280 signer,
281 issuer='your-issuer',
282 subject='your-subject',
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800283 audience=audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700284
285 The claims are considered immutable. If you want to modify the claims,
286 you can easily create another instance using :meth:`with_claims`::
287
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800288 new_audience = (
289 'https://pubsub.googleapis.com/google.pubsub.v1.Subscriber')
290 new_credentials = credentials.with_claims(audience=new_audience)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700291 """
292
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800293 def __init__(self, signer, issuer, subject, audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700294 additional_claims=None,
295 token_lifetime=_DEFAULT_TOKEN_LIFETIME_SECS):
296 """
297 Args:
298 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
299 issuer (str): The `iss` claim.
300 subject (str): The `sub` claim.
301 audience (str): the `aud` claim. The intended audience for the
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800302 credentials.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700303 additional_claims (Mapping[str, str]): Any additional claims for
304 the JWT payload.
305 token_lifetime (int): The amount of time in seconds for
306 which the token is valid. Defaults to 1 hour.
307 """
308 super(Credentials, self).__init__()
309 self._signer = signer
310 self._issuer = issuer
311 self._subject = subject
312 self._audience = audience
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700313 self._token_lifetime = token_lifetime
314
Danny Hermes93d1aa42016-10-17 13:15:07 -0700315 if additional_claims is not None:
316 self._additional_claims = additional_claims
317 else:
318 self._additional_claims = {}
319
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700320 @classmethod
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700321 def _from_signer_and_info(cls, signer, info, **kwargs):
322 """Creates a Credentials instance from a signer and service account
323 info.
324
325 Args:
326 signer (google.auth.crypt.Signer): The signer used to sign JWTs.
327 info (Mapping[str, str]): The service account info.
328 kwargs: Additional arguments to pass to the constructor.
329
330 Returns:
331 google.auth.jwt.Credentials: The constructed credentials.
332
333 Raises:
334 ValueError: If the info is not in the expected format.
335 """
336 kwargs.setdefault('subject', info['client_email'])
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800337 kwargs.setdefault('issuer', info['client_email'])
338 return cls(signer, **kwargs)
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700339
340 @classmethod
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700341 def from_service_account_info(cls, info, **kwargs):
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700342 """Creates a Credentials instance from a dictionary containing service
343 account info in Google format.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700344
345 Args:
346 info (Mapping[str, str]): The service account info in Google
347 format.
348 kwargs: Additional arguments to pass to the constructor.
349
350 Returns:
351 google.auth.jwt.Credentials: The constructed credentials.
352
353 Raises:
354 ValueError: If the info is not in the expected format.
355 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700356 signer = _service_account_info.from_dict(
357 info, require=['client_email'])
358 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700359
360 @classmethod
361 def from_service_account_file(cls, filename, **kwargs):
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700362 """Creates a Credentials instance from a service account .json file
363 in Google format.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700364
365 Args:
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700366 filename (str): The path to the service account .json file.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700367 kwargs: Additional arguments to pass to the constructor.
368
369 Returns:
370 google.auth.jwt.Credentials: The constructed credentials.
371 """
Jon Wayne Parrott807032c2016-10-18 09:38:26 -0700372 info, signer = _service_account_info.from_filename(
373 filename, require=['client_email'])
374 return cls._from_signer_and_info(signer, info, **kwargs)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700375
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800376 @classmethod
377 def from_signing_credentials(cls, credentials, audience, **kwargs):
378 """Creates a new :class:`google.auth.jwt.Credentials` instance from an
379 existing :class:`google.auth.credentials.Signing` instance.
380
381 The new instance will use the same signer as the existing instance and
382 will use the existing instance's signer email as the issuer and
383 subject by default.
384
385 Example::
386
387 svc_creds = service_account.Credentials.from_service_account_file(
388 'service_account.json')
389 audience = (
390 'https://pubsub.googleapis.com/google.pubsub.v1.Publisher')
391 jwt_creds = jwt.Credentials.from_signing_credentials(
392 svc_creds, audience=audience)
393
394 Args:
395 credentials (google.auth.credentials.Signing): The credentials to
396 use to construct the new credentials.
397 audience (str): the `aud` claim. The intended audience for the
398 credentials.
399 kwargs: Additional arguments to pass to the constructor.
400
401 Returns:
402 google.auth.jwt.Credentials: A new Credentials instance.
403 """
404 kwargs.setdefault('issuer', credentials.signer_email)
405 kwargs.setdefault('subject', credentials.signer_email)
406 return cls(
407 credentials.signer,
408 audience=audience,
409 **kwargs)
410
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700411 def with_claims(self, issuer=None, subject=None, audience=None,
412 additional_claims=None):
413 """Returns a copy of these credentials with modified claims.
414
415 Args:
416 issuer (str): The `iss` claim. If unspecified the current issuer
417 claim will be used.
418 subject (str): The `sub` claim. If unspecified the current subject
419 claim will be used.
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800420 audience (str): the `aud` claim. If unspecified the current
421 audience claim will be used.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700422 additional_claims (Mapping[str, str]): Any additional claims for
423 the JWT payload. This will be merged with the current
424 additional claims.
425
426 Returns:
427 google.auth.jwt.Credentials: A new credentials instance.
428 """
429 return Credentials(
430 self._signer,
431 issuer=issuer if issuer is not None else self._issuer,
432 subject=subject if subject is not None else self._subject,
433 audience=audience if audience is not None else self._audience,
434 additional_claims=self._additional_claims.copy().update(
435 additional_claims or {}))
436
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800437 def _make_jwt(self):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700438 """Make a signed JWT.
439
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700440 Returns:
Danny Hermes48c85f72016-11-08 09:30:44 -0800441 Tuple[bytes, datetime]: The encoded JWT and the expiration.
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700442 """
443 now = _helpers.utcnow()
444 lifetime = datetime.timedelta(seconds=self._token_lifetime)
445 expiry = now + lifetime
446
447 payload = {
448 'iss': self._issuer,
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800449 'sub': self._subject,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700450 'iat': _helpers.datetime_to_secs(now),
451 'exp': _helpers.datetime_to_secs(expiry),
Jon Wayne Parrottab086892017-02-23 09:20:14 -0800452 'aud': self._audience,
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700453 }
454
455 payload.update(self._additional_claims)
456
457 jwt = encode(self._signer, payload)
458
459 return jwt, expiry
460
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700461 def refresh(self, request):
462 """Refreshes the access token.
463
464 Args:
465 request (Any): Unused.
466 """
467 # pylint: disable=unused-argument
468 # (pylint doesn't correctly recognize overridden methods.)
469 self.token, self.expiry = self._make_jwt()
470
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800471 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700472 def sign_bytes(self, message):
Jon Wayne Parrottabcd3ed2016-10-17 11:23:47 -0700473 return self._signer.sign(message)
474
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800475 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800476 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrott4c883f02016-12-02 14:26:33 -0800477 def signer_email(self):
478 return self._issuer
479
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800480 @property
Jon Wayne Parrottb8f48d02017-02-24 09:03:24 -0800481 @_helpers.copy_docstring(google.auth.credentials.Signing)
Jon Wayne Parrottd7221672017-02-16 09:05:11 -0800482 def signer(self):
483 return self._signer