diff options
-rw-r--r-- | .travis.yml | 16 | ||||
-rwxr-xr-x | .travis/install.sh | 14 | ||||
-rw-r--r-- | CHANGELOG.rst | 4 | ||||
-rw-r--r-- | docs/x509.rst | 11 | ||||
-rw-r--r-- | src/cryptography/hazmat/backends/openssl/x509.py | 16 | ||||
-rw-r--r-- | src/cryptography/hazmat/bindings/openssl/x509v3.py | 6 | ||||
-rw-r--r-- | src/cryptography/x509.py | 6 | ||||
-rw-r--r-- | tests/test_x509.py | 90 |
8 files changed, 154 insertions, 9 deletions
diff --git a/.travis.yml b/.travis.yml index c7413ea9..343576fe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,11 @@ sudo: false + language: python + cache: directories: - $HOME/.cache/pip + matrix: include: - python: 2.6 # these are just to make travis's UI a bit prettier @@ -67,42 +70,55 @@ matrix: env: TOXENV=py3pep8 - language: generic os: osx + osx_image: beta-xcode6.3 env: TOXENV=py26 - language: generic os: osx + osx_image: beta-xcode6.3 env: TOXENV=py27 - language: generic os: osx + osx_image: beta-xcode6.3 env: TOXENV=py33 - language: generic os: osx + osx_image: beta-xcode6.3 env: TOXENV=py34 - language: generic os: osx + osx_image: beta-xcode6.3 env: TOXENV=pypy - language: generic os: osx + osx_image: beta-xcode6.3 env: TOXENV=pypy3 - language: generic os: osx + osx_image: beta-xcode6.3 env: TOXENV=py26 OPENSSL=0.9.8 - language: generic os: osx + osx_image: beta-xcode6.3 env: TOXENV=py27 OPENSSL=0.9.8 - language: generic os: osx + osx_image: beta-xcode6.3 env: TOXENV=py33 OPENSSL=0.9.8 - language: generic os: osx + osx_image: beta-xcode6.3 env: TOXENV=py34 OPENSSL=0.9.8 - language: generic os: osx + osx_image: beta-xcode6.3 env: TOXENV=pypy OPENSSL=0.9.8 - language: generic os: osx + osx_image: beta-xcode6.3 env: TOXENV=pypy3 OPENSSL=0.9.8 - language: generic os: osx + osx_image: beta-xcode6.3 env: TOXENV=docs install: diff --git a/.travis/install.sh b/.travis/install.sh index 9e14a92d..7c3e9de2 100755 --- a/.travis/install.sh +++ b/.travis/install.sh @@ -4,10 +4,10 @@ set -e set -x if [[ "$(uname -s)" == 'Darwin' ]]; then - brew update + brew update || brew update if [[ "${OPENSSL}" != "0.9.8" ]]; then - brew upgrade openssl + brew outdated openssl || brew upgrade openssl fi if which pyenv > /dev/null; then @@ -24,22 +24,22 @@ if [[ "$(uname -s)" == 'Darwin' ]]; then python get-pip.py --user ;; py33) - brew upgrade pyenv + brew outdated pyenv || brew upgrade pyenv pyenv install 3.3.6 pyenv global 3.3.6 ;; py34) - brew upgrade pyenv + brew outdated pyenv || brew upgrade pyenv pyenv install 3.4.2 pyenv global 3.4.2 ;; pypy) - brew upgrade pyenv + brew outdated pyenv || brew upgrade pyenv pyenv install pypy-2.5.1 pyenv global pypy-2.5.1 ;; pypy3) - brew upgrade pyenv + brew outdated pyenv || brew upgrade pyenv pyenv install pypy3-2.4.0 pyenv global pypy3-2.4.0 ;; @@ -49,7 +49,7 @@ if [[ "$(uname -s)" == 'Darwin' ]]; then ;; esac pyenv rehash - pip install --user virtualenv + python -m pip install --user virtualenv else pip install virtualenv fi diff --git a/CHANGELOG.rst b/CHANGELOG.rst index e2f18909..4d7a9a82 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,10 @@ Changelog .. note:: This version is not yet released and is under active development. +* Support serialization of certificate signing requests using the + ``public_bytes`` method of + :class:`~cryptography.x509.CertificateSigningRequest`. + 0.9 - 2015-05-13 ~~~~~~~~~~~~~~~~ diff --git a/docs/x509.rst b/docs/x509.rst index 3f1af86c..c8505a87 100644 --- a/docs/x509.rst +++ b/docs/x509.rst @@ -366,6 +366,17 @@ X.509 CSR (Certificate Signing Request) Object >>> isinstance(csr.signature_hash_algorithm, hashes.SHA1) True + .. method:: public_bytes(encoding) + + :param encoding: The + :class:`~cryptography.hazmat.primitives.serialization.Encoding` + that will be used to serialize the certificate request. + + :return bytes: The data that can be written to a file or sent + over the network to be signed by the certificate + authority. + + .. class:: Name .. versionadded:: 0.8 diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py index 6db6fc9c..72041366 100644 --- a/src/cryptography/hazmat/backends/openssl/x509.py +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -25,7 +25,7 @@ from six.moves import urllib_parse from cryptography import utils, x509 from cryptography.exceptions import UnsupportedAlgorithm -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, serialization def _obj2txt(backend, obj): @@ -689,3 +689,17 @@ class _CertificateSigningRequest(object): extensions.append(x509.Extension(oid, critical, value)) return x509.Extensions(extensions) + + def public_bytes(self, encoding): + if not isinstance(encoding, serialization.Encoding): + raise TypeError("encoding must be an item from the Encoding enum") + + bio = self._backend._create_mem_bio() + if encoding is serialization.Encoding.PEM: + res = self._backend._lib.PEM_write_bio_X509_REQ( + bio, self._x509_req + ) + elif encoding is serialization.Encoding.DER: + res = self._backend._lib.i2d_X509_REQ_bio(bio, self._x509_req) + assert res == 1 + return self._backend._read_mem_bio(bio) diff --git a/src/cryptography/hazmat/bindings/openssl/x509v3.py b/src/cryptography/hazmat/bindings/openssl/x509v3.py index 054ab624..e9bc461a 100644 --- a/src/cryptography/hazmat/bindings/openssl/x509v3.py +++ b/src/cryptography/hazmat/bindings/openssl/x509v3.py @@ -172,16 +172,20 @@ void BASIC_CONSTRAINTS_free(BASIC_CONSTRAINTS *); /* This is a macro defined by a call to DECLARE_ASN1_FUNCTIONS in the x509v3.h header. */ void AUTHORITY_KEYID_free(AUTHORITY_KEYID *); + void *X509V3_set_ctx_nodb(X509V3_CTX *); int sk_GENERAL_NAME_num(struct stack_st_GENERAL_NAME *); int sk_GENERAL_NAME_push(struct stack_st_GENERAL_NAME *, GENERAL_NAME *); GENERAL_NAME *sk_GENERAL_NAME_value(struct stack_st_GENERAL_NAME *, int); +Cryptography_STACK_OF_ACCESS_DESCRIPTION *sk_ACCESS_DESCRIPTION_new_null(void); int sk_ACCESS_DESCRIPTION_num(Cryptography_STACK_OF_ACCESS_DESCRIPTION *); ACCESS_DESCRIPTION *sk_ACCESS_DESCRIPTION_value( Cryptography_STACK_OF_ACCESS_DESCRIPTION *, int ); void sk_ACCESS_DESCRIPTION_free(Cryptography_STACK_OF_ACCESS_DESCRIPTION *); +int sk_ACCESS_DESCRIPTION_push(Cryptography_STACK_OF_ACCESS_DESCRIPTION *, + ACCESS_DESCRIPTION *); X509_EXTENSION *X509V3_EXT_conf_nid(Cryptography_LHASH_OF_CONF_VALUE *, X509V3_CTX *, int, char *); @@ -206,6 +210,8 @@ POLICYQUALINFO *sk_POLICYQUALINFO_value(Cryptography_STACK_OF_POLICYQUALINFO *, void sk_ASN1_INTEGER_free(Cryptography_STACK_OF_ASN1_INTEGER *); int sk_ASN1_INTEGER_num(Cryptography_STACK_OF_ASN1_INTEGER *); ASN1_INTEGER *sk_ASN1_INTEGER_value(Cryptography_STACK_OF_ASN1_INTEGER *, int); + +X509_EXTENSION *X509V3_EXT_i2d(int, int, void *); """ CUSTOMIZATIONS = """ diff --git a/src/cryptography/x509.py b/src/cryptography/x509.py index 7ac06622..9a3295ce 100644 --- a/src/cryptography/x509.py +++ b/src/cryptography/x509.py @@ -1194,3 +1194,9 @@ class CertificateSigningRequest(object): """ Returns the extensions in the signing request. """ + + @abc.abstractmethod + def public_bytes(self, encoding): + """ + Encodes the request to PEM or DER format. + """ diff --git a/tests/test_x509.py b/tests/test_x509.py index 47c1c647..72fc9d40 100644 --- a/tests/test_x509.py +++ b/tests/test_x509.py @@ -15,7 +15,7 @@ from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends.interfaces import ( DSABackend, EllipticCurveBackend, RSABackend, X509Backend ) -from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa from .hazmat.primitives.test_ec import _skip_curve_unsupported @@ -471,6 +471,94 @@ class TestRSACertificate(object): ), ] + def test_public_bytes_pem(self, backend): + # Load an existing CSR. + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + + # Encode it to PEM and load it back. + request = x509.load_pem_x509_csr(request.public_bytes( + encoding=serialization.Encoding.PEM, + ), backend) + + # We should recover what we had to start with. + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, 'cryptography.io'), + ] + + def test_public_bytes_der(self, backend): + # Load an existing CSR. + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + + # Encode it to DER and load it back. + request = x509.load_der_x509_csr(request.public_bytes( + encoding=serialization.Encoding.DER, + ), backend) + + # We should recover what we had to start with. + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(x509.OID_COUNTRY_NAME, 'US'), + x509.NameAttribute(x509.OID_STATE_OR_PROVINCE_NAME, 'Texas'), + x509.NameAttribute(x509.OID_LOCALITY_NAME, 'Austin'), + x509.NameAttribute(x509.OID_ORGANIZATION_NAME, 'PyCA'), + x509.NameAttribute(x509.OID_COMMON_NAME, 'cryptography.io'), + ] + + def test_public_bytes_invalid_encoding(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + + with pytest.raises(TypeError): + request.public_bytes('NotAnEncoding') + + @pytest.mark.parametrize( + ("request_path", "loader_func", "encoding"), + [ + ( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + serialization.Encoding.PEM, + ), + ( + os.path.join("x509", "requests", "rsa_sha1.der"), + x509.load_der_x509_csr, + serialization.Encoding.DER, + ), + ] + ) + def test_public_bytes_match(self, request_path, loader_func, encoding, + backend): + request_bytes = load_vectors_from_file( + request_path, lambda pemfile: pemfile.read(), mode="rb" + ) + request = loader_func(request_bytes, backend) + serialized = request.public_bytes(encoding) + assert serialized == request_bytes + @pytest.mark.requires_backend_interface(interface=DSABackend) @pytest.mark.requires_backend_interface(interface=X509Backend) |