Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cryptographic backend isolation tests #109

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,12 @@ docs/_build/

# PyBuilder
target/

# PyCharm
.idea/

# PyEnv
.python-version

# PyTest
.pytest_cache/
67 changes: 58 additions & 9 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,70 @@
# detail: https://blog.travis-ci.com/2017-06-21-trusty-updates-2017-Q2-launch
dist: precise
language: python
python:
- "2.7"
- "3.4"
- "3.5"
- "3.6"
- "pypy-5.3.1"
install:
- pip install -U setuptools && pip install -U tox codecov tox-travis
script:
- tox
after_success:
- codecov
matrix:
include:
# CPython 2.7
- python: 2.7
env: TOXENV=py27-base
- python: 2.7
env: TOXENV=py27-cryptography
- python: 2.7
env: TOXENV=py27-pycryptodome
- python: 2.7
env: TOXENV=py27-pycrypto
- python: 2.7
env: TOXENV=py27-compatibility
# CPython 3.4
- python: 3.4
env: TOXENV=py34-base
- python: 3.4
env: TOXENV=py34-cryptography
- python: 3.4
env: TOXENV=py34-pycryptodome
- python: 3.4
env: TOXENV=py34-pycrypto
- python: 3.4
env: TOXENV=py34-compatibility
# CPython 3.5
- python: 3.5
env: TOXENV=py35-base
- python: 3.5
env: TOXENV=py35-cryptography
- python: 3.5
env: TOXENV=py35-pycryptodome
- python: 3.5
env: TOXENV=py35-pycrypto
- python: 3.5
env: TOXENV=py35-compatibility
# CPython 3.5
- python: 3.5
env: TOXENV=py35-base
- python: 3.5
env: TOXENV=py35-cryptography
- python: 3.5
env: TOXENV=py35-pycryptodome
- python: 3.5
env: TOXENV=py35-pycrypto
- python: 3.5
env: TOXENV=py35-compatibility
# PyPy 5.3.1
- python: pypy-5.3.1
env: TOXENV=pypy-base
- python: pypy-5.3.1
env: TOXENV=pypy-cryptography
- python: pypy-5.3.1
env: TOXENV=pypy-pycryptodome
- python: pypy-5.3.1
env: TOXENV=pypy-pycrypto
- python: pypy-5.3.1
env: TOXENV=pypy-compatibility
# matrix:
# include:
# - python: 3.6
# env:
# - TOX_ENV=flake8
# script: tox -e $TOX_ENV
# env: TOX_ENV=flake8
4 changes: 2 additions & 2 deletions jose/backends/cryptography_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def __init__(self, key, algorithm, cryptography_backend=default_backend):

def _process_jwk(self, jwk_dict):
if not jwk_dict.get('kty') == 'EC':
raise JWKError("Incorrect key type. Expected: 'EC', Recieved: %s" % jwk_dict.get('kty'))
raise JWKError("Incorrect key type. Expected: 'EC', Received: %s" % jwk_dict.get('kty'))

if not all(k in jwk_dict for k in ['x', 'y', 'crv']):
raise JWKError('Mandatory parameters are missing')
Expand Down Expand Up @@ -212,7 +212,7 @@ def __init__(self, key, algorithm, cryptography_backend=default_backend):

def _process_jwk(self, jwk_dict):
if not jwk_dict.get('kty') == 'RSA':
raise JWKError("Incorrect key type. Expected: 'RSA', Recieved: %s" % jwk_dict.get('kty'))
raise JWKError("Incorrect key type. Expected: 'RSA', Received: %s" % jwk_dict.get('kty'))

e = base64_to_long(jwk_dict.get('e', 256))
n = base64_to_long(jwk_dict.get('n'))
Expand Down
1 change: 0 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@ def get_packages(package):
'pytest',
'pytest-cov',
'pytest-runner',
'cryptography',
],
install_requires=['six <2.0', 'ecdsa <1.0', 'rsa', 'future <1.0']
)
128 changes: 64 additions & 64 deletions tests/algorithms/test_EC.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,18 @@
from jose.constants import ALGORITHMS
from jose.exceptions import JOSEError, JWKError

