Skip to content

Commit 428c29c

Browse files
authored
Merge branch 'backend-explicit-tests' into fix-rsa
2 parents edc4438 + ff8a03b commit 428c29c

File tree

6 files changed

+119
-22
lines changed

6 files changed

+119
-22
lines changed

.travis.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ matrix:
1414
- python: 2.7
1515
env: TOXENV=py27-base
1616
- python: 2.7
17-
env: TOXENV=py27-cryptography
17+
env: TOXENV=py27-cryptography-only
1818
- python: 2.7
1919
env: TOXENV=py27-pycryptodome
2020
- python: 2.7
@@ -25,7 +25,7 @@ matrix:
2525
- python: 3.4
2626
env: TOXENV=py34-base
2727
- python: 3.4
28-
env: TOXENV=py34-cryptography
28+
env: TOXENV=py34-cryptography-only
2929
- python: 3.4
3030
env: TOXENV=py34-pycryptodome
3131
- python: 3.4
@@ -36,7 +36,7 @@ matrix:
3636
- python: 3.5
3737
env: TOXENV=py35-base
3838
- python: 3.5
39-
env: TOXENV=py35-cryptography
39+
env: TOXENV=py35-cryptography-only
4040
- python: 3.5
4141
env: TOXENV=py35-pycryptodome
4242
- python: 3.5
@@ -47,7 +47,7 @@ matrix:
4747
- python: 3.5
4848
env: TOXENV=py35-base
4949
- python: 3.5
50-
env: TOXENV=py35-cryptography
50+
env: TOXENV=py35-cryptography-only
5151
- python: 3.5
5252
env: TOXENV=py35-pycryptodome
5353
- python: 3.5
@@ -58,7 +58,7 @@ matrix:
5858
- python: pypy-5.3.1
5959
env: TOXENV=pypy-base
6060
- python: pypy-5.3.1
61-
env: TOXENV=pypy-cryptography
61+
env: TOXENV=pypy-cryptography-only
6262
- python: pypy-5.3.1
6363
env: TOXENV=pypy-pycryptodome
6464
- python: pypy-5.3.1

jose/backends/cryptography_backend.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
1+
from __future__ import division
2+
3+
import math
4+
15
import six
2-
import ecdsa
3-
from ecdsa.util import sigdecode_string, sigencode_string, sigdecode_der, sigencode_der
6+
7+
try:
8+
from ecdsa import SigningKey as EcdsaSigningKey, VerifyingKey as EcdsaVerifyingKey
9+
except ImportError:
10+
EcdsaSigningKey = EcdsaVerifyingKey = None
411

512
from jose.backends.base import Key
613
from jose.utils import base64_to_long, long_to_base64
@@ -11,7 +18,9 @@
1118
from cryptography.hazmat.backends import default_backend
1219
from cryptography.hazmat.primitives import hashes, serialization
1320
from cryptography.hazmat.primitives.asymmetric import ec, rsa, padding
21+
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature, encode_dss_signature
1422
from cryptography.hazmat.primitives.serialization import load_pem_private_key, load_pem_public_key
23+
from cryptography.utils import int_from_bytes, int_to_bytes
1524
from cryptography.x509 import load_pem_x509_certificate
1625

1726

@@ -37,7 +46,7 @@ def __init__(self, key, algorithm, cryptography_backend=default_backend):
3746
self.prepared_key = key
3847
return
3948

40-
if isinstance(key, (ecdsa.SigningKey, ecdsa.VerifyingKey)):
49+
if None not in (EcdsaSigningKey, EcdsaVerifyingKey) and isinstance(key, (EcdsaSigningKey, EcdsaVerifyingKey)):
4150
# convert to PEM and let cryptography below load it as PEM
4251
key = key.to_pem().decode('utf-8')
4352

@@ -90,19 +99,42 @@ def _process_jwk(self, jwk_dict):
9099
else:
91100
return public.public_key(self.cryptography_backend())
92101

