Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Merge master with rfk/pure-python-crypto

  • Loading branch information...
commit 6f0d3a3309cb876828b71a2925954e49e18b4a4b 2 parents 78bdd94 + f9e9701
Alexis Metaireau ametaireau authored
5 CHANGES.txt
... ... @@ -1,6 +1,11 @@
1 1 0.7.0 - XXXX-XX-XX
2 2 ==================
3 3
  4 + * Added a pure-python implementation of the JWT crypto routines, for
  5 + use when M2Crypto is not available.
  6 + * Added "from_pem_data" and "to_pem_data" methods to Key objects.
  7 + Currently these are only available when M2Crypto is installed.
  8 +
4 9 0.6.0 - 2012-31-05
5 10 ==================
6 11
18 browserid/crypto/__init__.py
... ... @@ -0,0 +1,18 @@
  1 +# This Source Code Form is subject to the terms of the Mozilla Public
  2 +# License, v. 2.0. If a copy of the MPL was not distributed with this file,
  3 +# You can obtain one at http://mozilla.org/MPL/2.0/.
  4 +"""
  5 +
  6 +Best-effort crypto primitives for PyBrowserID.
  7 +
  8 +If you have M2Crypto installed, this package will provide a nice fast
  9 +implementation of the RSKey and DSKey classes needed to do the crypto
  10 +work behind BrowserID. If you don't, you'll get a very slow but very
  11 +portable pure-python version.
  12 +
  13 +"""
  14 +
  15 +try:
  16 + from browserid.crypto.m2 import Key, RSKey, DSKey
  17 +except ImportError:
  18 + from browserid.crypto.fallback import Key, RSKey, DSKey # NOQA
2  browserid/_m2_monkeypatch.py → browserid/crypto/_m2_monkeypatch.py
... ... @@ -1,6 +1,6 @@
1 1 #
2 2 # This monkey-patches M2Crypto's RSA and DSA support to allow us
3   -# to create keys directly from a given set of parameters. It's
  3 +# to create keys directly from a given set of parameters.
