1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 import base64
19 import hashlib
20 import logging
21 import time
22
23 from OpenSSL import crypto
24 from anyjson import simplejson
25
26
27 CLOCK_SKEW_SECS = 300
28 AUTH_TOKEN_LIFETIME_SECS = 300
29 MAX_TOKEN_LIFETIME_SECS = 86400
34
37 """Verifies the signature on a message."""
38
40 """Constructor.
41
42 Args:
43 pubkey, OpenSSL.crypto.PKey, The public key to verify with.
44 """
45 self._pubkey = pubkey
46
47 - def verify(self, message, signature):
48 """Verifies a message against a signature.
49
50 Args:
51 message: string, The message to verify.
52 signature: string, The signature on the message.
53
54 Returns:
55 True if message was singed by the private key associated with the public
56 key that this object was constructed with.
57 """
58 try:
59 crypto.verify(self._pubkey, signature, message, 'sha256')
60 return True
61 except:
62 return False
63
64 @staticmethod
66 """Construct a Verified instance from a string.
67
68 Args:
69 key_pem: string, public key in PEM format.
70 is_x509_cert: bool, True if key_pem is an X509 cert, otherwise it is
71 expected to be an RSA key in PEM format.
72
73 Returns:
74 Verifier instance.
75
76 Raises:
77 OpenSSL.crypto.Error if the key_pem can't be parsed.
78 """
79 if is_x509_cert:
80 pubkey = crypto.load_certificate(crypto.FILETYPE_PEM, key_pem)
81 else:
82 pubkey = crypto.load_privatekey(crypto.FILETYPE_PEM, key_pem)
83 return Verifier(pubkey)
84
87 """Signs messages with a private key."""
88
90 """Constructor.
91
92 Args:
93 pkey, OpenSSL.crypto.PKey, The private key to sign with.
94 """
95 self._key = pkey
96
97 - def sign(self, message):
98 """Signs a message.
99
100 Args:
101 message: string, Message to be signed.
102
103 Returns:
104 string, The signature of the message for the given key.
105 """
106 return crypto.sign(self._key, message, 'sha256')
107
108 @staticmethod
110 """Construct a Signer instance from a string.
111
112 Args:
113 key: string, private key in P12 format.
114 password: string, password for the private key file.
115
116 Returns:
117 Signer instance.
118
119 Raises:
120 OpenSSL.crypto.Error if the key can't be parsed.
121 """
122 pkey = crypto.load_pkcs12(key, password).get_privatekey()
123 return Signer(pkey)
124
127 return base64.urlsafe_b64encode(raw_bytes).rstrip('=')
128
131
132 b64string = b64string.encode('ascii')
133 padded = b64string + '=' * (4 - len(b64string) % 4)
134 return base64.urlsafe_b64decode(padded)
135
138 return simplejson.dumps(data, separators = (',', ':'))
139
142 """Make a signed JWT.
143
144 See http://self-issued.info/docs/draft-jones-json-web-token.html.
145
146 Args:
147 signer: crypt.Signer, Cryptographic signer.
148 payload: dict, Dictionary of data to convert to JSON and then sign.
149
150 Returns:
151 string, The JWT for the payload.
152 """
153 header = {'typ': 'JWT', 'alg': 'RS256'}
154
155 segments = [
156 _urlsafe_b64encode(_json_encode(header)),
157 _urlsafe_b64encode(_json_encode(payload)),
158 ]
159 signing_input = '.'.join(segments)
160
161 signature = signer.sign(signing_input)
162 segments.append(_urlsafe_b64encode(signature))
163
164 logging.debug(str(segments))
165
166 return '.'.join(segments)
167
170 """Verify a JWT against public certs.
171
172 See http://self-issued.info/docs/draft-jones-json-web-token.html.
173
174 Args:
175 jwt: string, A JWT.
176 certs: dict, Dictionary where values of public keys in PEM format.
177 audience: string, The audience, 'aud', that this JWT should contain. If
178 None then the JWT's 'aud' parameter is not verified.
179
180 Returns:
181 dict, The deserialized JSON payload in the JWT.
182
183 Raises:
184 AppIdentityError if any checks are failed.
185 """
186 segments = jwt.split('.')
187
188 if (len(segments) != 3):
189 raise AppIdentityError(
190 'Wrong number of segments in token: %s' % jwt)
191 signed = '%s.%s' % (segments[0], segments[1])
192
193 signature = _urlsafe_b64decode(segments[2])
194
195
196 json_body = _urlsafe_b64decode(segments[1])
197 try:
198 parsed = simplejson.loads(json_body)
199 except:
200 raise AppIdentityError('Can\'t parse token: %s' % json_body)
201
202
203 verified = False
204 for (keyname, pem) in certs.items():
205 verifier = Verifier.from_string(pem, True)
206 if (verifier.verify(signed, signature)):
207 verified = True
208 break
209 if not verified:
210 raise AppIdentityError('Invalid token signature: %s' % jwt)
211
212
213 iat = parsed.get('iat')
214 if iat is None:
215 raise AppIdentityError('No iat field in token: %s' % json_body)
216 earliest = iat - CLOCK_SKEW_SECS
217
218
219 now = long(time.time())
220 exp = parsed.get('exp')
221 if exp is None:
222 raise AppIdentityError('No exp field in token: %s' % json_body)
223 if exp >= now + MAX_TOKEN_LIFETIME_SECS:
224 raise AppIdentityError(
225 'exp field too far in future: %s' % json_body)
226 latest = exp + CLOCK_SKEW_SECS
227
228 if now < earliest:
229 raise AppIdentityError('Token used too early, %d < %d: %s' %
230 (now, earliest, json_body))
231 if now > latest:
232 raise AppIdentityError('Token used too late, %d > %d: %s' %
233 (now, latest, json_body))
234
235
236 if audience is not None:
237 aud = parsed.get('aud')
238 if aud is None:
239 raise AppIdentityError('No aud field in token: %s' % json_body)
240 if aud != audience:
241 raise AppIdentityError('Wrong recipient, %s != %s: %s' %
242 (aud, audience, json_body))
243
244 return parsed
245