102+
def _sig_component_length(self):
103+
"""Determine the correct serialization length for an encoded signature component.
104+
105+
This is the number of bytes required to encode the maximum key value.
106+
"""
107+
return int(math.ceil(self.prepared_key.key_size / 8.0))
108+
109+
def _der_to_raw(self, der_signature):
110+
"""Convert signature from DER encoding to RAW encoding."""
111+
r, s = decode_dss_signature(der_signature)
112+
component_length = self._sig_component_length()
113+
return int_to_bytes(r, component_length) + int_to_bytes(s, component_length)
114+
115+
def _raw_to_der(self, raw_signature):
116+
"""Convert signature from RAW encoding to DER encoding."""
117+
component_length = self._sig_component_length()
118+
if len(raw_signature) != int(2 * component_length):
119+
raise ValueError("Invalid signature")
120+
121+
r_bytes = raw_signature[:component_length]
122+
s_bytes = raw_signature[component_length:]
123+
r = int_from_bytes(r_bytes, "big")
124+
s = int_from_bytes(s_bytes, "big")
125+
return encode_dss_signature(r, s)
126+
93127
def sign(self, msg):
94128
if self.hash_alg.digest_size * 8 > self.prepared_key.curve.key_size:
95129
raise TypeError("this curve (%s) is too short "
96130
"for your digest (%d)" % (self.prepared_key.curve.name,
97131
8 * self.hash_alg.digest_size))
98132
signature = self.prepared_key.sign(msg, ec.ECDSA(self.hash_alg()))
99-
order = (2 ** self.prepared_key.curve.key_size) - 1
100-
return sigencode_string(*sigdecode_der(signature, order), order=order)
133+
return self._der_to_raw(signature)
101134

102135
def verify(self, msg, sig):
103-
order = (2 ** self.prepared_key.curve.key_size) - 1
104-
signature = sigencode_der(*sigdecode_string(sig, order), order=order)
105136
try:
137+
signature = self._raw_to_der(sig)
106138
self.prepared_key.verify(signature, msg, ec.ECDSA(self.hash_alg()))
107139
return True
108140
except Exception:

tests/algorithms/test_EC.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111

1212
try:
1313
from jose.backends.cryptography_backend import CryptographyECKey
14+
from cryptography.hazmat.primitives.asymmetric import ec as CryptographyEc
15+
from cryptography.hazmat.backends import default_backend as CryptographyBackend
1416
except ImportError:
15-
CryptographyECKey = None
17+
CryptographyECKey = CryptographyEc = CryptographyBackend = None
1618

1719
import pytest
1820