4 4 # It's based on the following patch from the M2Crypt bugtracker:
5 5 #
6 6 # https://bugzilla.osafoundation.org/show_bug.cgi?id=12981
193 browserid/crypto/fallback.py
... ... @@ -0,0 +1,193 @@
  1 +# This Source Code Form is subject to the terms of the Mozilla Public
  2 +# License, v. 2.0. If a copy of the MPL was not distributed with this file,
  3 +# You can obtain one at http://mozilla.org/MPL/2.0/.
  4 +"""
  5 +
  6 +Crypto primitives implemented in pure python.
  7 +
  8 +This file provides the "slow path" crypto implementation for PyBrowserID.
  9 +It implements everything in pure python, so it's very slow but very portable.
  10 +
  11 +There is also a faster version built on M2Crypto, which should be picked up
  12 +automatically if you have that package installed.
  13 +
  14 +"""
  15 +
  16 +import os
  17 +from binascii import unhexlify
  18 +
  19 +
  20 +class Key(object):
  21 + """Generic base class for Key objects."""
  22 +
  23 + @classmethod
  24 + def from_pem_data(cls, data=None, filename=None):
  25 + """Alternative constructor for loading from PEM format data."""
  26 + msg = "PEM data loading is not implemented for pure-python crypto."
  27 + msg += " Please install M2Crypto to access this functionality."
  28 + raise NotImplementedError(msg)
  29 +
  30 + def to_pem_data(self):
  31 + """Save the public key data to a PEM format string."""
  32 + msg = "PEM data saving is not implemented for pure-python crypto."
  33 + msg += " Please install M2Crypto to access this functionality."
  34 + raise NotImplementedError(msg)
  35 +
  36 + def verify(self, signed_data, signature):
  37 + """Verify the given signature."""
  38 + raise NotImplementedError
  39 +
  40 + def sign(self, data):
  41 + """Sign the given data."""
  42 + raise NotImplementedError
  43 +
  44 +
  45 +# These constants are needed for encoding the name of the hash
  46 +# algorithm into the RSA signature, per PKCS #1.
  47 +RSA_DIGESTINFO_HEADER = {
  48 + "sha1": "3021300906052b0e03021a05000414",
  49 + "sha256": "3031300d060960864801650304020105000420",
  50 +}
  51 +
  52 +
  53 +class RSKey(Key):
  54 + """Generic base class for RSA key objects.
  55 +
  56 + Concrete subclasses should provide the SIZE, HASHNAME and HASHMOD
  57 + attributes.
  58 + """
  59 +
  60 + SIZE = None
  61 + HASHNAME = None
  62 + HASHMOD = None
  63 +
  64 + def __init__(self, data):
  65 + _check_keys(data, ("e", "n"))
  66 + self.e = long(data["e"])
  67 + self.n = long(data["n"])
  68 + try:
  69 + self.d = long(data["d"])
  70 + except KeyError:
  71 + self.d = None
  72 +
  73 + def verify(self, signed_data, signature):
  74 + n, e = self.n, self.e
  75 + m = long(signature, 16)
  76 + c = pow(m, e, n)
  77 + padded_digest = hex(c)[2:].rstrip("L").rjust(self.SIZE * 2, "0")
  78 + return padded_digest == self._get_digest(signed_data)
  79 +
  80 + def sign(self, data):
  81 + n, e, d = self.n, self.e, self.d
  82 + if not d:
  83 + raise ValueError("private key not present")
  84 + c = long(self._get_digest(data), 16)
  85 + m = pow(c, d, n)
  86 + return hex(m)[2:].rstrip("L")
  87 +
  88 + def _get_digest(self, data):
  89 + digest = self.HASHMOD(data).hexdigest()
  90 + padded_digest = "00" + RSA_DIGESTINFO_HEADER[self.HASHNAME] + digest
  91 + padding_len = (self.SIZE * 2) - 4 - len(padded_digest)
  92 + padded_digest = "0001" + ("f" * padding_len) + padded_digest
  93 + return padded_digest
  94 +
  95 +
  96 +class DSKey(Key):
  97 + """Generic base class for DSA key objects.
  98 +
  99 + Concrete subclasses should provide the BITLENGTH and HASHMOD attributes.
  100 + """
  101 +
  102 + BITLENGTH = None
  103 + HASHMOD = None
  104 +
  105 + def __init__(self, data):
  106 + _check_keys(data, ("p", "q", "g", "y"))
  107 + self.p = long(data["p"], 16)
  108 + self.q = long(data["q"], 16)
  109 + self.g = long(data["g"], 16)
  110 + self.y = long(data["y"], 16)
  111 + if "x" in data:
  112 + self.x = long(data["x"], 16)
  113 + else:
  114 + self.x = None
  115 +
  116 + def verify(self, signed_data, signature):
  117 + p, q, g, y = self.p, self.q, self.g, self.y
  118 + signature = signature.encode("hex")
  119 + hexlength = self.BITLENGTH / 4
  120 + signature = signature.rjust(hexlength * 2, "0")
  121 + if len(signature) != hexlength * 2:
  122 + return False
  123 + r = long(signature[:hexlength], 16)
  124 + s = long(signature[hexlength:], 16)
  125 + if r <= 0 or r >= q:
  126 + return False
  127 + if s <= 0 or s >= q:
  128 + return False
  129 + w = modinv(s, q)
  130 + u1 = (long(self.HASHMOD(signed_data).hexdigest(), 16) * w) % q
  131 + u2 = (r * w) % q
  132 + v = ((pow(g, u1, p) * pow(y, u2, p)) % p) % q
  133 + return (v == r)
  134 +
  135 + def sign(self, data):
  136 + p, q, g, y, x = self.p, self.q, self.g, self.y, self.x
  137 + if not x:
  138 + raise ValueError("private key not present")
  139 + # We need to do lots of if-not-this-then-start-over type tests.
  140 + # A while loop with continue statements is the cleanest way to do so.
  141 + while True:
  142 + k = long(os.urandom(self.BITLENGTH / 8).encode("hex"), 16) % q
  143 + if k == 0:
  144 + continue
  145 + r = pow(g, k, p) % q
  146 + if r == 0:
  147 + continue
  148 + h = (long(self.HASHMOD(data).hexdigest(), 16) + (x * r)) % q
  149 + s = (modinv(k, q) * h) % q
  150 + if s == 0:
  151 + continue
  152 + break
  153 + assert 0 < r < q
  154 + assert 0 < s < q
  155 + bytelength = self.BITLENGTH / 8
  156 + r_bytes = int2bytes(r).rjust(bytelength, "\x00")
  157 + s_bytes = int2bytes(s).rjust(bytelength, "\x00")
  158 + return r_bytes + s_bytes
  159 +
  160 +
  161 +def modinv(a, m):
  162 + """Find the modular inverse of a, with modulus m."""
  163 + # This is a transliteration of the algorithm as it was described
  164 + # to me by Wikipedia, using the Extended Euclidean Algorithm.
  165 + x = 0
  166 + lastx = 1
  167 + y = 1
  168 + lasty = 0
  169 + b = m
  170 + while b != 0:
  171 + a, (q, b) = b, divmod(a, b)
  172 + x, lastx = lastx - (q * x), x
  173 + y, lasty = lasty - (q * y), y
  174 + return lastx % m
  175 +
  176 +
  177 +def int2bytes(x):
  178 + """Convert a Python long integer to bigendian bytestring."""
  179 + # It's faster to go via hex encoding in C code than it is to try
  180 + # encoding directly into binary with a python-level loop.
  181 + # (and hex-slice-strip seems consistently faster than using "%x" format)
  182 + hexbytes = hex(x)[2:].rstrip("L")
  183 + if len(hexbytes) % 2:
  184 + hexbytes = "0" + hexbytes
  185 + return unhexlify(hexbytes)
  186 +
  187 +
  188 +def _check_keys(data, keys):
  189 + """Verify that the given data dict contains the specified keys."""
  190 + for key in keys:
  191 + if not key in data:
  192 + msg = 'missing %s in data - %s' % (key, str(data.keys()))
  193 + raise ValueError(msg)
