~matias-wilkman/calendar-indicator/fix-typo-and-tautology

« back to all changes in this revision

Viewing changes to src/oauth2client/crypt.py

  • Committer: Lorenzo Carbonell
  • Date: 2012-11-24 08:51:01 UTC
  • Revision ID: lorenzo.carbonell.cerezo@gmail.com-20121124085101-2kfixu1jmas4mknw
request + Google Calendar API 3.0

Show diffs side-by-side

added added

removed removed

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