@@ -31,6 +33,18 @@
3133
-----END EC PRIVATE KEY-----
3234
"""
3335

36+
# ES256 signatures generated to test conversion logic
37+
DER_SIGNATURE = (
38+
b"0F\x02!\x00\x89yG\x81W\x01\x11\x9b0\x08\xa4\xd0\xe3g([\x07\xb5\x01\xb3"
39+
b"\x9d\xdf \xd1\xbc\xedK\x01\x87:}\xf2\x02!\x00\xb2shTA\x00\x1a\x13~\xba"
40+
b"J\xdb\xeem\x12\x1e\xfeMO\x04\xb2[\x86A\xbd\xc6hu\x953X\x1e"
41+
)
42+
RAW_SIGNATURE = (
43+
b"\x89yG\x81W\x01\x11\x9b0\x08\xa4\xd0\xe3g([\x07\xb5\x01\xb3\x9d\xdf "
44+
b"\xd1\xbc\xedK\x01\x87:}\xf2\xb2shTA\x00\x1a\x13~\xbaJ\xdb\xeem\x12\x1e"
45+
b"\xfeMO\x04\xb2[\x86A\xbd\xc6hu\x953X\x1e"
46+
)
47+
3448

3549
def _backend_exception_types():
3650
"""Build the backend exception types based on available backends."""
@@ -51,6 +65,41 @@ def test_key_from_ecdsa():
5165
assert not ECKey(key, ALGORITHMS.ES256).is_public()
5266

5367

68+
@pytest.mark.cryptography
69+
@pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available")
70+
@pytest.mark.parametrize("algorithm, expected_length", (
71+
(ALGORITHMS.ES256, 32),
72+
(ALGORITHMS.ES384, 48),
73+
(ALGORITHMS.ES512, 66)
74+
))
75+
def test_cryptography_sig_component_length(algorithm, expected_length):
76+
# Put mapping inside here to avoid more complex handling for test runs that do not have pyca/cryptography
77+
mapping = {
78+
ALGORITHMS.ES256: CryptographyEc.SECP256R1,
79+
ALGORITHMS.ES384: CryptographyEc.SECP384R1,
80+
ALGORITHMS.ES512: CryptographyEc.SECP521R1,
81+
}
82+
key = CryptographyECKey(
83+
CryptographyEc.generate_private_key(mapping[algorithm](), backend=CryptographyBackend()),
84+
algorithm
85+
)
86+
assert key._sig_component_length() == expected_length
87+
88+
89+
@pytest.mark.cryptography
90+
@pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available")
91+
def test_cryptograhy_der_to_raw():
92+
key = CryptographyECKey(private_key, ALGORITHMS.ES256)
93+
assert key._der_to_raw(DER_SIGNATURE) == RAW_SIGNATURE
94+
95+
96+
@pytest.mark.cryptography
97+
@pytest.mark.skipif(CryptographyECKey is None, reason="pyca/cryptography backend not available")
98+
def test_cryptograhy_raw_to_der():
99+
key = CryptographyECKey(private_key, ALGORITHMS.ES256)
100+
assert key._raw_to_der(RAW_SIGNATURE) == DER_SIGNATURE
101+
102+
54103
class TestECAlgorithm:
55104

56105
def test_key_from_pem(self):

tests/algorithms/test_RSA.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,14 @@
1111
from Crypto.PublicKey import RSA as PyCryptoRSA
1212
from jose.backends.pycrypto_backend import RSAKey as PyCryptoRSAKey
1313
except ImportError:
14-
PyCryptoRSA = None
14+
PyCryptoRSA = PyCryptoRSAKey = None
1515

1616
try:
1717
from cryptography.hazmat.backends import default_backend
18-
from cryptography.hazmat.primitives.asymmetric import rsa
18+
from cryptography.hazmat.primitives.asymmetric import rsa as pyca_rsa
19+
from jose.backends.cryptography_backend import CryptographyRSAKey
1920
except ImportError:
20-
default_backend = rsa = None
21+
default_backend = pyca_rsa = CryptographyRSAKey = None
2122

2223
from jose.backends import RSAKey
2324
from jose.constants import ALGORITHMS
@@ -332,12 +333,12 @@ def test_python_rsa_private_key_pkcs8_to_pkcs1(self):
332333

333334
@pytest.mark.pycrypto
334335
@pytest.mark.pycryptodome
335-
@pytest.mark.skipif(PyCryptoRSA is None, reason="Pycrypto/dome backend not available")
336+
@pytest.mark.skipif(None in (PyCryptoRSA, PyCryptoRSAKey), reason="Pycrypto/dome backend not available")
336337
def test_pycrypto_RSA_key_instance():
337338
key = PyCryptoRSA.construct((long(
338339
26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819),
339340
long(65537)))
340-
RSAKey(key, ALGORITHMS.RS256)
341+
PyCryptoRSAKey(key, ALGORITHMS.RS256)
341342

342343

343344
# TODO: Unclear why this test was marked as only for pycrypto
@@ -356,17 +357,17 @@ def test_pycrypto_unencoded_cleartext(private_key):
356357

357358
@pytest.mark.cryptography
358359
@pytest.mark.skipif(
359-
None in (default_backend, rsa),
360+
None in (default_backend, pyca_rsa, CryptographyRSAKey),
360361
reason="Cryptography backend not available"
361362
)
362363
def test_cryptography_RSA_key_instance():
363364

364-
key = rsa.RSAPublicNumbers(
365+
key = pyca_rsa.RSAPublicNumbers(
365366
long(65537),
366367
long(26057131595212989515105618545799160306093557851986992545257129318694524535510983041068168825614868056510242030438003863929818932202262132630250203397069801217463517914103389095129323580576852108653940669240896817348477800490303630912852266209307160550655497615975529276169196271699168537716821419779900117025818140018436554173242441334827711966499484119233207097432165756707507563413323850255548329534279691658369466534587631102538061857114141268972476680597988266772849780811214198186940677291891818952682545840788356616771009013059992237747149380197028452160324144544057074406611859615973035412993832273216732343819),
367368
).public_key(default_backend())
368369

369-
pubkey = RSAKey(key, ALGORITHMS.RS256)
370+
pubkey = CryptographyRSAKey(key, ALGORITHMS.RS256)
370371
assert pubkey.is_public()
371372

372373
pem = pubkey.to_pem()

tests/test_firebase.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,16 @@
11

22
import json
33

4+
import pytest
5+
46
from jose import jwt
57

8+
from jose.backends import RSAKey
9+
try:
10+
from jose.backends.rsa_backend import RSAKey as RsaRSAKey
11+
except ImportError:
12+
RsaRSAKey = None
13+
614
firebase_certs = {
715
"6f83ab6e516e718fba9ddeb6647fd5fb752a151b": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIIP5V2bjX2bXUwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTYw\nODMxMDA0NTI2WhcNMTYwOTAzMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAKHHtOMXBD+0YTtZHuzFrERiiwa+D6Ybq4SUHlicgRPV3Uk2\nvnTOqg1EhxshEXqjkAQbbRop9hhHTc+p8rBxgYGuLcZsBhGrnRqU6FnTTiWB1x5V\nvOfCkPE60W07gi8p+HyB8cqw1Tz2LnRUw/15888CrspVeumtNUkhXSRKzeS2BI4l\nkuOMkqmsMSu1yB5IZm5meMyta1uhJnP93jKmdar19RkZXOlFcT+fsSY2FPuqvDvX\nssChgZgNV5qtk0CIzexmFJaUFzpKE/RxqdIJooB1H83fUBGVK+9v3Ko+BI+GEvUc\nxIGAEWu2KrbjwPNzzC3/UV9aSfHEOJxQoutPviECAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBAIHOiqxXm1IcuXE87ELyKYDG0/gZPzCHz98h/x0LExrs\nd0bOYOIA08rt6qllmP24oT3hQt86HmDb932pm/fjaLL68x81TjYq6cFO0JxOzts+\nY+9XxkdP8Qu7UJ8Dx+rRvDN1MUxLTvBVXdamhkhDusx7PB5kK1ixWtf91qrl/J9e\nUYQBnJ4E9wI8U5HVkW3IBWvsFt/+gMO1EcoNBdB2cY/4N3l3oxm5PSNDS4DTEs2f\nAYZDqo6PJt2tTRGSmvLBKSCqcT7eWBbIwBht3Uw8CvOMbVYGBWjbFeua3Q3fe+p7\n7UbFOLIvSGR516kyZqxy9pLoA9+2TvbpYwWu6mLCZtg=\n-----END CERTIFICATE-----\n",
816
"fc2da7fa53d92e3bcba8a17e74b34da9dd585065": "-----BEGIN CERTIFICATE-----\nMIIDHDCCAgSgAwIBAgIINfZYQW9uekMwDQYJKoZIhvcNAQEFBQAwMTEvMC0GA1UE\nAxMmc2VjdXJldG9rZW4uc3lzdGVtLmdzZXJ2aWNlYWNjb3VudC5jb20wHhcNMTYw\nODI5MDA0NTI2WhcNMTYwOTAxMDExNTI2WjAxMS8wLQYDVQQDEyZzZWN1cmV0b2tl\nbi5zeXN0ZW0uZ3NlcnZpY2VhY2NvdW50LmNvbTCCASIwDQYJKoZIhvcNAQEBBQAD\nggEPADCCAQoCggEBAMvfJ5DY7lV4txW0zn9ayMxwAp5BzUhyIbuZkmsmMLRrNl+i\nid4lawojB846YtcTPZLD/5QpXRumAAUI5NA023fxaUdriM25zewpSnZWs6eUf0O6\nONES8Xk4WD2fbyPz6cgnsFEfMslNd3NypRiB9fVG6LFj6TFHC64o/YEeQB2dwkJZ\nXknKSEkFJSRC83TiHUlWzaRjmTdGRrvGEWHxr+xJltP8tPPlJUKu2VadgMbGlkKU\n5dBRhvWwZZW0zJupuKzd27O2lPkxfbx9vrUbsfqZcN4OY5Xg+ijQJVTv0/qcplsd\nPZ9Uui0QsBOPbrIO+5/Tq9FIBqxzUlpWwetv6pMCAwEAAaM4MDYwDAYDVR0TAQH/\nBAIwADAOBgNVHQ8BAf8EBAMCB4AwFgYDVR0lAQH/BAwwCgYIKwYBBQUHAwIwDQYJ\nKoZIhvcNAQEFBQADggEBALqWwzIQSK94hxTmxlA+RoyMvb8fyTcECM2qY+n+PDb5\nMvt8zqM6AwGjK1hvcUg08BEsnqqRqC81dkSEReS9KCoTY/oQ0sCCpwL3QP3puoxp\nfZU9CSwvnrFTJjC2Q/b8BlWta4CSDwpxpy/K3wm6tRn5ED4rPcP4FRqWU5jyHiug\nRrNkKiG7TeBBvQ3ZlF9K4JSx1yn9g7EvPBcmygop5FIKI1uS+URxeyavtlwfnTTs\nDtRVV/x0LDkHoJ2Agy7l2MqT7eoRKh5VNucQONLrcZT1AY02eZi/WVSjgpzC48eP\nV9xlcgIaRbS/JDULYgW5h0uVdRNqSVGJ6yBLXT2uaBA=\n-----END CERTIFICATE-----\n",
@@ -14,6 +22,7 @@
1422
firebase_token = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImY0YjBhNWM3M2FkODVhNWRhMDlmMGU3Zjc2NDYzNjMxMzM5ZTBiYmYifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vd2Vkb3RyYW5zZmVyLTIwMTYiLCJhdWQiOiJ3ZWRvdHJhbnNmZXItMjAxNiIsImF1dGhfdGltZSI6MTQ2NzM0NjI3MCwidXNlcl9pZCI6IjRjemVXVllIekNNVnN0WEZOYldHVXBKYmJTZzEiLCJzdWIiOiI0Y3plV1ZZSHpDTVZzdFhGTmJXR1VwSmJiU2cxIiwiaWF0IjoxNDY3MzQ2MjcwLCJleHAiOjE0NjczNDk4NzAsImVtYWlsIjoic2V1bkBjbXUuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJmaXJlYmFzZSI6eyJpZGVudGl0aWVzIjp7InBhc3N3b3JkIjpbInNldW5AY211LmNvbSJdLCJlbWFpbCI6WyJzZXVuQGNtdS5jb20iXX19fQ.U-fYjx8rMm5tYV24r0uEcNQtIe3UKULxsHecLdGzTbi1v-VKzKDk_QPL26SPDoU8JUMY3nJQ1hOE9AapBrQck8NVUZSKFMD49XdtsyoN2kKdinpFR1hSxIE0L2dRStS7OZ8sGiX866lNa52Cr6TXSsnMD6N2P0OtVE5EeD1Nf-AiJ-gsaLrP4tBnmj1MNYhEYVHb6sAUrT3nEI9gWmeKcPWPfn76FGTdGWZ2mjdaeAG4RbuFL4cHdOISA_0HVLGJxuNyEHAHybDX8mVdNW_F4yzL3H-SmPFY5Kv3tCdBzpzhUKfNOnFFmf2ggFOJnDsqMp-TZaIPk6ce_ltqhQ0dnQ"
1523

1624

25+
@pytest.mark.skipif(RSAKey is RsaRSAKey, reason="python-rsa backend does not support certificates")
1726
class TestFirebase:
1827

1928
def test_individual_cert(self):

tox.ini

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
[tox]
2-
envlist = py{27,34,35,36,py}-{base,cryptography,pycryptodome,pycrypto,compatibility},flake8
2+
minversion = 3.4.0
3+
envlist =
4+
py{27,34,35,36,py}-{base,cryptography-only,pycryptodome,pycrypto,compatibility},
5+
flake8
36
skip_missing_interpreters = True
47

58
[testenv:basecommand]
@@ -19,6 +22,9 @@ deps =
1922
pytest-cov
2023
pytest-runner
2124
compatibility: {[testenv:compatibility]deps}
25+
commands_pre =
26+
# Remove the python-rsa backend
27+
only: pip uninstall -y ecdsa rsa
2228
commands =
2329
# Test the python-rsa backend
2430
base: {[testenv:basecommand]commands} -m "not (cryptography or pycryptodome or pycrypto or backend_compatibility)"

0 commit comments

Comments
 (0)