197 browserid/crypto/m2.py
... ... @@ -0,0 +1,197 @@
  1 +# This Source Code Form is subject to the terms of the Mozilla Public
  2 +# License, v. 2.0. If a copy of the MPL was not distributed with this file,
  3 +# You can obtain one at http://mozilla.org/MPL/2.0/.
  4 +"""
  5 +
  6 +Crypto primitives built on top of M2Crypto.
  7 +
  8 +This file provides the "fast path" crypto implementation for PyBrowserID.
  9 +It uses the public-key-crypto routines from M2Crypto for nice fast operation.
  10 +
  11 +There is also a pure-python fallback module that's slower, but avoid
  12 +having to install M2Crypto.
  13 +
  14 +"""
  15 +
  16 +import struct
  17 +from binascii import hexlify, unhexlify
  18 +
  19 +from M2Crypto import BIO
  20 +
  21 +from browserid.crypto._m2_monkeypatch import m2
  22 +from browserid.crypto._m2_monkeypatch import DSA as _DSA
  23 +from browserid.crypto._m2_monkeypatch import RSA as _RSA
  24 +
  25 +
  26 +class Key(object):
  27 + """Generic base class for Key objects."""
  28 +
  29 + KEY_MODULE = None
  30 +
  31 + @classmethod
  32 + def from_pem_data(cls, data=None, filename=None):
  33 + """Alternative constructor for loading from PEM format data."""
  34 + self = cls.__new__(cls)
  35 + if data is not None:
  36 + bio = BIO.MemoryBuffer(str(data))
  37 + elif filename is not None:
  38 + bio = BIO.openfile(filename)
  39 + else:
  40 + msg = "Please specify either 'data' or 'filename' argument."
  41 + raise ValueError(msg)
  42 + self.keyobj = self.KEY_MODULE.load_pub_key_bio(bio)
  43 + return self
  44 +
  45 + def to_pem_data(self):
  46 + """Save the public key data to a PEM format string."""
  47 + b = BIO.MemoryBuffer()
  48 + try:
  49 + self.keyobj.save_pub_key_bio(b)
  50 + return b.getvalue()
  51 + finally:
  52 + b.close()
  53 +
  54 + def verify(self, signed_data, signature):
  55 + """Verify the given signature."""
  56 + raise NotImplementedError # pragma: nocover
  57 +
  58 + def sign(self, data):
  59 + """Sign the given data."""
  60 + raise NotImplementedError # pragma: nocover
  61 +
  62 +
  63 +#
  64 +# RSA keys, implemented using the RSA support in M2Crypto.
  65 +#
  66 +
  67 +class RSKey(Key):
  68 +
  69 + KEY_MODULE = _RSA
  70 + SIZE = None
  71 + HASHNAME = None
  72 + HASHMOD = None
  73 +
  74 + def __init__(self, data):
  75 + _check_keys(data, ('e', 'n'))
  76 + e = int2mpint(int(data["e"]))
  77 + n = int2mpint(int(data["n"]))
  78 + try:
  79 + d = int2mpint(int(data["d"]))
  80 + except KeyError:
  81 + self.keyobj = _RSA.new_pub_key((e, n))
  82 + else:
  83 + self.keyobj = _RSA.new_key((e, n, d))
  84 +
  85 + def verify(self, signed_data, signature):
  86 + digest = self.HASHMOD(signed_data).digest()
  87 + try:
  88 + return self.keyobj.verify(digest, signature, self.HASHNAME)
  89 + except _RSA.RSAError:
  90 + return False
  91 +
  92 + def sign(self, data):
  93 + digest = self.HASHMOD(data).digest()
  94 + return self.keyobj.sign(digest, self.HASHNAME)
  95 +
  96 +
  97 +#
  98 +# DSA keys, implemented using the DSA support in M2Crypto, along with
  99 +# some formatting tweaks to match what the browserid node-js server does.
  100 +#
  101 +
  102 +class DSKey(Key):
  103 +
  104 + KEY_MODULE = _DSA
  105 + BITLENGTH = None
  106 + HASHMOD = None
  107 +
  108 + def __init__(self, data):
  109 + _check_keys(data, ('p', 'q', 'g', 'y'))
  110 + self.p = p = long(data["p"], 16)
  111 + self.q = q = long(data["q"], 16)
  112 + self.g = g = long(data["g"], 16)
  113 + self.y = y = long(data["y"], 16)
  114 + if "x" not in data:
  115 + self.x = None
  116 + self.keyobj = _DSA.load_pub_key_params(int2mpint(p), int2mpint(q),
  117 + int2mpint(g), int2mpint(y))
  118 + else:
  119 + self.x = x = long(data["x"], 16)
  120 + self.keyobj = _DSA.load_key_params(int2mpint(p), int2mpint(q),
  121 + int2mpint(g), int2mpint(y),
  122 + int2mpint(x))
  123 +
  124 + @classmethod
  125 + def from_pem_data(cls, data=None, filename=None):
  126 + self = super(DSKey, cls).from_pem_data(data, filename)
  127 + self.p = mpint2int(m2.dsa_get_p(self.keyobj.dsa))
  128 + self.q = mpint2int(m2.dsa_get_q(self.keyobj.dsa))
  129 + self.g = mpint2int(m2.dsa_get_g(self.keyobj.dsa))
  130 + self.y = None
  131 + self.x = None
  132 + return self
  133 +
  134 + def verify(self, signed_data, signature):
  135 + # Restore any leading zero bytes that might have been stripped.
  136 + signature = signature.encode("hex")
  137 + hexlength = self.BITLENGTH / 4
  138 + signature = signature.rjust(hexlength * 2, "0")
  139 + if len(signature) != hexlength * 2:
  140 + return False
  141 + # Split the signature into "r" and "s" components.
  142 + r = long(signature[:hexlength], 16)
  143 + s = long(signature[hexlength:], 16)
  144 + if r <= 0 or r >= self.q:
  145 + return False
  146 + if s <= 0 or s >= self.q:
  147 + return False
  148 + # Now we can check the digest.
  149 + digest = self.HASHMOD(signed_data).digest()
  150 + return self.keyobj.verify(digest, int2mpint(r), int2mpint(s))
  151 +
  152 + def sign(self, data):
  153 + if not self.x:
  154 + raise ValueError("private key not present")
  155 + digest = self.HASHMOD(data).digest()
  156 + r, s = self.keyobj.sign(digest)
  157 + # We need precisely "bytelength" bytes from each integer.
  158 + # M2Crypto might give us more or less, so snip and pad appropriately.
  159 + bytelength = self.BITLENGTH / 8
  160 + r_bytes = r[4:].rjust(bytelength, "\x00")[-bytelength:]
  161 + s_bytes = s[4:].rjust(bytelength, "\x00")[-bytelength:]
  162 + return r_bytes + s_bytes
  163 +
  164 +
  165 +#
  166 +# Other helper functions.
  167 +#
  168 +
  169 +
  170 +def int2mpint(x):
  171 + """Convert a Python long integer to a string in OpenSSL's MPINT format."""
  172 + # MPINT is big-endian bytes with a size prefix.
  173 + # It's faster to go via hex encoding in C code than it is to try
  174 + # encoding directly into binary with a python-level loop.
  175 + # (and hex-slice-strip seems consistently faster than using "%x" format)
  176 + hexbytes = hex(x)[2:].rstrip("L")
  177 + if len(hexbytes) % 2:
  178 + hexbytes = "0" + hexbytes
  179 + bytes = unhexlify(hexbytes)
  180 + # Add an extra significant byte that's just zero. I think this is only
  181 + # necessary if the number has its MSB set, to prevent it being mistaken
  182 + # for a sign bit. I do it uniformly since it's valid and simpler.
  183 + return struct.pack(">I", len(bytes) + 1) + "\x00" + bytes
  184 +
  185 +
  186 +def mpint2int(data):
  187 + """Convert a string in OpenSSL's MPINT format to a Python long integer."""
  188 + hexbytes = hexlify(data[4:])
  189 + return int(hexbytes, 16)
  190 +
  191 +
  192 +def _check_keys(data, keys):
  193 + """Verify that the given data dict contains the specified keys."""
  194 + for key in keys:
  195 + if not key in data:
  196 + msg = 'missing %s in data - %s' % (key, str(data.keys()))
  197 + raise ValueError(msg)
