blob: 5349e2982a43c52e7f63aba45d026b940a9c3ccb [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
22To encode a JWT::
23
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
31To decode a JWT and verify claims::
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 base64
44import collections
45import json
46
47from google.auth import crypt
48from google.auth import _helpers
49
50
51_DEFAULT_TOKEN_LIFETIME_SECS = 3600 # 1 hour in sections
52_CLOCK_SKEW_SECS = 300 # 5 minutes in seconds
53
54
55def encode(signer, payload, header=None, key_id=None):
56 """Make a signed JWT.
57
58 Args:
59 signer (google.auth.crypt.Signer): The signer used to sign the JWT.
60 payload (Mapping): The JWT payload.
61 header (Mapping): Additional JWT header payload.
62 key_id (str): The key id to add to the JWT header. If the
63 signer has a key id it will be used as the default. If this is
64 specified it will override the signer's key id.
65
66 Returns:
67 bytes: The encoded JWT.
68 """
69 if header is None:
70 header = {}
71
72 if key_id is None:
73 key_id = signer.key_id
74
75 header.update({'typ': 'JWT', 'alg': 'RS256'})
76
77 if key_id is not None:
78 header['kid'] = key_id
79
80 segments = [
81 base64.urlsafe_b64encode(json.dumps(header).encode('utf-8')),
82 base64.urlsafe_b64encode(json.dumps(payload).encode('utf-8')),
83 ]
84
85 signing_input = b'.'.join(segments)
86 signature = signer.sign(signing_input)
87 segments.append(base64.urlsafe_b64encode(signature))
88
89 return b'.'.join(segments)
90
91
92def _decode_jwt_segment(encoded_section):
93 """Decodes a single JWT segment."""
94 section_bytes = base64.urlsafe_b64decode(encoded_section)
95 try:
96 return json.loads(section_bytes.decode('utf-8'))
97 except ValueError:
98 raise ValueError('Can\'t parse segment: {0}'.format(section_bytes))
99
100
101def _unverified_decode(token):
102 """Decodes a token and does no verification.
103
104 Args:
105 token (Union[str, bytes]): The encoded JWT.
106
107 Returns:
108 Tuple(str, str, str, str): header, payload, signed_section, and
109 signature.
110
111 Raises:
112 ValueError: if there are an incorrect amount of segments in the token.
113 """
114 token = _helpers.to_bytes(token)
115
116 if token.count(b'.') != 2:
117 raise ValueError(
118 'Wrong number of segments in token: {0}'.format(token))
119
120 encoded_header, encoded_payload, signature = token.split(b'.')
121 signed_section = encoded_header + b'.' + encoded_payload
122 signature = base64.urlsafe_b64decode(signature)
123
124 # Parse segments
125 header = _decode_jwt_segment(encoded_header)
126 payload = _decode_jwt_segment(encoded_payload)
127
128 return header, payload, signed_section, signature
129
130
131def decode_header(token):
132 """Return the decoded header of a token.
133
134 No verification is done. This is useful to extract the key id from
135 the header in order to acquire the appropriate certificate to verify
136 the token.
137
138 Args:
139 token (Union[str, bytes]): the encoded JWT.
140
141 Returns:
142 Mapping: The decoded JWT header.
143 """
144 header, _, _, _ = _unverified_decode(token)
145 return header
146
147
148def _verify_iat_and_exp(payload):
149 """Verifies the iat (Issued At) and exp (Expires) claims in a token
150 payload.
151
152 Args:
153 payload (mapping): The JWT payload.
154
155 Raises:
156 ValueError: if any checks failed.
157 """
158 now = _helpers.datetime_to_secs(_helpers.utcnow())
159
160 # Make sure the iat and exp claims are present
161 for key in ('iat', 'exp'):
162 if key not in payload:
163 raise ValueError(
164 'Token does not contain required claim {}'.format(key))
165
166 # Make sure the token wasn't issued in the future
167 iat = payload['iat']
168 earliest = iat - _CLOCK_SKEW_SECS
169 if now < earliest:
170 raise ValueError('Token used too early, {} < {}'.format(now, iat))
171
172 # Make sure the token wasn't issue in the past
173 exp = payload['exp']
174 latest = exp + _CLOCK_SKEW_SECS
175 if latest < now:
176 raise ValueError('Token expired, {} < {}'.format(latest, now))
177
178
179def decode(token, certs=None, verify=True, audience=None):
180 """Decode and verify a JWT.
181
182 Args:
183 token (string): The encoded JWT.
184 certs (Union[str, bytes, Mapping]): The certificate used to
185 validate. If bytes or string, it must the the public key
186 certificate in PEM format. If a mapping, it must be a mapping of
187 key IDs to public key certificates in PEM format. The mapping must
188 contain the same key ID that's specified in the token's header.
189 verify (bool): Whether to perform signature and claim validation.
190 Verification is done by default.
191 audience (str): The audience claim, 'aud', that this JWT should
192 contain. If None then the JWT's 'aud' parameter is not verified.
193
194 Returns:
195 Mapping: The deserialized JSON payload in the JWT.
196
197 Raises:
198 ValueError: if any verification checks failed.
199 """
200 header, payload, signed_section, signature = _unverified_decode(token)
201
202 if not verify:
203 return payload
204
205 # If certs is specified as a dictionary of key IDs to certificates, then
206 # use the certificate identified by the key ID in the token header.
207 if isinstance(certs, collections.Mapping):
208 key_id = header.get('kid')
209 if key_id:
210 if key_id not in certs:
211 raise ValueError(
212 'Certificate for key id {} not found.'.format(key_id))
213 certs_to_check = [certs[key_id]]
214 # If there's no key id in the header, check against all of the certs.
215 else:
216 certs_to_check = certs.values()
217 else:
218 certs_to_check = certs
219
220 # Verify that the signature matches the message.
221 if not crypt.verify_signature(signed_section, signature, certs_to_check):
222 raise ValueError('Could not verify token signature.')
223
224 # Verify the issued at and created times in the payload.
225 _verify_iat_and_exp(payload)
226
227 # Check audience.
228 if audience is not None:
229 claim_audience = payload.get('aud')
230 if audience != claim_audience:
231 raise ValueError(
232 'Token has wrong audience {}, expected {}'.format(
233 claim_audience, audience))
234
235 return payload