Skip to content
Browse files

Merge master with rfk/pure-python-crypto

  • Loading branch information...
2 parents 78bdd94 + f9e9701 commit 6f0d3a3309cb876828b71a2925954e49e18b4a4b @almet almet committed May 31, 2012
View
5 CHANGES.txt
@@ -1,6 +1,11 @@
0.7.0 - XXXX-XX-XX
==================
+ * Added a pure-python implementation of the JWT crypto routines, for
+ use when M2Crypto is not available.
+ * Added "from_pem_data" and "to_pem_data" methods to Key objects.
+ Currently these are only available when M2Crypto is installed.
+
0.6.0 - 2012-31-05
==================
View
18 browserid/crypto/__init__.py
@@ -0,0 +1,18 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+"""
+
+Best-effort crypto primitives for PyBrowserID.
+
+If you have M2Crypto installed, this package will provide a nice fast
+implementation of the RSKey and DSKey classes needed to do the crypto
+work behind BrowserID. If you don't, you'll get a very slow but very
+portable pure-python version.
+
+"""
+
+try:
+ from browserid.crypto.m2 import Key, RSKey, DSKey
+except ImportError:
+ from browserid.crypto.fallback import Key, RSKey, DSKey # NOQA
View
2 browserid/_m2_monkeypatch.py → browserid/crypto/_m2_monkeypatch.py
@@ -1,6 +1,6 @@
#
# This monkey-patches M2Crypto's RSA and DSA support to allow us
-# to create keys directly from a given set of parameters. It's
+# to create keys directly from a given set of parameters.
# It's based on the following patch from the M2Crypt bugtracker:
#
# https://bugzilla.osafoundation.org/show_bug.cgi?id=12981
View
193 browserid/crypto/fallback.py
@@ -0,0 +1,193 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+"""
+
+Crypto primitives implemented in pure python.
+
+This file provides the "slow path" crypto implementation for PyBrowserID.
+It implements everything in pure python, so it's very slow but very portable.
+
+There is also a faster version built on M2Crypto, which should be picked up
+automatically if you have that package installed.
+
+"""
+
+import os
+from binascii import unhexlify
+
+
+class Key(object):
+ """Generic base class for Key objects."""
+
+ @classmethod
+ def from_pem_data(cls, data=None, filename=None):
+ """Alternative constructor for loading from PEM format data."""
+ msg = "PEM data loading is not implemented for pure-python crypto."
+ msg += " Please install M2Crypto to access this functionality."
+ raise NotImplementedError(msg)
+
+ def to_pem_data(self):
+ """Save the public key data to a PEM format string."""
+ msg = "PEM data saving is not implemented for pure-python crypto."
+ msg += " Please install M2Crypto to access this functionality."
+ raise NotImplementedError(msg)
+
+ def verify(self, signed_data, signature):
+ """Verify the given signature."""
+ raise NotImplementedError
+
+ def sign(self, data):
+ """Sign the given data."""
+ raise NotImplementedError
+
+
+# These constants are needed for encoding the name of the hash
+# algorithm into the RSA signature, per PKCS #1.
+RSA_DIGESTINFO_HEADER = {
+ "sha1": "3021300906052b0e03021a05000414",
+ "sha256": "3031300d060960864801650304020105000420",
+}
+
+
+class RSKey(Key):
+ """Generic base class for RSA key objects.
+
+ Concrete subclasses should provide the SIZE, HASHNAME and HASHMOD
+ attributes.
+ """
+
+ SIZE = None
+ HASHNAME = None
+ HASHMOD = None
+
+ def __init__(self, data):
+ _check_keys(data, ("e", "n"))
+ self.e = long(data["e"])
+ self.n = long(data["n"])
+ try:
+ self.d = long(data["d"])
+ except KeyError:
+ self.d = None
+
+ def verify(self, signed_data, signature):
+ n, e = self.n, self.e
+ m = long(signature, 16)
+ c = pow(m, e, n)
+ padded_digest = hex(c)[2:].rstrip("L").rjust(self.SIZE * 2, "0")
+ return padded_digest == self._get_digest(signed_data)
+
+ def sign(self, data):
+ n, e, d = self.n, self.e, self.d
+ if not d:
+ raise ValueError("private key not present")
+ c = long(self._get_digest(data), 16)
+ m = pow(c, d, n)
+ return hex(m)[2:].rstrip("L")
+
+ def _get_digest(self, data):
+ digest = self.HASHMOD(data).hexdigest()
+ padded_digest = "00" + RSA_DIGESTINFO_HEADER[self.HASHNAME] + digest
+ padding_len = (self.SIZE * 2) - 4 - len(padded_digest)
+ padded_digest = "0001" + ("f" * padding_len) + padded_digest
+ return padded_digest
+
+
+class DSKey(Key):
+ """Generic base class for DSA key objects.
+
+ Concrete subclasses should provide the BITLENGTH and HASHMOD attributes.
+ """
+
+ BITLENGTH = None
+ HASHMOD = None
+
+ def __init__(self, data):
+ _check_keys(data, ("p", "q", "g", "y"))
+ self.p = long(data["p"], 16)
+ self.q = long(data["q"], 16)
+ self.g = long(data["g"], 16)
+ self.y = long(data["y"], 16)
+ if "x" in data:
+ self.x = long(data["x"], 16)
+ else:
+ self.x = None
+
+ def verify(self, signed_data, signature):
+ p, q, g, y = self.p, self.q, self.g, self.y
+ signature = signature.encode("hex")
+ hexlength = self.BITLENGTH / 4
+ signature = signature.rjust(hexlength * 2, "0")
+ if len(signature) != hexlength * 2:
+ return False
+ r = long(signature[:hexlength], 16)
+ s = long(signature[hexlength:], 16)
+ if r <= 0 or r >= q:
+ return False
+ if s <= 0 or s >= q:
+ return False
+ w = modinv(s, q)
+ u1 = (long(self.HASHMOD(signed_data).hexdigest(), 16) * w) % q
+ u2 = (r * w) % q
+ v = ((pow(g, u1, p) * pow(y, u2, p)) % p) % q
+ return (v == r)
+
+ def sign(self, data):
+ p, q, g, y, x = self.p, self.q, self.g, self.y, self.x
+ if not x:
+ raise ValueError("private key not present")
+ # We need to do lots of if-not-this-then-start-over type tests.
+ # A while loop with continue statements is the cleanest way to do so.
+ while True:
+ k = long(os.urandom(self.BITLENGTH / 8).encode("hex"), 16) % q
+ if k == 0:
+ continue
+ r = pow(g, k, p) % q
+ if r == 0:
+ continue
+ h = (long(self.HASHMOD(data).hexdigest(), 16) + (x * r)) % q
+ s = (modinv(k, q) * h) % q
+ if s == 0:
+ continue
+ break
+ assert 0 < r < q
+ assert 0 < s < q
+ bytelength = self.BITLENGTH / 8
+ r_bytes = int2bytes(r).rjust(bytelength, "\x00")
+ s_bytes = int2bytes(s).rjust(bytelength, "\x00")
+ return r_bytes + s_bytes
+
+
+def modinv(a, m):
+ """Find the modular inverse of a, with modulus m."""
+ # This is a transliteration of the algorithm as it was described
+ # to me by Wikipedia, using the Extended Euclidean Algorithm.
+ x = 0
+ lastx = 1
+ y = 1
+ lasty = 0
+ b = m
+ while b != 0:
+ a, (q, b) = b, divmod(a, b)
+ x, lastx = lastx - (q * x), x
+ y, lasty = lasty - (q * y), y
+ return lastx % m
+
+
+def int2bytes(x):
+ """Convert a Python long integer to bigendian bytestring."""
+ # It's faster to go via hex encoding in C code than it is to try
+ # encoding directly into binary with a python-level loop.
+ # (and hex-slice-strip seems consistently faster than using "%x" format)
+ hexbytes = hex(x)[2:].rstrip("L")
+ if len(hexbytes) % 2:
+ hexbytes = "0" + hexbytes
+ return unhexlify(hexbytes)
+
+
+def _check_keys(data, keys):
+ """Verify that the given data dict contains the specified keys."""
+ for key in keys:
+ if not key in data:
+ msg = 'missing %s in data - %s' % (key, str(data.keys()))
+ raise ValueError(msg)
View
197 browserid/crypto/m2.py
@@ -0,0 +1,197 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this file,
+# You can obtain one at http://mozilla.org/MPL/2.0/.
+"""
+
+Crypto primitives built on top of M2Crypto.
+
+This file provides the "fast path" crypto implementation for PyBrowserID.
+It uses the public-key-crypto routines from M2Crypto for nice fast operation.
+
+There is also a pure-python fallback module that's slower, but avoid
+having to install M2Crypto.
+
+"""
+
+import struct
+from binascii import hexlify, unhexlify
+
+from M2Crypto import BIO
+
+from browserid.crypto._m2_monkeypatch import m2
+from browserid.crypto._m2_monkeypatch import DSA as _DSA
+from browserid.crypto._m2_monkeypatch import RSA as _RSA
+
+
+class Key(object):
+ """Generic base class for Key objects."""
+
+ KEY_MODULE = None
+
+ @classmethod
+ def from_pem_data(cls, data=None, filename=None):
+ """Alternative constructor for loading from PEM format data."""
+ self = cls.__new__(cls)
+ if data is not None:
+ bio = BIO.MemoryBuffer(str(data))
+ elif filename is not None:
+ bio = BIO.openfile(filename)
+ else:
+ msg = "Please specify either 'data' or 'filename' argument."
+ raise ValueError(msg)
+ self.keyobj = self.KEY_MODULE.load_pub_key_bio(bio)
+ return self
+
+ def to_pem_data(self):
+ """Save the public key data to a PEM format string."""
+ b = BIO.MemoryBuffer()
+ try:
+ self.keyobj.save_pub_key_bio(b)
+ return b.getvalue()
+ finally:
+ b.close()
+
+ def verify(self, signed_data, signature):
+ """Verify the given signature."""
+ raise NotImplementedError # pragma: nocover
+
+ def sign(self, data):
+ """Sign the given data."""
+ raise NotImplementedError # pragma: nocover
+
+
+#
+# RSA keys, implemented using the RSA support in M2Crypto.
+#
+
+class RSKey(Key):
+
+ KEY_MODULE = _RSA
+ SIZE = None
+ HASHNAME = None
+ HASHMOD = None
+
+ def __init__(self, data):
+ _check_keys(data, ('e', 'n'))
+ e = int2mpint(int(data["e"]))
+ n = int2mpint(int(data["n"]))
+ try:
+ d = int2mpint(int(data["d"]))
+ except KeyError:
+ self.keyobj = _RSA.new_pub_key((e, n))
+ else:
+ self.keyobj = _RSA.new_key((e, n, d))
+
+ def verify(self, signed_data, signature):
+ digest = self.HASHMOD(signed_data).digest()
+ try:
+ return self.keyobj.verify(digest, signature, self.HASHNAME)
+ except _RSA.RSAError:
+ return False
+
+ def sign(self, data):
+ digest = self.HASHMOD(data).digest()
+ return self.keyobj.sign(digest, self.HASHNAME)
+
+
+#
+# DSA keys, implemented using the DSA support in M2Crypto, along with
+# some formatting tweaks to match what the browserid node-js server does.
+#
+
+class DSKey(Key):
+
+ KEY_MODULE = _DSA
+ BITLENGTH = None
+ HASHMOD = None
+
+ def __init__(self, data):
+ _check_keys(data, ('p', 'q', 'g', 'y'))
+ self.p = p = long(data["p"], 16)
+ self.q = q = long(data["q"], 16)
+ self.g = g = long(data["g"], 16)
+ self.y = y = long(data["y"], 16)
+ if "x" not in data:
+ self.x = None
+ self.keyobj = _DSA.load_pub_key_params(int2mpint(p), int2mpint(q),
+ int2mpint(g), int2mpint(y))
+ else:
+ self.x = x = long(data["x"], 16)
+ self.keyobj = _DSA.load_key_params(int2mpint(p), int2mpint(q),
+ int2mpint(g), int2mpint(y),
+ int2mpint(x))
+
+ @classmethod
+ def from_pem_data(cls, data=None, filename=None):
+ self = super(DSKey, cls).from_pem_data(data, filename)
+ self.p = mpint2int(m2.dsa_get_p(self.keyobj.dsa))
+ self.q = mpint2int(m2.dsa_get_q(self.keyobj.dsa))
+ self.g = mpint2int(m2.dsa_get_g(self.keyobj.dsa))
+ self.y = None
+ self.x = None
+ return self
+
+ def verify(self, signed_data, signature):
+ # Restore any leading zero bytes that might have been stripped.
+ signature = signature.encode("hex")
+ hexlength = self.BITLENGTH / 4
+ signature = signature.rjust(hexlength * 2, "0")
+ if len(signature) != hexlength * 2:
+ return False
+ # Split the signature into "r" and "s" components.
+ r = long(signature[:hexlength], 16)
+ s = long(signature[hexlength:], 16)
+ if r <= 0 or r >= self.q:
+ return False
+ if s <= 0 or s >= self.q:
+ return False
+ # Now we can check the digest.
+ digest = self.HASHMOD(signed_data).digest()
+ return self.keyobj.verify(digest, int2mpint(r), int2mpint(s))
+
+ def sign(self, data):
+ if not self.x:
+ raise ValueError("private key not present")
+ digest = self.HASHMOD(data).digest()
+ r, s = self.keyobj.sign(digest)
+ # We need precisely "bytelength" bytes from each integer.
+ # M2Crypto might give us more or less, so snip and pad appropriately.
+ bytelength = self.BITLENGTH / 8
+ r_bytes = r[4:].rjust(bytelength, "\x00")[-bytelength:]
+ s_bytes = s[4:].rjust(bytelength, "\x00")[-bytelength:]
+ return r_bytes + s_bytes
+
+
+#
+# Other helper functions.
+#
+
+
+def int2mpint(x):
+ """Convert a Python long integer to a string in OpenSSL's MPINT format."""
+ # MPINT is big-endian bytes with a size prefix.
+ # It's faster to go via hex encoding in C code than it is to try
+ # encoding directly into binary with a python-level loop.
+ # (and hex-slice-strip seems consistently faster than using "%x" format)
+ hexbytes = hex(x)[2:].rstrip("L")
+ if len(hexbytes) % 2:
+ hexbytes = "0" + hexbytes
+ bytes = unhexlify(hexbytes)
+ # Add an extra significant byte that's just zero. I think this is only
+ # necessary if the number has its MSB set, to prevent it being mistaken
+ # for a sign bit. I do it uniformly since it's valid and simpler.
+ return struct.pack(">I", len(bytes) + 1) + "\x00" + bytes
+
+
+def mpint2int(data):
+ """Convert a string in OpenSSL's MPINT format to a Python long integer."""
+ hexbytes = hexlify(data[4:])
+ return int(hexbytes, 16)
+
+
+def _check_keys(data, keys):
+ """Verify that the given data dict contains the specified keys."""
+ for key in keys:
+ if not key in data:
+ msg = 'missing %s in data - %s' % (key, str(data.keys()))
+ raise ValueError(msg)
View
143 browserid/jwt.py
@@ -7,12 +7,9 @@
"""
-import struct
import hashlib
-from binascii import unhexlify
-from browserid._m2_monkeypatch import DSA as _DSA
-from browserid._m2_monkeypatch import RSA as _RSA
+from browserid.crypto import Key, DSKey, RSKey # NOQA
from browserid.utils import decode_bytes, encode_bytes
from browserid.utils import decode_json_bytes, encode_json_bytes
@@ -74,53 +71,6 @@ def load_key(algorithm, key_data):
return key_class(key_data)
-class Key(object):
- """Generic base class for Key objects."""
-
- def verify(self, signed_data, signature):
- raise NotImplementedError # pragma: nocover
-
- def sign(self, data):
- raise NotImplementedError # pragma: nocover
-
-
-#
-# RSA keys, implemented using the RSA support in M2Crypto.
-#
-
-class RSKey(object):
-
- SIZE = None
- HASHMOD = None
-
- def __init__(self, data=None, obj=None):
- if data is None and obj is None:
- raise ValueError('You should specify either data or obj')
- if obj is not None:
- self.rsa = obj
- else:
- _check_keys(data, ('e', 'n'))
- e = int2mpint(int(data["e"]))
- n = int2mpint(int(data["n"]))
- try:
- d = int2mpint(int(data["d"]))
- except KeyError:
- self.rsa = _RSA.new_pub_key((e, n))
- else:
- self.rsa = _RSA.new_key((e, n, d))
-
- def verify(self, signed_data, signature):
- digest = self.HASHMOD(signed_data).digest()
- try:
- return self.rsa.verify(digest, signature, self.HASHNAME)
- except _RSA.RSAError:
- return False
-
- def sign(self, data):
- digest = self.HASHMOD(data).digest()
- return self.rsa.sign(digest, self.HASHNAME)
-
-
class RS64Key(RSKey):
SIZE = 64
HASHNAME = "sha256"
@@ -139,69 +89,6 @@ class RS256Key(RSKey):
HASHMOD = hashlib.sha256
-#
-# DSA keys, implemented using the DSA support in M2Crypto, along with
-# some formatting tweaks to match what the browserid node-js server does.
-#
-
-class DSKey(object):
-
- BITLENGTH = None
- HASHMOD = None
-
- def __init__(self, data=None, obj=None):
- if data is None and obj is None:
- raise ValueError('You should specify either data or obj')
- if obj:
- self.dsa = obj
- else:
- _check_keys(data, ('p', 'q', 'g', 'y'))
-
- self.p = p = long(data["p"], 16)
- self.q = q = long(data["q"], 16)
- self.g = g = long(data["g"], 16)
- self.y = y = long(data["y"], 16)
- if "x" not in data:
- self.x = None
- self.dsa = _DSA.load_pub_key_params(int2mpint(p), int2mpint(q),
- int2mpint(g), int2mpint(y))
- else:
- self.x = x = long(data["x"], 16)
- self.dsa = _DSA.load_key_params(int2mpint(p), int2mpint(q),
- int2mpint(g), int2mpint(y),
- int2mpint(x))
-
- def verify(self, signed_data, signature):
- # Restore any leading zero bytes that might have been stripped.
- signature = signature.encode("hex")
- hexlength = self.BITLENGTH / 4
- signature = signature.rjust(hexlength * 2, "0")
- if len(signature) != hexlength * 2:
- return False
- # Split the signature into "r" and "s" components.
- r = long(signature[:hexlength], 16)
- s = long(signature[hexlength:], 16)
- if r <= 0 or r >= self.q:
- return False
- if s <= 0 or s >= self.q:
- return False
- # Now we can check the digest.
- digest = self.HASHMOD(signed_data).digest()
- return self.dsa.verify(digest, int2mpint(r), int2mpint(s))
-
- def sign(self, data):
- if not self.x:
- raise ValueError("private key not present")
- digest = self.HASHMOD(data).digest()
- r, s = self.dsa.sign(digest)
- # We need precisely "bytelength" bytes from each integer.
- # M2Crypto might give us more or less, so snip and pad appropriately.
- bytelength = self.BITLENGTH / 8
- r_bytes = r[4:].rjust(bytelength, "\x00")[-bytelength:]
- s_bytes = s[4:].rjust(bytelength, "\x00")[-bytelength:]
- return r_bytes + s_bytes
-
-
class DS128Key(DSKey):
BITLENGTH = 160
HASHMOD = hashlib.sha1
@@ -210,31 +97,3 @@ class DS128Key(DSKey):
class DS256Key(DSKey):
BITLENGTH = 256
HASHMOD = hashlib.sha256
-
-
-#
-# Other helper functions.
-#
-
-
-def int2mpint(x):
- """Convert a Python long integer to a string in OpenSSL's MPINT format."""
- # MPINT is big-endian bytes with a size prefix.
- # It's faster to go via hex encoding in C code than it is to try
- # encoding directly into binary with a python-level loop.
- # (and hex-slice-strip seems consistently faster than using "%x" format)
- hexbytes = hex(x)[2:].rstrip("L")
- if len(hexbytes) % 2:
- hexbytes = "0" + hexbytes
- bytes = unhexlify(hexbytes)
- # Add an extra significant byte that's just zero. I think this is only
- # necessary if the number has its MSB set, to prevent it being mistaken
- # for a sign bit. I do it uniformly since it's valid and simpler.
- return struct.pack(">I", len(bytes) + 1) + "\x00" + bytes
-
-
-def _check_keys(data, keys):
- for key in keys:
- if not key in data:
- msg = 'missing %s in data - %s' % (key, str(data.keys()))
- raise ValueError(msg)
View
2 browserid/tests/support.py
@@ -14,7 +14,7 @@
# if unittest2 isn't available, assume that we are python 2.7
try:
import unittest2 as unittest
-except:
+except ImportError:
import unittest # NOQA
View
132 browserid/tests/test_jwt.py
@@ -2,11 +2,57 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
+import tempfile
+
from browserid.tests.support import get_keypair, unittest
from browserid.utils import encode_json_bytes, encode_bytes
from browserid import jwt
+def _long(value):
+ return long(value.replace(" ", "").replace("\n", "").strip())
+
+
+def _hex(value):
+ return hex(long(value.replace(" ", "").replace("\n", "").strip()))
+
+
+# This is a dummy RSA key I generated via PyCrypto.
+# M2Crypto doesn't seem to let me get at the values of e, n and d.
+RSA_KEY_DATA = {
+ "e": 65537L,
+ "n": _long("""110897663942528265066856163966583557538666146275146
+ 569193074111045116764854772535689458732714049671807506
+ 396649306730328647317126800964431366624486416551078177
+ 528195103050868728550429561392842977259407335332582178
+ 624191611001106449477645116630750398871838788574825885
+ 770446686329706009000279629721965986677219L"""),
+ "d": _long("""295278123166626215026113502482091502365034141401240
+ 159363282304307076544046230487782634982660202141239450
+ 481640966544735782181647417005558287318200095948234745
+ 214183393770321992676297531378428617531522265932631860
+ 693144704788708252936752025413728425562033678747736289
+ 64114133156747686886305629893015763517873L"""),
+}
+
+
+# This is a dummy DSA key I generated via PyCrypto.
+# M2Crypto doesn't seem to let me get at the values of x and y.
+DSA_KEY_DATA = {
+ "p": _hex("""6703904104057623261995085583676902361410672713749348
+ 7374515589871295072792250899011720632358392764362903244
+ 12395020783955234715731001076129344181463063193L"""),
+ "q": hex(1006478751418673383937866166434285354892250535133L),
+ "g": _hex("""1801778249650423365253284139284406405780267098493217
+ 0320675876307450879812560049234773036938891018778074993
+ 01874343843218156663689824126183823813389886834L"""),
+ "y": _hex("""4148629652526876030475847300836791685289385792662680
+ 5886292874741635965095055693693232436255359496594291250
+ 77637642734034732001089176915352691113947372211L"""),
+ "x": hex(487025797851506801093339352420308364866214860934L),
+}
+
+
class TestJWT(unittest.TestCase):
def test_error_jwt_with_no_algorithm(self):
@@ -30,26 +76,7 @@ def test_loading_unknown_algorithms(self):
self.assertRaises(ValueError, jwt.load_key, "DS64", {})
def test_rsa_verification(self):
- # This is a dummy RSA key I generated via PyCrypto.
- # M2Crypto doesn't seem to let me get at the values of e, n and d.
- # I've line wrapped it for readability.
- def _long(value):
- return long(value.replace(" ", "").replace("\n", "").strip())
- data = {
- "e": 65537L,
- "n": _long("""110897663942528265066856163966583557538666146275146
- 569193074111045116764854772535689458732714049671807506
- 396649306730328647317126800964431366624486416551078177
- 528195103050868728550429561392842977259407335332582178
- 624191611001106449477645116630750398871838788574825885
- 770446686329706009000279629721965986677219L"""),
- "d": _long("""295278123166626215026113502482091502365034141401240
- 159363282304307076544046230487782634982660202141239450
- 481640966544735782181647417005558287318200095948234745
- 214183393770321992676297531378428617531522265932631860
- 693144704788708252936752025413728425562033678747736289
- 64114133156747686886305629893015763517873L"""),
- }
+ data = RSA_KEY_DATA.copy()
key = jwt.RS64Key(data)
data.pop("d")
pubkey = jwt.RS64Key(data)
@@ -58,24 +85,7 @@ def _long(value):
self.assertFalse(pubkey.verify("HELLO", key.sign("hello")))
def test_dsa_verification(self):
- # This is a dummy DSA key I generated via PyCrypto.
- # M2Crypto doesn't seem to let me get at the values of x and y.
- # I've line wrapped it for readability.
- def _hex(value):
- return hex(long(value.replace(" ", "").replace("\n", "").strip()))
- data = {
- "p": _hex("""6703904104057623261995085583676902361410672713749348
- 7374515589871295072792250899011720632358392764362903244
- 12395020783955234715731001076129344181463063193L"""),
- "q": hex(1006478751418673383937866166434285354892250535133L),
- "g": _hex("""1801778249650423365253284139284406405780267098493217
- 0320675876307450879812560049234773036938891018778074993
- 01874343843218156663689824126183823813389886834L"""),
- "y": _hex("""4148629652526876030475847300836791685289385792662680
- 5886292874741635965095055693693232436255359496594291250
- 77637642734034732001089176915352691113947372211L"""),
- "x": hex(487025797851506801093339352420308364866214860934L),
- }
+ data = DSA_KEY_DATA.copy()
key = jwt.DS128Key(data)
data.pop("x")
pubkey = jwt.DS128Key(data)
@@ -90,3 +100,49 @@ def _hex(value):
self.assertFalse(pubkey.verify("HELLO", ("\xFF" * 20) + "\x01" * 20))
# - "s" value too large
self.assertFalse(pubkey.verify("HELLO", "\x01" + ("\xFF" * 20)))
+
+ def test_loading_rsa_from_pem_data(self):
+ data = RSA_KEY_DATA.copy()
+ key = jwt.RS64Key(data)
+ try:
+ data = key.to_pem_data()
+ except NotImplementedError:
+ raise unittest.SkipTest
+ pubkey = jwt.RS64Key.from_pem_data(data)
+ self.assertTrue(pubkey.verify("hello", key.sign("hello")))
+
+ def test_loading_rsa_from_pem_data_filename(self):
+ data = RSA_KEY_DATA.copy()
+ key = jwt.RS64Key(data)
+ try:
+ data = key.to_pem_data()
+ except NotImplementedError:
+ raise unittest.SkipTest
+ with tempfile.NamedTemporaryFile() as f:
+ f.write(data)
+ f.flush()
+ pubkey = jwt.RS64Key.from_pem_data(filename=f.name)
+ self.assertTrue(pubkey.verify("hello", key.sign("hello")))
+
+ def test_loading_dsa_from_pem_data(self):
+ data = DSA_KEY_DATA.copy()
+ key = jwt.DS128Key(data)
+ try:
+ data = key.to_pem_data()
+ except NotImplementedError:
+ raise unittest.SkipTest
+ pubkey = jwt.DS128Key.from_pem_data(data)
+ self.assertTrue(pubkey.verify("hello", key.sign("hello")))
+
+ def test_loading_dsa_from_pem_data_filename(self):
+ data = DSA_KEY_DATA.copy()
+ key = jwt.DS128Key(data)
+ try:
+ data = key.to_pem_data()
+ except NotImplementedError:
+ raise unittest.SkipTest
+ with tempfile.NamedTemporaryFile() as f:
+ f.write(data)
+ f.flush()
+ pubkey = jwt.DS128Key.from_pem_data(filename=f.name)
+ self.assertTrue(pubkey.verify("hello", key.sign("hello")))
View
14 browserid/tests/test_m2_monkeypatch.py
@@ -3,8 +3,13 @@
# You can obtain one at http://mozilla.org/MPL/2.0/.
from browserid.tests.support import unittest
-from browserid.jwt import int2mpint
-import browserid._m2_monkeypatch as _m2
+
+try:
+ from browserid.crypto.m2 import int2mpint
+ import browserid.crypto._m2_monkeypatch as _m2
+ HAVE_M2CRYPTO = True
+except ImportError:
+ HAVE_M2CRYPTO = False
# Dummy RSA key for testing purposes.
@@ -28,6 +33,11 @@ def _long(value):
class TestM2MonkeyPatch(unittest.TestCase):
+ def setUp(self):
+ super(TestM2MonkeyPatch, self).setUp()
+ if not HAVE_M2CRYPTO:
+ raise unittest.SkipTest()
+
def test_setting_invalid_data_on_dsa_key(self):
k = _m2.DSA.gen_params(512)
k.gen_key()
View
2 setup.py
@@ -10,7 +10,7 @@
with open(os.path.join(here, 'CHANGES.txt')) as f:
CHANGES = f.read()
-requires = ['M2Crypto', 'requests']
+requires = ['requests', 'mock']
tests_require = requires + ['mock']

0 comments on commit 6f0d3a3

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