143 browserid/jwt.py
@@ -7,12 +7,9 @@
7 7
8 8 """
9 9
10   -import struct
11 10 import hashlib
12   -from binascii import unhexlify
13 11
14   -from browserid._m2_monkeypatch import DSA as _DSA
15   -from browserid._m2_monkeypatch import RSA as _RSA
  12 +from browserid.crypto import Key, DSKey, RSKey # NOQA
16 13
17 14 from browserid.utils import decode_bytes, encode_bytes
18 15 from browserid.utils import decode_json_bytes, encode_json_bytes
@@ -74,53 +71,6 @@ def load_key(algorithm, key_data):
74 71 return key_class(key_data)
75 72
76 73
77   -class Key(object):
78   - """Generic base class for Key objects."""
79   -
80   - def verify(self, signed_data, signature):
81   - raise NotImplementedError # pragma: nocover
82   -
83   - def sign(self, data):
84   - raise NotImplementedError # pragma: nocover
85   -
86   -
87   -#
88   -# RSA keys, implemented using the RSA support in M2Crypto.
89   -#
90   -
91   -class RSKey(object):
92   -
93   - SIZE = None
94   - HASHMOD = None
95   -
96   - def __init__(self, data=None, obj=None):
97   - if data is None and obj is None:
98   - raise ValueError('You should specify either data or obj')
99   - if obj is not None:
100   - self.rsa = obj
101   - else:
102   - _check_keys(data, ('e', 'n'))
103   - e = int2mpint(int(data["e"]))
104   - n = int2mpint(int(data["n"]))
105   - try:
106   - d = int2mpint(int(data["d"]))
107   - except KeyError:
108   - self.rsa = _RSA.new_pub_key((e, n))
109   - else:
110   - self.rsa = _RSA.new_key((e, n, d))
111   -
112   - def verify(self, signed_data, signature):
113   - digest = self.HASHMOD(signed_data).digest()
114   - try:
115   - return self.rsa.verify(digest, signature, self.HASHNAME)
116   - except _RSA.RSAError:
117   - return False
118   -
119   - def sign(self, data):
120   - digest = self.HASHMOD(data).digest()
121   - return self.rsa.sign(digest, self.HASHNAME)
122   -
123   -
124 74 class RS64Key(RSKey):
125 75 SIZE = 64
126 76 HASHNAME = "sha256"
@@ -139,69 +89,6 @@ class RS256Key(RSKey):
139 89 HASHMOD = hashlib.sha256
140 90
141 91
142   -#
143   -# DSA keys, implemented using the DSA support in M2Crypto, along with
144   -# some formatting tweaks to match what the browserid node-js server does.
145   -#
146   -
147   -class DSKey(object):
148   -
149   - BITLENGTH = None
150   - HASHMOD = None
151   -
152   - def __init__(self, data=None, obj=None):
153   - if data is None and obj is None:
154   - raise ValueError('You should specify either data or obj')
155   - if obj:
156   - self.dsa = obj
157   - else:
158   - _check_keys(data, ('p', 'q', 'g', 'y'))
159   -
160   - self.p = p = long(data["p"], 16)
161   - self.q = q = long(data["q"], 16)
162   - self.g = g = long(data["g"], 16)
163   - self.y = y = long(data["y"], 16)
164   - if "x" not in data:
165   - self.x = None
166   - self.dsa = _DSA.load_pub_key_params(int2mpint(p), int2mpint(q),
167   - int2mpint(g), int2mpint(y))
168   - else:
169   - self.x = x = long(data["x"], 16)
170   - self.dsa = _DSA.load_key_params(int2mpint(p), int2mpint(q),
171   - int2mpint(g), int2mpint(y),
172   - int2mpint(x))
173   -
174   - def verify(self, signed_data, signature):
175   - # Restore any leading zero bytes that might have been stripped.
176   - signature = signature.encode("hex")
177   - hexlength = self.BITLENGTH / 4
178   - signature = signature.rjust(hexlength * 2, "0")
179   - if len(signature) != hexlength * 2:
180   - return False
181   - # Split the signature into "r" and "s" components.
182   - r = long(signature[:hexlength], 16)
183   - s = long(signature[hexlength:], 16)
184   - if r <= 0 or r >= self.q:
185   - return False
186   - if s <= 0 or s >= self.q:
187   - return False
188   - # Now we can check the digest.
189   - digest = self.HASHMOD(signed_data).digest()
190   - return self.dsa.verify(digest, int2mpint(r), int2mpint(s))
191   -
192   - def sign(self, data):
193   - if not self.x:
194   - raise ValueError("private key not present")
195   - digest = self.HASHMOD(data).digest()
196   - r, s = self.dsa.sign(digest)
197   - # We need precisely "bytelength" bytes from each integer.
198   - # M2Crypto might give us more or less, so snip and pad appropriately.
199   - bytelength = self.BITLENGTH / 8
200   - r_bytes = r[4:].rjust(bytelength, "\x00")[-bytelength:]
201   - s_bytes = s[4:].rjust(bytelength, "\x00")[-bytelength:]
202   - return r_bytes + s_bytes
203   -
204   -
205 92 class DS128Key(DSKey):
206 93 BITLENGTH = 160
207 94 HASHMOD = hashlib.sha1
@@ -210,31 +97,3 @@ class DS128Key(DSKey):
210 97 class DS256Key(DSKey):
211 98 BITLENGTH = 256
212 99 HASHMOD = hashlib.sha256
213   -
214   -
215   -#
216   -# Other helper functions.
217   -#
218   -
219   -
220   -def int2mpint(x):
221   - """Convert a Python long integer to a string in OpenSSL's MPINT format."""
222   - # MPINT is big-endian bytes with a size prefix.
223   - # It's faster to go via hex encoding in C code than it is to try
224   - # encoding directly into binary with a python-level loop.
225   - # (and hex-slice-strip seems consistently faster than using "%x" format)
226   - hexbytes = hex(x)[2:].rstrip("L")
227   - if len(hexbytes) % 2:
228   - hexbytes = "0" + hexbytes
229   - bytes = unhexlify(hexbytes)
230   - # Add an extra significant byte that's just zero. I think this is only
231   - # necessary if the number has its MSB set, to prevent it being mistaken
232   - # for a sign bit. I do it uniformly since it's valid and simpler.
233   - return struct.pack(">I", len(bytes) + 1) + "\x00" + bytes
234   -
235   -
236   -def _check_keys(data, keys):
237   - for key in keys:
238   - if not key in data:
239   - msg = 'missing %s in data - %s' % (key, str(data.keys()))
240   - raise ValueError(msg)
2  browserid/tests/support.py
@@ -14,7 +14,7 @@
14 14 # if unittest2 isn't available, assume that we are python 2.7
15 15 try:
16 16 import unittest2 as unittest
17   -except:
  17 +except ImportError:
18 18 import unittest # NOQA
19 19
20 20
132 browserid/tests/test_jwt.py
@@ -2,11 +2,57 @@
2 2 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
3 3 # You can obtain one at http://mozilla.org/MPL/2.0/.
4 4
  5 +import tempfile
  6 +
5 7 from browserid.tests.support import get_keypair, unittest
6 8 from browserid.utils import encode_json_bytes, encode_bytes
7 9 from browserid import jwt
8 10
9 11
  12 +def _long(value):
  13 + return long(value.replace(" ", "").replace("\n", "").strip())
  14 +
  15 +
  16 +def _hex(value):
  17 + return hex(long(value.replace(" ", "").replace("\n", "").strip()))
  18 +
  19 +
  20 +# This is a dummy RSA key I generated via PyCrypto.
  21 +# M2Crypto doesn't seem to let me get at the values of e, n and d.
  22 +RSA_KEY_DATA = {
  23 + "e": 65537L,
  24 + "n": _long("""110897663942528265066856163966583557538666146275146
  25 + 569193074111045116764854772535689458732714049671807506
  26 + 396649306730328647317126800964431366624486416551078177
  27 + 528195103050868728550429561392842977259407335332582178
  28 + 624191611001106449477645116630750398871838788574825885
  29 + 770446686329706009000279629721965986677219L"""),
  30 + "d": _long("""295278123166626215026113502482091502365034141401240
  31 + 159363282304307076544046230487782634982660202141239450
  32 + 481640966544735782181647417005558287318200095948234745
  33 + 214183393770321992676297531378428617531522265932631860
  34 + 693144704788708252936752025413728425562033678747736289
  35 + 64114133156747686886305629893015763517873L"""),
  36 +}
  37 +
  38 +
  39 +# This is a dummy DSA key I generated via PyCrypto.
  40 +# M2Crypto doesn't seem to let me get at the values of x and y.
  41 +DSA_KEY_DATA = {
  42 + "p": _hex("""6703904104057623261995085583676902361410672713749348
  43 + 7374515589871295072792250899011720632358392764362903244
  44 + 12395020783955234715731001076129344181463063193L"""),
  45 + "q": hex(1006478751418673383937866166434285354892250535133L),
  46 + "g": _hex("""1801778249650423365253284139284406405780267098493217
  47 + 0320675876307450879812560049234773036938891018778074993
  48 + 01874343843218156663689824126183823813389886834L"""),
  49 + "y": _hex("""4148629652526876030475847300836791685289385792662680
  50 + 5886292874741635965095055693693232436255359496594291250
  51 + 77637642734034732001089176915352691113947372211L"""),
  52 + "x": hex(487025797851506801093339352420308364866214860934L),
  53 +}
  54 +
  55 +
