Package oauth2client :: Module crypt
[hide private]
[frames] | no frames]

Source Code for Module oauth2client.crypt

  1  #!/usr/bin/python2.4 
  2  # -*- coding: utf-8 -*- 
  3  # 
  4  # Copyright (C) 2011 Google Inc. 
  5  # 
  6  # Licensed under the Apache License, Version 2.0 (the "License"); 
  7  # you may not use this file except in compliance with the License. 
  8  # You may obtain a copy of the License at 
  9  # 
 10  #      http://www.apache.org/licenses/LICENSE-2.0 
 11  # 
 12  # Unless required by applicable law or agreed to in writing, software 
 13  # distributed under the License is distributed on an "AS IS" BASIS, 
 14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 15  # See the License for the specific language governing permissions and 
 16  # limitations under the License. 
 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  # 5 minutes in seconds 
 28  AUTH_TOKEN_LIFETIME_SECS = 300  # 5 minutes in seconds 
 29  MAX_TOKEN_LIFETIME_SECS = 86400  # 1 day in seconds 
30 31 32 -class AppIdentityError(Exception):
33 pass
34
35 36 -class Verifier(object):
37 """Verifies the signature on a message.""" 38
39 - def __init__(self, pubkey):
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
65 - def from_string(key_pem, is_x509_cert):
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
85 86 -class Signer(object):
87 """Signs messages with a private key.""" 88
89 - def __init__(self, pkey):
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
109 - def from_string(key, password='notasecret'):
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
125 126 -def _urlsafe_b64encode(raw_bytes):
127 return base64.urlsafe_b64encode(raw_bytes).rstrip('=')
128
129 130 -def _urlsafe_b64decode(b64string):
131 # Guard against unicode strings, which base64 can't handle. 132 b64string = b64string.encode('ascii') 133 padded = b64string + '=' * (4 - len(b64string) % 4) 134 return base64.urlsafe_b64decode(padded)
135
136 137 -def _json_encode(data):
138 return simplejson.dumps(data, separators = (',', ':'))
139
140 141 -def make_signed_jwt(signer, payload):
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
168 169 -def verify_signed_jwt_with_certs(jwt, certs, audience):
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 # Parse token. 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 # Check signature. 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 # Check creation timestamp. 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 # Check expiration timestamp. 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 # Check audience. 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