Skip to content

Commit a1cb19a

Browse files
author
Gasper Zejn
committed
Implement pure python rsa signing based on rsa module.
1 parent d1a1eac commit a1cb19a

File tree

4 files changed

+179
-0
lines changed

4 files changed

+179
-0
lines changed

jose/backends/rsa_backend.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import rsa as pyrsa
2+
import six
3+
4+
from jose.backends.base import Key
5+
from jose.constants import ALGORITHMS
6+
from jose.exceptions import JWKError
7+
from jose.utils import base64_to_long
8+
9+
10+
class RSAKey(Key):
11+
SHA256 = 'SHA-256'
12+
SHA384 = 'SHA-384'
13+
SHA512 = 'SHA-512'
14+
15+
def __init__(self, key, algorithm):
16+
if algorithm not in ALGORITHMS.RSA:
17+
raise JWKError('hash_alg: %s is not a valid hash algorithm' % algorithm)
18+
19+
self.hash_alg = {
20+
ALGORITHMS.RS256: self.SHA256,
21+
ALGORITHMS.RS384: self.SHA384,
22+
ALGORITHMS.RS512: self.SHA512
23+
}.get(algorithm)
24+
self._algorithm = algorithm
25+
26+
if isinstance(key, dict):
27+
self.prepared_key = self._process_jwk(key)
28+
return
29+
30+
if isinstance(key, (pyrsa.PublicKey, pyrsa.PrivateKey)):
31+
self._prepared_key = key
32+
return
33+
34+
if isinstance(key, six.string_types):
35+
key = key.encode('utf-8')
36+
37+
if isinstance(key, six.binary_type):
38+
try:
39+
self._prepared_key = pyrsa.PublicKey.load_pkcs1(key)
40+
except ValueError:
41+
try:
42+
self._prepared_key = pyrsa.PrivateKey.load_pkcs1(key)
43+
except ValueError as e:
44+
raise JWKError(e)
45+
return
46+
raise JWKError('Unable to parse an RSA_JWK from key: %s' % key)
47+
48+
def _process_jwk(self, jwk_dict):
49+
if not jwk_dict.get('kty') == 'RSA':
50+
raise JWKError("Incorrect key type. Expected: 'RSA', Recieved: %s" % jwk_dict.get('kty'))
51+
52+
e = base64_to_long(jwk_dict.get('e', 256))
53+
n = base64_to_long(jwk_dict.get('n'))
54+
55+
verifying_key = pyrsa.PublicKey(e=e, n=n)
56+
return verifying_key
57+
58+
def sign(self, msg):
59+
print(self._algorithm)
60+
return pyrsa.sign(msg, self._prepared_key, self.hash_alg)
61+
62+
def verify(self, msg, sig):
63+
try:
64+
return pyrsa.verify(msg, sig, self._prepared_key)
65+
except pyrsa.pkcs1.VerificationError:
66+
return False
67+
68+
def public_key(self):
69+
if isinstance(self._prepared_key, pyrsa.PublicKey):
70+
return self
71+
return self.__class__(pyrsa.PublicKey(n=self._prepared_key.n, e=self._prepared_key.e), self._algorithm)
72+
73+
def to_pem(self):
74+
import rsa.pem
75+
76+
if isinstance(self._prepared_key, rsa.PrivateKey):
77+
pem = self._prepared_key.save_pkcs1()
78+
else:
79+
# this is a PKCS#8 DER header to identify rsaEncryption
80+
header = b'0\x82\x04\xbd\x02\x01\x000\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00'
81+
der = self._prepared_key.save_pkcs1(format='DER')
82+
pem = rsa.pem.save_pem(header + der, pem_marker='PUBLIC KEY')
83+
return pem

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
pycrypto
22
six
33
future
4+
rsa
5+
ecdsa

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def get_install_requires():
3434
'six <2.0',
3535
'ecdsa <1.0',
3636
'future <1.0',
37+
'rsa'
3738
]
3839

3940