10 56 class TestJWT(unittest.TestCase):
11 57
12 58 def test_error_jwt_with_no_algorithm(self):
@@ -30,26 +76,7 @@ def test_loading_unknown_algorithms(self):
30 76 self.assertRaises(ValueError, jwt.load_key, "DS64", {})
31 77
32 78 def test_rsa_verification(self):
33   - # This is a dummy RSA key I generated via PyCrypto.
34   - # M2Crypto doesn't seem to let me get at the values of e, n and d.
35   - # I've line wrapped it for readability.
36   - def _long(value):
37   - return long(value.replace(" ", "").replace("\n", "").strip())
38   - data = {
39   - "e": 65537L,
40   - "n": _long("""110897663942528265066856163966583557538666146275146
41   - 569193074111045116764854772535689458732714049671807506
42   - 396649306730328647317126800964431366624486416551078177
43   - 528195103050868728550429561392842977259407335332582178
44   - 624191611001106449477645116630750398871838788574825885
45   - 770446686329706009000279629721965986677219L"""),
46   - "d": _long("""295278123166626215026113502482091502365034141401240
47   - 159363282304307076544046230487782634982660202141239450
48   - 481640966544735782181647417005558287318200095948234745
49   - 214183393770321992676297531378428617531522265932631860
50   - 693144704788708252936752025413728425562033678747736289
51   - 64114133156747686886305629893015763517873L"""),
52   - }
  79 + data = RSA_KEY_DATA.copy()
