aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml2
-rwxr-xr-x.travis/upload_coverage.sh10
-rw-r--r--CHANGELOG.rst6
-rw-r--r--docs/hazmat/primitives/asymmetric/serialization.rst4
-rw-r--r--docs/index.rst2
-rw-r--r--docs/x509/index.rst14
-rw-r--r--docs/x509/reference.rst (renamed from docs/x509.rst)10
-rw-r--r--src/_cffi_src/openssl/asn1.py1
-rw-r--r--src/_cffi_src/openssl/x509v3.py3
-rw-r--r--src/cryptography/hazmat/backends/openssl/backend.py22
-rw-r--r--src/cryptography/hazmat/backends/openssl/x509.py31
-rw-r--r--src/cryptography/hazmat/primitives/asymmetric/rsa.py14
-rw-r--r--src/cryptography/x509.py19
-rw-r--r--tests/conftest.py6
-rw-r--r--tests/hazmat/primitives/test_rsa.py16
-rw-r--r--tests/test_x509.py27
-rw-r--r--tests/test_x509_ext.py72
17 files changed, 236 insertions, 23 deletions
diff --git a/.travis.yml b/.travis.yml
index 8ad514fd..b8206e03 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -120,7 +120,7 @@ script:
- ./.travis/run.sh
after_success:
- - source ~/.venv/bin/activate && bash <(curl -s https://codecov.io/bash) -e TRAVIS_OS_NAME,TOXENV,OPENSSL
+ - ./.travis/upload_coverage.sh
notifications:
irc:
diff --git a/.travis/upload_coverage.sh b/.travis/upload_coverage.sh
new file mode 100755
index 00000000..554116f7
--- /dev/null
+++ b/.travis/upload_coverage.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+set -e
+set -x
+
+NO_COVERAGE_TOXENVS=(pypy pypy3 pep8 py3pep8 docs)
+if ! [[ "${NO_COVERAGE_TOXENVS[*]}" =~ "${TOXENV}" ]]; then
+ source ~/.venv/bin/activate
+ bash <(curl -s https://codecov.io/bash) -e TRAVIS_OS_NAME,TOXENV,OPENSSL
+fi
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 475a2a35..85f84477 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -93,7 +93,7 @@ Changelog
Note that unsupported extensions with the critical flag raise
:class:`~cryptography.x509.UnsupportedExtension` while unsupported extensions
set to non-critical are silently ignored. Read the
- :doc:`X.509 documentation</x509>` for more information.
+ :doc:`X.509 documentation</x509/index>` for more information.
0.8.2 - 2015-04-10
~~~~~~~~~~~~~~~~~~
@@ -120,7 +120,7 @@ Changelog
from :mod:`~cryptography.hazmat.primitives.interfaces` to
:mod:`~cryptography.hazmat.primitives.kdf`.
* Added support for parsing X.509 names. See the
- :doc:`X.509 documentation</x509>` for more information.
+ :doc:`X.509 documentation</x509/index>` for more information.
* Added
:func:`~cryptography.hazmat.primitives.serialization.load_der_private_key` to
support loading of DER encoded private keys and
@@ -256,7 +256,7 @@ Changelog
support the loading of OpenSSH public keys (:rfc:`4253`). Only RSA and DSA
keys are currently supported.
* Added initial support for X.509 certificate parsing. See the
- :doc:`X.509 documentation</x509>` for more information.
+ :doc:`X.509 documentation</x509/index>` for more information.
0.6.1 - 2014-10-15
~~~~~~~~~~~~~~~~~~
diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst
index 7839f346..8d51f0d7 100644
--- a/docs/hazmat/primitives/asymmetric/serialization.rst
+++ b/docs/hazmat/primitives/asymmetric/serialization.rst
@@ -97,8 +97,8 @@ all begin with ``-----BEGIN {format}-----`` and end with ``-----END
.. note::
A PEM block which starts with ``-----BEGIN CERTIFICATE-----`` is not a
- public or private key, it's an :doc:`X.509 Certificate </x509>`. You can
- load it using :func:`~cryptography.x509.load_pem_x509_certificate` and
+ public or private key, it's an :doc:`X.509 Certificate </x509/index>`. You
+ can load it using :func:`~cryptography.x509.load_pem_x509_certificate` and
extract the public key with
:meth:`Certificate.public_key <cryptography.x509.Certificate.public_key>`.
diff --git a/docs/index.rst b/docs/index.rst
index 35f80a2d..5c26a754 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -63,7 +63,7 @@ The recipes layer
:maxdepth: 2
fernet
- x509
+ x509/index
random-numbers
exceptions
faq
diff --git a/docs/x509/index.rst b/docs/x509/index.rst
new file mode 100644
index 00000000..c3fa1ed2
--- /dev/null
+++ b/docs/x509/index.rst
@@ -0,0 +1,14 @@
+X.509
+=====
+
+X.509 is an ITU-T standard for a `public key infrastructure`_. X.509v3 is
+defined in :rfc:`5280` (which obsoletes :rfc:`2459` and :rfc:`3280`). X.509
+certificates are commonly used in protocols like `TLS`_.
+
+.. toctree::
+ :maxdepth: 2
+
+ reference
+
+.. _`public key infrastructure`: https://en.wikipedia.org/wiki/Public_key_infrastructure
+.. _`TLS`: https://en.wikipedia.org/wiki/Transport_Layer_Security
diff --git a/docs/x509.rst b/docs/x509/reference.rst
index bcb6ee66..9179468f 100644
--- a/docs/x509.rst
+++ b/docs/x509/reference.rst
@@ -1,5 +1,5 @@
-X.509
-=====
+X.509 Reference
+===============
.. currentmodule:: cryptography.x509
@@ -86,10 +86,6 @@ X.509
-----END CERTIFICATE-----
""".strip()
-X.509 is an ITU-T standard for a `public key infrastructure`_. X.509v3 is
-defined in :rfc:`5280` (which obsoletes :rfc:`2459` and :rfc:`3280`). X.509
-certificates are commonly used in protocols like `TLS`_.
-
Loading Certificates
~~~~~~~~~~~~~~~~~~~~
@@ -1582,7 +1578,5 @@ Exceptions
types can be found in `RFC 5280 section 4.2.1.6`_.
-.. _`public key infrastructure`: https://en.wikipedia.org/wiki/Public_key_infrastructure
-.. _`TLS`: https://en.wikipedia.org/wiki/Transport_Layer_Security
.. _`RFC 5280 section 4.2.1.1`: https://tools.ietf.org/html/rfc5280#section-4.2.1.1
.. _`RFC 5280 section 4.2.1.6`: https://tools.ietf.org/html/rfc5280#section-4.2.1.6
diff --git a/src/_cffi_src/openssl/asn1.py b/src/_cffi_src/openssl/asn1.py
index 01d6f4c2..5f8ca697 100644
--- a/src/_cffi_src/openssl/asn1.py
+++ b/src/_cffi_src/openssl/asn1.py
@@ -157,6 +157,7 @@ int ASN1_UTCTIME_check(ASN1_UTCTIME *);
int ASN1_STRING_set_default_mask_asc(char *);
int i2d_ASN1_TYPE(ASN1_TYPE *, unsigned char **);
+ASN1_TYPE *d2i_ASN1_TYPE(ASN1_TYPE **, const unsigned char **, long);
"""
CUSTOMIZATIONS = """
diff --git a/src/_cffi_src/openssl/x509v3.py b/src/_cffi_src/openssl/x509v3.py
index 0f5306d0..8e42b65d 100644
--- a/src/_cffi_src/openssl/x509v3.py
+++ b/src/_cffi_src/openssl/x509v3.py
@@ -193,6 +193,9 @@ void AUTHORITY_KEYID_free(AUTHORITY_KEYID *);
NAME_CONSTRAINTS *NAME_CONSTRAINTS_new(void);
void NAME_CONSTRAINTS_free(NAME_CONSTRAINTS *);
+OTHERNAME *OTHERNAME_new(void);
+void OTHERNAME_free(OTHERNAME *);
+
void *X509V3_set_ctx_nodb(X509V3_CTX *);
int i2d_GENERAL_NAMES(GENERAL_NAMES *, unsigned char **);
diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py
index 7255b470..637b28cc 100644
--- a/src/cryptography/hazmat/backends/openssl/backend.py
+++ b/src/cryptography/hazmat/backends/openssl/backend.py
@@ -199,6 +199,28 @@ def _encode_subject_alt_name(backend, san):
)
gn.type = backend._lib.GEN_IPADD
gn.d.iPAddress = ipaddr
+ elif isinstance(alt_name, x509.OtherName):
+ gn = backend._lib.GENERAL_NAME_new()
+ assert gn != backend._ffi.NULL
+ other_name = backend._lib.OTHERNAME_new()
+ assert other_name != backend._ffi.NULL
+
+ type_id = backend._lib.OBJ_txt2obj(
+ alt_name.type_id.dotted_string.encode('ascii'), 1
+ )
+ assert type_id != backend._ffi.NULL
+ data = backend._ffi.new("unsigned char[]", alt_name.value)
+ data_ptr_ptr = backend._ffi.new("unsigned char **")
+ data_ptr_ptr[0] = data
+ value = backend._lib.d2i_ASN1_TYPE(
+ backend._ffi.NULL, data_ptr_ptr, len(alt_name.value)
+ )
+ if value == backend._ffi.NULL:
+ raise ValueError("Invalid ASN.1 data")
+ other_name.type_id = type_id
+ other_name.value = value
+ gn.type = backend._lib.GEN_OTHERNAME
+ gn.d.otherName = other_name
else:
raise NotImplementedError(
"Only DNSName and RegisteredID supported right now"
diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py
index d78c60fa..096cbc9e 100644
--- a/src/cryptography/hazmat/backends/openssl/x509.py
+++ b/src/cryptography/hazmat/backends/openssl/x509.py
@@ -141,11 +141,32 @@ def _decode_general_name(backend, gn):
oid = _obj2txt(backend, gn.d.registeredID)
return x509.RegisteredID(x509.ObjectIdentifier(oid))
elif gn.type == backend._lib.GEN_IPADD:
- return x509.IPAddress(
- ipaddress.ip_address(
- _asn1_string_to_bytes(backend, gn.d.iPAddress)
- )
- )
+ data = _asn1_string_to_bytes(backend, gn.d.iPAddress)
+ data_len = len(data)
+ if data_len == 8 or data_len == 32:
+ # This is an IPv4 or IPv6 Network and not a single IP. This
+ # type of data appears in Name Constraints. Unfortunately,
+ # ipaddress doesn't support packed bytes + netmask. Additionally,
+ # IPv6Network can only handle CIDR rather than the full 16 byte
+ # netmask. To handle this we convert the netmask to integer, then
+ # find the first 0 bit, which will be the prefix. If another 1
+ # bit is present after that the netmask is invalid.
+ base = ipaddress.ip_address(data[:data_len // 2])
+ netmask = ipaddress.ip_address(data[data_len // 2:])
+ bits = bin(int(netmask))[2:]
+ prefix = bits.find('0')
+ # If no 0 bits are found it is a /32 or /128
+ if prefix == -1:
+ prefix = len(bits)
+
+ if "1" in bits[prefix:]:
+ raise ValueError("Invalid netmask")
+
+ ip = ipaddress.ip_network(base.exploded + u"/{0}".format(prefix))
+ else:
+ ip = ipaddress.ip_address(data)
+
+ return x509.IPAddress(ip)
elif gn.type == backend._lib.GEN_DIRNAME:
return x509.DirectoryName(
_decode_x509_name(backend, gn.d.directoryName)
diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py
index 89eac4d4..41b0089e 100644
--- a/src/cryptography/hazmat/primitives/asymmetric/rsa.py
+++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py
@@ -307,6 +307,17 @@ class RSAPrivateNumbers(object):
def __ne__(self, other):
return not self == other
+ def __hash__(self):
+ return hash((
+ self.p,
+ self.q,
+ self.d,
+ self.dmp1,
+ self.dmq1,
+ self.iqmp,
+ self.public_numbers,
+ ))
+
class RSAPublicNumbers(object):
def __init__(self, e, n):
@@ -336,3 +347,6 @@ class RSAPublicNumbers(object):
def __ne__(self, other):
return not self == other
+
+ def __hash__(self):
+ return hash((self.e, self.n))
diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py
index 33c64168..8bed79e2 100644
--- a/src/cryptography/x509.py
+++ b/src/cryptography/x509.py
@@ -6,8 +6,11 @@ from __future__ import absolute_import, division, print_function
import abc
import ipaddress
+from email.utils import parseaddr
from enum import Enum
+import idna
+
import six
from cryptography import utils
@@ -901,7 +904,23 @@ class RFC822Name(object):
if not isinstance(value, six.text_type):
raise TypeError("value must be a unicode string")
+ name, address = parseaddr(value)
+ parts = address.split(u"@")
+ if name or not address:
+ # parseaddr has found a name (e.g. Name <email>) or the entire
+ # value is an empty string.
+ raise ValueError("Invalid rfc822name value")
+ elif len(parts) == 1:
+ # Single label email name. This is valid for local delivery.
+ # No IDNA encoding needed since there is no domain component.
+ encoded = address.encode("ascii")
+ else:
+ # A normal email of the form user@domain.com. Let's attempt to
+ # encode the domain component and reconstruct the address.
+ encoded = parts[0].encode("ascii") + b"@" + idna.encode(parts[1])
+
self._value = value
+ self._encoded = encoded
value = utils.read_only_property("_value")
diff --git a/tests/conftest.py b/tests/conftest.py
index 6599a643..bdd17fb7 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -12,10 +12,10 @@ from .utils import check_backend_support, select_backends, skip_if_empty
def pytest_generate_tests(metafunc):
- names = metafunc.config.getoption("--backend")
- selected_backends = select_backends(names, _available_backends())
-
if "backend" in metafunc.fixturenames:
+ names = metafunc.config.getoption("--backend")
+ selected_backends = select_backends(names, _available_backends())
+
filtered_backends = []
required = metafunc.function.requires_backend_interface
required_interfaces = [
diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py
index bfeab8dd..0c5f7042 100644
--- a/tests/hazmat/primitives/test_rsa.py
+++ b/tests/hazmat/primitives/test_rsa.py
@@ -1705,6 +1705,22 @@ class TestRSANumbersEquality(object):
)
assert num != object()
+ def test_public_numbers_hash(self):
+ pub1 = RSAPublicNumbers(3, 17)
+ pub2 = RSAPublicNumbers(3, 17)
+ pub3 = RSAPublicNumbers(7, 21)
+
+ assert hash(pub1) == hash(pub2)
+ assert hash(pub1) != hash(pub3)
+
+ def test_private_numbers_hash(self):
+ priv1 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 2))
+ priv2 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 2))
+ priv3 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 3))
+
+ assert hash(priv1) == hash(priv2)
+ assert hash(priv1) != hash(priv3)
+
class TestRSAPrimeFactorRecovery(object):
@pytest.mark.parametrize(
diff --git a/tests/test_x509.py b/tests/test_x509.py
index 9b6b8826..cb617268 100644
--- a/tests/test_x509.py
+++ b/tests/test_x509.py
@@ -1004,6 +1004,10 @@ class TestCertificateSigningRequestBuilder(object):
])),
x509.IPAddress(ipaddress.ip_address(u"127.0.0.1")),
x509.IPAddress(ipaddress.ip_address(u"ff::")),
+ x509.OtherName(
+ type_id=x509.ObjectIdentifier("1.2.3.3.3.3"),
+ value=b"0\x03\x02\x01\x05"
+ ),
]),
critical=False,
).sign(private_key, hashes.SHA256(), backend)
@@ -1026,8 +1030,31 @@ class TestCertificateSigningRequestBuilder(object):
])),
x509.IPAddress(ipaddress.ip_address(u"127.0.0.1")),
x509.IPAddress(ipaddress.ip_address(u"ff::")),
+ x509.OtherName(
+ type_id=x509.ObjectIdentifier("1.2.3.3.3.3"),
+ value=b"0\x03\x02\x01\x05"
+ ),
]
+ def test_invalid_asn1_othername(self, backend):
+ private_key = RSA_KEY_2048.private_key(backend)
+
+ builder = x509.CertificateSigningRequestBuilder().subject_name(
+ x509.Name([
+ x509.NameAttribute(x509.OID_COMMON_NAME, u"SAN"),
+ ])
+ ).add_extension(
+ x509.SubjectAlternativeName([
+ x509.OtherName(
+ type_id=x509.ObjectIdentifier("1.2.3.3.3.3"),
+ value=b"\x01\x02\x01\x05"
+ ),
+ ]),
+ critical=False,
+ )
+ with pytest.raises(ValueError):
+ builder.sign(private_key, hashes.SHA256(), backend)
+
def test_subject_alt_name_unsupported_general_name(self, backend):
private_key = RSA_KEY_2048.private_key(backend)
diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py
index 993802b8..84a40995 100644
--- a/tests/test_x509_ext.py
+++ b/tests/test_x509_ext.py
@@ -1087,6 +1087,24 @@ class TestDirectoryName(object):
assert gn != object()
+class TestRFC822Name(object):
+ def test_invalid_email(self):
+ with pytest.raises(ValueError):
+ x509.RFC822Name(u"Name <email>")
+
+ with pytest.raises(ValueError):
+ x509.RFC822Name(u"")
+
+ def test_single_label(self):
+ gn = x509.RFC822Name(u"administrator")
+ assert gn.value == u"administrator"
+
+ def test_idna(self):
+ gn = x509.RFC822Name(u"email@em\xe5\xefl.com")
+ assert gn.value == u"email@em\xe5\xefl.com"
+ assert gn._encoded == b"email@xn--eml-vla4c.com"
+
+
class TestRegisteredID(object):
def test_not_oid(self):
with pytest.raises(TypeError):
@@ -2184,6 +2202,60 @@ class TestNameConstraintsExtension(object):
]
)
+ def test_permitted_excluded_with_ips(self, backend):
+ cert = _load_cert(
+ os.path.join(
+ "x509", "custom", "nc_permitted_excluded.pem"
+ ),
+ x509.load_pem_x509_certificate,
+ backend
+ )
+ nc = cert.extensions.get_extension_for_oid(
+ x509.OID_NAME_CONSTRAINTS
+ ).value
+ assert nc == x509.NameConstraints(
+ permitted_subtrees=[
+ x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24")),
+ x509.IPAddress(ipaddress.IPv6Network(u"FF:0:0:0:0:0:0:0/96")),
+ ],
+ excluded_subtrees=[
+ x509.DNSName(u".domain.com"),
+ x509.UniformResourceIdentifier(u"http://test.local"),
+ ]
+ )
+
+ def test_single_ip_netmask(self, backend):
+ cert = _load_cert(
+ os.path.join(
+ "x509", "custom", "nc_single_ip_netmask.pem"
+ ),
+ x509.load_pem_x509_certificate,
+ backend
+ )
+ nc = cert.extensions.get_extension_for_oid(
+ x509.OID_NAME_CONSTRAINTS
+ ).value
+ assert nc == x509.NameConstraints(
+ permitted_subtrees=[
+ x509.IPAddress(ipaddress.IPv6Network(u"FF:0:0:0:0:0:0:0/128")),
+ x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.1/32")),
+ ],
+ excluded_subtrees=None
+ )
+
+ def test_invalid_netmask(self, backend):
+ cert = _load_cert(
+ os.path.join(
+ "x509", "custom", "nc_invalid_ip_netmask.pem"
+ ),
+ x509.load_pem_x509_certificate,
+ backend
+ )
+ with pytest.raises(ValueError):
+ cert.extensions.get_extension_for_oid(
+ x509.OID_NAME_CONSTRAINTS
+ )
+
class TestDistributionPoint(object):
def test_distribution_point_full_name_not_general_names(self):