tests/algorithms/test_RSA.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from jose.backends.pycrypto_backend import RSAKey
55
from jose.backends.cryptography_backend import CryptographyRSAKey
6+
from jose.backends.rsa_backend import RSAKey as PurePythonRSAKey
67
from jose.constants import ALGORITHMS
78
from jose.exceptions import JOSEError, JWKError
89

@@ -185,3 +186,95 @@ def test_pycrypto_invalid_signature(self):
185186

186187
assert public_key.verify(msg, signature) == True
187188
assert public_key.verify(msg, 1) == False
189+
190+
191+
class TestPythonRSA:
192+
def test_RSA_key(self):
193+
PurePythonRSAKey(private_key, ALGORITHMS.RS256)
194+
195+
def test_RSA_key_instance(self):
196+
import rsa
197+
key = rsa.PublicKey(
198+
e=65537,
199+
n=26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819,
200+
)
201+
202+
pubkey = PurePythonRSAKey(key, ALGORITHMS.RS256)
203+
pem = pubkey.to_pem()
204+
assert pem.startswith(b'-----BEGIN PUBLIC KEY-----')
205+
206+
def test_invalid_algorithm(self):
207+
with pytest.raises(JWKError):
208+
PurePythonRSAKey(private_key, ALGORITHMS.ES256)
209+
210+
with pytest.raises(JWKError):
211+
PurePythonRSAKey({'kty': 'bla'}, ALGORITHMS.RS256)
212+
213+
def test_RSA_jwk(self):
214+
d = {
215+
"kty": "RSA",
216+
"n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",
217+
"e": "AQAB",
218+
}
219+
PurePythonRSAKey(d, ALGORITHMS.RS256)
220+
221+
def test_string_secret(self):
222+
key = 'secret'
223+
with pytest.raises(JOSEError):
224+
PurePythonRSAKey(key, ALGORITHMS.RS256)
225+
226+
def test_object(self):
227+
key = object()
228+
with pytest.raises(JOSEError):
229+
PurePythonRSAKey(key, ALGORITHMS.RS256)
230+
231+
def test_bad_cert(self):
232+
key = '-----BEGIN CERTIFICATE-----'
233+
with pytest.raises(JOSEError):
234+
PurePythonRSAKey(key, ALGORITHMS.RS256)
235+
236+
def test_get_public_key(self):
237+
key = PurePythonRSAKey(private_key, ALGORITHMS.RS256)
238+
public_key = key.public_key()
239+
public_key2 = public_key.public_key()
240+
assert public_key == public_key2
241+
242+
key = RSAKey(private_key, ALGORITHMS.RS256)
243+
public_key = key.public_key()
244+
public_key2 = public_key.public_key()
245+
assert public_key == public_key2
246+
247+
def test_to_pem(self):
248+
key = PurePythonRSAKey(private_key, ALGORITHMS.RS256)
249+
assert key.to_pem().strip() == private_key.strip()
250+
251+
key = RSAKey(private_key, ALGORITHMS.RS256)
252+
assert key.to_pem().strip() == private_key.strip()
253+
254+
def test_signing_parity(self):
255+
key1 = RSAKey(private_key, ALGORITHMS.RS256)
256+
vkey1 = key1.public_key()
257+
key2 = PurePythonRSAKey(private_key, ALGORITHMS.RS256)
258+
vkey2 = key2.public_key()
259+
260+
msg = b'test'
261+
sig1 = key1.sign(msg)
262+
sig2 = key2.sign(msg)
263+
264+
assert vkey1.verify(msg, sig1)
265+
assert vkey1.verify(msg, sig2)
266+
assert vkey2.verify(msg, sig1)
267+
assert vkey2.verify(msg, sig2)
268+
269+
# invalid signature
270+
assert not vkey2.verify(msg, b'n' * 64)
271+
272+
def test_pycrypto_invalid_signature(self):
273+
274+
key = RSAKey(private_key, ALGORITHMS.RS256)
275+
msg = b'test'
276+
signature = key.sign(msg)
277+
public_key = key.public_key()
278+
279+
assert public_key.verify(msg, signature) == True
280+
assert public_key.verify(msg, 1) == False

0 commit comments

Comments
 (0)