53 80 key = jwt.RS64Key(data)
54 81 data.pop("d")
55 82 pubkey = jwt.RS64Key(data)
@@ -58,24 +85,7 @@ def _long(value):
58 85 self.assertFalse(pubkey.verify("HELLO", key.sign("hello")))
59 86
60 87 def test_dsa_verification(self):
61   - # This is a dummy DSA key I generated via PyCrypto.
62   - # M2Crypto doesn't seem to let me get at the values of x and y.
63   - # I've line wrapped it for readability.
64   - def _hex(value):
65   - return hex(long(value.replace(" ", "").replace("\n", "").strip()))
66   - data = {
67   - "p": _hex("""6703904104057623261995085583676902361410672713749348
68   - 7374515589871295072792250899011720632358392764362903244
69   - 12395020783955234715731001076129344181463063193L"""),
70   - "q": hex(1006478751418673383937866166434285354892250535133L),
71   - "g": _hex("""1801778249650423365253284139284406405780267098493217
72   - 0320675876307450879812560049234773036938891018778074993
73   - 01874343843218156663689824126183823813389886834L"""),
74   - "y": _hex("""4148629652526876030475847300836791685289385792662680
75   - 5886292874741635965095055693693232436255359496594291250
76   - 77637642734034732001089176915352691113947372211L"""),
77   - "x": hex(487025797851506801093339352420308364866214860934L),
78   - }
  88 + data = DSA_KEY_DATA.copy()