from jose.backends.ecdsa_backend import ECDSAECKey
from jose.backends.cryptography_backend import CryptographyECKey
from jose.backends import ECKey
try:
from jose.backends.ecdsa_backend import ECDSAECKey
import ecdsa
except ImportError:
ECDSAECKey = ecdsa = None

try:
from jose.backends.cryptography_backend import CryptographyECKey
except ImportError:
CryptographyECKey = None

import ecdsa
import pytest

private_key = """-----BEGIN EC PRIVATE KEY-----
Expand All @@ -14,69 +22,78 @@
WkG0HJWIORlPbvXME+DRh6G/yVOKnTm88Q==
-----END EC PRIVATE KEY-----"""

# Private key generated using NIST256p curve
TOO_SHORT_PRIVATE_KEY = b"""\
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIMlUyYGOpjV4bbW0C9FKS2zkspD0L/5vJLnr6sJoLdc+oAoGCCqGSM49
AwEHoUQDQgAE6TDUNj5QXl+RKdZvBV+cg7Td6cJRB+Ta8XAhIuCAzonq0Ix//1+C
pNSsy11sIKmMl61YJzxvZ6WkNluBmkDPCQ==
-----END EC PRIVATE KEY-----
"""


def _backend_exception_types():
"""Build the backend exception types based on available backends."""
if None not in (ECDSAECKey, ecdsa):
yield ECDSAECKey, ecdsa.BadDigestError

if CryptographyECKey is not None:
yield CryptographyECKey, TypeError

class TestECAlgorithm:

@pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey])
def test_key_from_pem(self, Backend):
assert not Backend(private_key, ALGORITHMS.ES256).is_public()
@pytest.mark.ecdsa
@pytest.mark.skipif(
None in (ECDSAECKey, ecdsa),
reason="python-ecdsa backend not available"
)
def test_key_from_ecdsa():
key = ecdsa.SigningKey.from_pem(private_key)
assert not ECKey(key, ALGORITHMS.ES256).is_public()


class TestECAlgorithm:

@pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey])
def test_key_from_ecdsa(self, Backend):
key = ecdsa.SigningKey.from_pem(private_key)
assert not Backend(key, ALGORITHMS.ES256).is_public()
def test_key_from_pem(self):
assert not ECKey(private_key, ALGORITHMS.ES256).is_public()

@pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey])
def test_to_pem(self, Backend):
key = Backend(private_key, ALGORITHMS.ES256)
def test_to_pem(self):
key = ECKey(private_key, ALGORITHMS.ES256)
assert not key.is_public()
assert key.to_pem().strip() == private_key.strip().encode('utf-8')

public_pem = key.public_key().to_pem()
assert Backend(public_pem, ALGORITHMS.ES256).is_public()

@pytest.mark.parametrize(
"Backend,ExceptionType",
[
(ECDSAECKey, ecdsa.BadDigestError),
(CryptographyECKey, TypeError)
]
)
assert ECKey(public_pem, ALGORITHMS.ES256).is_public()

@pytest.mark.parametrize("Backend,ExceptionType", _backend_exception_types())
def test_key_too_short(self, Backend, ExceptionType):
priv_key = ecdsa.SigningKey.generate(curve=ecdsa.NIST256p).to_pem()
key = Backend(priv_key, ALGORITHMS.ES512)
key = Backend(TOO_SHORT_PRIVATE_KEY, ALGORITHMS.ES512)
with pytest.raises(ExceptionType):
key.sign(b'foo')

@pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey])
def test_get_public_key(self, Backend):
key = Backend(private_key, ALGORITHMS.ES256)
def test_get_public_key(self):
key = ECKey(private_key, ALGORITHMS.ES256)
pubkey = key.public_key()
pubkey2 = pubkey.public_key()
assert pubkey == pubkey2

@pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey])
def test_string_secret(self, Backend):
def test_string_secret(self):
key = 'secret'
with pytest.raises(JOSEError):
Backend(key, ALGORITHMS.ES256)
ECKey(key, ALGORITHMS.ES256)

@pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey])
def test_object(self, Backend):
def test_object(self):
key = object()
with pytest.raises(JOSEError):
Backend(key, ALGORITHMS.ES256)
ECKey(key, ALGORITHMS.ES256)

@pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey])
def test_invalid_algorithm(self, Backend):
def test_invalid_algorithm(self):
with pytest.raises(JWKError):
Backend(private_key, 'nonexistent')
ECKey(private_key, 'nonexistent')

with pytest.raises(JWKError):
Backend({'kty': 'bla'}, ALGORITHMS.ES256)
ECKey({'kty': 'bla'}, ALGORITHMS.ES256)

@pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey])
def test_EC_jwk(self, Backend):
def test_EC_jwk(self):
key = {
"kty": "EC",
"kid": "bilbo.baggins@hobbiton.example",
Expand All @@ -87,22 +104,21 @@ def test_EC_jwk(self, Backend):
"d": "AAhRON2r9cqXX1hg-RoI6R1tX5p2rUAYdmpHZoC1XNM56KtscrX6zbKipQrCW9CGZH3T4ubpnoTKLDYJ_fF3_rJt",
}

assert not Backend(key, ALGORITHMS.ES512).is_public()
assert not ECKey(key, ALGORITHMS.ES512).is_public()

del key['d']

# We are now dealing with a public key.
assert Backend(key, ALGORITHMS.ES512).is_public()
assert ECKey(key, ALGORITHMS.ES512).is_public()

del key['x']

# This key is missing a required parameter.
with pytest.raises(JWKError):
Backend(key, ALGORITHMS.ES512)
ECKey(key, ALGORITHMS.ES512)

@pytest.mark.parametrize("Backend", [ECDSAECKey])
def test_verify(self, Backend):
key = Backend(private_key, ALGORITHMS.ES256)
def test_verify(self):
key = ECKey(private_key, ALGORITHMS.ES256)
msg = b'test'
signature = key.sign(msg)
public_key = key.public_key()
Expand All @@ -129,23 +145,7 @@ def assert_parameters(self, as_dict, private):
# Private parameters should be absent
assert 'd' not in as_dict

@pytest.mark.parametrize("Backend", [ECDSAECKey, CryptographyECKey])
def test_to_dict(self, Backend):
key = Backend(private_key, ALGORITHMS.ES256)
def test_to_dict(self):
key = ECKey(private_key, ALGORITHMS.ES256)
self.assert_parameters(key.to_dict(), private=True)
self.assert_parameters(key.public_key().to_dict(), private=False)

@pytest.mark.parametrize("BackendSign", [ECDSAECKey, CryptographyECKey])
@pytest.mark.parametrize("BackendVerify", [ECDSAECKey, CryptographyECKey])
def test_signing_parity(self, BackendSign, BackendVerify):
key_sign = BackendSign(private_key, ALGORITHMS.ES256)
key_verify = BackendVerify(private_key, ALGORITHMS.ES256).public_key()

msg = b'test'
sig = key_sign.sign(msg)

# valid signature
assert key_verify.verify(msg, sig)

# invalid signature
assert not key_verify.verify(msg, b'n' * 64)
33 changes: 33 additions & 0 deletions tests/algorithms/test_EC_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import pytest

try:
from jose.backends.ecdsa_backend import ECDSAECKey
from jose.backends.cryptography_backend import CryptographyECKey
except ImportError:
ECDSAECKey = CryptographyECKey = None
from jose.constants import ALGORITHMS

from .test_EC import private_key


@pytest.mark.backend_compatibility
@pytest.mark.skipif(
None in (ECDSAECKey, CryptographyECKey),
reason="Multiple crypto backends not available for backend compatibility tests"
)
class TestBackendRsaCompatibility(object):

@pytest.mark.parametrize("BackendSign", [ECDSAECKey, CryptographyECKey])
@pytest.mark.parametrize("BackendVerify", [ECDSAECKey, CryptographyECKey])
def test_signing_parity(self, BackendSign, BackendVerify):
key_sign = BackendSign(private_key, ALGORITHMS.ES256)
key_verify = BackendVerify(private_key, ALGORITHMS.ES256).public_key()

msg = b'test'
sig = key_sign.sign(msg)

# valid signature
assert key_verify.verify(msg, sig)

# invalid signature
assert not key_verify.verify(msg, b'n' * 64)
Loading