79 89 key = jwt.DS128Key(data)
80 90 data.pop("x")
81 91 pubkey = jwt.DS128Key(data)
@@ -90,3 +100,49 @@ def _hex(value):
90 100 self.assertFalse(pubkey.verify("HELLO", ("\xFF" * 20) + "\x01" * 20))
91 101 # - "s" value too large
92 102 self.assertFalse(pubkey.verify("HELLO", "\x01" + ("\xFF" * 20)))
  103 +
  104 + def test_loading_rsa_from_pem_data(self):
  105 + data = RSA_KEY_DATA.copy()
  106 + key = jwt.RS64Key(data)
  107 + try:
  108 + data = key.to_pem_data()
  109 + except NotImplementedError:
  110 + raise unittest.SkipTest
  111 + pubkey = jwt.RS64Key.from_pem_data(data)
  112 + self.assertTrue(pubkey.verify("hello", key.sign("hello")))
  113 +
  114 + def test_loading_rsa_from_pem_data_filename(self):
  115 + data = RSA_KEY_DATA.copy()
  116 + key = jwt.RS64Key(data)
  117 + try:
  118 + data = key.to_pem_data()
  119 + except NotImplementedError:
  120 + raise unittest.SkipTest
  121 + with tempfile.NamedTemporaryFile() as f:
  122 + f.write(data)
  123 + f.flush()
  124 + pubkey = jwt.RS64Key.from_pem_data(filename=f.name)
  125 + self.assertTrue(pubkey.verify("hello", key.sign("hello")))
  126 +
  127 + def test_loading_dsa_from_pem_data(self):
  128 + data = DSA_KEY_DATA.copy()
  129 + key = jwt.DS128Key(data)
  130 + try:
  131 + data = key.to_pem_data()
  132 + except NotImplementedError:
  133 + raise unittest.SkipTest
  134 + pubkey = jwt.DS128Key.from_pem_data(data)
  135 + self.assertTrue(pubkey.verify("hello", key.sign("hello")))
  136 +
  137 + def test_loading_dsa_from_pem_data_filename(self):
  138 + data = DSA_KEY_DATA.copy()
  139 + key = jwt.DS128Key(data)
  140 + try:
  141 + data = key.to_pem_data()
  142 + except NotImplementedError:
  143 + raise unittest.SkipTest
  144 + with tempfile.NamedTemporaryFile() as f:
  145 + f.write(data)
  146 + f.flush()
  147 + pubkey = jwt.DS128Key.from_pem_data(filename=f.name)
  148 + self.assertTrue(pubkey.verify("hello", key.sign("hello")))
14 browserid/tests/test_m2_monkeypatch.py
@@ -3,8 +3,13 @@
3 3 # You can obtain one at http://mozilla.org/MPL/2.0/.
4 4
5 5 from browserid.tests.support import unittest
6   -from browserid.jwt import int2mpint
7   -import browserid._m2_monkeypatch as _m2
  6 +
  7 +try:
  8 + from browserid.crypto.m2 import int2mpint
  9 + import browserid.crypto._m2_monkeypatch as _m2
  10 + HAVE_M2CRYPTO = True
  11 +except ImportError:
  12 + HAVE_M2CRYPTO = False
8 13
9 14
10 15 # Dummy RSA key for testing purposes.
@@ -28,6 +33,11 @@ def _long(value):
28 33
29 34 class TestM2MonkeyPatch(unittest.TestCase):
30 35
  36 + def setUp(self):
  37 + super(TestM2MonkeyPatch, self).setUp()
  38 + if not HAVE_M2CRYPTO:
  39 + raise unittest.SkipTest()
  40 +
31 41 def test_setting_invalid_data_on_dsa_key(self):
32 42 k = _m2.DSA.gen_params(512)
33 43 k.gen_key()
2  setup.py
@@ -10,7 +10,7 @@
10 10 with open(os.path.join(here, 'CHANGES.txt')) as f:
11 11 CHANGES = f.read()
12 12
13   -requires = ['M2Crypto', 'requests']
  13 +requires = ['requests', 'mock']
14 14
15 15 tests_require = requires + ['mock']
16 16

0 comments on commit 6f0d3a3

Please sign in to comment.
Something went wrong with that request. Please try again.