From 443c9c11a110d5b86822f1ae53961d7d81df6032 Mon Sep 17 00:00:00 2001 From: Tristan Seligmann Date: Wed, 14 Dec 2016 06:59:21 +0100 Subject: [PATCH] Import python-cryptography_1.7.1.orig.tar.gz [dgit import orig python-cryptography_1.7.1.orig.tar.gz] --- AUTHORS.rst | 38 + CHANGELOG.rst | 792 ++++ CONTRIBUTING.rst | 23 + LICENSE | 7 + LICENSE.APACHE | 202 + LICENSE.BSD | 27 + MANIFEST.in | 13 + PKG-INFO | 94 + README.rst | 64 + docs/Makefile | 153 + docs/_static/.keep | 0 docs/api-stability.rst | 51 + docs/changelog.rst | 1 + docs/community.rst | 20 + docs/conf.py | 179 + docs/cryptography-docs.py | 70 + docs/development/c-bindings.rst | 196 + docs/development/custom-vectors/arc4.rst | 30 + .../custom-vectors/arc4/generate_arc4.py | 98 + .../custom-vectors/arc4/verify_arc4.go | 111 + docs/development/custom-vectors/cast5.rst | 31 + .../custom-vectors/cast5/generate_cast5.py | 68 + .../custom-vectors/cast5/verify_cast5.go | 164 + docs/development/custom-vectors/idea.rst | 32 + .../custom-vectors/idea/generate_idea.py | 61 + .../custom-vectors/idea/verify_idea.py | 39 + .../custom-vectors/rsa-oaep-sha2.rst | 56 + .../rsa-oaep-sha2/VerifyRSAOAEPSHA2.java | 416 ++ .../rsa-oaep-sha2/generate_rsa_oaep_sha2.py | 128 + docs/development/custom-vectors/secp256k1.rst | 32 + .../secp256k1/generate_secp256k1.py | 90 + .../secp256k1/verify_secp256k1.py | 59 + docs/development/custom-vectors/seed.rst | 32 + .../custom-vectors/seed/generate_seed.py | 58 + .../custom-vectors/seed/verify_seed.py | 35 + docs/development/getting-started.rst | 130 + docs/development/index.rst | 20 + docs/development/reviewing-patches.rst | 60 + docs/development/submitting-patches.rst | 161 + docs/development/test-vectors.rst | 473 ++ docs/doing-a-release.rst | 91 + docs/exceptions.rst | 39 + docs/faq.rst | 68 + docs/fernet.rst | 169 + docs/glossary.rst | 74 + docs/hazmat/backends/commoncrypto.rst | 30 + docs/hazmat/backends/index.rst | 36 + docs/hazmat/backends/interfaces.rst | 706 +++ docs/hazmat/backends/multibackend.rst | 45 + docs/hazmat/backends/openssl.rst | 110 + docs/hazmat/bindings/commoncrypto.rst | 30 + docs/hazmat/bindings/index.rst | 23 + docs/hazmat/bindings/openssl.rst | 48 + docs/hazmat/primitives/asymmetric/dh.rst | 238 + docs/hazmat/primitives/asymmetric/dsa.rst | 462 ++ docs/hazmat/primitives/asymmetric/ec.rst | 648 +++ docs/hazmat/primitives/asymmetric/index.rst | 38 + .../primitives/asymmetric/interfaces.rst | 32 + docs/hazmat/primitives/asymmetric/rsa.rst | 745 ++++ .../primitives/asymmetric/serialization.rst | 438 ++ docs/hazmat/primitives/asymmetric/utils.rst | 88 + docs/hazmat/primitives/constant-time.rst | 43 + .../primitives/cryptographic-hashes.rst | 229 + docs/hazmat/primitives/index.rst | 18 + docs/hazmat/primitives/interfaces.rst | 80 + .../primitives/key-derivation-functions.rst | 909 ++++ docs/hazmat/primitives/keywrap.rst | 59 + docs/hazmat/primitives/mac/cmac.rst | 110 + docs/hazmat/primitives/mac/hmac.rst | 109 + docs/hazmat/primitives/mac/index.rst | 18 + docs/hazmat/primitives/padding.rst | 129 + .../primitives/symmetric-encryption.rst | 616 +++ docs/hazmat/primitives/twofactor.rst | 244 ++ docs/index.rst | 104 + docs/installation.rst | 277 ++ docs/limitations.rst | 19 + docs/make.bat | 190 + docs/random-numbers.rst | 32 + docs/security.rst | 115 + docs/spelling_wordlist.txt | 94 + docs/x509/index.rst | 15 + docs/x509/reference.rst | 2697 ++++++++++++ docs/x509/tutorial.rst | 154 + setup.cfg | 5 + setup.py | 335 ++ src/_cffi_src/__init__.py | 0 src/_cffi_src/build_commoncrypto.py | 33 + src/_cffi_src/build_constant_time.py | 27 + src/_cffi_src/build_openssl.py | 86 + src/_cffi_src/build_padding.py | 27 + src/_cffi_src/commoncrypto/__init__.py | 5 + src/_cffi_src/commoncrypto/cf.py | 113 + src/_cffi_src/commoncrypto/common_cryptor.py | 99 + src/_cffi_src/commoncrypto/common_digest.py | 58 + src/_cffi_src/commoncrypto/common_hmac.py | 37 + .../commoncrypto/common_key_derivation.py | 39 + .../commoncrypto/common_symmetric_key_wrap.py | 35 + src/_cffi_src/commoncrypto/seccertificate.py | 23 + src/_cffi_src/commoncrypto/secimport.py | 86 + src/_cffi_src/commoncrypto/secitem.py | 27 + src/_cffi_src/commoncrypto/seckey.py | 24 + src/_cffi_src/commoncrypto/seckeychain.py | 25 + src/_cffi_src/commoncrypto/secpolicy.py | 23 + src/_cffi_src/commoncrypto/sectransform.py | 68 + src/_cffi_src/commoncrypto/sectrust.py | 39 + .../commoncrypto/secure_transport.py | 308 ++ src/_cffi_src/hazmat_src/constant_time.c | 22 + src/_cffi_src/hazmat_src/constant_time.h | 6 + src/_cffi_src/hazmat_src/padding.c | 65 + src/_cffi_src/hazmat_src/padding.h | 6 + src/_cffi_src/openssl/__init__.py | 5 + src/_cffi_src/openssl/aes.py | 50 + src/_cffi_src/openssl/asn1.py | 164 + src/_cffi_src/openssl/bignum.py | 88 + src/_cffi_src/openssl/bio.py | 147 + src/_cffi_src/openssl/callbacks.py | 124 + src/_cffi_src/openssl/cmac.py | 44 + src/_cffi_src/openssl/cms.py | 116 + src/_cffi_src/openssl/conf.py | 24 + src/_cffi_src/openssl/crypto.py | 105 + src/_cffi_src/openssl/cryptography.py | 72 + src/_cffi_src/openssl/dh.py | 115 + src/_cffi_src/openssl/dsa.py | 106 + src/_cffi_src/openssl/ec.py | 381 ++ src/_cffi_src/openssl/ecdh.py | 45 + src/_cffi_src/openssl/ecdsa.py | 78 + src/_cffi_src/openssl/engine.py | 146 + src/_cffi_src/openssl/err.py | 286 ++ src/_cffi_src/openssl/evp.py | 219 + src/_cffi_src/openssl/hmac.py | 51 + src/_cffi_src/openssl/nid.py | 230 + src/_cffi_src/openssl/objects.py | 44 + src/_cffi_src/openssl/ocsp.py | 67 + src/_cffi_src/openssl/opensslv.py | 25 + src/_cffi_src/openssl/osrandom_engine.py | 29 + src/_cffi_src/openssl/pem.py | 95 + src/_cffi_src/openssl/pkcs12.py | 30 + src/_cffi_src/openssl/pkcs7.py | 86 + src/_cffi_src/openssl/rand.py | 49 + src/_cffi_src/openssl/rsa.py | 195 + src/_cffi_src/openssl/src/osrandom_engine.c | 576 +++ src/_cffi_src/openssl/src/osrandom_engine.h | 88 + src/_cffi_src/openssl/ssl.py | 719 +++ src/_cffi_src/openssl/x509.py | 443 ++ src/_cffi_src/openssl/x509_vfy.py | 285 ++ src/_cffi_src/openssl/x509name.py | 89 + src/_cffi_src/openssl/x509v3.py | 304 ++ src/_cffi_src/utils.py | 100 + src/cryptography.egg-info/PKG-INFO | 94 + src/cryptography.egg-info/SOURCES.txt | 290 ++ .../dependency_links.txt | 1 + src/cryptography.egg-info/entry_points.txt | 4 + src/cryptography.egg-info/not-zip-safe | 1 + src/cryptography.egg-info/requires.txt | 28 + src/cryptography.egg-info/top_level.txt | 1 + src/cryptography/__about__.py | 23 + src/cryptography/__init__.py | 27 + src/cryptography/exceptions.py | 57 + src/cryptography/fernet.py | 143 + src/cryptography/hazmat/__init__.py | 11 + src/cryptography/hazmat/backends/__init__.py | 73 + .../hazmat/backends/commoncrypto/__init__.py | 10 + .../hazmat/backends/commoncrypto/backend.py | 250 ++ .../hazmat/backends/commoncrypto/ciphers.py | 193 + .../hazmat/backends/commoncrypto/hashes.py | 55 + .../hazmat/backends/commoncrypto/hmac.py | 59 + .../hazmat/backends/interfaces.py | 376 ++ .../hazmat/backends/multibackend.py | 505 +++ .../hazmat/backends/openssl/__init__.py | 10 + .../hazmat/backends/openssl/backend.py | 1913 ++++++++ .../hazmat/backends/openssl/ciphers.py | 204 + .../hazmat/backends/openssl/cmac.py | 80 + .../hazmat/backends/openssl/decode_asn1.py | 784 ++++ .../hazmat/backends/openssl/dh.py | 182 + .../hazmat/backends/openssl/dsa.py | 290 ++ .../hazmat/backends/openssl/ec.py | 323 ++ .../hazmat/backends/openssl/encode_asn1.py | 616 +++ .../hazmat/backends/openssl/hashes.py | 61 + .../hazmat/backends/openssl/hmac.py | 74 + .../hazmat/backends/openssl/rsa.py | 527 +++ .../hazmat/backends/openssl/utils.py | 46 + .../hazmat/backends/openssl/x509.py | 435 ++ src/cryptography/hazmat/bindings/__init__.py | 5 + .../hazmat/bindings/commoncrypto/__init__.py | 5 + .../hazmat/bindings/commoncrypto/binding.py | 15 + .../hazmat/bindings/openssl/__init__.py | 5 + .../hazmat/bindings/openssl/_conditional.py | 325 ++ .../hazmat/bindings/openssl/binding.py | 175 + .../hazmat/primitives/__init__.py | 5 + .../hazmat/primitives/asymmetric/__init__.py | 40 + .../hazmat/primitives/asymmetric/dh.py | 189 + .../hazmat/primitives/asymmetric/dsa.py | 254 ++ .../hazmat/primitives/asymmetric/ec.py | 377 ++ .../hazmat/primitives/asymmetric/padding.py | 79 + .../hazmat/primitives/asymmetric/rsa.py | 364 ++ .../hazmat/primitives/asymmetric/utils.py | 83 + .../hazmat/primitives/ciphers/__init__.py | 20 + .../hazmat/primitives/ciphers/algorithms.py | 140 + .../hazmat/primitives/ciphers/base.py | 203 + .../hazmat/primitives/ciphers/modes.py | 185 + src/cryptography/hazmat/primitives/cmac.py | 66 + .../hazmat/primitives/constant_time.py | 26 + src/cryptography/hazmat/primitives/hashes.py | 205 + src/cryptography/hazmat/primitives/hmac.py | 69 + .../hazmat/primitives/interfaces/__init__.py | 37 + .../hazmat/primitives/kdf/__init__.py | 26 + .../hazmat/primitives/kdf/concatkdf.py | 125 + .../hazmat/primitives/kdf/hkdf.py | 116 + .../hazmat/primitives/kdf/kbkdf.py | 148 + .../hazmat/primitives/kdf/pbkdf2.py | 58 + .../hazmat/primitives/kdf/scrypt.py | 59 + .../hazmat/primitives/kdf/x963kdf.py | 70 + src/cryptography/hazmat/primitives/keywrap.py | 85 + src/cryptography/hazmat/primitives/padding.py | 202 + .../hazmat/primitives/serialization.py | 197 + .../hazmat/primitives/twofactor/__init__.py | 9 + .../hazmat/primitives/twofactor/hotp.py | 68 + .../hazmat/primitives/twofactor/totp.py | 40 + .../hazmat/primitives/twofactor/utils.py | 30 + src/cryptography/utils.py | 137 + src/cryptography/x509/__init__.py | 179 + src/cryptography/x509/base.py | 730 +++ src/cryptography/x509/extensions.py | 1212 +++++ src/cryptography/x509/general_name.py | 271 ++ src/cryptography/x509/name.py | 138 + src/cryptography/x509/oid.py | 258 ++ tests/__init__.py | 5 + tests/conftest.py | 51 + tests/doubles.py | 43 + tests/hazmat/__init__.py | 5 + tests/hazmat/backends/__init__.py | 5 + tests/hazmat/backends/test_backendinit.py | 17 + tests/hazmat/backends/test_commoncrypto.py | 54 + tests/hazmat/backends/test_multibackend.py | 648 +++ tests/hazmat/backends/test_openssl.py | 751 ++++ tests/hazmat/bindings/test_commoncrypto.py | 26 + tests/hazmat/bindings/test_openssl.py | 115 + tests/hazmat/primitives/__init__.py | 5 + tests/hazmat/primitives/fixtures_dsa.py | 152 + tests/hazmat/primitives/fixtures_ec.py | 296 ++ tests/hazmat/primitives/fixtures_rsa.py | 603 +++ tests/hazmat/primitives/test_3des.py | 205 + tests/hazmat/primitives/test_aes.py | 305 ++ tests/hazmat/primitives/test_arc4.py | 41 + tests/hazmat/primitives/test_asym_utils.py | 83 + tests/hazmat/primitives/test_block.py | 209 + tests/hazmat/primitives/test_blowfish.py | 84 + tests/hazmat/primitives/test_camellia.py | 90 + tests/hazmat/primitives/test_cast5.py | 101 + tests/hazmat/primitives/test_ciphers.py | 135 + tests/hazmat/primitives/test_cmac.py | 205 + tests/hazmat/primitives/test_concatkdf.py | 272 ++ tests/hazmat/primitives/test_constant_time.py | 30 + tests/hazmat/primitives/test_dh.py | 287 ++ tests/hazmat/primitives/test_dsa.py | 1142 +++++ tests/hazmat/primitives/test_ec.py | 1106 +++++ tests/hazmat/primitives/test_hash_vectors.py | 194 + tests/hazmat/primitives/test_hashes.py | 214 + tests/hazmat/primitives/test_hkdf.py | 220 + tests/hazmat/primitives/test_hkdf_vectors.py | 43 + tests/hazmat/primitives/test_hmac.py | 98 + tests/hazmat/primitives/test_hmac_vectors.py | 125 + tests/hazmat/primitives/test_idea.py | 84 + tests/hazmat/primitives/test_kbkdf.py | 151 + tests/hazmat/primitives/test_kbkdf_vectors.py | 23 + tests/hazmat/primitives/test_keywrap.py | 116 + tests/hazmat/primitives/test_padding.py | 209 + tests/hazmat/primitives/test_pbkdf2hmac.py | 65 + .../primitives/test_pbkdf2hmac_vectors.py | 29 + tests/hazmat/primitives/test_rsa.py | 2317 ++++++++++ tests/hazmat/primitives/test_scrypt.py | 136 + tests/hazmat/primitives/test_seed.py | 84 + tests/hazmat/primitives/test_serialization.py | 1181 +++++ tests/hazmat/primitives/test_x963_vectors.py | 66 + tests/hazmat/primitives/test_x963kdf.py | 120 + tests/hazmat/primitives/twofactor/__init__.py | 5 + .../hazmat/primitives/twofactor/test_hotp.py | 119 + .../hazmat/primitives/twofactor/test_totp.py | 149 + tests/hazmat/primitives/utils.py | 482 ++ tests/hypothesis/__init__.py | 9 + tests/hypothesis/test_fernet.py | 15 + tests/hypothesis/test_padding.py | 32 + tests/test_cryptography_utils.py | 11 + tests/test_fernet.py | 158 + tests/test_interfaces.py | 84 + tests/test_utils.py | 3625 +++++++++++++++ tests/test_warnings.py | 88 + tests/test_x509.py | 3901 +++++++++++++++++ tests/test_x509_crlbuilder.py | 487 ++ tests/test_x509_ext.py | 3671 ++++++++++++++++ tests/test_x509_revokedcertbuilder.py | 205 + tests/utils.py | 848 ++++ 292 files changed, 63385 insertions(+) create mode 100644 AUTHORS.rst create mode 100644 CHANGELOG.rst create mode 100644 CONTRIBUTING.rst create mode 100644 LICENSE create mode 100644 LICENSE.APACHE create mode 100644 LICENSE.BSD create mode 100644 MANIFEST.in create mode 100644 PKG-INFO create mode 100644 README.rst create mode 100644 docs/Makefile create mode 100644 docs/_static/.keep create mode 100644 docs/api-stability.rst create mode 100644 docs/changelog.rst create mode 100644 docs/community.rst create mode 100644 docs/conf.py create mode 100644 docs/cryptography-docs.py create mode 100644 docs/development/c-bindings.rst create mode 100644 docs/development/custom-vectors/arc4.rst create mode 100644 docs/development/custom-vectors/arc4/generate_arc4.py create mode 100644 docs/development/custom-vectors/arc4/verify_arc4.go create mode 100644 docs/development/custom-vectors/cast5.rst create mode 100644 docs/development/custom-vectors/cast5/generate_cast5.py create mode 100644 docs/development/custom-vectors/cast5/verify_cast5.go create mode 100644 docs/development/custom-vectors/idea.rst create mode 100644 docs/development/custom-vectors/idea/generate_idea.py create mode 100644 docs/development/custom-vectors/idea/verify_idea.py create mode 100644 docs/development/custom-vectors/rsa-oaep-sha2.rst create mode 100644 docs/development/custom-vectors/rsa-oaep-sha2/VerifyRSAOAEPSHA2.java create mode 100644 docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py create mode 100644 docs/development/custom-vectors/secp256k1.rst create mode 100644 docs/development/custom-vectors/secp256k1/generate_secp256k1.py create mode 100644 docs/development/custom-vectors/secp256k1/verify_secp256k1.py create mode 100644 docs/development/custom-vectors/seed.rst create mode 100644 docs/development/custom-vectors/seed/generate_seed.py create mode 100644 docs/development/custom-vectors/seed/verify_seed.py create mode 100644 docs/development/getting-started.rst create mode 100644 docs/development/index.rst create mode 100644 docs/development/reviewing-patches.rst create mode 100644 docs/development/submitting-patches.rst create mode 100644 docs/development/test-vectors.rst create mode 100644 docs/doing-a-release.rst create mode 100644 docs/exceptions.rst create mode 100644 docs/faq.rst create mode 100644 docs/fernet.rst create mode 100644 docs/glossary.rst create mode 100644 docs/hazmat/backends/commoncrypto.rst create mode 100644 docs/hazmat/backends/index.rst create mode 100644 docs/hazmat/backends/interfaces.rst create mode 100644 docs/hazmat/backends/multibackend.rst create mode 100644 docs/hazmat/backends/openssl.rst create mode 100644 docs/hazmat/bindings/commoncrypto.rst create mode 100644 docs/hazmat/bindings/index.rst create mode 100644 docs/hazmat/bindings/openssl.rst create mode 100644 docs/hazmat/primitives/asymmetric/dh.rst create mode 100644 docs/hazmat/primitives/asymmetric/dsa.rst create mode 100644 docs/hazmat/primitives/asymmetric/ec.rst create mode 100644 docs/hazmat/primitives/asymmetric/index.rst create mode 100644 docs/hazmat/primitives/asymmetric/interfaces.rst create mode 100644 docs/hazmat/primitives/asymmetric/rsa.rst create mode 100644 docs/hazmat/primitives/asymmetric/serialization.rst create mode 100644 docs/hazmat/primitives/asymmetric/utils.rst create mode 100644 docs/hazmat/primitives/constant-time.rst create mode 100644 docs/hazmat/primitives/cryptographic-hashes.rst create mode 100644 docs/hazmat/primitives/index.rst create mode 100644 docs/hazmat/primitives/interfaces.rst create mode 100644 docs/hazmat/primitives/key-derivation-functions.rst create mode 100644 docs/hazmat/primitives/keywrap.rst create mode 100644 docs/hazmat/primitives/mac/cmac.rst create mode 100644 docs/hazmat/primitives/mac/hmac.rst create mode 100644 docs/hazmat/primitives/mac/index.rst create mode 100644 docs/hazmat/primitives/padding.rst create mode 100644 docs/hazmat/primitives/symmetric-encryption.rst create mode 100644 docs/hazmat/primitives/twofactor.rst create mode 100644 docs/index.rst create mode 100644 docs/installation.rst create mode 100644 docs/limitations.rst create mode 100644 docs/make.bat create mode 100644 docs/random-numbers.rst create mode 100644 docs/security.rst create mode 100644 docs/spelling_wordlist.txt create mode 100644 docs/x509/index.rst create mode 100644 docs/x509/reference.rst create mode 100644 docs/x509/tutorial.rst create mode 100644 setup.cfg create mode 100644 setup.py create mode 100644 src/_cffi_src/__init__.py create mode 100644 src/_cffi_src/build_commoncrypto.py create mode 100644 src/_cffi_src/build_constant_time.py create mode 100644 src/_cffi_src/build_openssl.py create mode 100644 src/_cffi_src/build_padding.py create mode 100644 src/_cffi_src/commoncrypto/__init__.py create mode 100644 src/_cffi_src/commoncrypto/cf.py create mode 100644 src/_cffi_src/commoncrypto/common_cryptor.py create mode 100644 src/_cffi_src/commoncrypto/common_digest.py create mode 100644 src/_cffi_src/commoncrypto/common_hmac.py create mode 100644 src/_cffi_src/commoncrypto/common_key_derivation.py create mode 100644 src/_cffi_src/commoncrypto/common_symmetric_key_wrap.py create mode 100644 src/_cffi_src/commoncrypto/seccertificate.py create mode 100644 src/_cffi_src/commoncrypto/secimport.py create mode 100644 src/_cffi_src/commoncrypto/secitem.py create mode 100644 src/_cffi_src/commoncrypto/seckey.py create mode 100644 src/_cffi_src/commoncrypto/seckeychain.py create mode 100644 src/_cffi_src/commoncrypto/secpolicy.py create mode 100644 src/_cffi_src/commoncrypto/sectransform.py create mode 100644 src/_cffi_src/commoncrypto/sectrust.py create mode 100644 src/_cffi_src/commoncrypto/secure_transport.py create mode 100644 src/_cffi_src/hazmat_src/constant_time.c create mode 100644 src/_cffi_src/hazmat_src/constant_time.h create mode 100644 src/_cffi_src/hazmat_src/padding.c create mode 100644 src/_cffi_src/hazmat_src/padding.h create mode 100644 src/_cffi_src/openssl/__init__.py create mode 100644 src/_cffi_src/openssl/aes.py create mode 100644 src/_cffi_src/openssl/asn1.py create mode 100644 src/_cffi_src/openssl/bignum.py create mode 100644 src/_cffi_src/openssl/bio.py create mode 100644 src/_cffi_src/openssl/callbacks.py create mode 100644 src/_cffi_src/openssl/cmac.py create mode 100644 src/_cffi_src/openssl/cms.py create mode 100644 src/_cffi_src/openssl/conf.py create mode 100644 src/_cffi_src/openssl/crypto.py create mode 100644 src/_cffi_src/openssl/cryptography.py create mode 100644 src/_cffi_src/openssl/dh.py create mode 100644 src/_cffi_src/openssl/dsa.py create mode 100644 src/_cffi_src/openssl/ec.py create mode 100644 src/_cffi_src/openssl/ecdh.py create mode 100644 src/_cffi_src/openssl/ecdsa.py create mode 100644 src/_cffi_src/openssl/engine.py create mode 100644 src/_cffi_src/openssl/err.py create mode 100644 src/_cffi_src/openssl/evp.py create mode 100644 src/_cffi_src/openssl/hmac.py create mode 100644 src/_cffi_src/openssl/nid.py create mode 100644 src/_cffi_src/openssl/objects.py create mode 100644 src/_cffi_src/openssl/ocsp.py create mode 100644 src/_cffi_src/openssl/opensslv.py create mode 100644 src/_cffi_src/openssl/osrandom_engine.py create mode 100644 src/_cffi_src/openssl/pem.py create mode 100644 src/_cffi_src/openssl/pkcs12.py create mode 100644 src/_cffi_src/openssl/pkcs7.py create mode 100644 src/_cffi_src/openssl/rand.py create mode 100644 src/_cffi_src/openssl/rsa.py create mode 100644 src/_cffi_src/openssl/src/osrandom_engine.c create mode 100644 src/_cffi_src/openssl/src/osrandom_engine.h create mode 100644 src/_cffi_src/openssl/ssl.py create mode 100644 src/_cffi_src/openssl/x509.py create mode 100644 src/_cffi_src/openssl/x509_vfy.py create mode 100644 src/_cffi_src/openssl/x509name.py create mode 100644 src/_cffi_src/openssl/x509v3.py create mode 100644 src/_cffi_src/utils.py create mode 100644 src/cryptography.egg-info/PKG-INFO create mode 100644 src/cryptography.egg-info/SOURCES.txt create mode 100644 src/cryptography.egg-info/dependency_links.txt create mode 100644 src/cryptography.egg-info/entry_points.txt create mode 100644 src/cryptography.egg-info/not-zip-safe create mode 100644 src/cryptography.egg-info/requires.txt create mode 100644 src/cryptography.egg-info/top_level.txt create mode 100644 src/cryptography/__about__.py create mode 100644 src/cryptography/__init__.py create mode 100644 src/cryptography/exceptions.py create mode 100644 src/cryptography/fernet.py create mode 100644 src/cryptography/hazmat/__init__.py create mode 100644 src/cryptography/hazmat/backends/__init__.py create mode 100644 src/cryptography/hazmat/backends/commoncrypto/__init__.py create mode 100644 src/cryptography/hazmat/backends/commoncrypto/backend.py create mode 100644 src/cryptography/hazmat/backends/commoncrypto/ciphers.py create mode 100644 src/cryptography/hazmat/backends/commoncrypto/hashes.py create mode 100644 src/cryptography/hazmat/backends/commoncrypto/hmac.py create mode 100644 src/cryptography/hazmat/backends/interfaces.py create mode 100644 src/cryptography/hazmat/backends/multibackend.py create mode 100644 src/cryptography/hazmat/backends/openssl/__init__.py create mode 100644 src/cryptography/hazmat/backends/openssl/backend.py create mode 100644 src/cryptography/hazmat/backends/openssl/ciphers.py create mode 100644 src/cryptography/hazmat/backends/openssl/cmac.py create mode 100644 src/cryptography/hazmat/backends/openssl/decode_asn1.py create mode 100644 src/cryptography/hazmat/backends/openssl/dh.py create mode 100644 src/cryptography/hazmat/backends/openssl/dsa.py create mode 100644 src/cryptography/hazmat/backends/openssl/ec.py create mode 100644 src/cryptography/hazmat/backends/openssl/encode_asn1.py create mode 100644 src/cryptography/hazmat/backends/openssl/hashes.py create mode 100644 src/cryptography/hazmat/backends/openssl/hmac.py create mode 100644 src/cryptography/hazmat/backends/openssl/rsa.py create mode 100644 src/cryptography/hazmat/backends/openssl/utils.py create mode 100644 src/cryptography/hazmat/backends/openssl/x509.py create mode 100644 src/cryptography/hazmat/bindings/__init__.py create mode 100644 src/cryptography/hazmat/bindings/commoncrypto/__init__.py create mode 100644 src/cryptography/hazmat/bindings/commoncrypto/binding.py create mode 100644 src/cryptography/hazmat/bindings/openssl/__init__.py create mode 100644 src/cryptography/hazmat/bindings/openssl/_conditional.py create mode 100644 src/cryptography/hazmat/bindings/openssl/binding.py create mode 100644 src/cryptography/hazmat/primitives/__init__.py create mode 100644 src/cryptography/hazmat/primitives/asymmetric/__init__.py create mode 100644 src/cryptography/hazmat/primitives/asymmetric/dh.py create mode 100644 src/cryptography/hazmat/primitives/asymmetric/dsa.py create mode 100644 src/cryptography/hazmat/primitives/asymmetric/ec.py create mode 100644 src/cryptography/hazmat/primitives/asymmetric/padding.py create mode 100644 src/cryptography/hazmat/primitives/asymmetric/rsa.py create mode 100644 src/cryptography/hazmat/primitives/asymmetric/utils.py create mode 100644 src/cryptography/hazmat/primitives/ciphers/__init__.py create mode 100644 src/cryptography/hazmat/primitives/ciphers/algorithms.py create mode 100644 src/cryptography/hazmat/primitives/ciphers/base.py create mode 100644 src/cryptography/hazmat/primitives/ciphers/modes.py create mode 100644 src/cryptography/hazmat/primitives/cmac.py create mode 100644 src/cryptography/hazmat/primitives/constant_time.py create mode 100644 src/cryptography/hazmat/primitives/hashes.py create mode 100644 src/cryptography/hazmat/primitives/hmac.py create mode 100644 src/cryptography/hazmat/primitives/interfaces/__init__.py create mode 100644 src/cryptography/hazmat/primitives/kdf/__init__.py create mode 100644 src/cryptography/hazmat/primitives/kdf/concatkdf.py create mode 100644 src/cryptography/hazmat/primitives/kdf/hkdf.py create mode 100644 src/cryptography/hazmat/primitives/kdf/kbkdf.py create mode 100644 src/cryptography/hazmat/primitives/kdf/pbkdf2.py create mode 100644 src/cryptography/hazmat/primitives/kdf/scrypt.py create mode 100644 src/cryptography/hazmat/primitives/kdf/x963kdf.py create mode 100644 src/cryptography/hazmat/primitives/keywrap.py create mode 100644 src/cryptography/hazmat/primitives/padding.py create mode 100644 src/cryptography/hazmat/primitives/serialization.py create mode 100644 src/cryptography/hazmat/primitives/twofactor/__init__.py create mode 100644 src/cryptography/hazmat/primitives/twofactor/hotp.py create mode 100644 src/cryptography/hazmat/primitives/twofactor/totp.py create mode 100644 src/cryptography/hazmat/primitives/twofactor/utils.py create mode 100644 src/cryptography/utils.py create mode 100644 src/cryptography/x509/__init__.py create mode 100644 src/cryptography/x509/base.py create mode 100644 src/cryptography/x509/extensions.py create mode 100644 src/cryptography/x509/general_name.py create mode 100644 src/cryptography/x509/name.py create mode 100644 src/cryptography/x509/oid.py create mode 100644 tests/__init__.py create mode 100644 tests/conftest.py create mode 100644 tests/doubles.py create mode 100644 tests/hazmat/__init__.py create mode 100644 tests/hazmat/backends/__init__.py create mode 100644 tests/hazmat/backends/test_backendinit.py create mode 100644 tests/hazmat/backends/test_commoncrypto.py create mode 100644 tests/hazmat/backends/test_multibackend.py create mode 100644 tests/hazmat/backends/test_openssl.py create mode 100644 tests/hazmat/bindings/test_commoncrypto.py create mode 100644 tests/hazmat/bindings/test_openssl.py create mode 100644 tests/hazmat/primitives/__init__.py create mode 100644 tests/hazmat/primitives/fixtures_dsa.py create mode 100644 tests/hazmat/primitives/fixtures_ec.py create mode 100644 tests/hazmat/primitives/fixtures_rsa.py create mode 100644 tests/hazmat/primitives/test_3des.py create mode 100644 tests/hazmat/primitives/test_aes.py create mode 100644 tests/hazmat/primitives/test_arc4.py create mode 100644 tests/hazmat/primitives/test_asym_utils.py create mode 100644 tests/hazmat/primitives/test_block.py create mode 100644 tests/hazmat/primitives/test_blowfish.py create mode 100644 tests/hazmat/primitives/test_camellia.py create mode 100644 tests/hazmat/primitives/test_cast5.py create mode 100644 tests/hazmat/primitives/test_ciphers.py create mode 100644 tests/hazmat/primitives/test_cmac.py create mode 100644 tests/hazmat/primitives/test_concatkdf.py create mode 100644 tests/hazmat/primitives/test_constant_time.py create mode 100644 tests/hazmat/primitives/test_dh.py create mode 100644 tests/hazmat/primitives/test_dsa.py create mode 100644 tests/hazmat/primitives/test_ec.py create mode 100644 tests/hazmat/primitives/test_hash_vectors.py create mode 100644 tests/hazmat/primitives/test_hashes.py create mode 100644 tests/hazmat/primitives/test_hkdf.py create mode 100644 tests/hazmat/primitives/test_hkdf_vectors.py create mode 100644 tests/hazmat/primitives/test_hmac.py create mode 100644 tests/hazmat/primitives/test_hmac_vectors.py create mode 100644 tests/hazmat/primitives/test_idea.py create mode 100644 tests/hazmat/primitives/test_kbkdf.py create mode 100644 tests/hazmat/primitives/test_kbkdf_vectors.py create mode 100644 tests/hazmat/primitives/test_keywrap.py create mode 100644 tests/hazmat/primitives/test_padding.py create mode 100644 tests/hazmat/primitives/test_pbkdf2hmac.py create mode 100644 tests/hazmat/primitives/test_pbkdf2hmac_vectors.py create mode 100644 tests/hazmat/primitives/test_rsa.py create mode 100644 tests/hazmat/primitives/test_scrypt.py create mode 100644 tests/hazmat/primitives/test_seed.py create mode 100644 tests/hazmat/primitives/test_serialization.py create mode 100644 tests/hazmat/primitives/test_x963_vectors.py create mode 100644 tests/hazmat/primitives/test_x963kdf.py create mode 100644 tests/hazmat/primitives/twofactor/__init__.py create mode 100644 tests/hazmat/primitives/twofactor/test_hotp.py create mode 100644 tests/hazmat/primitives/twofactor/test_totp.py create mode 100644 tests/hazmat/primitives/utils.py create mode 100644 tests/hypothesis/__init__.py create mode 100644 tests/hypothesis/test_fernet.py create mode 100644 tests/hypothesis/test_padding.py create mode 100644 tests/test_cryptography_utils.py create mode 100644 tests/test_fernet.py create mode 100644 tests/test_interfaces.py create mode 100644 tests/test_utils.py create mode 100644 tests/test_warnings.py create mode 100644 tests/test_x509.py create mode 100644 tests/test_x509_crlbuilder.py create mode 100644 tests/test_x509_ext.py create mode 100644 tests/test_x509_revokedcertbuilder.py create mode 100644 tests/utils.py diff --git a/AUTHORS.rst b/AUTHORS.rst new file mode 100644 index 0000000..13e552d --- /dev/null +++ b/AUTHORS.rst @@ -0,0 +1,38 @@ +AUTHORS +======= + +PGP key fingerprints are enclosed in parentheses. + +* Alex Gaynor (E27D 4AA0 1651 72CB C5D2 AF2B 125F 5C67 DFE9 4084) +* Hynek Schlawack (C2A0 4F86 ACE2 8ADC F817 DBB7 AE25 3622 7F69 F181) +* Donald Stufft +* Laurens Van Houtven <_@lvh.io> (D9DC 4315 772F 8E91 DD22 B153 DFD1 3DF7 A8DD 569B) +* Christian Heimes +* Paul Kehrer (05FD 9FA1 6CF7 5735 0D91 A560 235A E5F1 29F9 ED98) +* Jarret Raim +* Alex Stapleton (A1C7 E50B 66DE 39ED C847 9665 8E3C 20D1 9BD9 5C4C) +* David Reid (0F83 CC87 B32F 482B C726 B58A 9FBF D8F4 DA89 6D74) +* Matthew Lefkowitz (06AB F638 E878 CD29 1264 18AB 7EC2 8125 0FBC 4A07) +* Konstantinos Koukopoulos (D6BD 52B6 8C99 A91C E2C8 934D 3300 566B 3A46 726E) +* Stephen Holsapple +* Terry Chia +* Matthew Iversen (2F04 3DCC D6E6 D5AC D262 2E0B C046 E8A8 7452 2973) +* Mohammed Attia +* Michael Hart +* Mark Adams (A18A 7DD3 283C CF2A B0CE FE0E C7A0 5E3F C972 098C) +* Gregory Haynes (6FB6 44BF 9FD0 EBA2 1CE9 471F B08F 42F9 0DC6 599F) +* Chelsea Winfree +* Steven Buss (1FB9 2EC1 CF93 DFD6 B47F F583 B1A5 6C22 290D A4C3) +* Andre Caron +* Jiangge Zhang (BBEC 782B 015F 71B1 5FF7 EACA 1A8C AA98 255F 5000) +* Major Hayden (1BF9 9264 9596 0033 698C 252B 7370 51E0 C101 1FB1) +* Phoebe Queen (10D4 7741 AB65 50F4 B264 3888 DA40 201A 072B C1FA) +* Google Inc. +* Amaury Forgeot d'Arc +* Dirkjan Ochtman (25BB BAC1 13C1 BFD5 AA59 4A4C 9F96 B929 3038 0381) +* Maximilian Hils +* Simo Sorce +* Thomas Sileo +* Fraser Tweedale +* Ofek Lev (FFB6 B92B 30B1 7848 546E 9912 972F E913 DAD5 A46E) +* Erik Daguerre diff --git a/CHANGELOG.rst b/CHANGELOG.rst new file mode 100644 index 0000000..dd5e887 --- /dev/null +++ b/CHANGELOG.rst @@ -0,0 +1,792 @@ +Changelog +========= + +1.7.1 - 2016-12-13 +~~~~~~~~~~~~~~~~~~ + +* Fixed a regression in ``int_from_bytes`` where it failed to accept + ``bytearray``. + +1.7 - 2016-12-12 +~~~~~~~~~~~~~~~~ + +* Support for OpenSSL 1.0.0 has been removed. Users on older version of OpenSSL + will need to upgrade. +* Added support for Diffie-Hellman key exchange using + :meth:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKeyWithSerialization.exchange` +* The OS random engine for OpenSSL has been rewritten to improve compatibility + with embedded Python and other edge cases. More information about this change + can be found in the + `pull request `_. + +1.6 - 2016-11-22 +~~~~~~~~~~~~~~~~ + +* Deprecated support for OpenSSL 1.0.0. Support will be removed in + ``cryptography`` 1.7. +* Replaced the Python-based OpenSSL locking callbacks with a C version to fix + a potential deadlock that could occur if a garbage collection cycle occurred + while inside the lock. +* Added support for :class:`~cryptography.hazmat.primitives.hashes.BLAKE2b` and + :class:`~cryptography.hazmat.primitives.hashes.BLAKE2s` when using OpenSSL + 1.1.0. +* Added + :attr:`~cryptography.x509.Certificate.signature_algorithm_oid` support to + :class:`~cryptography.x509.Certificate`. +* Added + :attr:`~cryptography.x509.CertificateSigningRequest.signature_algorithm_oid` + support to :class:`~cryptography.x509.CertificateSigningRequest`. +* Added + :attr:`~cryptography.x509.CertificateRevocationList.signature_algorithm_oid` + support to :class:`~cryptography.x509.CertificateRevocationList`. +* Added support for :class:`~cryptography.hazmat.primitives.kdf.scrypt.Scrypt` + when using OpenSSL 1.1.0. +* Added a workaround to improve compatibility with Python application bundling + tools like ``PyInstaller`` and ``cx_freeze``. +* Added support for generating a + :meth:`~cryptography.x509.random_serial_number`. +* Added support for encoding ``IPv4Network`` and ``IPv6Network`` in X.509 + certificates for use with :class:`~cryptography.x509.NameConstraints`. +* Added :meth:`~cryptography.x509.Name.public_bytes` to + :class:`~cryptography.x509.Name`. +* Added :class:`~cryptography.x509.RelativeDistinguishedName` +* :class:`~cryptography.x509.DistributionPoint` now accepts + :class:`~cryptography.x509.RelativeDistinguishedName` for + :attr:`~cryptography.x509.DistributionPoint.relative_name`. + Deprecated use of :class:`~cryptography.x509.Name` as + :attr:`~cryptography.x509.DistributionPoint.relative_name`. +* :class:`~cryptography.x509.Name` now accepts an iterable of + :class:`~cryptography.x509.RelativeDistinguishedName`. RDNs can + be accessed via the :attr:`~cryptography.x509.Name.rdns` + attribute. When constructed with an iterable of + :class:`~cryptography.x509.NameAttribute`, each attribute becomes + a single-valued RDN. +* Added + :func:`~cryptography.hazmat.primitives.asymmetric.ec.derive_private_key`. +* Added support for signing and verifying RSA, DSA, and ECDSA signatures with + :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` + digests. + +1.5.3 - 2016-11-05 +~~~~~~~~~~~~~~~~~~ + +* **SECURITY ISSUE**: Fixed a bug where ``HKDF`` would return an empty + byte-string if used with a ``length`` less than ``algorithm.digest_size``. + Credit to **Markus Döring** for reporting the issue. *CVE-2016-9243* + +1.5.2 - 2016-09-26 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows and OS X wheels to be compiled against OpenSSL 1.0.2j. + +1.5.1 - 2016-09-22 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows and OS X wheels to be compiled against OpenSSL 1.0.2i. +* Resolved a ``UserWarning`` when used with cffi 1.8.3. +* Fixed a memory leak in name creation with X.509. +* Added a workaround for old versions of setuptools. +* Fixed an issue preventing ``cryptography`` from compiling against + OpenSSL 1.0.2i. + + +1.5 - 2016-08-26 +~~~~~~~~~~~~~~~~ + +* Added + :func:`~cryptography.hazmat.primitives.asymmetric.padding.calculate_max_pss_salt_length`. +* Added "one shot" + :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.sign` + and + :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey.verify` + methods to DSA keys. +* Added "one shot" + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.sign` + and + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.verify` + methods to ECDSA keys. +* Switched back to the older callback model on Python 3.5 in order to mitigate + the locking callback problem with OpenSSL <1.1.0. +* :class:`~cryptography.x509.CertificateBuilder`, + :class:`~cryptography.x509.CertificateRevocationListBuilder`, and + :class:`~cryptography.x509.RevokedCertificateBuilder` now accept timezone + aware ``datetime`` objects as method arguments +* ``cryptography`` now supports OpenSSL 1.1.0 as a compilation target. + + +1.4 - 2016-06-04 +~~~~~~~~~~~~~~~~ + +* Support for OpenSSL 0.9.8 has been removed. Users on older versions of + OpenSSL will need to upgrade. +* Added :class:`~cryptography.hazmat.primitives.kdf.kbkdf.KBKDFHMAC`. +* Added support for ``OpenSSH`` public key serialization. +* Added support for SHA-2 in RSA + :class:`~cryptography.hazmat.primitives.asymmetric.padding.OAEP` when using + OpenSSL 1.0.2 or greater. +* Added "one shot" + :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.sign` + and + :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey.verify` + methods to RSA keys. + + +1.3.4 - 2016-06-03 +~~~~~~~~~~~~~~~~~~ + +* Added another OpenSSL function to the bindings to support an upcoming + ``pyOpenSSL`` release. + + +1.3.3 - 2016-06-02 +~~~~~~~~~~~~~~~~~~ + +* Added two new OpenSSL functions to the bindings to support an upcoming + ``pyOpenSSL`` release. + +1.3.2 - 2016-05-04 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows and OS X wheels to be compiled against OpenSSL 1.0.2h. +* Fixed an issue preventing ``cryptography`` from compiling against + LibreSSL 2.3.x. + +1.3.1 - 2016-03-21 +~~~~~~~~~~~~~~~~~~ + +* Fixed a bug that caused an ``AttributeError`` when using ``mock`` to patch + some ``cryptography`` modules. + +1.3 - 2016-03-18 +~~~~~~~~~~~~~~~~ + +* Added support for padding ANSI X.923 with + :class:`~cryptography.hazmat.primitives.padding.ANSIX923`. +* Deprecated support for OpenSSL 0.9.8. Support will be removed in + ``cryptography`` 1.4. +* Added support for the :class:`~cryptography.x509.PolicyConstraints` + X.509 extension including both parsing and generation using + :class:`~cryptography.x509.CertificateBuilder` and + :class:`~cryptography.x509.CertificateSigningRequestBuilder`. +* Added :attr:`~cryptography.x509.CertificateSigningRequest.is_signature_valid` + to :class:`~cryptography.x509.CertificateSigningRequest`. +* Fixed an intermittent ``AssertionError`` when performing an RSA decryption on + an invalid ciphertext, ``ValueError`` is now correctly raised in all cases. +* Added + :meth:`~cryptography.x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier`. + +1.2.3 - 2016-03-01 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows and OS X wheels to be compiled against OpenSSL 1.0.2g. + +1.2.2 - 2016-01-29 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows and OS X wheels to be compiled against OpenSSL 1.0.2f. + +1.2.1 - 2016-01-08 +~~~~~~~~~~~~~~~~~~ + +* Reverts a change to an OpenSSL ``EVP_PKEY`` object that caused errors with + ``pyOpenSSL``. + +1.2 - 2016-01-08 +~~~~~~~~~~~~~~~~ + +* **BACKWARDS INCOMPATIBLE:** + :class:`~cryptography.x509.RevokedCertificate` + :attr:`~cryptography.x509.RevokedCertificate.extensions` now uses extension + classes rather than returning raw values inside the + :class:`~cryptography.x509.Extension` + :attr:`~cryptography.x509.Extension.value`. The new classes + are: + + * :class:`~cryptography.x509.CertificateIssuer` + * :class:`~cryptography.x509.CRLReason` + * :class:`~cryptography.x509.InvalidityDate` +* Deprecated support for OpenSSL 0.9.8 and 1.0.0. At this time there is no time + table for actually dropping support, however we strongly encourage all users + to upgrade, as those versions no longer receive support from the OpenSSL + project. +* The :class:`~cryptography.x509.Certificate` class now has + :attr:`~cryptography.x509.Certificate.signature` and + :attr:`~cryptography.x509.Certificate.tbs_certificate_bytes` attributes. +* The :class:`~cryptography.x509.CertificateSigningRequest` class now has + :attr:`~cryptography.x509.CertificateSigningRequest.signature` and + :attr:`~cryptography.x509.CertificateSigningRequest.tbs_certrequest_bytes` + attributes. +* The :class:`~cryptography.x509.CertificateRevocationList` class now has + :attr:`~cryptography.x509.CertificateRevocationList.signature` and + :attr:`~cryptography.x509.CertificateRevocationList.tbs_certlist_bytes` + attributes. +* :class:`~cryptography.x509.NameConstraints` are now supported in the + :class:`~cryptography.x509.CertificateBuilder` and + :class:`~cryptography.x509.CertificateSigningRequestBuilder`. +* Support serialization of certificate revocation lists using the + :meth:`~cryptography.x509.CertificateRevocationList.public_bytes` method of + :class:`~cryptography.x509.CertificateRevocationList`. +* Add support for parsing :class:`~cryptography.x509.CertificateRevocationList` + :meth:`~cryptography.x509.CertificateRevocationList.extensions` in the + OpenSSL backend. The following extensions are currently supported: + + * :class:`~cryptography.x509.AuthorityInformationAccess` + * :class:`~cryptography.x509.AuthorityKeyIdentifier` + * :class:`~cryptography.x509.CRLNumber` + * :class:`~cryptography.x509.IssuerAlternativeName` +* Added :class:`~cryptography.x509.CertificateRevocationListBuilder` and + :class:`~cryptography.x509.RevokedCertificateBuilder` to allow creation of + CRLs. +* Unrecognized non-critical X.509 extensions are now parsed into an + :class:`~cryptography.x509.UnrecognizedExtension` object. + +1.1.2 - 2015-12-10 +~~~~~~~~~~~~~~~~~~ + +* Fixed a SIGBUS crash with the OS X wheels caused by redefinition of a + method. +* Fixed a runtime error ``undefined symbol EC_GFp_nistp224_method`` that + occurred with some OpenSSL installations. +* Updated Windows and OS X wheels to be compiled against OpenSSL 1.0.2e. + +1.1.1 - 2015-11-19 +~~~~~~~~~~~~~~~~~~ + +* Fixed several small bugs related to compiling the OpenSSL bindings with + unusual OpenSSL configurations. +* Resolved an issue where, depending on the method of installation and + which Python interpreter they were using, users on El Capitan (OS X 10.11) + may have seen an ``InternalError`` on import. + +1.1 - 2015-10-28 +~~~~~~~~~~~~~~~~ + +* Added support for Elliptic Curve Diffie-Hellman with + :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDH`. +* Added :class:`~cryptography.hazmat.primitives.kdf.x963kdf.X963KDF`. +* Added support for parsing certificate revocation lists (CRLs) using + :func:`~cryptography.x509.load_pem_x509_crl` and + :func:`~cryptography.x509.load_der_x509_crl`. +* Add support for AES key wrapping with + :func:`~cryptography.hazmat.primitives.keywrap.aes_key_wrap` and + :func:`~cryptography.hazmat.primitives.keywrap.aes_key_unwrap`. +* Added a ``__hash__`` method to :class:`~cryptography.x509.Name`. +* Add support for encoding and decoding elliptic curve points to a byte string + form using + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.encode_point` + and + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers.from_encoded_point`. +* Added :meth:`~cryptography.x509.Extensions.get_extension_for_class`. +* :class:`~cryptography.x509.CertificatePolicies` are now supported in the + :class:`~cryptography.x509.CertificateBuilder`. +* ``countryName`` is now encoded as a ``PrintableString`` when creating subject + and issuer distinguished names with the Certificate and CSR builder classes. + +1.0.2 - 2015-09-27 +~~~~~~~~~~~~~~~~~~ +* **SECURITY ISSUE**: The OpenSSL backend prior to 1.0.2 made extensive use + of assertions to check response codes where our tests could not trigger a + failure. However, when Python is run with ``-O`` these asserts are optimized + away. If a user ran Python with this flag and got an invalid response code + this could result in undefined behavior or worse. Accordingly, all response + checks from the OpenSSL backend have been converted from ``assert`` + to a true function call. Credit **Emilia Käsper (Google Security Team)** + for the report. + +1.0.1 - 2015-09-05 +~~~~~~~~~~~~~~~~~~ + +* We now ship OS X wheels that statically link OpenSSL by default. When + installing a wheel on OS X 10.10+ (and using a Python compiled against the + 10.10 SDK) users will no longer need to compile. See :doc:`/installation` for + alternate installation methods if required. +* Set the default string mask to UTF-8 in the OpenSSL backend to resolve + character encoding issues with older versions of OpenSSL. +* Several new OpenSSL bindings have been added to support a future pyOpenSSL + release. +* Raise an error during install on PyPy < 2.6. 1.0+ requires PyPy 2.6+. + +1.0 - 2015-08-12 +~~~~~~~~~~~~~~~~ + +* Switched to the new `cffi`_ ``set_source`` out-of-line API mode for + compilation. This results in significantly faster imports and lowered + memory consumption. Due to this change we no longer support PyPy releases + older than 2.6 nor do we support any released version of PyPy3 (until a + version supporting cffi 1.0 comes out). +* Fix parsing of OpenSSH public keys that have spaces in comments. +* Support serialization of certificate signing requests using the + ``public_bytes`` method of + :class:`~cryptography.x509.CertificateSigningRequest`. +* Support serialization of certificates using the ``public_bytes`` method of + :class:`~cryptography.x509.Certificate`. +* Add ``get_provisioning_uri`` method to + :class:`~cryptography.hazmat.primitives.twofactor.hotp.HOTP` and + :class:`~cryptography.hazmat.primitives.twofactor.totp.TOTP` for generating + provisioning URIs. +* Add :class:`~cryptography.hazmat.primitives.kdf.concatkdf.ConcatKDFHash` + and :class:`~cryptography.hazmat.primitives.kdf.concatkdf.ConcatKDFHMAC`. +* Raise a ``TypeError`` when passing objects that are not text as the value to + :class:`~cryptography.x509.NameAttribute`. +* Add support for :class:`~cryptography.x509.OtherName` as a general name + type. +* Added new X.509 extension support in :class:`~cryptography.x509.Certificate` + The following new extensions are now supported: + + * :class:`~cryptography.x509.OCSPNoCheck` + * :class:`~cryptography.x509.InhibitAnyPolicy` + * :class:`~cryptography.x509.IssuerAlternativeName` + * :class:`~cryptography.x509.NameConstraints` + +* Extension support was added to + :class:`~cryptography.x509.CertificateSigningRequest`. +* Add support for creating signed certificates with + :class:`~cryptography.x509.CertificateBuilder`. This includes support for + the following extensions: + + * :class:`~cryptography.x509.BasicConstraints` + * :class:`~cryptography.x509.SubjectAlternativeName` + * :class:`~cryptography.x509.KeyUsage` + * :class:`~cryptography.x509.ExtendedKeyUsage` + * :class:`~cryptography.x509.SubjectKeyIdentifier` + * :class:`~cryptography.x509.AuthorityKeyIdentifier` + * :class:`~cryptography.x509.AuthorityInformationAccess` + * :class:`~cryptography.x509.CRLDistributionPoints` + * :class:`~cryptography.x509.InhibitAnyPolicy` + * :class:`~cryptography.x509.IssuerAlternativeName` + * :class:`~cryptography.x509.OCSPNoCheck` + +* Add support for creating certificate signing requests with + :class:`~cryptography.x509.CertificateSigningRequestBuilder`. This includes + support for the same extensions supported in the ``CertificateBuilder``. +* Deprecate ``encode_rfc6979_signature`` and ``decode_rfc6979_signature`` in + favor of + :func:`~cryptography.hazmat.primitives.asymmetric.utils.encode_dss_signature` + and + :func:`~cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature`. + + +0.9.3 - 2015-07-09 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows wheels to be compiled against OpenSSL 1.0.2d. + +0.9.2 - 2015-07-04 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows wheels to be compiled against OpenSSL 1.0.2c. + +0.9.1 - 2015-06-06 +~~~~~~~~~~~~~~~~~~ + +* **SECURITY ISSUE**: Fixed a double free in the OpenSSL backend when using DSA + to verify signatures. Note that this only affects PyPy 2.6.0 and (presently + unreleased) CFFI versions greater than 1.1.0. + +0.9 - 2015-05-13 +~~~~~~~~~~~~~~~~ + +* Removed support for Python 3.2. This version of Python is rarely used + and caused support headaches. Users affected by this should upgrade to 3.3+. +* Deprecated support for Python 2.6. At the time there is no time table for + actually dropping support, however we strongly encourage all users to upgrade + their Python, as Python 2.6 no longer receives support from the Python core + team. +* Add support for the + :class:`~cryptography.hazmat.primitives.asymmetric.ec.SECP256K1` elliptic + curve. +* Fixed compilation when using an OpenSSL which was compiled with the + ``no-comp`` (``OPENSSL_NO_COMP``) option. +* Support :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER` + serialization of public keys using the ``public_bytes`` method of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKeyWithSerialization`, + and + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization`. +* Support :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER` + serialization of private keys using the ``private_bytes`` method of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`, + and + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`. +* Add support for parsing X.509 certificate signing requests (CSRs) with + :func:`~cryptography.x509.load_pem_x509_csr` and + :func:`~cryptography.x509.load_der_x509_csr`. +* Moved ``cryptography.exceptions.InvalidToken`` to + :class:`cryptography.hazmat.primitives.twofactor.InvalidToken` and deprecated + the old location. This was moved to minimize confusion between this exception + and :class:`cryptography.fernet.InvalidToken`. +* Added support for X.509 extensions in :class:`~cryptography.x509.Certificate` + objects. The following extensions are supported as of this release: + + * :class:`~cryptography.x509.BasicConstraints` + * :class:`~cryptography.x509.AuthorityKeyIdentifier` + * :class:`~cryptography.x509.SubjectKeyIdentifier` + * :class:`~cryptography.x509.KeyUsage` + * :class:`~cryptography.x509.SubjectAlternativeName` + * :class:`~cryptography.x509.ExtendedKeyUsage` + * :class:`~cryptography.x509.CRLDistributionPoints` + * :class:`~cryptography.x509.AuthorityInformationAccess` + * :class:`~cryptography.x509.CertificatePolicies` + + 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` for more information. + +0.8.2 - 2015-04-10 +~~~~~~~~~~~~~~~~~~ + +* Fixed a race condition when initializing the OpenSSL or CommonCrypto backends + in a multi-threaded scenario. + +0.8.1 - 2015-03-20 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows wheels to be compiled against OpenSSL 1.0.2a. + +0.8 - 2015-03-08 +~~~~~~~~~~~~~~~~ + +* :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` can + now load elliptic curve public keys. +* Added + :attr:`~cryptography.x509.Certificate.signature_hash_algorithm` support to + :class:`~cryptography.x509.Certificate`. +* Added + :func:`~cryptography.hazmat.primitives.asymmetric.rsa.rsa_recover_prime_factors` +* :class:`~cryptography.hazmat.primitives.kdf.KeyDerivationFunction` was moved + 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` for more information. +* Added + :func:`~cryptography.hazmat.primitives.serialization.load_der_private_key` to + support loading of DER encoded private keys and + :func:`~cryptography.hazmat.primitives.serialization.load_der_public_key` to + support loading DER encoded public keys. +* Fixed building against LibreSSL, a compile-time substitute for OpenSSL. +* FreeBSD 9.2 was removed from the continuous integration system. +* Updated Windows wheels to be compiled against OpenSSL 1.0.2. +* :func:`~cryptography.hazmat.primitives.serialization.load_pem_public_key` + and :func:`~cryptography.hazmat.primitives.serialization.load_der_public_key` + now support PKCS1 RSA public keys (in addition to the previous support for + SubjectPublicKeyInfo format for RSA, EC, and DSA). +* Added + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` + and deprecated ``EllipticCurvePrivateKeyWithNumbers``. +* Added + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization.private_bytes` + to + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization`. +* Added + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` + and deprecated ``RSAPrivateKeyWithNumbers``. +* Added + :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes` + to + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization`. +* Added + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization` + and deprecated ``DSAPrivateKeyWithNumbers``. +* Added + :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization.private_bytes` + to + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`. +* Added + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization` + and deprecated ``RSAPublicKeyWithNumbers``. +* Added ``public_bytes`` to + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization`. +* Added + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization` + and deprecated ``EllipticCurvePublicKeyWithNumbers``. +* Added ``public_bytes`` to + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization`. +* Added + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKeyWithSerialization` + and deprecated ``DSAPublicKeyWithNumbers``. +* Added ``public_bytes`` to + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKeyWithSerialization`. +* :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` and + :class:`~cryptography.hazmat.primitives.hashes.HashContext` were moved from + :mod:`~cryptography.hazmat.primitives.interfaces` to + :mod:`~cryptography.hazmat.primitives.hashes`. +* :class:`~cryptography.hazmat.primitives.ciphers.CipherContext`, + :class:`~cryptography.hazmat.primitives.ciphers.AEADCipherContext`, + :class:`~cryptography.hazmat.primitives.ciphers.AEADEncryptionContext`, + :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm`, and + :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm` + were moved from :mod:`~cryptography.hazmat.primitives.interfaces` to + :mod:`~cryptography.hazmat.primitives.ciphers`. +* :class:`~cryptography.hazmat.primitives.ciphers.modes.Mode`, + :class:`~cryptography.hazmat.primitives.ciphers.modes.ModeWithInitializationVector`, + :class:`~cryptography.hazmat.primitives.ciphers.modes.ModeWithNonce`, and + :class:`~cryptography.hazmat.primitives.ciphers.modes.ModeWithAuthenticationTag` + were moved from :mod:`~cryptography.hazmat.primitives.interfaces` to + :mod:`~cryptography.hazmat.primitives.ciphers.modes`. +* :class:`~cryptography.hazmat.primitives.padding.PaddingContext` was moved + from :mod:`~cryptography.hazmat.primitives.interfaces` to + :mod:`~cryptography.hazmat.primitives.padding`. +* + :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding` + was moved from :mod:`~cryptography.hazmat.primitives.interfaces` to + :mod:`~cryptography.hazmat.primitives.asymmetric.padding`. +* + :class:`~cryptography.hazmat.primitives.asymmetric.AsymmetricSignatureContext` + and + :class:`~cryptography.hazmat.primitives.asymmetric.AsymmetricVerificationContext` + were moved from :mod:`~cryptography.hazmat.primitives.interfaces` to + :mod:`~cryptography.hazmat.primitives.asymmetric`. +* :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParametersWithNumbers`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, + ``DSAPrivateKeyWithNumbers``, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` and + ``DSAPublicKeyWithNumbers`` were moved from + :mod:`~cryptography.hazmat.primitives.interfaces` to + :mod:`~cryptography.hazmat.primitives.asymmetric.dsa` +* :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurveSignatureAlgorithm`, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`, + ``EllipticCurvePrivateKeyWithNumbers``, + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`, + and ``EllipticCurvePublicKeyWithNumbers`` + were moved from :mod:`~cryptography.hazmat.primitives.interfaces` to + :mod:`~cryptography.hazmat.primitives.asymmetric.ec`. +* :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + ``RSAPrivateKeyWithNumbers``, + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` and + ``RSAPublicKeyWithNumbers`` were moved from + :mod:`~cryptography.hazmat.primitives.interfaces` to + :mod:`~cryptography.hazmat.primitives.asymmetric.rsa`. + +0.7.2 - 2015-01-16 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows wheels to be compiled against OpenSSL 1.0.1l. +* ``enum34`` is no longer installed on Python 3.4, where it is included in + the standard library. +* Added a new function to the OpenSSL bindings to support additional + functionality in pyOpenSSL. + +0.7.1 - 2014-12-28 +~~~~~~~~~~~~~~~~~~ + +* Fixed an issue preventing compilation on platforms where ``OPENSSL_NO_SSL3`` + was defined. + +0.7 - 2014-12-17 +~~~~~~~~~~~~~~~~ + +* Cryptography has been relicensed from the Apache Software License, Version + 2.0, to being available under *either* the Apache Software License, Version + 2.0, or the BSD license. +* Added key-rotation support to :doc:`Fernet ` with + :class:`~cryptography.fernet.MultiFernet`. +* More bit-lengths are now supported for ``p`` and ``q`` when loading DSA keys + from numbers. +* Added :class:`~cryptography.hazmat.primitives.interfaces.MACContext` as a + common interface for CMAC and HMAC and deprecated ``CMACContext``. +* Added support for encoding and decoding :rfc:`6979` signatures in + :doc:`/hazmat/primitives/asymmetric/utils`. +* Added + :func:`~cryptography.hazmat.primitives.serialization.load_ssh_public_key` to + 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` for more information. + +0.6.1 - 2014-10-15 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows wheels to be compiled against OpenSSL 1.0.1j. +* Fixed an issue where OpenSSL 1.0.1j changed the errors returned by some + functions. +* Added our license file to the ``cryptography-vectors`` package. +* Implemented DSA hash truncation support (per FIPS 186-3) in the OpenSSL + backend. This works around an issue in 1.0.0, 1.0.0a, and 1.0.0b where + truncation was not implemented. + +0.6 - 2014-09-29 +~~~~~~~~~~~~~~~~ + +* Added + :func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key` to + ease loading private keys, and + :func:`~cryptography.hazmat.primitives.serialization.load_pem_public_key` to + support loading public keys. +* Removed the, deprecated in 0.4, support for the ``salt_length`` argument to + the :class:`~cryptography.hazmat.primitives.asymmetric.padding.MGF1` + constructor. The ``salt_length`` should be passed to + :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` instead. +* Fix compilation on OS X Yosemite. +* Deprecated ``elliptic_curve_private_key_from_numbers`` and + ``elliptic_curve_public_key_from_numbers`` in favor of + ``load_elliptic_curve_private_numbers`` and + ``load_elliptic_curve_public_numbers`` on + :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. +* Added ``EllipticCurvePrivateKeyWithNumbers`` and + ``EllipticCurvePublicKeyWithNumbers`` support. +* Work around three GCM related bugs in CommonCrypto and OpenSSL. + + * On the CommonCrypto backend adding AAD but not subsequently calling update + would return null tag bytes. + + * One the CommonCrypto backend a call to update without an empty add AAD call + would return null ciphertext bytes. + + * On the OpenSSL backend with certain versions adding AAD only would give + invalid tag bytes. + +* Support loading EC private keys from PEM. + +0.5.4 - 2014-08-20 +~~~~~~~~~~~~~~~~~~ + +* Added several functions to the OpenSSL bindings to support new + functionality in pyOpenSSL. +* Fixed a redefined constant causing compilation failure with Solaris 11.2. + +0.5.3 - 2014-08-06 +~~~~~~~~~~~~~~~~~~ + +* Updated Windows wheels to be compiled against OpenSSL 1.0.1i. + +0.5.2 - 2014-07-09 +~~~~~~~~~~~~~~~~~~ + +* Add ``TraditionalOpenSSLSerializationBackend`` support to + :doc:`/hazmat/backends/multibackend`. +* Fix compilation error on OS X 10.8 (Mountain Lion). + +0.5.1 - 2014-07-07 +~~~~~~~~~~~~~~~~~~ + +* Add ``PKCS8SerializationBackend`` support to + :doc:`/hazmat/backends/multibackend`. + +0.5 - 2014-07-07 +~~~~~~~~~~~~~~~~ + +* **BACKWARDS INCOMPATIBLE:** + :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` no longer allows + truncation of tags by default. Previous versions of ``cryptography`` allowed + tags to be truncated by default, applications wishing to preserve this + behavior (not recommended) can pass the ``min_tag_length`` argument. +* Windows builds now statically link OpenSSL by default. When installing a + wheel on Windows you no longer need to install OpenSSL separately. Windows + users can switch between static and dynamic linking with an environment + variable. See :doc:`/installation` for more details. +* Added :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDFExpand`. +* Added :class:`~cryptography.hazmat.primitives.ciphers.modes.CFB8` support + for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` and + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` on + :doc:`/hazmat/backends/commoncrypto` and :doc:`/hazmat/backends/openssl`. +* Added ``AES`` :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR` + support to the OpenSSL backend when linked against 0.9.8. +* Added ``PKCS8SerializationBackend`` and + ``TraditionalOpenSSLSerializationBackend`` support to the + :doc:`/hazmat/backends/openssl`. +* Added :doc:`/hazmat/primitives/asymmetric/ec` and + :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. +* Added :class:`~cryptography.hazmat.primitives.ciphers.modes.ECB` support + for :class:`~cryptography.hazmat.primitives.ciphers.algorithms.TripleDES` on + :doc:`/hazmat/backends/commoncrypto` and :doc:`/hazmat/backends/openssl`. +* Deprecated the concrete ``RSAPrivateKey`` class in favor of backend + specific providers of the + :class:`cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` + interface. +* Deprecated the concrete ``RSAPublicKey`` in favor of backend specific + providers of the + :class:`cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` + interface. +* Deprecated the concrete ``DSAPrivateKey`` class in favor of backend + specific providers of the + :class:`cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` + interface. +* Deprecated the concrete ``DSAPublicKey`` class in favor of backend specific + providers of the + :class:`cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` + interface. +* Deprecated the concrete ``DSAParameters`` class in favor of backend specific + providers of the + :class:`cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters` + interface. +* Deprecated ``encrypt_rsa``, ``decrypt_rsa``, ``create_rsa_signature_ctx`` and + ``create_rsa_verification_ctx`` on + :class:`~cryptography.hazmat.backends.interfaces.RSABackend`. +* Deprecated ``create_dsa_signature_ctx`` and ``create_dsa_verification_ctx`` + on :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. + +0.4 - 2014-05-03 +~~~~~~~~~~~~~~~~ + +* Deprecated ``salt_length`` on + :class:`~cryptography.hazmat.primitives.asymmetric.padding.MGF1` and added it + to :class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS`. It will + be removed from ``MGF1`` in two releases per our :doc:`/api-stability` + policy. +* Added :class:`~cryptography.hazmat.primitives.ciphers.algorithms.SEED` + support. +* Added :class:`~cryptography.hazmat.primitives.cmac.CMAC`. +* Added decryption support to + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` + and encryption support to + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. +* Added signature support to + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` + and verification support to + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`. + +0.3 - 2014-03-27 +~~~~~~~~~~~~~~~~ + +* Added :class:`~cryptography.hazmat.primitives.twofactor.hotp.HOTP`. +* Added :class:`~cryptography.hazmat.primitives.twofactor.totp.TOTP`. +* Added :class:`~cryptography.hazmat.primitives.ciphers.algorithms.IDEA` + support. +* Added signature support to + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey` + and verification support to + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. +* Moved test vectors to the new ``cryptography_vectors`` package. + +0.2.2 - 2014-03-03 +~~~~~~~~~~~~~~~~~~ + +* Removed a constant definition that was causing compilation problems with + specific versions of OpenSSL. + +0.2.1 - 2014-02-22 +~~~~~~~~~~~~~~~~~~ + +* Fix a bug where importing cryptography from multiple paths could cause + initialization to fail. + +0.2 - 2014-02-20 +~~~~~~~~~~~~~~~~ + +* Added :doc:`/hazmat/backends/commoncrypto`. +* Added initial :doc:`/hazmat/bindings/commoncrypto`. +* Removed ``register_cipher_adapter`` method from + :class:`~cryptography.hazmat.backends.interfaces.CipherBackend`. +* Added support for the OpenSSL backend under Windows. +* Improved thread-safety for the OpenSSL backend. +* Fixed compilation on systems where OpenSSL's ``ec.h`` header is not + available, such as CentOS. +* Added :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`. +* Added :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF`. +* Added :doc:`/hazmat/backends/multibackend`. +* Set default random for the :doc:`/hazmat/backends/openssl` to the OS + random engine. +* Added :class:`~cryptography.hazmat.primitives.ciphers.algorithms.CAST5` + (CAST-128) support. + +0.1 - 2014-01-08 +~~~~~~~~~~~~~~~~ + +* Initial release. + +.. _`master`: https://github.com/pyca/cryptography/ +.. _`cffi`: https://cffi.readthedocs.io/ diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst new file mode 100644 index 0000000..6cd409a --- /dev/null +++ b/CONTRIBUTING.rst @@ -0,0 +1,23 @@ +Contributing to cryptography +============================ + +As an open source project, cryptography welcomes contributions of many forms. + +Examples of contributions include: + +* Code patches +* Documentation improvements +* Bug reports and patch reviews + +Extensive contribution guidelines are available in the repository at +``docs/development/index.rst``, or online at: + +https://cryptography.io/en/latest/development/ + +Security issues +--------------- + +To report a security issue, please follow the special `security reporting +guidelines`_, do not report them in the public issue tracker. + +.. _`security reporting guidelines`: https://cryptography.io/en/latest/security/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e290cd1 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +This software is made available under the terms of *either* of the licenses +found in LICENSE.APACHE or LICENSE.BSD. Contributions to cryptography are made +under the terms of *both* these licenses. + +The code used in the OpenSSL locking callback and OS random engine is derived +from the same in CPython itself, and is licensed under the terms of the PSF +License Agreement. diff --git a/LICENSE.APACHE b/LICENSE.APACHE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE.APACHE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/LICENSE.BSD b/LICENSE.BSD new file mode 100644 index 0000000..ec1a29d --- /dev/null +++ b/LICENSE.BSD @@ -0,0 +1,27 @@ +Copyright (c) Individual contributors. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of PyCA Cryptography nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..b05a869 --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,13 @@ +include AUTHORS.rst +include CHANGELOG.rst +include CONTRIBUTING.rst +include LICENSE +include LICENSE.APACHE +include LICENSE.BSD +include README.rst + +recursive-include docs * +recursive-include src/_cffi_src *.py *.c *.h +prune docs/_build +recursive-include tests *.py +recursive-exclude vectors * diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..4409380 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,94 @@ +Metadata-Version: 1.1 +Name: cryptography +Version: 1.7.1 +Summary: cryptography is a package which provides cryptographic recipes and primitives to Python developers. +Home-page: https://github.com/pyca/cryptography +Author: The cryptography developers +Author-email: cryptography-dev@python.org +License: BSD or Apache License, Version 2.0 +Description: Cryptography + ============ + + .. image:: https://img.shields.io/pypi/v/cryptography.svg + :target: https://pypi.python.org/pypi/cryptography/ + :alt: Latest Version + + .. image:: https://readthedocs.org/projects/cryptography/badge/?version=latest + :target: https://cryptography.io + :alt: Latest Docs + + .. image:: https://travis-ci.org/pyca/cryptography.svg?branch=master + :target: https://travis-ci.org/pyca/cryptography + + .. image:: https://codecov.io/github/pyca/cryptography/coverage.svg?branch=master + :target: https://codecov.io/github/pyca/cryptography?branch=master + + + ``cryptography`` is a package which provides cryptographic recipes and + primitives to Python developers. Our goal is for it to be your "cryptographic + standard library". It supports Python 2.6-2.7, Python 3.3+, and PyPy 2.6+. + + ``cryptography`` includes both high level recipes, and low level interfaces to + common cryptographic algorithms such as symmetric ciphers, message digests and + key derivation functions. For example, to encrypt something with + ``cryptography``'s high level symmetric encryption recipe: + + .. code-block:: pycon + + >>> from cryptography.fernet import Fernet + >>> # Put this somewhere safe! + >>> key = Fernet.generate_key() + >>> f = Fernet(key) + >>> token = f.encrypt(b"A really secret message. Not for prying eyes.") + >>> token + '...' + >>> f.decrypt(token) + 'A really secret message. Not for prying eyes.' + + You can find more information in the `documentation`_. + + You can install ``cryptography`` with: + + .. code-block:: console + + $ pip install cryptography + + For full details see `the installation documentation`_. + + Discussion + ~~~~~~~~~~ + + If you run into bugs, you can file them in our `issue tracker`_. + + We maintain a `cryptography-dev`_ mailing list for development discussion. + + You can also join ``#cryptography-dev`` on Freenode to ask questions or get + involved. + + + .. _`documentation`: https://cryptography.io/ + .. _`the installation documentation`: https://cryptography.io/en/latest/installation/ + .. _`issue tracker`: https://github.com/pyca/cryptography/issues + .. _`cryptography-dev`: https://mail.python.org/mailman/listinfo/cryptography-dev + +Platform: UNKNOWN +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: License :: OSI Approved :: BSD License +Classifier: Natural Language :: English +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: POSIX +Classifier: Operating System :: POSIX :: BSD +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Microsoft :: Windows +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Security :: Cryptography diff --git a/README.rst b/README.rst new file mode 100644 index 0000000..a58af53 --- /dev/null +++ b/README.rst @@ -0,0 +1,64 @@ +Cryptography +============ + +.. image:: https://img.shields.io/pypi/v/cryptography.svg + :target: https://pypi.python.org/pypi/cryptography/ + :alt: Latest Version + +.. image:: https://readthedocs.org/projects/cryptography/badge/?version=latest + :target: https://cryptography.io + :alt: Latest Docs + +.. image:: https://travis-ci.org/pyca/cryptography.svg?branch=master + :target: https://travis-ci.org/pyca/cryptography + +.. image:: https://codecov.io/github/pyca/cryptography/coverage.svg?branch=master + :target: https://codecov.io/github/pyca/cryptography?branch=master + + +``cryptography`` is a package which provides cryptographic recipes and +primitives to Python developers. Our goal is for it to be your "cryptographic +standard library". It supports Python 2.6-2.7, Python 3.3+, and PyPy 2.6+. + +``cryptography`` includes both high level recipes, and low level interfaces to +common cryptographic algorithms such as symmetric ciphers, message digests and +key derivation functions. For example, to encrypt something with +``cryptography``'s high level symmetric encryption recipe: + +.. code-block:: pycon + + >>> from cryptography.fernet import Fernet + >>> # Put this somewhere safe! + >>> key = Fernet.generate_key() + >>> f = Fernet(key) + >>> token = f.encrypt(b"A really secret message. Not for prying eyes.") + >>> token + '...' + >>> f.decrypt(token) + 'A really secret message. Not for prying eyes.' + +You can find more information in the `documentation`_. + +You can install ``cryptography`` with: + +.. code-block:: console + + $ pip install cryptography + +For full details see `the installation documentation`_. + +Discussion +~~~~~~~~~~ + +If you run into bugs, you can file them in our `issue tracker`_. + +We maintain a `cryptography-dev`_ mailing list for development discussion. + +You can also join ``#cryptography-dev`` on Freenode to ask questions or get +involved. + + +.. _`documentation`: https://cryptography.io/ +.. _`the installation documentation`: https://cryptography.io/en/latest/installation/ +.. _`issue tracker`: https://github.com/pyca/cryptography/issues +.. _`cryptography-dev`: https://mail.python.org/mailman/listinfo/cryptography-dev diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..323aeca --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,153 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + -rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Cryptography.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Cryptography.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/Cryptography" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Cryptography" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." diff --git a/docs/_static/.keep b/docs/_static/.keep new file mode 100644 index 0000000..e69de29 diff --git a/docs/api-stability.rst b/docs/api-stability.rst new file mode 100644 index 0000000..53669b0 --- /dev/null +++ b/docs/api-stability.rst @@ -0,0 +1,51 @@ +API stability +============= + +From its first release, ``cryptography`` will have a strong API stability +policy. + +What does this policy cover? +---------------------------- + +This policy includes any API or behavior that is documented in this +documentation. + +What does "stable" mean? +------------------------ + +* Public APIs will not be removed or renamed without providing a compatibility + alias. +* The behavior of existing APIs will not change. + +What doesn't this policy cover? +------------------------------- + +* We may add new features, things like the result of ``dir(obj))`` or the + contents of ``obj.__dict__`` may change. +* Objects are not guaranteed to be pickleable, and pickled objects from one + version of ``cryptography`` may not be loadable in future versions. +* Development versions of ``cryptography``. Before a feature is in a release, + it is not covered by this policy and may change. + +Security +~~~~~~~~ + +One exception to our API stability policy is for security. We will violate this +policy as necessary in order to resolve a security issue or harden +``cryptography`` against a possible attack. + +Deprecation +----------- + +From time to time we will want to change the behavior of an API or remove it +entirely. In that case, here's how the process will work: + +* In ``cryptography X.Y`` the feature exists. +* In ``cryptography X.Y+1`` using that feature will emit a + ``PendingDeprecationWarning``. +* In ``cryptography X.Y+2`` using that feature will emit a + ``DeprecationWarning``. +* In ``cryptography X.Y+3`` the feature will be removed or changed. + +In short, code that runs without warnings will always continue to work for a +period of two releases. diff --git a/docs/changelog.rst b/docs/changelog.rst new file mode 100644 index 0000000..565b052 --- /dev/null +++ b/docs/changelog.rst @@ -0,0 +1 @@ +.. include:: ../CHANGELOG.rst diff --git a/docs/community.rst b/docs/community.rst new file mode 100644 index 0000000..2b7cdc6 --- /dev/null +++ b/docs/community.rst @@ -0,0 +1,20 @@ +Community +========= + +You can find ``cryptography`` all over the web: + +* `Mailing list`_ +* `Source code`_ +* `Issue tracker`_ +* `Documentation`_ +* IRC: ``#cryptography-dev`` on ``irc.freenode.net`` + +Wherever we interact, we strive to follow the `Python Community Code of +Conduct`_. + + +.. _`Mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev +.. _`Source code`: https://github.com/pyca/cryptography +.. _`Issue tracker`: https://github.com/pyca/cryptography/issues +.. _`Documentation`: https://cryptography.io/ +.. _`Python Community Code of Conduct`: https://www.python.org/psf/codeofconduct/ diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..85a569a --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- + +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +# +# Cryptography documentation build configuration file, created by +# sphinx-quickstart on Tue Aug 6 19:19:14 2013. +# +# This file is execfile()d with the current directory set to its containing dir +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +from __future__ import absolute_import, division, print_function + +import os +import sys + +try: + import sphinx_rtd_theme +except ImportError: + sphinx_rtd_theme = None + +try: + from sphinxcontrib import spelling +except ImportError: + spelling = None + + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ---------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +# needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.doctest', + 'sphinx.ext.intersphinx', + 'sphinx.ext.viewcode', + 'cryptography-docs', +] + +if spelling is not None: + extensions.append('sphinxcontrib.spelling') + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +nitpicky = True + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +# source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = 'Cryptography' +copyright = '2013-2016, Individual Contributors' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# + +base_dir = os.path.join(os.path.dirname(__file__), os.pardir) +about = {} +with open(os.path.join(base_dir, "src", "cryptography", "__about__.py")) as f: + exec(f.read(), about) + +version = release = about["__version__"] + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +# language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +# today = '' +# Else, today_fmt is used as the format for a strftime call. +# today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents +# default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +# add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +# add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +# show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# -- Options for HTML output -------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. + +if sphinx_rtd_theme: + html_theme = "sphinx_rtd_theme" + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] +else: + html_theme = "default" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] + +# Output file base name for HTML help builder. +htmlhelp_basename = 'Cryptographydoc' + + +# -- Options for LaTeX output ------------------------------------------------- + +latex_elements = { +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]) +latex_documents = [ + ('index', 'Cryptography.tex', 'Cryptography Documentation', + 'Individual Contributors', 'manual'), +] + +# -- Options for manual page output ------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'cryptography', 'Cryptography Documentation', + ['Individual Contributors'], 1) +] + +# -- Options for Texinfo output ----------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'Cryptography', 'Cryptography Documentation', + 'Individual Contributors', 'Cryptography', + 'One line description of project.', + 'Miscellaneous'), +] + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'https://docs.python.org/3': None} + +epub_theme = 'epub' + +# Retry requests in the linkcheck builder so that we're resillient against +# transient network errors. +linkcheck_retries = 2 diff --git a/docs/cryptography-docs.py b/docs/cryptography-docs.py new file mode 100644 index 0000000..6aa7847 --- /dev/null +++ b/docs/cryptography-docs.py @@ -0,0 +1,70 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from docutils import nodes + +from sphinx.util.compat import Directive, make_admonition + + +DANGER_MESSAGE = """ +This is a "Hazardous Materials" module. You should **ONLY** use it if you're +100% absolutely sure that you know what you're doing because this module is +full of land mines, dragons, and dinosaurs with laser guns. +""" + +DANGER_ALTERNATE = """ + +You may instead be interested in :doc:`{alternate}`. +""" + + +class HazmatDirective(Directive): + has_content = True + + def run(self): + message = DANGER_MESSAGE + if self.content: + message += DANGER_ALTERNATE.format(alternate=self.content[0]) + + ad = make_admonition( + Hazmat, + self.name, + [], + self.options, + nodes.paragraph("", message), + self.lineno, + self.content_offset, + self.block_text, + self.state, + self.state_machine + ) + ad[0].line = self.lineno + return ad + + +class Hazmat(nodes.Admonition, nodes.Element): + pass + + +def html_visit_hazmat_node(self, node): + return self.visit_admonition(node, "danger") + + +def latex_visit_hazmat_node(self, node): + return self.visit_admonition(node) + + +def depart_hazmat_node(self, node): + return self.depart_admonition(node) + + +def setup(app): + app.add_node( + Hazmat, + html=(html_visit_hazmat_node, depart_hazmat_node), + latex=(latex_visit_hazmat_node, depart_hazmat_node), + ) + app.add_directive("hazmat", HazmatDirective) diff --git a/docs/development/c-bindings.rst b/docs/development/c-bindings.rst new file mode 100644 index 0000000..cbd8fab --- /dev/null +++ b/docs/development/c-bindings.rst @@ -0,0 +1,196 @@ +C bindings +========== + +C bindings are bindings to C libraries, using cffi_ whenever possible. + +.. _cffi: https://cffi.readthedocs.io + +Bindings live in :py:mod:`cryptography.hazmat.bindings`. + +When modifying the bindings you will need to recompile the C extensions to +test the changes. This can be accomplished with ``pip install -e .`` in the +project root. If you do not do this a ``RuntimeError`` will be raised. + +Style guide +----------- + +Don't name parameters: + +.. code-block:: c + + /* Good */ + long f(long); + /* Bad */ + long f(long x); + +...unless they're inside a struct: + +.. code-block:: c + + struct my_struct { + char *name; + int number; + ...; + }; + +Include ``void`` if the function takes no arguments: + +.. code-block:: c + + /* Good */ + long f(void); + /* Bad */ + long f(); + +Wrap lines at 80 characters like so: + +.. code-block:: c + + /* Pretend this went to 80 characters */ + long f(long, long, + int *) + +Include a space after commas between parameters: + +.. code-block:: c + + /* Good */ + long f(int, char *) + /* Bad */ + long f(int,char *) + +Use C-style ``/* */`` comments instead of C++-style ``//``: + +.. code-block:: c + + // Bad + /* Good */ + +Values set by ``#define`` should be assigned the appropriate type. If you see +this: + +.. code-block:: c + + #define SOME_INTEGER_LITERAL 0x0; + #define SOME_UNSIGNED_INTEGER_LITERAL 0x0001U; + #define SOME_STRING_LITERAL "hello"; + +...it should be added to the bindings like so: + +.. code-block:: c + + static const int SOME_INTEGER_LITERAL; + static const unsigned int SOME_UNSIGNED_INTEGER_LITERAL; + static const char *const SOME_STRING_LITERAL; + +Adding constant, types, functions... +------------------------------------ + +You can create bindings for any name that exists in some version of +the library you're binding against. However, the project also has to +keep supporting older versions of the library. In order to achieve +this, binding modules have ``CUSTOMIZATIONS`` and +``CONDITIONAL_NAMES`` constants. + +Let's say you want to enable quantum transmogrification. The upstream +library implements this as the following API:: + + static const int QM_TRANSMOGRIFICATION_ALIGNMENT_LEFT; + static const int QM_TRANSMOGRIFICATION_ALIGNMENT_RIGHT; + typedef ... QM_TRANSMOGRIFICATION_CTX; + int QM_transmogrify(QM_TRANSMOGRIFICATION_CTX *, int); + +To start, create a new constant that defines if the *actual* library +has the feature you want, and add it to ``TYPES``:: + + static const long Cryptography_HAS_QUANTUM_TRANSMOGRIFICATION; + +This should start with ``Cryptography_``, since we're adding it in +this library. This prevents namespace collisions. + +Then, define the actual features (constants, types, functions...) you +want to expose. If it's a constant, just add it to ``TYPES``:: + + static const int QM_TRANSMOGRIFICATION_ALIGNMENT_LEFT; + static const int QM_TRANSMOGRIFICATION_ALIGNMENT_RIGHT; + +If it's a struct, add it to ``TYPES`` as well. The following is an +opaque struct:: + + typedef ... QM_TRANSMOGRIFICATION_CTX; + +... but you can also make some or all items in the struct accessible:: + + typedef struct { + /* Fundamental constant k for your particular universe */ + BIGNUM *k; + ...; + } QM_TRANSMOGRIFICATION_CTX; + +Confusingly, functions that aren't always available on all supported +versions of the library, should be defined in ``MACROS`` and *not* in +``FUNCTIONS``. Fortunately, you just have to copy the signature:: + + int QM_transmogrify(QM_TRANSMOGRIFICATION_CTX *, int); + +Then, we define the ``CUSTOMIZATIONS`` entry. To do that, we have to +come up with a C preprocessor expression that decides whether or not a +feature exists in the library. For example:: + + #ifdef QM_transmogrify + +Then, we set the flag that signifies the feature exists:: + + static const long Cryptography_HAS_QUANTUM_TRANSMOGRIFICATION = 1; + +Otherwise, we set that flag to 0:: + + #else + static const long Cryptography_HAS_QUANTUM_TRANSMOGRIFICATION = 0; + +Then, in that ``#else`` block, we define the names that aren't +available as dummy values. For an integer constant, use 0:: + + static const int QM_TRANSMOGRIFICATION_ALIGNMENT_LEFT = 0; + static const int QM_TRANSMOGRIFICATION_ALIGNMENT_RIGHT = 0; + +For a function, it's a bit trickier. You have to define a function +pointer of the appropriate type to be NULL:: + + int (*QM_transmogrify)(QM_TRANSMOGRIFICATION_CTX *, int) = NULL; + +(To do that, copy the signature, put a ``*`` in front of the function +name and wrap it in parentheses, and then put ``= NULL`` at the end). + +Note how types don't need to be conditionally defined, as long as all +the necessarily type definitions are in place. + +Finally, add an entry to ``CONDITIONAL_NAMES`` with all of the things +you want to conditionally export:: + + CONDITIONAL_NAMES = { + ... + "Cryptography_HAS_QUANTUM_TRANSMOGRIFICATION": [ + "QM_TRANSMOGRIFICATION_ALIGNMENT_LEFT", + "QM_TRANSMOGRIFICATION_ALIGNMENT_RIGHT", + "QM_transmogrify" + ] + } + +Caveats +~~~~~~~ + +Sometimes, a set of loosely related features are added in the same +version, and it's impractical to create ``#ifdef`` statements for each +one. In that case, it may make sense to either check for a particular +version. For example, to check for OpenSSL 1.0.0 or newer:: + + #if OPENSSL_VERSION_NUMBER >= 0x10000000L + +Sometimes, the version of a library on a particular platform will have +features that you thought it wouldn't, based on its version. +Occasionally, packagers appear to ship arbitrary VCS checkouts. As a +result, sometimes you may have to add separate ``#ifdef`` statements +for particular features. This kind of issue is typically only caught +by running the tests on a wide variety of systems, which is the job of +our continuous integration infrastructure. diff --git a/docs/development/custom-vectors/arc4.rst b/docs/development/custom-vectors/arc4.rst new file mode 100644 index 0000000..ed8cd54 --- /dev/null +++ b/docs/development/custom-vectors/arc4.rst @@ -0,0 +1,30 @@ +ARC4 vector creation +==================== + +This page documents the code that was used to generate the ARC4 test +vectors for key lengths not available in RFC 6229. All the vectors +were generated using OpenSSL and verified with Go. + +Creation +-------- + +``cryptography`` was modified to support ARC4 key lengths not listed +in RFC 6229. Then the following Python script was run to generate the +vector files. + +.. literalinclude:: /development/custom-vectors/arc4/generate_arc4.py + +Download link: :download:`generate_arc4.py +` + + +Verification +------------ + +The following Go code was used to verify the vectors. + +.. literalinclude:: /development/custom-vectors/arc4/verify_arc4.go + :language: go + +Download link: :download:`verify_arc4.go +` diff --git a/docs/development/custom-vectors/arc4/generate_arc4.py b/docs/development/custom-vectors/arc4/generate_arc4.py new file mode 100644 index 0000000..3dee44a --- /dev/null +++ b/docs/development/custom-vectors/arc4/generate_arc4.py @@ -0,0 +1,98 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import ciphers +from cryptography.hazmat.primitives.ciphers import algorithms + + +_RFC6229_KEY_MATERIALS = [ + (True, + 8 * '0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20'), + (False, + 8 * '1ada31d5cf688221c109163908ebe51debb46227c6cc8b37641910833222772a') +] + + +_RFC6229_OFFSETS = [ + 0, + 16, + 240, + 256, + 496, + 512, + 752, + 768, + 1008, + 1024, + 1520, + 1536, + 2032, + 2048, + 3056, + 3072, + 4080, + 4096 +] + + +_SIZES_TO_GENERATE = [ + 160 +] + + +def _key_for_size(size, keyinfo): + msb, key = keyinfo + if msb: + return key[:size // 4] + else: + return key[-size // 4:] + + +def _build_vectors(): + count = 0 + output = [] + key = None + plaintext = binascii.unhexlify(32 * '0') + for size in _SIZES_TO_GENERATE: + for keyinfo in _RFC6229_KEY_MATERIALS: + key = _key_for_size(size, keyinfo) + cipher = ciphers.Cipher( + algorithms.ARC4(binascii.unhexlify(key)), + None, + default_backend()) + encryptor = cipher.encryptor() + current_offset = 0 + for offset in _RFC6229_OFFSETS: + if offset % 16 != 0: + raise ValueError( + "Offset {} is not evenly divisible by 16" + .format(offset)) + while current_offset < offset: + encryptor.update(plaintext) + current_offset += len(plaintext) + output.append("\nCOUNT = {}".format(count)) + count += 1 + output.append("KEY = {}".format(key)) + output.append("OFFSET = {}".format(offset)) + output.append("PLAINTEXT = {}".format( + binascii.hexlify(plaintext))) + output.append("CIPHERTEXT = {}".format( + binascii.hexlify(encryptor.update(plaintext)))) + current_offset += len(plaintext) + assert not encryptor.finalize() + return "\n".join(output) + + +def _write_file(data, filename): + with open(filename, 'w') as f: + f.write(data) + + +if __name__ == '__main__': + _write_file(_build_vectors(), 'arc4.txt') diff --git a/docs/development/custom-vectors/arc4/verify_arc4.go b/docs/development/custom-vectors/arc4/verify_arc4.go new file mode 100644 index 0000000..508fe98 --- /dev/null +++ b/docs/development/custom-vectors/arc4/verify_arc4.go @@ -0,0 +1,111 @@ +package main + +import ( + "bufio" + "bytes" + "crypto/rc4" + "encoding/hex" + "fmt" + "os" + "strconv" + "strings" +) + +func unhexlify(s string) []byte { + bytes, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return bytes +} + +type vectorArgs struct { + count string + offset uint64 + key string + plaintext string + ciphertext string +} + +type vectorVerifier interface { + validate(count string, offset uint64, key, plaintext, expectedCiphertext []byte) +} + +type arc4Verifier struct{} + +func (o arc4Verifier) validate(count string, offset uint64, key, plaintext, expectedCiphertext []byte) { + if offset%16 != 0 || len(plaintext) != 16 || len(expectedCiphertext) != 16 { + panic(fmt.Errorf("Unexpected input value encountered: offset=%v; len(plaintext)=%v; len(expectedCiphertext)=%v", + offset, + len(plaintext), + len(expectedCiphertext))) + } + stream, err := rc4.NewCipher(key) + if err != nil { + panic(err) + } + + var currentOffset uint64 = 0 + ciphertext := make([]byte, len(plaintext)) + for currentOffset <= offset { + stream.XORKeyStream(ciphertext, plaintext) + currentOffset += uint64(len(plaintext)) + } + if !bytes.Equal(ciphertext, expectedCiphertext) { + panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n %s != %s\n", + count, + hex.EncodeToString(expectedCiphertext), + hex.EncodeToString(ciphertext))) + } +} + +func validateVectors(verifier vectorVerifier, filename string) { + vectors, err := os.Open(filename) + if err != nil { + panic(err) + } + defer vectors.Close() + + var segments []string + var vector *vectorArgs + + scanner := bufio.NewScanner(vectors) + for scanner.Scan() { + segments = strings.Split(scanner.Text(), " = ") + + switch { + case strings.ToUpper(segments[0]) == "COUNT": + if vector != nil { + verifier.validate(vector.count, + vector.offset, + unhexlify(vector.key), + unhexlify(vector.plaintext), + unhexlify(vector.ciphertext)) + } + vector = &vectorArgs{count: segments[1]} + case strings.ToUpper(segments[0]) == "OFFSET": + vector.offset, err = strconv.ParseUint(segments[1], 10, 64) + if err != nil { + panic(err) + } + case strings.ToUpper(segments[0]) == "KEY": + vector.key = segments[1] + case strings.ToUpper(segments[0]) == "PLAINTEXT": + vector.plaintext = segments[1] + case strings.ToUpper(segments[0]) == "CIPHERTEXT": + vector.ciphertext = segments[1] + } + } + if vector != nil { + verifier.validate(vector.count, + vector.offset, + unhexlify(vector.key), + unhexlify(vector.plaintext), + unhexlify(vector.ciphertext)) + } +} + +func main() { + validateVectors(arc4Verifier{}, "vectors/cryptography_vectors/ciphers/ARC4/arc4.txt") + fmt.Println("ARC4 OK.") +} diff --git a/docs/development/custom-vectors/cast5.rst b/docs/development/custom-vectors/cast5.rst new file mode 100644 index 0000000..97de901 --- /dev/null +++ b/docs/development/custom-vectors/cast5.rst @@ -0,0 +1,31 @@ +CAST5 vector creation +===================== + +This page documents the code that was used to generate the CAST5 CBC, CFB, OFB, +and CTR test vectors as well as the code used to verify them against another +implementation. The CBC, CFB, and OFB vectors were generated using OpenSSL and +the CTR vectors were generated using Apple's CommonCrypto. All the generated +vectors were verified with Go. + +Creation +-------- + +``cryptography`` was modified to support CAST5 in CBC, CFB, and OFB modes. Then +the following Python script was run to generate the vector files. + +.. literalinclude:: /development/custom-vectors/cast5/generate_cast5.py + +Download link: :download:`generate_cast5.py +` + + +Verification +------------ + +The following Go code was used to verify the vectors. + +.. literalinclude:: /development/custom-vectors/cast5/verify_cast5.go + :language: go + +Download link: :download:`verify_cast5.go +` diff --git a/docs/development/custom-vectors/cast5/generate_cast5.py b/docs/development/custom-vectors/cast5/generate_cast5.py new file mode 100644 index 0000000..a0e28e3 --- /dev/null +++ b/docs/development/custom-vectors/cast5/generate_cast5.py @@ -0,0 +1,68 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives.ciphers import algorithms, base, modes + + +def encrypt(mode, key, iv, plaintext): + cipher = base.Cipher( + algorithms.CAST5(binascii.unhexlify(key)), + mode(binascii.unhexlify(iv)), + default_backend() + ) + encryptor = cipher.encryptor() + ct = encryptor.update(binascii.unhexlify(plaintext)) + ct += encryptor.finalize() + return binascii.hexlify(ct) + + +def build_vectors(mode, filename): + vector_file = open(filename, "r") + + count = 0 + output = [] + key = None + iv = None + plaintext = None + for line in vector_file: + line = line.strip() + if line.startswith("KEY"): + if count != 0: + output.append("CIPHERTEXT = {}".format( + encrypt(mode, key, iv, plaintext)) + ) + output.append("\nCOUNT = {}".format(count)) + count += 1 + name, key = line.split(" = ") + output.append("KEY = {}".format(key)) + elif line.startswith("IV"): + name, iv = line.split(" = ") + iv = iv[0:16] + output.append("IV = {}".format(iv)) + elif line.startswith("PLAINTEXT"): + name, plaintext = line.split(" = ") + output.append("PLAINTEXT = {}".format(plaintext)) + + output.append("CIPHERTEXT = {}".format(encrypt(mode, key, iv, plaintext))) + return "\n".join(output) + + +def write_file(data, filename): + with open(filename, "w") as f: + f.write(data) + + +cbc_path = "tests/hazmat/primitives/vectors/ciphers/AES/CBC/CBCMMT128.rsp" +write_file(build_vectors(modes.CBC, cbc_path), "cast5-cbc.txt") +ofb_path = "tests/hazmat/primitives/vectors/ciphers/AES/OFB/OFBMMT128.rsp" +write_file(build_vectors(modes.OFB, ofb_path), "cast5-ofb.txt") +cfb_path = "tests/hazmat/primitives/vectors/ciphers/AES/CFB/CFB128MMT128.rsp" +write_file(build_vectors(modes.CFB, cfb_path), "cast5-cfb.txt") +ctr_path = "tests/hazmat/primitives/vectors/ciphers/AES/CTR/aes-128-ctr.txt" +write_file(build_vectors(modes.CTR, ctr_path), "cast5-ctr.txt") diff --git a/docs/development/custom-vectors/cast5/verify_cast5.go b/docs/development/custom-vectors/cast5/verify_cast5.go new file mode 100644 index 0000000..cbc89a0 --- /dev/null +++ b/docs/development/custom-vectors/cast5/verify_cast5.go @@ -0,0 +1,164 @@ +package main + +import ( + "bufio" + "bytes" + "code.google.com/p/go.crypto/cast5" + "crypto/cipher" + "encoding/hex" + "fmt" + "os" + "strings" +) + +func unhexlify(s string) []byte { + bytes, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return bytes +} + +type vectorArgs struct { + count string + key string + iv string + plaintext string + ciphertext string +} + +type vectorVerifier interface { + validate(count string, key, iv, plaintext, expectedCiphertext []byte) +} + +type ofbVerifier struct{} + +func (o ofbVerifier) validate(count string, key, iv, plaintext, expectedCiphertext []byte) { + block, err := cast5.NewCipher(key) + if err != nil { + panic(err) + } + + ciphertext := make([]byte, len(plaintext)) + stream := cipher.NewOFB(block, iv) + stream.XORKeyStream(ciphertext, plaintext) + + if !bytes.Equal(ciphertext, expectedCiphertext) { + panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n %s != %s\n", + count, + hex.EncodeToString(expectedCiphertext), + hex.EncodeToString(ciphertext))) + } +} + +type cbcVerifier struct{} + +func (o cbcVerifier) validate(count string, key, iv, plaintext, expectedCiphertext []byte) { + block, err := cast5.NewCipher(key) + if err != nil { + panic(err) + } + + ciphertext := make([]byte, len(plaintext)) + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(ciphertext, plaintext) + + if !bytes.Equal(ciphertext, expectedCiphertext) { + panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n %s != %s\n", + count, + hex.EncodeToString(expectedCiphertext), + hex.EncodeToString(ciphertext))) + } +} + +type cfbVerifier struct{} + +func (o cfbVerifier) validate(count string, key, iv, plaintext, expectedCiphertext []byte) { + block, err := cast5.NewCipher(key) + if err != nil { + panic(err) + } + + ciphertext := make([]byte, len(plaintext)) + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext, plaintext) + + if !bytes.Equal(ciphertext, expectedCiphertext) { + panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n %s != %s\n", + count, + hex.EncodeToString(expectedCiphertext), + hex.EncodeToString(ciphertext))) + } +} + +type ctrVerifier struct{} + +func (o ctrVerifier) validate(count string, key, iv, plaintext, expectedCiphertext []byte) { + block, err := cast5.NewCipher(key) + if err != nil { + panic(err) + } + + ciphertext := make([]byte, len(plaintext)) + stream := cipher.NewCTR(block, iv) + stream.XORKeyStream(ciphertext, plaintext) + + if !bytes.Equal(ciphertext, expectedCiphertext) { + panic(fmt.Errorf("vector mismatch @ COUNT = %s:\n %s != %s\n", + count, + hex.EncodeToString(expectedCiphertext), + hex.EncodeToString(ciphertext))) + } +} + +func validateVectors(verifier vectorVerifier, filename string) { + vectors, err := os.Open(filename) + if err != nil { + panic(err) + } + defer vectors.Close() + + var segments []string + var vector *vectorArgs + + scanner := bufio.NewScanner(vectors) + for scanner.Scan() { + segments = strings.Split(scanner.Text(), " = ") + + switch { + case strings.ToUpper(segments[0]) == "COUNT": + if vector != nil { + verifier.validate(vector.count, + unhexlify(vector.key), + unhexlify(vector.iv), + unhexlify(vector.plaintext), + unhexlify(vector.ciphertext)) + } + vector = &vectorArgs{count: segments[1]} + case strings.ToUpper(segments[0]) == "IV": + vector.iv = segments[1][:16] + case strings.ToUpper(segments[0]) == "KEY": + vector.key = segments[1] + case strings.ToUpper(segments[0]) == "PLAINTEXT": + vector.plaintext = segments[1] + case strings.ToUpper(segments[0]) == "CIPHERTEXT": + vector.ciphertext = segments[1] + } + } + +} + +func main() { + validateVectors(ofbVerifier{}, + "vectors/cryptography_vectors/ciphers/CAST5/cast5-ofb.txt") + fmt.Println("OFB OK.") + validateVectors(cfbVerifier{}, + "vectors/cryptography_vectors/ciphers/CAST5/cast5-cfb.txt") + fmt.Println("CFB OK.") + validateVectors(cbcVerifier{}, + "vectors/cryptography_vectors/ciphers/CAST5/cast5-cbc.txt") + fmt.Println("CBC OK.") + validateVectors(ctrVerifier{}, + "vectors/cryptography_vectors/ciphers/CAST5/cast5-ctr.txt") + fmt.Println("CTR OK.") +} diff --git a/docs/development/custom-vectors/idea.rst b/docs/development/custom-vectors/idea.rst new file mode 100644 index 0000000..758a108 --- /dev/null +++ b/docs/development/custom-vectors/idea.rst @@ -0,0 +1,32 @@ +IDEA vector creation +===================== + +This page documents the code that was used to generate the IDEA CBC, CFB, and +OFB test vectors as well as the code used to verify them against another +implementation. The vectors were generated using OpenSSL and verified with +`Botan`_. + +Creation +-------- + +``cryptography`` was modified to support IDEA in CBC, CFB, and OFB modes. Then +the following python script was run to generate the vector files. + +.. literalinclude:: /development/custom-vectors/idea/generate_idea.py + +Download link: :download:`generate_idea.py +` + + +Verification +------------ + +The following Python code was used to verify the vectors using the `Botan`_ +project's Python bindings. + +.. literalinclude:: /development/custom-vectors/idea/verify_idea.py + +Download link: :download:`verify_idea.py +` + +.. _`Botan`: https://botan.randombit.net diff --git a/docs/development/custom-vectors/idea/generate_idea.py b/docs/development/custom-vectors/idea/generate_idea.py new file mode 100644 index 0000000..2eb6996 --- /dev/null +++ b/docs/development/custom-vectors/idea/generate_idea.py @@ -0,0 +1,61 @@ +import binascii + +from cryptography.hazmat.backends.openssl.backend import backend +from cryptography.hazmat.primitives.ciphers import algorithms, base, modes + + +def encrypt(mode, key, iv, plaintext): + cipher = base.Cipher( + algorithms.IDEA(binascii.unhexlify(key)), + mode(binascii.unhexlify(iv)), + backend + ) + encryptor = cipher.encryptor() + ct = encryptor.update(binascii.unhexlify(plaintext)) + ct += encryptor.finalize() + return binascii.hexlify(ct) + + +def build_vectors(mode, filename): + with open(filename, "r") as f: + vector_file = f.read().splitlines() + + count = 0 + output = [] + key = None + iv = None + plaintext = None + for line in vector_file: + line = line.strip() + if line.startswith("KEY"): + if count != 0: + output.append("CIPHERTEXT = {0}".format( + encrypt(mode, key, iv, plaintext)) + ) + output.append("\nCOUNT = {0}".format(count)) + count += 1 + name, key = line.split(" = ") + output.append("KEY = {0}".format(key)) + elif line.startswith("IV"): + name, iv = line.split(" = ") + iv = iv[0:16] + output.append("IV = {0}".format(iv)) + elif line.startswith("PLAINTEXT"): + name, plaintext = line.split(" = ") + output.append("PLAINTEXT = {0}".format(plaintext)) + + output.append("CIPHERTEXT = {0}".format(encrypt(mode, key, iv, plaintext))) + return "\n".join(output) + + +def write_file(data, filename): + with open(filename, "w") as f: + f.write(data) + + +CBC_PATH = "tests/hazmat/primitives/vectors/ciphers/AES/CBC/CBCMMT128.rsp" +write_file(build_vectors(modes.CBC, CBC_PATH), "idea-cbc.txt") +OFB_PATH = "tests/hazmat/primitives/vectors/ciphers/AES/OFB/OFBMMT128.rsp" +write_file(build_vectors(modes.OFB, OFB_PATH), "idea-ofb.txt") +CFB_PATH = "tests/hazmat/primitives/vectors/ciphers/AES/CFB/CFB128MMT128.rsp" +write_file(build_vectors(modes.CFB, CFB_PATH), "idea-cfb.txt") diff --git a/docs/development/custom-vectors/idea/verify_idea.py b/docs/development/custom-vectors/idea/verify_idea.py new file mode 100644 index 0000000..89713c8 --- /dev/null +++ b/docs/development/custom-vectors/idea/verify_idea.py @@ -0,0 +1,39 @@ +import binascii + +import botan + +from tests.utils import load_nist_vectors + +BLOCK_SIZE = 64 + + +def encrypt(mode, key, iv, plaintext): + encryptor = botan.Cipher("IDEA/{0}/NoPadding".format(mode), "encrypt", + binascii.unhexlify(key)) + + cipher_text = encryptor.cipher(binascii.unhexlify(plaintext), + binascii.unhexlify(iv)) + return binascii.hexlify(cipher_text) + + +def verify_vectors(mode, filename): + with open(filename, "r") as f: + vector_file = f.read().splitlines() + + vectors = load_nist_vectors(vector_file) + for vector in vectors: + ct = encrypt( + mode, + vector["key"], + vector["iv"], + vector["plaintext"] + ) + assert ct == vector["ciphertext"] + + +cbc_path = "tests/hazmat/primitives/vectors/ciphers/IDEA/idea-cbc.txt" +verify_vectors("CBC", cbc_path) +ofb_path = "tests/hazmat/primitives/vectors/ciphers/IDEA/idea-ofb.txt" +verify_vectors("OFB", ofb_path) +cfb_path = "tests/hazmat/primitives/vectors/ciphers/IDEA/idea-cfb.txt" +verify_vectors("CFB", cfb_path) diff --git a/docs/development/custom-vectors/rsa-oaep-sha2.rst b/docs/development/custom-vectors/rsa-oaep-sha2.rst new file mode 100644 index 0000000..36f256d --- /dev/null +++ b/docs/development/custom-vectors/rsa-oaep-sha2.rst @@ -0,0 +1,56 @@ +RSA OAEP SHA2 vector creation +============================= + +This page documents the code that was used to generate the RSA OAEP SHA2 +test vectors as well as code used to verify them against another +implementation. + + +Creation +-------- + +``cryptography`` was modified to allow the use of SHA2 in OAEP encryption. Then +the following python script was run to generate the vector files. + +.. literalinclude:: /development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py + +Download link: :download:`generate_rsa_oaep_sha2.py +` + + +Verification +------------ + +A Java 8 program was written using `Bouncy Castle`_ to load and verify the test +vectors. + + +.. literalinclude:: /development/custom-vectors/rsa-oaep-sha2/VerifyRSAOAEPSHA2.java + +Download link: :download:`VerifyRSAOAEPSHA2.java +` + +Using the Verifier +------------------ + +Download and install the `Java 8 SDK`_. Initial verification was performed +using ``jdk-8u77-macosx-x64.dmg``. + +Download the latest `Bouncy Castle`_ JAR. Initial verification was performed +using ``bcprov-jdk15on-154.jar``. + +Set the ``-classpath`` to include the Bouncy Castle jar and the path to +``VerifyRSAOAEPSHA2.java`` and compile the program. + +.. code-block:: console + + $ javac -classpath ~/Downloads/bcprov-jdk15on-154.jar:./ VerifyRSAOAEPSHA2.java + +Finally, run the program with the path to the SHA-2 vectors: + +.. code-block:: console + + $ java -classpath ~/Downloads/bcprov-jdk15on-154.jar:./ VerifyRSAOAEPSHA2 + +.. _`Bouncy Castle`: https://www.bouncycastle.org/ +.. _`Java 8 SDK`: https://www.oracle.com/technetwork/java/javase/downloads/index.html diff --git a/docs/development/custom-vectors/rsa-oaep-sha2/VerifyRSAOAEPSHA2.java b/docs/development/custom-vectors/rsa-oaep-sha2/VerifyRSAOAEPSHA2.java new file mode 100644 index 0000000..e1bfd3d --- /dev/null +++ b/docs/development/custom-vectors/rsa-oaep-sha2/VerifyRSAOAEPSHA2.java @@ -0,0 +1,416 @@ +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.math.BigInteger; +import java.security.AlgorithmParameters; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.Security; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.MGF1ParameterSpec; +import java.security.spec.RSAPrivateKeySpec; +import java.util.Arrays; + +import javax.crypto.Cipher; +import javax.crypto.spec.OAEPParameterSpec; +import javax.crypto.spec.PSource; +import javax.xml.bind.DatatypeConverter; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; + +class TestVectorData { + public BigInteger pub_key_modulus; + public BigInteger pub_key_exponent; + public BigInteger priv_key_public_exponent; + public BigInteger priv_key_modulus; + public BigInteger priv_key_exponent; + public BigInteger priv_key_prime_1; + public BigInteger priv_key_prime_2; + public BigInteger priv_key_prime_exponent_1; + public BigInteger priv_key_prime_exponent_2; + public BigInteger priv_key_coefficient; + public byte[] plaintext; + public byte[] ciphertext; +} + +class TestVectorLoader { + private static final String FILE_HEADER = "# RSA OAEP SHA2 vectors built"; + private static final String EXAMPLE_HEADER = "# ====="; + private static final String EXAMPLE = "# Example"; + private static final String PUBLIC_KEY = "# Public key"; + private static final String PUB_MODULUS = "# Modulus:"; + private static final String PUB_EXPONENT = "# Exponent:"; + private static final String PRIVATE_KEY = "# Private key"; + private static final String PRIV_MODULUS = "# Modulus:"; + private static final String PRIV_PUBLIC_EXPONENT = "# Public exponent:"; + private static final String PRIV_EXPONENT = "# Exponent:"; + private static final String PRIV_PRIME_1 = "# Prime 1:"; + private static final String PRIV_PRIME_2 = "# Prime 2:"; + private static final String PRIV_PRIME_EXPONENT_1 = "# Prime exponent 1:"; + private static final String PRIV_PRIME_EXPONENT_2 = "# Prime exponent 2:"; + private static final String PRIV_COEFFICIENT = "# Coefficient:"; + private static final String OAEP_EXAMPLE_HEADER = "# OAEP Example"; + private static final String MESSAGE = "# Message:"; + private static final String ENCRYPTION = "# Encryption:"; + + private BufferedReader m_reader = null; + private FileReader m_file_reader = null; + private TestVectorData m_data = null; + + TestVectorLoader() { + + } + + protected void finalize() { + close(); + } + + public void open(String path) throws IOException { + close(); + m_file_reader = new FileReader(path); + m_reader = new BufferedReader(m_file_reader); + m_data = new TestVectorData(); + } + + public void close() { + try { + if (m_reader != null) { + m_reader.close(); + m_reader = null; + } + if (m_file_reader != null) { + m_file_reader.close(); + m_file_reader = null; + } + m_data = null; + } catch (IOException e) { + System.out.println("Exception closing files"); + e.printStackTrace(); + } + } + + public TestVectorData loadNextTest() throws IOException { + if (m_file_reader == null || m_reader == null || m_data == null) { + throw new IOException("A test vector file must be opened first"); + } + + String line = m_reader.readLine(); + + if (line == null) { + // end of file + return null; + } + + if (line.startsWith(FILE_HEADER)) { + // start of file + skipFileHeader(m_reader); + line = m_reader.readLine(); + } + + if (line.startsWith(OAEP_EXAMPLE_HEADER)) { + // Next example, keep existing keys and load next message + loadMessage(m_reader, m_data); + return m_data; + } + + // otherwise it's a new example + if (!line.startsWith(EXAMPLE_HEADER)) { + throw new IOException("Test Header Missing"); + } + startNewTest(m_reader); + m_data = new TestVectorData(); + + line = m_reader.readLine(); + if (!line.startsWith(PUBLIC_KEY)) + throw new IOException("Public Key Missing"); + loadPublicKey(m_reader, m_data); + + line = m_reader.readLine(); + if (!line.startsWith(PRIVATE_KEY)) + throw new IOException("Private Key Missing"); + loadPrivateKey(m_reader, m_data); + + line = m_reader.readLine(); + if (!line.startsWith(OAEP_EXAMPLE_HEADER)) + throw new IOException("Message Missing"); + loadMessage(m_reader, m_data); + + return m_data; + } + + private byte[] unhexlify(String line) { + byte[] bytes = DatatypeConverter.parseHexBinary(line); + return bytes; + } + + private BigInteger readBigInteger(BufferedReader br) throws IOException { + return new BigInteger(br.readLine(), 16); + } + + private void skipFileHeader(BufferedReader br) throws IOException { + br.readLine(); // # # Derived from the NIST OAEP SHA1 vectors + br.readLine(); // # # Verified against the Bouncy Castle OAEP SHA2 implementation + br.readLine(); // # + } + + private void startNewTest(BufferedReader br) throws IOException { + String line = br.readLine(); + if (!line.startsWith(EXAMPLE)) + throw new IOException("Example Header Missing"); + } + + private void loadPublicKey(BufferedReader br, TestVectorData data) throws IOException { + String line = br.readLine(); + if (!line.startsWith(PUB_MODULUS)) + throw new IOException("Public Key Modulus Missing"); + data.pub_key_modulus = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PUB_EXPONENT)) + throw new IOException("Public Key Exponent Missing"); + data.pub_key_exponent = readBigInteger(br); + } + + private void loadPrivateKey(BufferedReader br, TestVectorData data) throws IOException { + String line = br.readLine(); + if (!line.startsWith(PRIV_MODULUS)) + throw new IOException("Private Key Modulus Missing"); + data.priv_key_modulus = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PUBLIC_EXPONENT)) + throw new IOException("Private Key Public Exponent Missing"); + data.priv_key_public_exponent = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_EXPONENT)) + throw new IOException("Private Key Exponent Missing"); + data.priv_key_exponent = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PRIME_1)) + throw new IOException("Private Key Prime 1 Missing"); + data.priv_key_prime_1 = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PRIME_2)) + throw new IOException("Private Key Prime 2 Missing"); + data.priv_key_prime_2 = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PRIME_EXPONENT_1)) + throw new IOException("Private Key Prime Exponent 1 Missing"); + data.priv_key_prime_exponent_1 = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_PRIME_EXPONENT_2)) + throw new IOException("Private Key Prime Exponent 2 Missing"); + data.priv_key_prime_exponent_2 = readBigInteger(br); + + line = br.readLine(); + if (!line.startsWith(PRIV_COEFFICIENT)) + throw new IOException("Private Key Coefficient Missing"); + data.priv_key_coefficient = readBigInteger(br); + } + + private void loadMessage(BufferedReader br, TestVectorData data) throws IOException { + String line = br.readLine(); + if (!line.startsWith(MESSAGE)) + throw new IOException("Plaintext Missing"); + data.plaintext = unhexlify(br.readLine()); + + line = br.readLine(); + if (!line.startsWith(ENCRYPTION)) + throw new IOException("Ciphertext Missing"); + data.ciphertext = unhexlify(br.readLine()); + } + +} + +public class VerifyRSAOAEPSHA2 { + + public enum SHAHash { + SHA1, SHA224, SHA256, SHA384, SHA512 + } + + private SHAHash m_mgf1_hash; + private SHAHash m_alg_hash; + private Cipher m_cipher; + private PrivateKey m_private_key; + private AlgorithmParameters m_algo_param; + + VerifyRSAOAEPSHA2(SHAHash mgf1_hash, SHAHash alg_hash, TestVectorData test_data) throws Exception { + + m_mgf1_hash = mgf1_hash; + m_alg_hash = alg_hash; + + MGF1ParameterSpec mgf1_spec = getMGF1ParameterSpec(m_mgf1_hash); + AlgorithmParameterSpec algo_param_spec = getAlgorithmParameterSpec(m_alg_hash, mgf1_spec); + + m_algo_param = AlgorithmParameters.getInstance("OAEP"); + m_algo_param.init(algo_param_spec); + + m_private_key = loadPrivateKey(test_data); + + m_cipher = getCipher(m_alg_hash); + } + + private Cipher getCipher(SHAHash alg_hash) throws GeneralSecurityException { + Cipher cipher = null; + + switch (alg_hash) { + + case SHA1: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA1andMGF1Padding", "BC"); + break; + + case SHA224: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-224andMGF1Padding", "BC"); + break; + + case SHA256: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding", "BC"); + break; + + case SHA384: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-384andMGF1Padding", "BC"); + break; + + case SHA512: + cipher = Cipher.getInstance("RSA/ECB/OAEPwithSHA-512andMGF1Padding", "BC"); + break; + } + + return cipher; + } + + private MGF1ParameterSpec getMGF1ParameterSpec(SHAHash mgf1_hash) { + MGF1ParameterSpec mgf1 = null; + + switch (mgf1_hash) { + + case SHA1: + mgf1 = MGF1ParameterSpec.SHA1; + break; + case SHA224: + mgf1 = MGF1ParameterSpec.SHA224; + break; + + case SHA256: + mgf1 = MGF1ParameterSpec.SHA256; + break; + + case SHA384: + mgf1 = MGF1ParameterSpec.SHA384; + break; + + case SHA512: + mgf1 = MGF1ParameterSpec.SHA512; + break; + } + + return mgf1; + } + + private AlgorithmParameterSpec getAlgorithmParameterSpec(SHAHash alg_hash, MGF1ParameterSpec mgf1_spec) { + + OAEPParameterSpec oaep_spec = null; + + switch (alg_hash) { + + case SHA1: + oaep_spec = new OAEPParameterSpec("SHA1", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + + case SHA224: + oaep_spec = new OAEPParameterSpec("SHA-224", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + + case SHA256: + oaep_spec = new OAEPParameterSpec("SHA-256", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + + case SHA384: + oaep_spec = new OAEPParameterSpec("SHA-384", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + + case SHA512: + oaep_spec = new OAEPParameterSpec("SHA-512", "MGF1", mgf1_spec, PSource.PSpecified.DEFAULT); + break; + } + + return oaep_spec; + } + + private PrivateKey loadPrivateKey(TestVectorData test_data) throws Exception { + KeyFactory kf = KeyFactory.getInstance("RSA"); + + RSAPrivateKeySpec keySpec = new RSAPrivateKeySpec(test_data.priv_key_modulus, test_data.priv_key_exponent); + + return kf.generatePrivate(keySpec); + } + + public void testDecrypt(byte[] plaintext, byte[] ciphertext) throws Exception { + System.out.println("Verifying OAEP with mgf1_hash: " + m_mgf1_hash + " alg_hash: " + m_alg_hash + " - " + + ciphertext.length + " bytes ciphertext - " + + plaintext.length + " bytes plaintext"); + + m_cipher.init(Cipher.DECRYPT_MODE, m_private_key, m_algo_param); + byte[] java_plaintext = m_cipher.doFinal(ciphertext); + + if (Arrays.equals(java_plaintext, plaintext) == false) { + throw new Exception("Verification failure - plaintext does not match after decryption."); + } + } + + public static void main(String[] args) { + Security.addProvider(new BouncyCastleProvider()); + + // assume current directory if no path given on command line + String vector_path = "./vectors/cryptography_vectors/asymmetric/RSA/oaep-custom"; + + if (args.length > 0) { + vector_path = args[0]; + } + + System.out.println("Vector file path: " + vector_path); + + try { + // loop over each combination of hash loading the vector file + // to verify for each + for (SHAHash mgf1_hash : SHAHash.values()) { + for (SHAHash alg_hash : SHAHash.values()) { + if (mgf1_hash.name().toLowerCase().equals("sha1") && + alg_hash.name().toLowerCase().equals("sha1")) { + continue; + } + String filename = "oaep-" + mgf1_hash.name().toLowerCase() + + "-" + alg_hash.name().toLowerCase() + ".txt"; + + System.out.println("Loading " + filename + "..."); + + TestVectorLoader loader = new TestVectorLoader(); + loader.open(vector_path + filename); + + TestVectorData test_data; + + // load each test in the file and verify + while ((test_data = loader.loadNextTest()) != null) { + VerifyRSAOAEPSHA2 verify = new VerifyRSAOAEPSHA2(mgf1_hash, alg_hash, test_data); + verify.testDecrypt(test_data.plaintext, test_data.ciphertext); + } + + System.out.println("Verifying " + filename + " completed successfully."); + } + } + + System.out.println("All verification completed successfully"); + + } catch (Exception e) { + // if any exception is thrown the verification has failed + e.printStackTrace(); + System.out.println("Verification Failed!"); + } + } +} diff --git a/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py b/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py new file mode 100644 index 0000000..bd5148f --- /dev/null +++ b/docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py @@ -0,0 +1,128 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import itertools +import os + +from cryptography.hazmat.backends.openssl.backend import backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding, rsa + +from tests.utils import load_pkcs1_vectors, load_vectors_from_file + + +def build_vectors(mgf1alg, hashalg, filename): + vectors = load_vectors_from_file(filename, load_pkcs1_vectors) + + output = [] + for vector in vectors: + # RSA keys for this must be long enough to accommodate the length of + # the underlying hash function. This means we can't use the keys from + # the sha1 test vectors for sha512 tests because 1024-bit keys are too + # small. Instead we parse the vectors for the test cases, then + # generate our own 2048-bit keys for each. + private, _ = vector + skey = rsa.generate_private_key(65537, 2048, backend) + pn = skey.private_numbers() + examples = private["examples"] + output.append(b"# =============================================") + output.append(b"# Example") + output.append(b"# Public key") + output.append(b"# Modulus:") + output.append(format(pn.public_numbers.n, "x")) + output.append(b"# Exponent:") + output.append(format(pn.public_numbers.e, "x")) + output.append(b"# Private key") + output.append(b"# Modulus:") + output.append(format(pn.public_numbers.n, "x")) + output.append(b"# Public exponent:") + output.append(format(pn.public_numbers.e, "x")) + output.append(b"# Exponent:") + output.append(format(pn.d, "x")) + output.append(b"# Prime 1:") + output.append(format(pn.p, "x")) + output.append(b"# Prime 2:") + output.append(format(pn.q, "x")) + output.append(b"# Prime exponent 1:") + output.append(format(pn.dmp1, "x")) + output.append(b"# Prime exponent 2:") + output.append(format(pn.dmq1, "x")) + output.append(b"# Coefficient:") + output.append(format(pn.iqmp, "x")) + pkey = skey.public_key() + vectorkey = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], + n=private["modulus"] + ) + ).private_key(backend) + count = 1 + + for example in examples: + message = vectorkey.decrypt( + binascii.unhexlify(example["encryption"]), + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ) + assert message == binascii.unhexlify(example["message"]) + ct = pkey.encrypt( + message, + padding.OAEP( + mgf=padding.MGF1(algorithm=mgf1alg), + algorithm=hashalg, + label=None + ) + ) + output.append( + b"# OAEP Example {0} alg={1} mgf1={2}".format( + count, hashalg.name, mgf1alg.name + ) + ) + count += 1 + output.append(b"# Message:") + output.append(example["message"]) + output.append(b"# Encryption:") + output.append(binascii.hexlify(ct)) + + return b"\n".join(output) + + +def write_file(data, filename): + with open(filename, "w") as f: + f.write(data) + + +oaep_path = os.path.join( + "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "oaep-vect.txt" +) +hashalgs = [ + hashes.SHA1(), + hashes.SHA224(), + hashes.SHA256(), + hashes.SHA384(), + hashes.SHA512(), +] +for hashtuple in itertools.product(hashalgs, hashalgs): + if ( + isinstance(hashtuple[0], hashes.SHA1) and + isinstance(hashtuple[1], hashes.SHA1) + ): + continue + + write_file( + build_vectors(hashtuple[0], hashtuple[1], oaep_path), + "oaep-{0}-{1}.txt".format(hashtuple[0].name, hashtuple[1].name) + ) diff --git a/docs/development/custom-vectors/secp256k1.rst b/docs/development/custom-vectors/secp256k1.rst new file mode 100644 index 0000000..b19bf4e --- /dev/null +++ b/docs/development/custom-vectors/secp256k1.rst @@ -0,0 +1,32 @@ +SECP256K1 vector creation +========================= + +This page documents the code that was used to generate the SECP256K1 elliptic +curve test vectors as well as code used to verify them against another +implementation. + + +Creation +-------- + +The vectors are generated using a `pure Python ecdsa`_ implementation. The test +messages and combinations of algorithms are derived from the NIST vector data. + +.. literalinclude:: /development/custom-vectors/secp256k1/generate_secp256k1.py + +Download link: :download:`generate_secp256k1.py +` + + +Verification +------------ + +``cryptography`` was modified to support the SECP256K1 curve. Then +the following python script was run to generate the vector files. + +.. literalinclude:: /development/custom-vectors/secp256k1/verify_secp256k1.py + +Download link: :download:`verify_secp256k1.py +` + +.. _`pure Python ecdsa`: https://pypi.python.org/pypi/ecdsa diff --git a/docs/development/custom-vectors/secp256k1/generate_secp256k1.py b/docs/development/custom-vectors/secp256k1/generate_secp256k1.py new file mode 100644 index 0000000..d6a2071 --- /dev/null +++ b/docs/development/custom-vectors/secp256k1/generate_secp256k1.py @@ -0,0 +1,90 @@ +from __future__ import absolute_import, print_function + +import hashlib +import os +from binascii import hexlify +from collections import defaultdict + +from ecdsa import SECP256k1, SigningKey +from ecdsa.util import sigdecode_der, sigencode_der + +from cryptography_vectors import open_vector_file + +from tests.utils import ( + load_fips_ecdsa_signing_vectors, load_vectors_from_file +) + +HASHLIB_HASH_TYPES = { + "SHA-1": hashlib.sha1, + "SHA-224": hashlib.sha224, + "SHA-256": hashlib.sha256, + "SHA-384": hashlib.sha384, + "SHA-512": hashlib.sha512, +} + + +class TruncatedHash(object): + def __init__(self, hasher): + self.hasher = hasher + + def __call__(self, data): + self.hasher.update(data) + return self + + def digest(self): + return self.hasher.digest()[:256 // 8] + + +def build_vectors(fips_vectors): + vectors = defaultdict(list) + for vector in fips_vectors: + vectors[vector['digest_algorithm']].append(vector['message']) + + for digest_algorithm, messages in vectors.items(): + if digest_algorithm not in HASHLIB_HASH_TYPES: + continue + + yield "" + yield "[K-256,{0}]".format(digest_algorithm) + yield "" + + for message in messages: + # Make a hash context + hash_func = TruncatedHash(HASHLIB_HASH_TYPES[digest_algorithm]()) + + # Sign the message using warner/ecdsa + secret_key = SigningKey.generate(curve=SECP256k1) + public_key = secret_key.get_verifying_key() + signature = secret_key.sign(message, hashfunc=hash_func, + sigencode=sigencode_der) + + r, s = sigdecode_der(signature, None) + + yield "Msg = {0}".format(hexlify(message)) + yield "d = {0:x}".format(secret_key.privkey.secret_multiplier) + yield "Qx = {0:x}".format(public_key.pubkey.point.x()) + yield "Qy = {0:x}".format(public_key.pubkey.point.y()) + yield "R = {0:x}".format(r) + yield "S = {0:x}".format(s) + yield "" + + +def write_file(lines, dest): + for line in lines: + print(line) + print(line, file=dest) + + +source_path = os.path.join("asymmetric", "ECDSA", "FIPS_186-3", "SigGen.txt") +dest_path = os.path.join("asymmetric", "ECDSA", "SECP256K1", "SigGen.txt") + +fips_vectors = load_vectors_from_file( + source_path, + load_fips_ecdsa_signing_vectors +) + +with open_vector_file(dest_path, "w") as dest_file: + write_file( + build_vectors(fips_vectors), + dest_file + ) diff --git a/docs/development/custom-vectors/secp256k1/verify_secp256k1.py b/docs/development/custom-vectors/secp256k1/verify_secp256k1.py new file mode 100644 index 0000000..b236d77 --- /dev/null +++ b/docs/development/custom-vectors/secp256k1/verify_secp256k1.py @@ -0,0 +1,59 @@ +from __future__ import absolute_import, print_function + +import os + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.utils import ( + encode_dss_signature +) + +from tests.utils import ( + load_fips_ecdsa_signing_vectors, load_vectors_from_file +) + +CRYPTOGRAPHY_HASH_TYPES = { + "SHA-1": hashes.SHA1, + "SHA-224": hashes.SHA224, + "SHA-256": hashes.SHA256, + "SHA-384": hashes.SHA384, + "SHA-512": hashes.SHA512, +} + + +def verify_one_vector(vector): + digest_algorithm = vector['digest_algorithm'] + message = vector['message'] + x = vector['x'] + y = vector['y'] + signature = encode_dss_signature(vector['r'], vector['s']) + + numbers = ec.EllipticCurvePublicNumbers( + x, y, + ec.SECP256K1() + ) + + key = numbers.public_key(default_backend()) + + verifier = key.verifier( + signature, + ec.ECDSA(CRYPTOGRAPHY_HASH_TYPES[digest_algorithm]()) + ) + verifier.update(message) + return verifier.verify() + + +def verify_vectors(vectors): + for vector in vectors: + assert verify_one_vector(vector) + + +vector_path = os.path.join("asymmetric", "ECDSA", "SECP256K1", "SigGen.txt") + +secp256k1_vectors = load_vectors_from_file( + vector_path, + load_fips_ecdsa_signing_vectors +) + +verify_vectors(secp256k1_vectors) diff --git a/docs/development/custom-vectors/seed.rst b/docs/development/custom-vectors/seed.rst new file mode 100644 index 0000000..8c4a7aa --- /dev/null +++ b/docs/development/custom-vectors/seed.rst @@ -0,0 +1,32 @@ +SEED vector creation +===================== + +This page documents the code that was used to generate the SEED CFB and OFB +test vectors as well as the code used to verify them against another +implementation. The vectors were generated using OpenSSL and verified +with `Botan`_. + +Creation +-------- + +``cryptography`` was modified to support SEED in CFB and OFB modes. Then +the following python script was run to generate the vector files. + +.. literalinclude:: /development/custom-vectors/seed/generate_seed.py + +Download link: :download:`generate_seed.py +` + + +Verification +------------ + +The following Python code was used to verify the vectors using the `Botan`_ +project's Python bindings. + +.. literalinclude:: /development/custom-vectors/seed/verify_seed.py + +Download link: :download:`verify_seed.py +` + +.. _`Botan`: https://botan.randombit.net diff --git a/docs/development/custom-vectors/seed/generate_seed.py b/docs/development/custom-vectors/seed/generate_seed.py new file mode 100644 index 0000000..5c62d67 --- /dev/null +++ b/docs/development/custom-vectors/seed/generate_seed.py @@ -0,0 +1,58 @@ +import binascii + +from cryptography.hazmat.backends.openssl.backend import backend +from cryptography.hazmat.primitives.ciphers import algorithms, base, modes + + +def encrypt(mode, key, iv, plaintext): + cipher = base.Cipher( + algorithms.SEED(binascii.unhexlify(key)), + mode(binascii.unhexlify(iv)), + backend + ) + encryptor = cipher.encryptor() + ct = encryptor.update(binascii.unhexlify(plaintext)) + ct += encryptor.finalize() + return binascii.hexlify(ct) + + +def build_vectors(mode, filename): + with open(filename, "r") as f: + vector_file = f.read().splitlines() + + count = 0 + output = [] + key = None + iv = None + plaintext = None + for line in vector_file: + line = line.strip() + if line.startswith("KEY"): + if count != 0: + output.append("CIPHERTEXT = {0}".format( + encrypt(mode, key, iv, plaintext)) + ) + output.append("\nCOUNT = {0}".format(count)) + count += 1 + name, key = line.split(" = ") + output.append("KEY = {0}".format(key)) + elif line.startswith("IV"): + name, iv = line.split(" = ") + output.append("IV = {0}".format(iv)) + elif line.startswith("PLAINTEXT"): + name, plaintext = line.split(" = ") + output.append("PLAINTEXT = {0}".format(plaintext)) + + output.append("CIPHERTEXT = {0}".format(encrypt(mode, key, iv, plaintext))) + return "\n".join(output) + + +def write_file(data, filename): + with open(filename, "w") as f: + f.write(data) + + +OFB_PATH = "vectors/cryptography_vectors/ciphers/AES/OFB/OFBMMT128.rsp" +write_file(build_vectors(modes.OFB, OFB_PATH), "seed-ofb.txt") +CFB_PATH = "vectors/cryptography_vectors/ciphers/AES/CFB/CFB128MMT128.rsp" +write_file(build_vectors(modes.CFB, CFB_PATH), "seed-cfb.txt") diff --git a/docs/development/custom-vectors/seed/verify_seed.py b/docs/development/custom-vectors/seed/verify_seed.py new file mode 100644 index 0000000..e626428 --- /dev/null +++ b/docs/development/custom-vectors/seed/verify_seed.py @@ -0,0 +1,35 @@ +import binascii + +import botan + +from tests.utils import load_nist_vectors + + +def encrypt(mode, key, iv, plaintext): + encryptor = botan.Cipher("SEED/{0}/NoPadding".format(mode), "encrypt", + binascii.unhexlify(key)) + + cipher_text = encryptor.cipher(binascii.unhexlify(plaintext), + binascii.unhexlify(iv)) + return binascii.hexlify(cipher_text) + + +def verify_vectors(mode, filename): + with open(filename, "r") as f: + vector_file = f.read().splitlines() + + vectors = load_nist_vectors(vector_file) + for vector in vectors: + ct = encrypt( + mode, + vector["key"], + vector["iv"], + vector["plaintext"] + ) + assert ct == vector["ciphertext"] + + +ofb_path = "vectors/cryptography_vectors/ciphers/SEED/seed-ofb.txt" +verify_vectors("OFB", ofb_path) +cfb_path = "vectors/cryptography_vectors/ciphers/SEED/seed-cfb.txt" +verify_vectors("CFB", cfb_path) diff --git a/docs/development/getting-started.rst b/docs/development/getting-started.rst new file mode 100644 index 0000000..dc55893 --- /dev/null +++ b/docs/development/getting-started.rst @@ -0,0 +1,130 @@ +Getting started +=============== + +Development dependencies +------------------------ +Working on ``cryptography`` requires the installation of a small number of +development dependencies in addition to the dependencies for +:doc:`/installation`. These are listed in ``dev-requirements.txt`` and they can +be installed in a `virtualenv`_ using `pip`_. Before you install them, follow +the **build** instructions in :doc:`/installation` (be sure to stop before +actually installing ``cryptography``). Once you've done that, install the +development dependencies, and then install ``cryptography`` in ``editable`` +mode. For example: + +.. code-block:: console + + $ # Create a virtualenv and activate it + $ # Set up your cryptography build environment + $ pip install --requirement dev-requirements.txt + $ pip install --editable . + +You will also need to install ``enchant`` using your system's package manager +to check spelling in the documentation. + +.. note:: + There is an upstream bug in ``enchant`` that prevents its installation on + Windows with 64-bit Python. See `this Github issue`_ for more information. + The easiest workaround is to use 32-bit Python for ``cryptography`` + development, even on 64-bit Windows. + +You are now ready to run the tests and build the documentation. + +OpenSSL on OS X +~~~~~~~~~~~~~~~ + +You must have installed `OpenSSL`_ via `Homebrew`_ or `MacPorts`_ and must set +``CFLAGS`` and ``LDFLAGS`` environment variables before installing the +``dev-requirements.txt`` otherwise pip will fail with include errors. + +For example, with `Homebrew`_: + +.. code-block:: console + + $ env LDFLAGS="-L$(brew --prefix openssl)/lib" \ + CFLAGS="-I$(brew --prefix openssl)/include" \ + pip install --requirement ./dev-requirements.txt + +Alternatively for a static build you can specify +``CRYPTOGRAPHY_OSX_NO_LINK_FLAGS=1`` and ensure ``LDFLAGS`` points to the +absolute path for the `OpenSSL`_ libraries before calling pip. + +.. tip:: + You will also need to set these values when `Building documentation`_. + +Running tests +------------- + +``cryptography`` unit tests are found in the ``tests/`` directory and are +designed to be run using `pytest`_. `pytest`_ will discover the tests +automatically, so all you have to do is: + +.. code-block:: console + + $ py.test + ... + 62746 passed in 220.43 seconds + +This runs the tests with the default Python interpreter. + +You can also verify that the tests pass on other supported Python interpreters. +For this we use `tox`_, which will automatically create a `virtualenv`_ for +each supported Python version and run the tests. For example: + +.. code-block:: console + + $ tox + ... + ERROR: py26: InterpreterNotFound: python2.6 + py27: commands succeeded + ERROR: pypy: InterpreterNotFound: pypy + py33: commands succeeded + docs: commands succeeded + pep8: commands succeeded + +You may not have all the required Python versions installed, in which case you +will see one or more ``InterpreterNotFound`` errors. + + +Explicit backend selection +-------------------------- + +While testing you may want to run tests against a subset of the backends that +cryptography supports. Explicit backend selection can be done via the +``--backend`` flag. This flag should be passed to ``py.test`` with a comma +delimited list of backend names. + + +.. code-block:: console + + $ tox -- --backend=openssl + $ py.test --backend=openssl,commoncrypto + +Building documentation +---------------------- + +``cryptography`` documentation is stored in the ``docs/`` directory. It is +written in `reStructured Text`_ and rendered using `Sphinx`_. + +Use `tox`_ to build the documentation. For example: + +.. code-block:: console + + $ tox -e docs + ... + docs: commands succeeded + congratulations :) + +The HTML documentation index can now be found at +``docs/_build/html/index.html``. + +.. _`Homebrew`: http://brew.sh +.. _`MacPorts`: https://www.macports.org +.. _`OpenSSL`: https://www.openssl.org +.. _`pytest`: https://pypi.python.org/pypi/pytest +.. _`tox`: https://pypi.python.org/pypi/tox +.. _`virtualenv`: https://pypi.python.org/pypi/virtualenv +.. _`pip`: https://pypi.python.org/pypi/pip +.. _`sphinx`: https://pypi.python.org/pypi/Sphinx +.. _`reStructured Text`: http://sphinx-doc.org/rest.html +.. _`this Github issue`: https://github.com/rfk/pyenchant/issues/42 diff --git a/docs/development/index.rst b/docs/development/index.rst new file mode 100644 index 0000000..f9bc9ee --- /dev/null +++ b/docs/development/index.rst @@ -0,0 +1,20 @@ +Development +=========== + +As an open source project, ``cryptography`` welcomes contributions of all +forms. The sections below will help you get started. + +File bugs and feature requests on our issue tracker on `GitHub`_. If it is a +bug check out `what to put in your bug report`_. + +.. toctree:: + :maxdepth: 2 + + getting-started + submitting-patches + reviewing-patches + test-vectors + c-bindings + +.. _`GitHub`: https://github.com/pyca/cryptography +.. _`what to put in your bug report`: http://www.contribution-guide.org/#what-to-put-in-your-bug-report diff --git a/docs/development/reviewing-patches.rst b/docs/development/reviewing-patches.rst new file mode 100644 index 0000000..bd3ee96 --- /dev/null +++ b/docs/development/reviewing-patches.rst @@ -0,0 +1,60 @@ +Reviewing and merging patches +============================= + +Everyone is encouraged to review open pull requests. We only ask that you try +and think carefully, ask questions and are `excellent to one another`_. Code +review is our opportunity to share knowledge, design ideas and make friends. + +When reviewing a patch try to keep each of these concepts in mind: + +Architecture +------------ + +* Is the proposed change being made in the correct place? Is it a fix in a + backend when it should be in the primitives? + +Intent +------ + +* What is the change being proposed? +* Do we want this feature or is the bug they're fixing really a bug? + +Implementation +-------------- + +* Does the change do what the author claims? +* Are there sufficient tests? +* Has it been documented? +* Will this change introduce new bugs? + +Grammar and style +----------------- + +These are small things that are not caught by the automated style checkers. + +* Does a variable need a better name? +* Should this be a keyword argument? + +Merge requirements +------------------ + +Because cryptography is so complex, and the implications of getting it wrong so +devastating, ``cryptography`` has a strict merge policy for committers: + +* Patches must *never* be pushed directly to ``master``, all changes (even the + most trivial typo fixes!) must be submitted as a pull request. +* A committer may *never* merge their own pull request, a second party must + merge their changes. If multiple people work on a pull request, it must be + merged by someone who did not work on it. +* A patch that breaks tests, or introduces regressions by changing or removing + existing tests should not be merged. Tests must always be passing on + ``master``. +* If somehow the tests get into a failing state on ``master`` (such as by a + backwards incompatible release of a dependency) no pull requests may be + merged until this is rectified. +* All merged patches must have 100% test coverage. + +The purpose of these policies is to minimize the chances we merge a change +that jeopardizes our users' security. + +.. _`excellent to one another`: https://speakerdeck.com/ohrite/better-code-review diff --git a/docs/development/submitting-patches.rst b/docs/development/submitting-patches.rst new file mode 100644 index 0000000..63eed19 --- /dev/null +++ b/docs/development/submitting-patches.rst @@ -0,0 +1,161 @@ +Submitting patches +================== + +* Always make a new branch for your work. +* Patches should be small to facilitate easier review. `Studies have shown`_ + that review quality falls off as patch size grows. Sometimes this will result + in many small PRs to land a single large feature. +* Larger changes should be discussed on `our mailing list`_ before submission. +* New features and significant bug fixes should be documented in the + :doc:`/changelog`. +* You must have legal permission to distribute any code you contribute to + ``cryptography``, and it must be available under both the BSD and Apache + Software License Version 2.0 licenses. + +If you believe you've identified a security issue in ``cryptography``, please +follow the directions on the :doc:`security page `. + +Code +---- + +When in doubt, refer to :pep:`8` for Python code. You can check if your code +meets our automated requirements by running ``flake8`` against it. If you've +installed the development requirements this will automatically use our +configuration. You can also run the ``tox`` job with ``tox -e pep8``. + +`Write comments as complete sentences.`_ + +Class names which contains acronyms or initialisms should always be +capitalized. A class should be named ``HTTPClient``, not ``HttpClient``. + +Every code file must start with the boilerplate licensing notice: + +.. code-block:: python + + # This file is dual licensed under the terms of the Apache License, Version + # 2.0, and the BSD License. See the LICENSE file in the root of this repository + # for complete details. + +Additionally, every Python code file must contain + +.. code-block:: python + + from __future__ import absolute_import, division, print_function + +API considerations +~~~~~~~~~~~~~~~~~~ + +Most projects' APIs are designed with a philosophy of "make easy things easy, +and make hard things possible". One of the perils of writing cryptographic code +is that secure code looks just like insecure code, and its results are almost +always indistinguishable. As a result, ``cryptography`` has, as a design +philosophy: "make it hard to do insecure things". Here are a few strategies for +API design that should be both followed, and should inspire other API choices: + +If it is necessary to compare a user provided value with a computed value (for +example, verifying a signature), there should be an API provided that performs +the verification in a secure way (for example, using a constant time +comparison), rather than requiring the user to perform the comparison +themselves. + +If it is incorrect to ignore the result of a method, it should raise an +exception, and not return a boolean ``True``/``False`` flag. For example, a +method to verify a signature should raise ``InvalidSignature``, and not return +whether the signature was valid. + +.. code-block:: python + + # This is bad. + def verify(sig): + # ... + return is_valid + + # Good! + def verify(sig): + # ... + if not is_valid: + raise InvalidSignature + +Every recipe should include a version or algorithmic marker of some sort in its +output in order to allow transparent upgrading of the algorithms in use, as +the algorithms or parameters needed to achieve a given security margin evolve. + +APIs at the :doc:`/hazmat/primitives/index` layer should always take an +explicit backend, APIs at the recipes layer should automatically use the +:func:`~cryptography.hazmat.backends.default_backend`, but optionally allow +specifying a different backend. + +C bindings +~~~~~~~~~~ + +More information on C bindings can be found in :doc:`the dedicated +section of the documentation `. + +Tests +----- + +All code changes must be accompanied by unit tests with 100% code coverage (as +measured by the combined metrics across our build matrix). + +When implementing a new primitive or recipe ``cryptography`` requires that you +provide a set of test vectors. See :doc:`/development/test-vectors` for more +details. + +Documentation +------------- + +All features should be documented with prose in the ``docs`` section. To ensure +it builds and passes `doc8`_ style checks you can run ``tox -e docs``. + +Because of the inherent challenges in implementing correct cryptographic +systems, we want to make our documentation point people in the right directions +as much as possible. To that end: + +* When documenting a generic interface, use a strong algorithm in examples. + (e.g. when showing a hashing example, don't use + :class:`~cryptography.hazmat.primitives.hashes.MD5`) +* When giving prescriptive advice, always provide references and supporting + material. +* When there is real disagreement between cryptographic experts, represent both + sides of the argument and describe the trade-offs clearly. + +When documenting a new module in the ``hazmat`` package, its documentation +should begin with the "Hazardous Materials" warning: + +.. code-block:: rest + + .. hazmat:: + +Always prefer terminology that is most broadly accepted. For example: + +* When referring to class instances use "an instance of ``Foo``" + instead of "a ``Foo`` provider". + +When referring to a hypothetical individual (such as "a person receiving an +encrypted message") use gender neutral pronouns (they/them/their). + +Docstrings are typically only used when writing abstract classes, but should +be written like this if required: + +.. code-block:: python + + def some_function(some_arg): + """ + Does some things. + + :param some_arg: Some argument. + """ + +So, specifically: + +* Always use three double quotes. +* Put the three double quotes on their own line. +* No blank line at the end. +* Use Sphinx parameter/attribute documentation `syntax`_. + + +.. _`Write comments as complete sentences.`: http://nedbatchelder.com/blog/201401/comments_should_be_sentences.html +.. _`syntax`: http://sphinx-doc.org/domains.html#info-field-lists +.. _`Studies have shown`: https://smartbear.com/SmartBear/media/pdfs/11_Best_Practices_for_Peer_Code_Review.pdf +.. _`our mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev +.. _`doc8`: https://github.com/openstack/doc8 diff --git a/docs/development/test-vectors.rst b/docs/development/test-vectors.rst new file mode 100644 index 0000000..fb72240 --- /dev/null +++ b/docs/development/test-vectors.rst @@ -0,0 +1,473 @@ +Test vectors +============ + +Testing the correctness of the primitives implemented in each ``cryptography`` +backend requires trusted test vectors. Where possible these vectors are +obtained from official sources such as `NIST`_ or `IETF`_ RFCs. When this is +not possible ``cryptography`` has chosen to create a set of custom vectors +using an official vector file as input to verify consistency between +implemented backends. + +Vectors are kept in the `cryptography_vectors` package rather than within our +main test suite. + +Sources +------- + +Asymmetric ciphers +~~~~~~~~~~~~~~~~~~ + +* RSA PKCS #1 from the RSA FTP site (ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/ + and ftp://ftp.rsa.com/pub/rsalabs/tmp/). +* RSA FIPS 186-2 and PKCS1 v1.5 vulnerability test vectors from `NIST CAVP`_. +* FIPS 186-2 and FIPS 186-3 DSA test vectors from `NIST CAVP`_. +* FIPS 186-2 and FIPS 186-3 ECDSA test vectors from `NIST CAVP`_. +* DH and ECDH and ECDH+KDF(17.4) test vectors from `NIST CAVP`_. +* Ed25519 test vectors from the `Ed25519 website_`. +* OpenSSL PEM RSA serialization vectors from the `OpenSSL example key`_ and + `GnuTLS key parsing tests`_. +* OpenSSL PEM DSA serialization vectors from the `GnuTLS example keys`_. +* PKCS #8 PEM serialization vectors from + + * GnuTLS: `enc-rsa-pkcs8.pem`_, `enc2-rsa-pkcs8.pem`_, + `unenc-rsa-pkcs8.pem`_, `pkcs12_s2k_pem.c`_. The contents of + `enc2-rsa-pkcs8.pem`_ was re-encrypted using a stronger PKCS#8 cipher. + * `Botan's ECC private keys`_. +* `asymmetric/public/PKCS1/dsa.pub.pem`_ is a PKCS1 DSA public key from the + Ruby test suite. + + +Custom asymmetric vectors +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. toctree:: + :maxdepth: 1 + + custom-vectors/secp256k1 + custom-vectors/rsa-oaep-sha2 + +* ``asymmetric/PEM_Serialization/ec_private_key.pem`` and + ``asymmetric/DER_Serialization/ec_private_key.der`` - Contains an Elliptic + Curve key generated by OpenSSL from the curve ``secp256r1``. +* ``asymmetric/PEM_Serialization/ec_private_key_encrypted.pem`` and + ``asymmetric/DER_Serialization/ec_private_key_encrypted.der``- Contains the + same Elliptic Curve key as ``ec_private_key.pem``, except that it is + encrypted with AES-128 with the password "123456". +* ``asymmetric/PEM_Serialization/ec_public_key.pem`` and + ``asymmetric/DER_Serialization/ec_public_key.der``- Contains the public key + corresponding to ``ec_private_key.pem``, generated using OpenSSL. +* ``asymmetric/PEM_Serialization/rsa_private_key.pem`` - Contains an RSA 2048 + bit key generated using OpenSSL, protected by the secret "123456" with DES3 + encryption. +* ``asymmetric/PEM_Serialization/rsa_public_key.pem`` and + ``asymmetric/DER_Serialization/rsa_public_key.der``- Contains an RSA 2048 + bit public generated using OpenSSL from ``rsa_private_key.pem``. +* ``asymmetric/PEM_Serialization/dsaparam.pem`` - Contains 2048-bit DSA + parameters generated using OpenSSL; contains no keys. +* ``asymmetric/PEM_Serialization/dsa_private_key.pem`` - Contains a DSA 2048 + bit key generated using OpenSSL from the parameters in ``dsaparam.pem``, + protected by the secret "123456" with DES3 encryption. +* ``asymmetric/PEM_Serialization/dsa_public_key.pem`` and + ``asymmetric/DER_Serialization/dsa_public_key.der`` - Contains a DSA 2048 bit + key generated using OpenSSL from ``dsa_private_key.pem``. +* ``asymmetric/PKCS8/unenc-dsa-pkcs8.pem`` and + ``asymmetric/DER_Serialization/unenc-dsa-pkcs8.der`` - Contains a DSA 1024 + bit key generated using OpenSSL. +* ``asymmetric/PKCS8/unenc-dsa-pkcs8.pub.pem`` and + ``asymmetric/DER_Serialization/unenc-dsa-pkcs8.pub.der`` - Contains a DSA + 2048 bit public key generated using OpenSSL from ``unenc-dsa-pkcs8.pem``. +* DER conversions of the `GnuTLS example keys`_ for DSA as well as the + `OpenSSL example key`_ for RSA. +* DER conversions of `enc-rsa-pkcs8.pem`_, `enc2-rsa-pkcs8.pem`_, and + `unenc-rsa-pkcs8.pem`_. +* ``asymmetric/public/PKCS1/rsa.pub.pem`` and + ``asymmetric/public/PKCS1/rsa.pub.der`` are PKCS1 conversions of the public + key from ``asymmetric/PKCS8/unenc-rsa-pkcs8.pem`` using PEM and DER encoding. + + +Key exchange +~~~~~~~~~~~~ + +* ``vectors/cryptography_vectors/asymmetric/DH/RFC5114.txt`` contains + Diffie-Hellman examples from appendix A.1, A.2 and A.3 of :rfc:`5114`. + +* ``vectors/cryptography_vectors/asymmetric/DH/vec.txt`` contains + Diffie-Hellman examples from `botan`_. + +* ``vectors/cryptography_vectors/asymmetric/DH/bad_exchange.txt`` contains + Diffie-Hellman vector pairs that were generated using OpenSSL + DH_generate_parameters_ex and DH_generate_key. + +X.509 +~~~~~ + +* PKITS test suite from `NIST PKI Testing`_. +* ``v1_cert.pem`` from the OpenSSL source tree (`testx509.pem`_). +* ``ecdsa_root.pem`` - `DigiCert Global Root G3`_, a ``secp384r1`` ECDSA root + certificate. +* ``verisign-md2-root.pem`` - A legacy Verisign public root signed using the + MD2 algorithm. This is a PEM conversion of the `root data`_ in the NSS source + tree. +* ``cryptography.io.pem`` - A leaf certificate issued by RapidSSL for the + cryptography website. +* ``rapidssl_sha256_ca_g3.pem`` - The intermediate CA that issued the + ``cryptography.io.pem`` certificate. +* ``wildcard_san.pem`` - A leaf certificate issued by a public CA for + ``langui.sh`` that contains wildcard entries in the SAN extension. +* ``san_edipartyname.der`` - A DSA certificate from a `Mozilla bug`_ + containing a SAN extension with an ``ediPartyName`` general name. +* ``san_x400address.der`` - A DSA certificate from a `Mozilla bug`_ containing + a SAN extension with an ``x400Address`` general name. +* ``department-of-state-root.pem`` - The intermediary CA for the Department of + State, issued by the United States Federal Government's Common Policy CA. + Notably has a ``critical`` policy constraints extensions. +* ``e-trust.ru.der`` - A certificate from a `Russian CA`_ signed using the GOST + cipher and containing numerous unusual encodings such as NUMERICSTRING in + the subject DN. +* ``alternate-rsa-sha1-oid.pem`` - A certificate from an + `unknown signature OID`_ Mozilla bug that uses an alternate signature OID for + RSA with SHA1. + +Custom X.509 Vectors +~~~~~~~~~~~~~~~~~~~~ + +* ``invalid_version.pem`` - Contains an RSA 2048 bit certificate with the + X.509 version field set to ``0x7``. +* ``post2000utctime.pem`` - Contains an RSA 2048 bit certificate with the + ``notBefore`` and ``notAfter`` fields encoded as post-2000 ``UTCTime``. +* ``dsa_selfsigned_ca.pem`` - Contains a DSA self-signed CA certificate + generated using OpenSSL. +* ``ec_no_named_curve.pem`` - Contains an ECDSA certificate that does not have + an embedded OID defining the curve. +* ``all_supported_names.pem`` - An RSA 2048 bit certificate generated using + OpenSSL that contains a subject and issuer that have two of each supported + attribute type from :rfc:`5280`. +* ``unsupported_subject_name.pem`` - An RSA 2048 bit self-signed CA certificate + generated using OpenSSL that contains the unsupported "initials" name. +* ``utf8_common_name.pem`` - An RSA 2048 bit self-signed CA certificate + generated using OpenSSL that contains a UTF8String common name with the value + "We heart UTF8!™". +* ``two_basic_constraints.pem`` - An RSA 2048 bit self-signed certificate + containing two basic constraints extensions. +* ``basic_constraints_not_critical.pem`` - An RSA 2048 bit self-signed + certificate containing a basic constraints extension that is not marked as + critical. +* ``bc_path_length_zero.pem`` - An RSA 2048 bit self-signed + certificate containing a basic constraints extension with a path length of + zero. +* ``unsupported_extension.pem`` - An RSA 2048 bit self-signed certificate + containing an unsupported extension type. The OID was encoded as + "1.2.3.4" with an ``extnValue`` of "value". +* ``unsupported_extension_2.pem`` - A ``secp256r1`` certificate + containing two unsupported extensions. The OIDs are ``1.3.6.1.4.1.41482.2`` + with an ``extnValue`` of ``1.3.6.1.4.1.41482.1.2`` and + ``1.3.6.1.4.1.45724.2.1.1`` with an ``extnValue`` of ``\x03\x02\x040`` +* ``unsupported_extension_critical.pem`` - An RSA 2048 bit self-signed + certificate containing an unsupported extension type marked critical. The OID + was encoded as "1.2.3.4" with an ``extnValue`` of "value". +* ``san_email_dns_ip_dirname_uri.pem`` - An RSA 2048 bit self-signed + certificate containing a subject alternative name extension with the + following general names: ``rfc822Name``, ``dNSName``, ``iPAddress``, + ``directoryName``, and ``uniformResourceIdentifier``. +* ``san_empty_hostname.pem`` - An RSA 2048 bit self-signed certificate + containing a subject alternative extension with an empty ``dNSName`` + general name. +* ``san_other_name.pem`` - An RSA 2048 bit self-signed certificate containing + a subject alternative name extension with the ``otherName`` general name. +* ``san_registered_id.pem`` - An RSA 1024 bit certificate containing a + subject alternative name extension with the ``registeredID`` general name. +* ``all_key_usages.pem`` - An RSA 2048 bit self-signed certificate containing + a key usage extension with all nine purposes set to true. +* ``extended_key_usage.pem`` - An RSA 2048 bit self-signed certificate + containing an extended key usage extension with eight usages. +* ``san_idna_names.pem`` - An RSA 2048 bit self-signed certificate containing + a subject alternative name extension with ``rfc822Name``, ``dNSName``, and + ``uniformResourceIdentifier`` general names with IDNA (:rfc:`5895`) encoding. +* ``san_wildcard_idna.pem`` - An RSA 2048 bit self-signed certificate + containing a subject alternative name extension with a ``dNSName`` general + name with a wildcard IDNA (:rfc:`5895`) domain. +* ``san_idna2003_dnsname.pem`` - An RSA 2048 bit self-signed certificate + containing a subject alternative name extension with an IDNA 2003 + (:rfc:`3490`) ``dNSName``. +* ``san_rfc822_names.pem`` - An RSA 2048 bit self-signed certificate containing + a subject alternative name extension with various ``rfc822Name`` values. +* ``san_rfc822_idna.pem`` - An RSA 2048 bit self-signed certificate containing + a subject alternative name extension with an IDNA ``rfc822Name``. +* ``san_uri_with_port.pem`` - An RSA 2048 bit self-signed certificate + containing a subject alternative name extension with various + ``uniformResourceIdentifier`` values. +* ``san_ipaddr.pem`` - An RSA 2048 bit self-signed certificate containing a + subject alternative name extension with an ``iPAddress`` value. +* ``san_dirname.pem`` - An RSA 2048 bit self-signed certificate containing a + subject alternative name extension with a ``directoryName`` value. +* ``inhibit_any_policy_5.pem`` - An RSA 2048 bit self-signed certificate + containing an inhibit any policy extension with the value 5. +* ``inhibit_any_policy_negative.pem`` - An RSA 2048 bit self-signed certificate + containing an inhibit any policy extension with the value -1. +* ``authority_key_identifier.pem`` - An RSA 2048 bit self-signed certificate + containing an authority key identifier extension with key identifier, + authority certificate issuer, and authority certificate serial number fields. +* ``authority_key_identifier_no_keyid.pem`` - An RSA 2048 bit self-signed + certificate containing an authority key identifier extension with authority + certificate issuer and authority certificate serial number fields. +* ``aia_ocsp_ca_issuers.pem`` - An RSA 2048 bit self-signed certificate + containing an authority information access extension with two OCSP and one + CA issuers entry. +* ``aia_ocsp.pem`` - An RSA 2048 bit self-signed certificate + containing an authority information access extension with an OCSP entry. +* ``aia_ca_issuers.pem`` - An RSA 2048 bit self-signed certificate + containing an authority information access extension with a CA issuers entry. +* ``cdp_empty_hostname.pem`` - An RSA 2048 bit self-signed certificate + containing a CRL distribution point extension with ``fullName`` URI without + a hostname. +* ``cdp_fullname_reasons_crl_issuer.pem`` - An RSA 1024 bit certificate + containing a CRL distribution points extension with ``fullName``, + ``cRLIssuer``, and ``reasons`` data. +* ``cdp_crl_issuer.pem`` - An RSA 1024 bit certificate containing a CRL + distribution points extension with ``cRLIssuer`` data. +* ``cdp_all_reasons.pem`` - An RSA 1024 bit certificate containing a CRL + distribution points extension with all ``reasons`` bits set. +* ``cdp_reason_aa_compromise.pem`` - An RSA 1024 bit certificate containing a + CRL distribution points extension with the ``AACompromise`` ``reasons`` bit + set. +* ``nc_permitted_excluded.pem`` - An RSA 2048 bit self-signed certificate + containing a name constraints extension with both permitted and excluded + elements. Contains ``IPv4`` and ``IPv6`` addresses with network mask as well + as ``dNSName`` with a leading period. +* ``nc_permitted_excluded_2.pem`` - An RSA 2048 bit self-signed certificate + containing a name constraints extension with both permitted and excluded + elements. Unlike ``nc_permitted_excluded.pem``, the general names do not + contain any name constraints specific values. +* ``nc_permitted.pem`` - An RSA 2048 bit self-signed certificate containing a + name constraints extension with permitted elements. +* ``nc_permitted_2.pem`` - An RSA 2048 bit self-signed certificate containing a + name constraints extension with permitted elements that do not contain any + name constraints specific values. +* ``nc_excluded.pem`` - An RSA 2048 bit self-signed certificate containing a + name constraints extension with excluded elements. +* ``nc_invalid_ip_netmask.pem`` - An RSA 2048 bit self-signed certificate + containing a name constraints extension with a permitted element that has an + ``IPv6`` IP and an invalid network mask. +* ``nc_single_ip_netmask.pem`` - An RSA 2048 bit self-signed certificate + containing a name constraints extension with a permitted element that has two + IPs with ``/32`` and ``/128`` network masks. +* ``cp_user_notice_with_notice_reference.pem`` - An RSA 2048 bit self-signed + certificate containing a certificate policies extension with a + notice reference in the user notice. +* ``cp_user_notice_with_explicit_text.pem`` - An RSA 2048 bit self-signed + certificate containing a certificate policies extension with explicit + text and no notice reference. +* ``cp_cps_uri.pem`` - An RSA 2048 bit self-signed certificate containing a + certificate policies extension with a CPS URI and no user notice. +* ``cp_user_notice_no_explicit_text.pem`` - An RSA 2048 bit self-signed + certificate containing a certificate policies extension with a user notice + with no explicit text. +* ``cp_invalid.pem`` - An RSA 2048 bit self-signed certificate containing a + certificate policies extension with invalid data. +* ``ian_uri.pem`` - An RSA 2048 bit certificate containing an issuer + alternative name extension with a ``URI`` general name. +* ``ocsp_nocheck.pem`` - An RSA 2048 bit self-signed certificate containing + an ``OCSPNoCheck`` extension. +* ``pc_inhibit_require.pem`` - An RSA 2048 bit self-signed certificate + containing a policy constraints extension with both inhibit policy mapping + and require explicit policy elements. +* ``pc_inhibit.pem`` - An RSA 2048 bit self-signed certificate containing a + policy constraints extension with an inhibit policy mapping element. +* ``pc_require.pem`` - An RSA 2048 bit self-signed certificate containing a + policy constraints extension with a require explicit policy element. +* ``unsupported_subject_public_key_info.pem`` - A certificate whose public key + is an unknown OID (``1.3.6.1.4.1.8432.1.1.2``). +* ``policy_constraints_explicit.pem`` - A self-signed certificate containing + a ``policyConstraints`` extension with a ``requireExplicitPolicy`` value. + +Custom X.509 Request Vectors +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* ``dsa_sha1.pem`` and ``dsa_sha1.der`` - Contain a certificate request using + 1024-bit DSA parameters and SHA1 generated using OpenSSL. +* ``rsa_md4.pem`` and ``rsa_md4.der`` - Contain a certificate request using + 2048 bit RSA and MD4 generated using OpenSSL. +* ``rsa_sha1.pem`` and ``rsa_sha1.der`` - Contain a certificate request using + 2048 bit RSA and SHA1 generated using OpenSSL. +* ``rsa_sha256.pem`` and ``rsa_sha256.der`` - Contain a certificate request + using 2048 bit RSA and SHA256 generated using OpenSSL. +* ``ec_sha256.pem`` and ``ec_sha256.der`` - Contain a certificate request + using EC (``secp384r1``) and SHA256 generated using OpenSSL. +* ``san_rsa_sha1.pem`` and ``san_rsa_sha1.der`` - Contain a certificate + request using RSA and SHA1 with a subject alternative name extension + generated using OpenSSL. +* ``two_basic_constraints.pem`` - A certificate signing request + for an RSA 2048 bit key containing two basic constraints extensions. +* ``unsupported_extension.pem`` - A certificate signing request + for an RSA 2048 bit key containing containing an unsupported + extension type. The OID was encoded as "1.2.3.4" with an + ``extnValue`` of "value". +* ``unsupported_extension_critical.pem`` - A certificate signing + request for an RSA 2048 bit key containing containing an unsupported + extension type marked critical. The OID was encoded as "1.2.3.4" + with an ``extnValue`` of "value". +* ``basic_constraints.pem`` - A certificate signing request for an RSA + 2048 bit key containing a basic constraints extension marked as + critical. +* ``invalid_signature.pem`` - A certificate signing request for an RSA + 1024 bit key containing an invalid signature with correct padding. + +Custom X.509 Certificate Revocation List Vectors +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* ``crl_all_reasons.pem`` - Contains a CRL with 12 revoked certificates, whose + serials match their list position. It includes one revocation without + any entry extensions, 10 revocations with every supported reason code and one + revocation with an unsupported, non-critical entry extension with the OID + value set to "1.2.3.4". +* ``crl_dup_entry_ext.pem`` - Contains a CRL with one revocation which has a + duplicate entry extension. +* ``crl_md2_unknown_crit_entry_ext.pem`` - Contains a CRL with one revocation + which contains an unsupported critical entry extension with the OID value set + to "1.2.3.4". The CRL uses an unsupported MD2 signature algorithm. +* ``crl_unsupported_reason.pem`` - Contains a CRL with one revocation which has + an unsupported reason code. +* ``crl_inval_cert_issuer_entry_ext.pem`` - Contains a CRL with one revocation + which has one entry extension for certificate issuer with an empty value. +* ``crl_empty.pem`` - Contains a CRL with no revoked certificates. +* ``crl_ian_aia_aki.pem`` - Contains a CRL with ``IssuerAlternativeName``, + ``AuthorityInformationAccess``, ``AuthorityKeyIdentifier`` and ``CRLNumber`` + extensions. + +Hashes +~~~~~~ + +* MD5 from :rfc:`1321`. +* RIPEMD160 from the `RIPEMD website`_. +* SHA1 from `NIST CAVP`_. +* SHA2 (224, 256, 384, 512) from `NIST CAVP`_. +* Whirlpool from the `Whirlpool website`_. +* Blake2s and Blake2b from OpenSSL `test/evptests.txt`_. + +HMAC +~~~~ + +* HMAC-MD5 from :rfc:`2202`. +* HMAC-SHA1 from :rfc:`2202`. +* HMAC-RIPEMD160 from :rfc:`2286`. +* HMAC-SHA2 (224, 256, 384, 512) from :rfc:`4231`. + +Key derivation functions +~~~~~~~~~~~~~~~~~~~~~~~~ + +* HKDF (SHA1, SHA256) from :rfc:`5869`. +* PBKDF2 (HMAC-SHA1) from :rfc:`6070`. +* scrypt from the `draft RFC`_. +* X9.63 KDF from `NIST CAVP`_. +* SP 800-108 Counter Mode KDF (HMAC-SHA1, HMAC-SHA224, HMAC-SHA256, + HMAC-SHA384, HMAC-SHA512) from `NIST CAVP`_. + +Key wrapping +~~~~~~~~~~~~ + +* AES key wrap (AESKW) and 3DES key wrap test vectors from `NIST CAVP`_. + +Recipes +~~~~~~~ + +* Fernet from its `specification repository`_. + +Symmetric ciphers +~~~~~~~~~~~~~~~~~ + +* AES (CBC, CFB, ECB, GCM, OFB) from `NIST CAVP`_. +* AES CTR from :rfc:`3686`. +* 3DES (CBC, CFB, ECB, OFB) from `NIST CAVP`_. +* ARC4 (KEY-LENGTH: 40, 56, 64, 80, 128, 192, 256) from :rfc:`6229`. +* ARC4 (KEY-LENGTH: 160) generated by this project. + See: :doc:`/development/custom-vectors/arc4` +* Blowfish (CBC, CFB, ECB, OFB) from `Bruce Schneier's vectors`_. +* Camellia (ECB) from NTT's `Camellia page`_ as linked by `CRYPTREC`_. +* Camellia (CBC, CFB, OFB) from `OpenSSL's test vectors`_. +* CAST5 (ECB) from :rfc:`2144`. +* CAST5 (CBC, CFB, OFB) generated by this project. + See: :doc:`/development/custom-vectors/cast5` +* IDEA (ECB) from the `NESSIE IDEA vectors`_ created by `NESSIE`_. +* IDEA (CBC, CFB, OFB) generated by this project. + See: :doc:`/development/custom-vectors/idea` +* SEED (ECB) from :rfc:`4269`. +* SEED (CBC) from :rfc:`4196`. +* SEED (CFB, OFB) generated by this project. + See: :doc:`/development/custom-vectors/seed` + +Two factor authentication +~~~~~~~~~~~~~~~~~~~~~~~~~ + +* HOTP from :rfc:`4226` +* TOTP from :rfc:`6238` (Note that an `errata`_ for the test vectors in RFC + 6238 exists) + +CMAC +~~~~ + +* AES-128, AES-192, AES-256, 3DES from `NIST SP-800-38B`_ + +Creating test vectors +--------------------- + +When official vectors are unavailable ``cryptography`` may choose to build +its own using existing vectors as source material. + +Custom Symmetric Vectors +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. toctree:: + :maxdepth: 1 + + custom-vectors/arc4 + custom-vectors/cast5 + custom-vectors/idea + custom-vectors/seed + +If official test vectors appear in the future the custom generated vectors +should be discarded. + +Any vectors generated by this method must also be prefixed with the following +header format (substituting the correct information): + +.. code-block:: python + + # CAST5 CBC vectors built for https://github.com/pyca/cryptography + # Derived from the AESVS MMT test data for CBC + # Verified against the CommonCrypto and Go crypto packages + # Key Length : 128 + +.. _`NIST`: https://www.nist.gov/ +.. _`IETF`: https://www.ietf.org/ +.. _`NIST CAVP`: http://csrc.nist.gov/groups/STM/cavp/ +.. _`Bruce Schneier's vectors`: https://www.schneier.com/code/vectors.txt +.. _`Camellia page`: https://info.isl.ntt.co.jp/crypt/eng/camellia/ +.. _`CRYPTREC`: https://www.cryptrec.go.jp +.. _`OpenSSL's test vectors`: https://github.com/openssl/openssl/blob/97cf1f6c2854a3a955fd7dd3a1f113deba00c9ef/crypto/evp/evptests.txt#L232 +.. _`RIPEMD website`: http://homes.esat.kuleuven.be/~bosselae/ripemd160.html +.. _`Whirlpool website`: http://www.larc.usp.br/~pbarreto/WhirlpoolPage.html +.. _`draft RFC`: https://tools.ietf.org/html/draft-josefsson-scrypt-kdf-01 +.. _`Specification repository`: https://github.com/fernet/spec +.. _`errata`: https://www.rfc-editor.org/errata_search.php?rfc=6238 +.. _`OpenSSL example key`: https://github.com/openssl/openssl/blob/d02b48c63a58ea4367a0e905979f140b7d090f86/test/testrsa.pem +.. _`GnuTLS key parsing tests`: https://gitlab.com/gnutls/gnutls/commit/f16ef39ef0303b02d7fa590a37820440c466ce8d +.. _`enc-rsa-pkcs8.pem`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs8-decode/encpkcs8.pem +.. _`enc2-rsa-pkcs8.pem`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs8-decode/enc2pkcs8.pem +.. _`unenc-rsa-pkcs8.pem`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs8-decode/unencpkcs8.pem +.. _`pkcs12_s2k_pem.c`: https://gitlab.com/gnutls/gnutls/blob/f8d943b38bf74eaaa11d396112daf43cb8aa82ae/tests/pkcs12_s2k_pem.c +.. _`Botan's ECC private keys`: https://github.com/randombit/botan/tree/4917f26a2b154e841cd27c1bcecdd41d2bdeb6ce/src/tests/data/ecc +.. _`GnuTLS example keys`: https://gitlab.com/gnutls/gnutls/commit/ad2061deafdd7db78fd405f9d143b0a7c579da7b +.. _`NESSIE IDEA vectors`: https://www.cosic.esat.kuleuven.be/nessie/testvectors/bc/idea/Idea-128-64.verified.test-vectors +.. _`NESSIE`: https://en.wikipedia.org/wiki/NESSIE +.. _`Ed25519 website`: http://ed25519.cr.yp.to/software.html +.. _`NIST SP-800-38B`: http://csrc.nist.gov/publications/nistpubs/800-38B/Updated_CMAC_Examples.pdf +.. _`NIST PKI Testing`: http://csrc.nist.gov/groups/ST/crypto_apps_infra/pki/pkitesting.html +.. _`testx509.pem`: https://github.com/openssl/openssl/blob/master/test/testx509.pem +.. _`DigiCert Global Root G3`: http://cacerts.digicert.com/DigiCertGlobalRootG3.crt +.. _`root data`: https://hg.mozilla.org/projects/nss/file/25b2922cc564/security/nss/lib/ckfw/builtins/certdata.txt#l2053 +.. _`asymmetric/public/PKCS1/dsa.pub.pem`: https://github.com/ruby/ruby/blob/4ccb387f3bc436a08fc6d72c4931994f5de95110/test/openssl/test_pkey_dsa.rb#L53 +.. _`Mozilla bug`: https://bugzilla.mozilla.org/show_bug.cgi?id=233586 +.. _`Russian CA`: https://e-trust.gosuslugi.ru/MainCA +.. _`test/evptests.txt`: https://github.com/openssl/openssl/blob/2d0b44126763f989a4cbffbffe9d0c7518158bb7/test/evptests.txt +.. _`unknown signature OID`: https://bugzilla.mozilla.org/show_bug.cgi?id=405966 +.. _`botan`: https://github.com/randombit/botan/blob/57789bdfc55061002b2727d0b32587612829a37c/src/tests/data/pubkey/dh.vec diff --git a/docs/doing-a-release.rst b/docs/doing-a-release.rst new file mode 100644 index 0000000..0feb59b --- /dev/null +++ b/docs/doing-a-release.rst @@ -0,0 +1,91 @@ +Doing a release +=============== + +Doing a release of ``cryptography`` requires a few steps. + +Verifying and upgrading OpenSSL version +--------------------------------------- + +The release process uses a static build for Windows and OS X wheels. Check that +the Windows and OS X Jenkins builders have the latest version of OpenSSL +installed before performing the release. If they do not: + +Upgrading Windows +~~~~~~~~~~~~~~~~~ + +Run the ``openssl-release`` Jenkins job, then copy the resulting artifacts to +the Windows builders and unzip them in the root of the file system. + +Upgrading OS X +~~~~~~~~~~~~~~ + +``brew update`` and then ``brew upgrade openssl --universal --build-bottle`` to +build a universal library (32-bit and 64-bit) compatible with all Intel Macs. +This can be confirmed by using +``lipo -info /usr/local/opt/openssl/lib/libssl.dylib`` to see the available +architectures. + +Bumping the version number +-------------------------- + +The next step in doing a release is bumping the version number in the +software. + +* Update the version number in ``src/cryptography/__about__.py``. +* Update the version number in ``vectors/cryptography_vectors/__about__.py``. +* Set the release date in the :doc:`/changelog`. +* Do a commit indicating this. +* Send a pull request with this. +* Wait for it to be merged. + +Performing the release +---------------------- + +The commit that merged the version number bump is now the official release +commit for this release. You will need to have ``gpg`` installed and a ``gpg`` +key in order to do a release. Once this has happened: + +* Run ``invoke release {version}``. + +The release should now be available on PyPI and a tag should be available in +the repository. + +Verifying the release +--------------------- + +You should verify that ``pip install cryptography`` works correctly: + +.. code-block:: pycon + + >>> import cryptography + >>> cryptography.__version__ + '...' + >>> import cryptography_vectors + >>> cryptography_vectors.__version__ + '...' + +Verify that this is the version you just released. + +For the Windows wheels check the builds for the ``cryptography-wheel-builder`` +job and verify that the final output for each build shows it loaded and linked +the expected OpenSSL version. + +Post-release tasks +------------------ + +* Update the version number to the next major (e.g. ``0.5.dev1``) in + ``cryptography/__about__.py`` and + ``vectors/cryptography_vectors/__about__.py``. +* Close the `milestone`_ for the previous release on GitHub. +* Add new :doc:`/changelog` entry with next version and note that it is under + active development +* Send a pull request with these items +* Check for any outstanding code undergoing a deprecation cycle by looking in + ``cryptography.utils`` for ``DeprecatedIn**`` definitions. If any exist open + a ticket to increment them for the next release. +* Send an email to the `mailing list`_ and `python-announce`_ announcing the + release. + +.. _`milestone`: https://github.com/pyca/cryptography/milestones +.. _`mailing list`: https://mail.python.org/mailman/listinfo/cryptography-dev +.. _`python-announce`: https://mail.python.org/mailman/listinfo/python-announce-list diff --git a/docs/exceptions.rst b/docs/exceptions.rst new file mode 100644 index 0000000..59d7d9d --- /dev/null +++ b/docs/exceptions.rst @@ -0,0 +1,39 @@ +Exceptions +========== + +.. currentmodule:: cryptography.exceptions + + +.. class:: UnsupportedAlgorithm + + Raised when the requested algorithm, or combination of algorithms is not + supported. + + +.. class:: AlreadyFinalized + + This is raised when a context is used after being finalized. + + +.. class:: InvalidSignature + + This is raised when signature verification fails. This can occur with + HMAC or asymmetric key signature validation. + + +.. class:: NotYetFinalized + + This is raised when the AEAD tag property is accessed on a context + before it is finalized. + + +.. class:: AlreadyUpdated + + This is raised when additional data is added to a context after update + has already been called. + + +.. class:: InvalidKey + + This is raised when the verify method of a key derivation function's + computed key does not match the expected key. diff --git a/docs/faq.rst b/docs/faq.rst new file mode 100644 index 0000000..76117a9 --- /dev/null +++ b/docs/faq.rst @@ -0,0 +1,68 @@ +Frequently asked questions +========================== + +How does ``cryptography`` compare to NaCl (Networking and Cryptography Library)? +-------------------------------------------------------------------------------- + +While ``cryptography`` and `NaCl`_ both share the goal of making cryptography +easier, and safer, to use for developers, ``cryptography`` is designed to be a +general purpose library, interoperable with existing systems, while NaCl +features a collection of hand selected algorithms. + +``cryptography``'s :ref:`recipes ` layer has similar goals +to NaCl. + +If you prefer NaCl's design, we highly recommend `PyNaCl`_. + +Compiling ``cryptography`` on OS X produces a ``fatal error: 'openssl/aes.h' file not found`` error +--------------------------------------------------------------------------------------------------- + +This happens because OS X 10.11 no longer includes a copy of OpenSSL. +``cryptography`` now provides wheels which include a statically linked copy of +OpenSSL. You're seeing this error because your copy of pip is too old to find +our wheel files. Upgrade your copy of pip with ``pip install -U pip`` and then +try install ``cryptography`` again. + +Starting ``cryptography`` using ``mod_wsgi`` produces an ``InternalError`` during a call in ``_register_osrandom_engine`` +------------------------------------------------------------------------------------------------------------------------- + +This happens because ``mod_wsgi`` uses sub-interpreters, which can cause a +problem during initialization of the OpenSSL backend. To resolve this set the +`WSGIApplicationGroup`_ to ``%{GLOBAL}`` in the ``mod_wsgi`` configuration. + +``cryptography`` raised an ``InternalError`` and I'm not sure what to do? +------------------------------------------------------------------------- + +Frequently ``InternalError`` is raised when there are errors on the OpenSSL +error stack that were placed there by other libraries that are also using +OpenSSL. Try removing the other libraries and see if the problem persists. +If you have no other libraries using OpenSSL in your process, or they do not +appear to be at fault, it's possible that this is a bug in ``cryptography``. +Please file an `issue`_ with instructions on how to reproduce it. + +Importing cryptography causes a ``RuntimeError`` about OpenSSL 1.0.0 +-------------------------------------------------------------------- + +The OpenSSL project has dropped support for the 1.0.0 release series. Since it +is no longer receiving security patches from upstream, ``cryptography`` is also +dropping support for it. To fix this issue you should upgrade to a newer +version of OpenSSL (1.0.1 or later). This may require you to upgrade to a newer +operating system. + +For the 1.7 release, you can set the ``CRYPTOGRAPHY_ALLOW_OPENSSL_100`` +environment variable. Please note that this is *temporary* and will be removed +in ``cryptography`` 1.8. + +Installing cryptography with OpenSSL 0.9.8 fails +------------------------------------------------ + +The OpenSSL project has dropped support for the 0.9.8 release series. Since it +is no longer receiving security patches from upstream, ``cryptography`` is also +dropping support for it. To fix this issue you should upgrade to a newer +version of OpenSSL (1.0.1 or later). This may require you to upgrade to a newer +operating system. + +.. _`NaCl`: https://nacl.cr.yp.to/ +.. _`PyNaCl`: https://pynacl.readthedocs.io +.. _`WSGIApplicationGroup`: https://modwsgi.readthedocs.io/en/develop/configuration-directives/WSGIApplicationGroup.html +.. _`issue`: https://github.com/pyca/cryptography/issues diff --git a/docs/fernet.rst b/docs/fernet.rst new file mode 100644 index 0000000..a2bab32 --- /dev/null +++ b/docs/fernet.rst @@ -0,0 +1,169 @@ +Fernet (symmetric encryption) +============================= + +.. currentmodule:: cryptography.fernet + +Fernet guarantees that a message encrypted using it cannot be +manipulated or read without the key. `Fernet`_ is an implementation of +symmetric (also known as "secret key") authenticated cryptography. Fernet also +has support for implementing key rotation via :class:`MultiFernet`. + +.. class:: Fernet(key) + + This class provides both encryption and decryption facilities. + + .. doctest:: + + >>> from cryptography.fernet import Fernet + >>> key = Fernet.generate_key() + >>> f = Fernet(key) + >>> token = f.encrypt(b"my deep dark secret") + >>> token + '...' + >>> f.decrypt(token) + 'my deep dark secret' + + :param bytes key: A URL-safe base64-encoded 32-byte key. This **must** be + kept secret. Anyone with this key is able to create and + read messages. + + .. classmethod:: generate_key() + + Generates a fresh fernet key. Keep this some place safe! If you lose it + you'll no longer be able to decrypt messages; if anyone else gains + access to it, they'll be able to decrypt all of your messages, and + they'll also be able forge arbitrary messages that will be + authenticated and decrypted. + + .. method:: encrypt(data) + + :param bytes data: The message you would like to encrypt. + :returns bytes: A secure message that cannot be read or altered + without the key. It is URL-safe base64-encoded. This is + referred to as a "Fernet token". + :raises TypeError: This exception is raised if ``data`` is not + ``bytes``. + + .. note:: + + The encrypted message contains the current time when it was + generated in *plaintext*, the time a message was created will + therefore be visible to a possible attacker. + + .. method:: decrypt(token, ttl=None) + + :param bytes token: The Fernet token. This is the result of calling + :meth:`encrypt`. + :param int ttl: Optionally, the number of seconds old a message may be + for it to be valid. If the message is older than + ``ttl`` seconds (from the time it was originally + created) an exception will be raised. If ``ttl`` is not + provided (or is ``None``), the age of the message is + not considered. + :returns bytes: The original plaintext. + :raises cryptography.fernet.InvalidToken: If the ``token`` is in any + way invalid, this exception + is raised. A token may be + invalid for a number of + reasons: it is older than the + ``ttl``, it is malformed, or + it does not have a valid + signature. + :raises TypeError: This exception is raised if ``token`` is not + ``bytes``. + + +.. class:: MultiFernet(fernets) + + .. versionadded:: 0.7 + + This class implements key rotation for Fernet. It takes a ``list`` of + :class:`Fernet` instances, and implements the same API: + + .. doctest:: + + >>> from cryptography.fernet import Fernet, MultiFernet + >>> key1 = Fernet(Fernet.generate_key()) + >>> key2 = Fernet(Fernet.generate_key()) + >>> f = MultiFernet([key1, key2]) + >>> token = f.encrypt(b"Secret message!") + >>> token + '...' + >>> f.decrypt(token) + 'Secret message!' + + MultiFernet performs all encryption options using the *first* key in the + ``list`` provided. MultiFernet attempts to decrypt tokens with each key in + turn. A :class:`cryptography.fernet.InvalidToken` exception is raised if + the correct key is not found in the ``list`` provided. + + Key rotation makes it easy to replace old keys. You can add your new key at + the front of the list to start encrypting new messages, and remove old keys + as they are no longer needed. + + +.. class:: InvalidToken + + See :meth:`Fernet.decrypt` for more information. + + +Using passwords with Fernet +--------------------------- + +It is possible to use passwords with Fernet. To do this, you need to run the +password through a key derivation function such as +:class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC`, bcrypt or +scrypt. + +.. doctest:: + + >>> import base64 + >>> import os + >>> from cryptography.fernet import Fernet + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + >>> password = b"password" + >>> salt = os.urandom(16) + >>> kdf = PBKDF2HMAC( + ... algorithm=hashes.SHA256(), + ... length=32, + ... salt=salt, + ... iterations=100000, + ... backend=default_backend() + ... ) + >>> key = base64.urlsafe_b64encode(kdf.derive(password)) + >>> f = Fernet(key) + >>> token = f.encrypt(b"Secret message!") + >>> token + '...' + >>> f.decrypt(token) + 'Secret message!' + +In this scheme, the salt has to be stored in a retrievable location in order +to derive the same key from the password in the future. + +The iteration count used should be adjusted to be as high as your server can +tolerate. A good default is at least 100,000 iterations which is what Django +`recommends`_ in 2014. + +Implementation +-------------- + +Fernet is built on top of a number of standard cryptographic primitives. +Specifically it uses: + +* :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` in + :class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` mode with a + 128-bit key for encryption; using + :class:`~cryptography.hazmat.primitives.padding.PKCS7` padding. +* :class:`~cryptography.hazmat.primitives.hmac.HMAC` using + :class:`~cryptography.hazmat.primitives.hashes.SHA256` for authentication. +* Initialization vectors are generated using ``os.urandom()``. + +For complete details consult the `specification`_. + + +.. _`Fernet`: https://github.com/fernet/spec/ +.. _`specification`: https://github.com/fernet/spec/blob/master/Spec.md +.. _`recommends`: https://github.com/django/django/blob/master/django/utils/crypto.py#L148 diff --git a/docs/glossary.rst b/docs/glossary.rst new file mode 100644 index 0000000..202fa2d --- /dev/null +++ b/docs/glossary.rst @@ -0,0 +1,74 @@ +Glossary +======== + +.. glossary:: + :sorted: + + plaintext + User-readable data you care about. + + ciphertext + The encoded data, it's not user readable. Potential attackers are able + to see this. + + encryption + The process of converting plaintext to ciphertext. + + decryption + The process of converting ciphertext to plaintext. + + key + Secret data is encoded with a function using this key. Sometimes + multiple keys are used. These **must** be kept secret, if a key is + exposed to an attacker, any data encrypted with it will be exposed. + + symmetric cryptography + Cryptographic operations where encryption and decryption use the same + key. + + public-key cryptography + asymmetric cryptography + Cryptographic operations where encryption and decryption use different + keys. There are separate encryption and decryption keys. Typically + encryption is performed using a :term:`public key`, and it can then be + decrypted using a :term:`private key`. Asymmetric cryptography can also + be used to create signatures, which can be generated with a + :term:`private key` and verified with a :term:`public key`. + + public key + This is one of two keys involved in :term:`public-key cryptography`. It + can be used to encrypt messages for someone possessing the + corresponding :term:`private key` and to verify signatures created with + the corresponding :term:`private key`. This can be distributed + publicly, hence the name. + + private key + This is one of two keys involved in :term:`public-key cryptography`. It + can be used to decrypt messages which were encrypted with the + corresponding :term:`public key`, as well as to create signatures, + which can be verified with the corresponding :term:`public key`. These + **must** be kept secret, if they are exposed, all encrypted messages + are compromised, and an attacker will be able to forge signatures. + + authentication + The process of verifying that a message was created by a specific + individual (or program). Like encryption, authentication can be either + symmetric or asymmetric. Authentication is necessary for effective + encryption. + + ciphertext indistinguishability + This is a property of encryption systems whereby two encrypted messages + aren't distinguishable without knowing the encryption key. This is + considered a basic, necessary property for a working encryption system. + + text + This type corresponds to ``unicode`` on Python 2 and ``str`` on Python + 3. This is equivalent to ``six.text_type``. + + nonce + A nonce is a **n**\ umber used **once**. Nonces are used in many + cryptographic protocols. Generally, a nonce does not have to be secret + or unpredictable, but it must be unique. A nonce is often a random + or pseudo-random number (see :doc:`Random number generation + `). Since a nonce does not have to be unpredictable, + it can also take a form of a counter. diff --git a/docs/hazmat/backends/commoncrypto.rst b/docs/hazmat/backends/commoncrypto.rst new file mode 100644 index 0000000..a6eb490 --- /dev/null +++ b/docs/hazmat/backends/commoncrypto.rst @@ -0,0 +1,30 @@ +.. hazmat:: + +CommonCrypto backend +==================== + +The `CommonCrypto`_ C library provided by Apple on OS X and iOS. The +CommonCrypto backend is only supported on OS X versions 10.8 and above. + +.. currentmodule:: cryptography.hazmat.backends.commoncrypto.backend + +.. versionadded:: 0.2 + +.. data:: cryptography.hazmat.backends.commoncrypto.backend + + This is the exposed API for the CommonCrypto backend. + + It implements the following interfaces: + + * :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` + * :class:`~cryptography.hazmat.backends.interfaces.HashBackend` + * :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + * :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend` + + It has one additional public attribute. + + .. attribute:: name + + The string name of this backend: ``"commoncrypto"`` + +.. _`CommonCrypto`: https://developer.apple.com/library/content/documentation/Security/Conceptual/cryptoservices/GeneralPurposeCrypto/GeneralPurposeCrypto.html#//apple_ref/doc/uid/TP40011172-CH9-SW10 diff --git a/docs/hazmat/backends/index.rst b/docs/hazmat/backends/index.rst new file mode 100644 index 0000000..aec7a1e --- /dev/null +++ b/docs/hazmat/backends/index.rst @@ -0,0 +1,36 @@ +.. hazmat:: + +Backends +======== + +Getting a backend +----------------- + +.. currentmodule:: cryptography.hazmat.backends + +``cryptography`` aims to support multiple backends to ensure it can provide +the widest number of supported cryptographic algorithms as well as supporting +platform specific implementations. + +You can get the default backend by calling :func:`~default_backend`. + +The default backend will change over time as we implement new backends and +the libraries we use in those backends changes. + + +.. function:: default_backend() + + :returns: An object that provides at least + :class:`~interfaces.CipherBackend`, :class:`~interfaces.HashBackend`, and + :class:`~interfaces.HMACBackend`. + +Individual backends +------------------- + +.. toctree:: + :maxdepth: 1 + + openssl + commoncrypto + multibackend + interfaces diff --git a/docs/hazmat/backends/interfaces.rst b/docs/hazmat/backends/interfaces.rst new file mode 100644 index 0000000..87fc6ab --- /dev/null +++ b/docs/hazmat/backends/interfaces.rst @@ -0,0 +1,706 @@ +.. hazmat:: + +Backend interfaces +================== + +.. currentmodule:: cryptography.hazmat.backends.interfaces + + +Backend implementations may provide a number of interfaces to support +operations such as :doc:`/hazmat/primitives/symmetric-encryption`, +:doc:`/hazmat/primitives/cryptographic-hashes`, and +:doc:`/hazmat/primitives/mac/hmac`. + +A specific ``backend`` may provide one or more of these interfaces. + + +.. class:: CipherBackend + + A backend that provides methods for using ciphers for encryption + and decryption. + + The following backends implement this interface: + + * :doc:`/hazmat/backends/openssl` + * :doc:`/hazmat/backends/commoncrypto` + + .. method:: cipher_supported(cipher, mode) + + Check if a ``cipher`` and ``mode`` combination is supported by + this backend. + + :param cipher: An instance of + :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm`. + + :param mode: An instance of + :class:`~cryptography.hazmat.primitives.ciphers.modes.Mode`. + + :returns: ``True`` if the specified ``cipher`` and ``mode`` combination + is supported by this backend, otherwise ``False`` + + + .. method:: create_symmetric_encryption_ctx(cipher, mode) + + Create a + :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` that + can be used for encrypting data with the symmetric ``cipher`` using + the given ``mode``. + + :param cipher: An instance of + :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm`. + + :param mode: An instance of + :class:`~cryptography.hazmat.primitives.ciphers.modes.Mode`. + + :returns: + :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` + + :raises ValueError: When tag is not None in an AEAD mode + + + .. method:: create_symmetric_decryption_ctx(cipher, mode) + + Create a + :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` that + can be used for decrypting data with the symmetric ``cipher`` using + the given ``mode``. + + :param cipher: An instance of + :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm`. + + :param mode: An instance of + :class:`~cryptography.hazmat.primitives.ciphers.modes.Mode`. + + :returns: + :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` + + :raises ValueError: When tag is None in an AEAD mode + + +.. class:: HashBackend + + A backend with methods for using cryptographic hash functions. + + The following backends implement this interface: + + * :doc:`/hazmat/backends/openssl` + * :doc:`/hazmat/backends/commoncrypto` + + .. method:: hash_supported(algorithm) + + Check if the specified ``algorithm`` is supported by this backend. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :returns: ``True`` if the specified ``algorithm`` is supported by this + backend, otherwise ``False``. + + + .. method:: create_hash_ctx(algorithm) + + Create a + :class:`~cryptography.hazmat.primitives.hashes.HashContext` that + uses the specified ``algorithm`` to calculate a message digest. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :returns: + :class:`~cryptography.hazmat.primitives.hashes.HashContext` + + +.. class:: HMACBackend + + A backend with methods for using cryptographic hash functions as message + authentication codes. + + The following backends implement this interface: + + * :doc:`/hazmat/backends/openssl` + * :doc:`/hazmat/backends/commoncrypto` + + .. method:: hmac_supported(algorithm) + + Check if the specified ``algorithm`` is supported by this backend. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :returns: ``True`` if the specified ``algorithm`` is supported for HMAC + by this backend, otherwise ``False``. + + .. method:: create_hmac_ctx(key, algorithm) + + Create a + :class:`~cryptography.hazmat.primitives.hashes.HashContext` that + uses the specified ``algorithm`` to calculate a hash-based message + authentication code. + + :param bytes key: Secret key as ``bytes``. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :returns: + :class:`~cryptography.hazmat.primitives.hashes.HashContext` + + +.. class:: CMACBackend + + .. versionadded:: 0.4 + + A backend with methods for using CMAC + + .. method:: cmac_algorithm_supported(algorithm) + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm`. + + :return: Returns True if the block cipher is supported for CMAC by this backend + + .. method:: create_cmac_ctx(algorithm) + + Create a + :class:`~cryptography.hazmat.primitives.interfaces.MACContext` that + uses the specified ``algorithm`` to calculate a message authentication code. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm`. + + :returns: + :class:`~cryptography.hazmat.primitives.interfaces.MACContext` + + +.. class:: PBKDF2HMACBackend + + .. versionadded:: 0.2 + + A backend with methods for using PBKDF2 using HMAC as a PRF. + + The following backends implement this interface: + + * :doc:`/hazmat/backends/openssl` + * :doc:`/hazmat/backends/commoncrypto` + + .. method:: pbkdf2_hmac_supported(algorithm) + + Check if the specified ``algorithm`` is supported by this backend. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :returns: ``True`` if the specified ``algorithm`` is supported for + PBKDF2 HMAC by this backend, otherwise ``False``. + + .. method:: derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, key_material) + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :param int length: The desired length of the derived key. Maximum is + (2\ :sup:`32` - 1) * ``algorithm.digest_size`` + + :param bytes salt: A salt. + + :param int iterations: The number of iterations to perform of the hash + function. This can be used to control the length of time the + operation takes. Higher numbers help mitigate brute force attacks + against derived keys. + + :param bytes key_material: The key material to use as a basis for + the derived key. This is typically a password. + + :return bytes: Derived key. + + +.. class:: RSABackend + + .. versionadded:: 0.2 + + A backend with methods for using RSA. + + .. method:: generate_rsa_private_key(public_exponent, key_size) + + :param int public_exponent: The public exponent of the new key. + Often one of the small Fermat primes 3, 5, 17, 257 or 65537. + + :param int key_size: The length in bits of the modulus. Should be + at least 2048. + + :return: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`. + + :raises ValueError: If the public_exponent is not valid. + + .. method:: rsa_padding_supported(padding) + + Check if the specified ``padding`` is supported by the backend. + + :param padding: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. + + :returns: ``True`` if the specified ``padding`` is supported by this + backend, otherwise ``False``. + + .. method:: generate_rsa_parameters_supported(public_exponent, key_size) + + Check if the specified parameters are supported for key generation by + the backend. + + :param int public_exponent: The public exponent. + + :param int key_size: The bit length of the generated modulus. + + .. method:: load_rsa_private_numbers(numbers) + + :param numbers: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateNumbers`. + + :returns: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`. + + :raises ValueError: This is raised when the values of ``p``, ``q``, + ``private_exponent``, ``public_exponent``, or ``modulus`` do not + match the bounds specified in :rfc:`3447`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised + when any backend specific criteria are not met. + + .. method:: load_rsa_public_numbers(numbers) + + :param numbers: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateNumbers`. + + :returns: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. + + :raises ValueError: This is raised when the values of + ``public_exponent`` or ``modulus`` do not match the bounds + specified in :rfc:`3447`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised + when any backend specific criteria are not met. + + +.. class:: DSABackend + + .. versionadded:: 0.4 + + A backend with methods for using DSA. + + .. method:: generate_dsa_parameters(key_size) + + :param int key_size: The length of the modulus in bits. It should be + either 1024, 2048 or 3072. For keys generated in 2015 this should + be at least 2048. + Note that some applications (such as SSH) have not yet gained + support for larger key sizes specified in FIPS 186-3 and are still + restricted to only the 1024-bit keys specified in FIPS 186-2. + + :return: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`. + + .. method:: generate_dsa_private_key(parameters) + + :param parameters: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`. + + :return: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. + + :raises ValueError: This is raised if the key size is not one of 1024, + 2048, or 3072. + + .. method:: generate_dsa_private_key_and_parameters(key_size) + + :param int key_size: The length of the modulus in bits. It should be + either 1024, 2048 or 3072. For keys generated in 2015 this should + be at least 2048. + Note that some applications (such as SSH) have not yet gained + support for larger key sizes specified in FIPS 186-3 and are still + restricted to only the 1024-bit keys specified in FIPS 186-2. + + :return: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. + + :raises ValueError: This is raised if the key size is not supported + by the backend. + + .. method:: dsa_hash_supported(algorithm) + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :returns: ``True`` if the specified ``algorithm`` is supported by this + backend, otherwise ``False``. + + .. method:: dsa_parameters_supported(p, q, g) + + :param int p: The p value of a DSA key. + + :param int q: The q value of a DSA key. + + :param int g: The g value of a DSA key. + + :returns: ``True`` if the given values of ``p``, ``q``, and ``g`` are + supported by this backend, otherwise ``False``. + + .. method:: load_dsa_parameter_numbers(numbers) + + :param numbers: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameterNumbers`. + + :returns: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised + when any backend specific criteria are not met. + + .. method:: load_dsa_private_numbers(numbers) + + :param numbers: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateNumbers`. + + :returns: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised + when any backend specific criteria are not met. + + .. method:: load_dsa_public_numbers(numbers) + + :param numbers: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicNumbers`. + + :returns: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised + when any backend specific criteria are not met. + + +.. class:: EllipticCurveBackend + + .. versionadded:: 0.5 + + .. method:: elliptic_curve_supported(curve) + + :param curve: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`. + + :returns: True if the elliptic curve is supported by this backend. + + .. method:: elliptic_curve_signature_algorithm_supported(signature_algorithm, curve) + + :param signature_algorithm: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurveSignatureAlgorithm`. + + :param curve: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`. + + :returns: True if the signature algorithm and curve are supported by this backend. + + .. method:: generate_elliptic_curve_private_key(curve) + + :param curve: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`. + + .. method:: load_elliptic_curve_private_numbers(numbers) + + :param numbers: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateNumbers`. + + :returns: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`. + + .. method:: load_elliptic_curve_public_numbers(numbers) + + :param numbers: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers`. + + :returns: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`. + + .. method:: derive_elliptic_curve_private_key(private_value, curve) + + :param private_value: A secret scalar value. + + :param curve: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`. + + :returns: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey`. + +.. class:: PEMSerializationBackend + + .. versionadded:: 0.6 + + A backend with methods for working with any PEM encoded keys. + + .. method:: load_pem_private_key(data, password) + + :param bytes data: PEM data to load. + :param bytes password: The password to use if the data is encrypted. + Should be ``None`` if the data is not encrypted. + :return: A new instance of the appropriate type of private key that the + serialized data contains. + :raises ValueError: If the data could not be deserialized. + :raises cryptography.exceptions.UnsupportedAlgorithm: If the data is + encrypted with an unsupported algorithm. + + .. method:: load_pem_public_key(data) + + :param bytes data: PEM data to load. + :return: A new instance of the appropriate type of public key + serialized data contains. + :raises ValueError: If the data could not be deserialized. + +.. class:: DERSerializationBackend + + .. versionadded:: 0.8 + + A backend with methods for working with DER encoded keys. + + .. method:: load_der_private_key(data, password) + + :param bytes data: DER data to load. + :param bytes password: The password to use if the data is encrypted. + Should be ``None`` if the data is not encrypted. + :return: A new instance of the appropriate type of private key that the + serialized data contains. + :raises ValueError: If the data could not be deserialized. + :raises cryptography.exceptions.UnsupportedAlgorithm: If the data is + encrypted with an unsupported algorithm. + + .. method:: load_der_public_key(data) + + :param bytes data: DER data to load. + :return: A new instance of the appropriate type of public key + serialized data contains. + :raises ValueError: If the data could not be deserialized. + +.. class:: X509Backend + + .. versionadded:: 0.7 + + A backend with methods for working with X.509 objects. + + .. method:: load_pem_x509_certificate(data) + + :param bytes data: PEM formatted certificate data. + + :returns: An instance of :class:`~cryptography.x509.Certificate`. + + .. method:: load_der_x509_certificate(data) + + :param bytes data: DER formatted certificate data. + + :returns: An instance of :class:`~cryptography.x509.Certificate`. + + .. method:: load_pem_x509_csr(data) + + .. versionadded:: 0.9 + + :param bytes data: PEM formatted certificate signing request data. + + :returns: An instance of + :class:`~cryptography.x509.CertificateSigningRequest`. + + .. method:: load_der_x509_csr(data) + + .. versionadded:: 0.9 + + :param bytes data: DER formatted certificate signing request data. + + :returns: An instance of + :class:`~cryptography.x509.CertificateSigningRequest`. + + .. method:: create_x509_csr(builder, private_key, algorithm) + + .. versionadded:: 1.0 + + :param builder: An instance of + :class:`~cryptography.x509.CertificateSigningRequestBuilder`. + + :param private_key: The + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + that will be used to sign the request. When the request is + signed by a certificate authority, the private key's associated + public key will be stored in the resulting certificate. + + :param algorithm: The + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + that will be used to generate the request signature. + + :returns: A new instance of + :class:`~cryptography.x509.CertificateSigningRequest`. + + .. method:: create_x509_certificate(builder, private_key, algorithm) + + .. versionadded:: 1.0 + + :param builder: An instance of + :class:`~cryptography.x509.CertificateBuilder`. + + :param private_key: The + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + that will be used to sign the certificate. + + :param algorithm: The + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + that will be used to generate the certificate signature. + + :returns: A new instance of :class:`~cryptography.x509.Certificate`. + + .. method:: create_x509_crl(builder, private_key, algorithm) + + .. versionadded:: 1.2 + + :param builder: An instance of + :class:`~cryptography.x509.CertificateRevocationListBuilder`. + + :param private_key: The + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + that will be used to sign the CRL. + + :param algorithm: The + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + that will be used to generate the CRL signature. + + :returns: A new instance of + :class:`~cryptography.x509.CertificateRevocationList`. + + .. method:: create_x509_revoked_certificate(builder) + + .. versionadded:: 1.2 + + :param builder: An instance of RevokedCertificateBuilder. + + :returns: A new instance of + :class:`~cryptography.x509.RevokedCertificate`. + + .. method:: x509_name_bytes(name) + + .. versionadded:: 1.6 + + :param name: An instance of :class:`~cryptography.x509.Name`. + + :return bytes: The DER encoded bytes. + +.. class:: DHBackend + + .. versionadded:: 0.9 + + A backend with methods for doing Diffie-Hellman key exchange. + + .. method:: generate_dh_parameters(generator, key_size) + + :param int generator: The generator to use. Often 2 or 5. + + :param int key_size: The bit length of the prime modulus to generate. + + :return: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. + + :raises ValueError: If ``key_size`` is not at least 512. + + .. method:: generate_dh_private_key(parameters) + + :param parameters: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. + + :return: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`. + + .. method:: generate_dh_private_key_and_parameters(generator, key_size) + + :param int generator: The generator to use. Often 2 or 5. + + :param int key_size: The bit length of the prime modulus to generate. + + :return: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`. + + :raises ValueError: If ``key_size`` is not at least 512. + + .. method:: load_dh_private_numbers(numbers) + + :param numbers: A + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateNumbers` + instance. + + :return: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised + when any backend specific criteria are not met. + + .. method:: load_dh_public_numbers(numbers) + + :param numbers: A + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicNumbers` + instance. + + :return: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised + when any backend specific criteria are not met. + + .. method:: load_dh_parameter_numbers(numbers) + + :param numbers: A + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameterNumbers` + instance. + + :return: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised + when any backend specific criteria are not met. + + .. method:: dh_parameters_supported(p, g) + + :param int p: The p value of the DH key. + + :param int g: The g value of the DH key. + + :returns: ``True`` if the given values of ``p`` and ``g`` are supported + by this backend, otherwise ``False``. + + +.. class:: ScryptBackend + + .. versionadded:: 1.6 + + A backend with methods for using Scrypt. + + The following backends implement this interface: + + * :doc:`/hazmat/backends/openssl` + + .. method:: derive_scrypt(self, key_material, salt, length, n, r, p) + + :param bytes key_material: The key material to use as a basis for + the derived key. This is typically a password. + + :param bytes salt: A salt. + + :param int length: The desired length of the derived key. + + :param int n: CPU/Memory cost parameter. It must be larger than 1 and be a + power of 2. + + :param int r: Block size parameter. + + :param int p: Parallelization parameter. + + :return bytes: Derived key. + diff --git a/docs/hazmat/backends/multibackend.rst b/docs/hazmat/backends/multibackend.rst new file mode 100644 index 0000000..0aae04a --- /dev/null +++ b/docs/hazmat/backends/multibackend.rst @@ -0,0 +1,45 @@ +.. hazmat:: + +MultiBackend +============ + +.. currentmodule:: cryptography.hazmat.backends.multibackend + +.. class:: MultiBackend(backends) + + .. versionadded:: 0.2 + + This class allows you to combine multiple backends into a single backend + that offers the combined features of all of its constituents. + + .. testsetup:: + + from cryptography import utils + from cryptography.exceptions import UnsupportedAlgorithm, _Reasons + from cryptography.hazmat.backends.interfaces import HashBackend + from cryptography.hazmat.backends.openssl.backend import backend as backend2 + + @utils.register_interface(HashBackend) + class DummyHashBackend(object): + def hash_supported(self, algorithm): + return False + + def create_hash_ctx(self, algorithm): + raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_HASH) + + backend1 = DummyHashBackend() + + .. doctest:: + + >>> from cryptography.hazmat.backends.multibackend import MultiBackend + >>> from cryptography.hazmat.primitives import hashes + >>> backend1.hash_supported(hashes.SHA256()) + False + >>> backend2.hash_supported(hashes.SHA256()) + True + >>> multi_backend = MultiBackend([backend1, backend2]) + >>> multi_backend.hash_supported(hashes.SHA256()) + True + + :param backends: A ``list`` of backend objects. Backends are checked for + feature support in the order they appear in this list. diff --git a/docs/hazmat/backends/openssl.rst b/docs/hazmat/backends/openssl.rst new file mode 100644 index 0000000..f97ec57 --- /dev/null +++ b/docs/hazmat/backends/openssl.rst @@ -0,0 +1,110 @@ +.. hazmat:: + +OpenSSL backend +=============== + +The `OpenSSL`_ C library. Cryptography supports OpenSSL version ``1.0.0`` and +greater. + +.. data:: cryptography.hazmat.backends.openssl.backend + + This is the exposed API for the OpenSSL backend. + + It implements the following interfaces: + + * :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` + * :class:`~cryptography.hazmat.backends.interfaces.CMACBackend` + * :class:`~cryptography.hazmat.backends.interfaces.DERSerializationBackend` + * :class:`~cryptography.hazmat.backends.interfaces.DHBackend` + * :class:`~cryptography.hazmat.backends.interfaces.DSABackend` + * :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend` + * :class:`~cryptography.hazmat.backends.interfaces.HashBackend` + * :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + * :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend` + * :class:`~cryptography.hazmat.backends.interfaces.RSABackend` + * :class:`~cryptography.hazmat.backends.interfaces.PEMSerializationBackend` + * :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + + It also implements the following interface for OpenSSL versions ``1.1.0`` + and above. + + * :class:`~cryptography.hazmat.backends.interfaces.ScryptBackend` + + It also exposes the following: + + .. attribute:: name + + The string name of this backend: ``"openssl"`` + + .. method:: activate_osrandom_engine() + + Activates the OS random engine. This will effectively disable OpenSSL's + default CSPRNG. + + .. method:: osrandom_engine_implementation() + + .. versionadded:: 1.7 + + Returns the implementation of OS random engine. + + .. method:: activate_builtin_random() + + This will activate the default OpenSSL CSPRNG. + +OS random engine +---------------- + +By default OpenSSL uses a user-space CSPRNG that is seeded from system random ( +``/dev/urandom`` or ``CryptGenRandom``). This CSPRNG is not reseeded +automatically when a process calls ``fork()``. This can result in situations +where two different processes can return similar or identical keys and +compromise the security of the system. + +The approach this project has chosen to mitigate this vulnerability is to +include an engine that replaces the OpenSSL default CSPRNG with one that +sources its entropy from ``/dev/urandom`` on UNIX-like operating systems and +uses ``CryptGenRandom`` on Windows. This method of pulling from the system pool +allows us to avoid potential issues with `initializing the RNG`_ as well as +protecting us from the ``fork()`` weakness. + +This engine is **active** by default when importing the OpenSSL backend. When +active this engine will be used to generate all the random data OpenSSL +requests. + +When importing only the binding it is added to the engine list but +**not activated**. + + +OS random sources +----------------- + +On OS X and FreeBSD ``/dev/urandom`` is an alias for ``/dev/random`` and +utilizes the `Yarrow`_ algorithm. + +On Windows the implementation of ``CryptGenRandom`` depends on which version of +the operation system you are using. See the `Microsoft documentation`_ for more +details. + +Linux uses its own PRNG design. ``/dev/urandom`` is a non-blocking source +seeded from the same pool as ``/dev/random``. + ++------------------------------------------+------------------------------+ +| Windows | ``CryptGenRandom()`` | ++------------------------------------------+------------------------------+ +| Linux >= 3.4.17 with working | ``getrandom(GRND_NONBLOCK)`` | +| ``SYS_getrandom`` syscall | | ++------------------------------------------+------------------------------+ +| OpenBSD >= 5.6 | ``getentropy()`` | ++------------------------------------------+------------------------------+ +| BSD family (including macOS 10.12+) with | ``getentropy()`` | +| ``SYS_getentropy`` in ``sys/syscall.h`` | | ++------------------------------------------+------------------------------+ +| fallback | ``/dev/urandom`` with | +| | cached file descriptor | ++------------------------------------------+------------------------------+ + + +.. _`OpenSSL`: https://www.openssl.org/ +.. _`initializing the RNG`: https://en.wikipedia.org/wiki/OpenSSL#Predictable_private_keys_.28Debian-specific.29 +.. _`Yarrow`: https://en.wikipedia.org/wiki/Yarrow_algorithm +.. _`Microsoft documentation`: https://msdn.microsoft.com/en-us/library/windows/desktop/aa379942(v=vs.85).aspx diff --git a/docs/hazmat/bindings/commoncrypto.rst b/docs/hazmat/bindings/commoncrypto.rst new file mode 100644 index 0000000..d5a739c --- /dev/null +++ b/docs/hazmat/bindings/commoncrypto.rst @@ -0,0 +1,30 @@ +.. hazmat:: + +CommonCrypto binding +==================== + +.. currentmodule:: cryptography.hazmat.bindings.commoncrypto.binding + +.. versionadded:: 0.2 + +These are `CFFI`_ bindings to the `CommonCrypto`_ C library. It is only +available on Mac OS X versions 10.8 and above. + +.. class:: cryptography.hazmat.bindings.commoncrypto.binding.Binding() + + This is the exposed API for the CommonCrypto bindings. It has two public + attributes: + + .. attribute:: ffi + + This is a ``cffi.FFI`` instance. It can be used to allocate and + otherwise manipulate CommonCrypto structures. + + .. attribute:: lib + + This is a ``cffi`` library. It can be used to call CommonCrypto + functions, and access constants. + + +.. _`CFFI`: https://cffi.readthedocs.io +.. _`CommonCrypto`: https://developer.apple.com/library/content/documentation/Security/Conceptual/cryptoservices/GeneralPurposeCrypto/GeneralPurposeCrypto.html#//apple_ref/doc/uid/TP40011172-CH9-SW10 diff --git a/docs/hazmat/bindings/index.rst b/docs/hazmat/bindings/index.rst new file mode 100644 index 0000000..8075be1 --- /dev/null +++ b/docs/hazmat/bindings/index.rst @@ -0,0 +1,23 @@ +.. hazmat:: + +Bindings +======== + +.. module:: cryptography.hazmat.bindings + +``cryptography`` aims to provide low-level CFFI based bindings to multiple +native C libraries. These provide no automatic initialization of the library +and may not provide complete wrappers for its API. + +Using these functions directly is likely to require you to be careful in +managing memory allocation, locking and other resources. + + +Individual bindings +------------------- + +.. toctree:: + :maxdepth: 1 + + openssl + commoncrypto diff --git a/docs/hazmat/bindings/openssl.rst b/docs/hazmat/bindings/openssl.rst new file mode 100644 index 0000000..482cfaf --- /dev/null +++ b/docs/hazmat/bindings/openssl.rst @@ -0,0 +1,48 @@ +.. hazmat:: + +OpenSSL binding +=============== + +.. currentmodule:: cryptography.hazmat.bindings.openssl.binding + +These are `CFFI`_ bindings to the `OpenSSL`_ C library. Cryptography supports +OpenSSL version ``1.0.0`` and greater. + +.. class:: cryptography.hazmat.bindings.openssl.binding.Binding() + + This is the exposed API for the OpenSSL bindings. It has two public + attributes: + + .. attribute:: ffi + + This is a ``cffi.FFI`` instance. It can be used to allocate and + otherwise manipulate OpenSSL structures. + + .. attribute:: lib + + This is a ``cffi`` library. It can be used to call OpenSSL functions, + and access constants. + + .. classmethod:: init_static_locks + + Enables the best available locking callback for OpenSSL. + See :ref:`openssl-threading`. + +.. _openssl-threading: + +Threading +--------- + +``cryptography`` enables OpenSSLs `thread safety facilities`_ in two different +ways depending on the configuration of your system. Normally the locking +callbacks provided by your Python implementation specifically for OpenSSL will +be used. However, if you have linked ``cryptography`` to a different version of +OpenSSL than that used by your Python implementation we enable an alternative +locking callback. This version is implemented in Python and so may result in +lower performance in some situations. In particular parallelism is reduced +because it has to acquire the GIL whenever any lock operations occur within +OpenSSL. + +.. _`CFFI`: https://cffi.readthedocs.io +.. _`OpenSSL`: https://www.openssl.org/ +.. _`thread safety facilities`: https://www.openssl.org/docs/man1.0.2/crypto/threads.html diff --git a/docs/hazmat/primitives/asymmetric/dh.rst b/docs/hazmat/primitives/asymmetric/dh.rst new file mode 100644 index 0000000..463df90 --- /dev/null +++ b/docs/hazmat/primitives/asymmetric/dh.rst @@ -0,0 +1,238 @@ +.. hazmat:: + +Diffie-Hellman key exchange +=========================== + +.. currentmodule:: cryptography.hazmat.primitives.asymmetric.dh + + +`Diffie-Hellman key exchange`_ (D–H) is a method that allows two parties +to jointly agree on a shared secret using an insecure channel. + + +Exchange Algorithm +~~~~~~~~~~~~~~~~~~ + +For most applications the ``shared_key`` should be passed to a key +derivation function. + +.. code-block:: pycon + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives.asymmetric import dh + >>> parameters = dh.generate_parameters(generator=2, key_size=2048, + ... backend=default_backend()) + >>> private_key = parameters.generate_private_key() + >>> peer_public_key = parameters.generate_private_key().public_key() + >>> shared_key = private_key.exchange(peer_public_key) + +DHE (or EDH), the ephemeral form of this exchange, is **strongly +preferred** over simple DH and provides `forward secrecy`_ when used. +You must generate a new private key using :func:`~DHParameters.generate_private_key` for +each :meth:`~DHPrivateKeyWithSerialization.exchange` when performing an DHE key +exchange. + +To assemble a :class:`~DHParameters` and a :class:`~DHPublicKey` from +primitive integers, you must first create the +:class:`~DHParameterNumbers` and :class:`~DHPublicNumbers` objects. For +example if **p**, **g**, and **y** are :class:`int` objects received from a +peer:: + + pn = dh.DHParameterNumbers(p, g) + parameters = pn.parameters(default_backend()) + peer_public_numbers = dh.DHPublicNumbers(y, pn) + peer_public_key = peer_public_numbers.public_key(default_backend()) + + +See also the :class:`~cryptography.hazmat.backends.interfaces.DHBackend` +API for additional functionality. + +Group parameters +~~~~~~~~~~~~~~~~ + +.. function:: generate_parameters(generator, key_size, backend) + + .. versionadded:: 0.9 + + Generate a new DH parameter group for use with ``backend``. + + :param generator: The :class:`int` to use as a generator. Must be + 2 or 5. + + :param key_size: The bit length of the prime modulus to generate. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.DHBackend` + instance. + + :returns: DH parameters as a new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. + + :raises ValueError: If ``key_size`` is not at least 512. + + +.. class:: DHParameters + + .. versionadded:: 0.9 + + + .. method:: generate_private_key() + + .. versionadded:: 0.9 + + Generate a DH private key. This method can be used to generate many + new private keys from a single set of parameters. + + :return: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`. + + +.. class:: DHParametersWithSerialization + + .. versionadded:: 0.9 + + Inherits from :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. + + .. method:: parameter_numbers() + + Return the numbers that make up this set of parameters. + + :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameterNumbers`. + + +Key interfaces +~~~~~~~~~~~~~~ + +.. class:: DHPrivateKey + + .. versionadded:: 0.9 + + .. attribute:: key_size + + The bit length of the prime modulus. + + .. method:: public_key() + + Return the public key associated with this private key. + + :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`. + + .. method:: parameters() + + Return the parameters associated with this private key. + + :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. + + +.. class:: DHPrivateKeyWithSerialization + + .. versionadded:: 0.9 + + Inherits from :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateKey`. + + .. method:: private_numbers() + + Return the numbers that make up this private key. + + :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPrivateNumbers`. + + .. method:: exchange(peer_public_key) + + .. versionadded:: 1.7 + + :param DHPublicKeyWithSerialization peer_public_key: The public key for the + peer. + + :return bytes: The agreed key. The bytes are ordered in 'big' endian. + + +.. class:: DHPublicKey + + .. versionadded:: 0.9 + + .. attribute:: key_size + + The bit length of the prime modulus. + + .. method:: parameters() + + Return the parameters associated with this private key. + + :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameters`. + + +.. class:: DHPublicKeyWithSerialization + + .. versionadded:: 0.9 + + Inherits from :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicKey`. + + .. method:: public_numbers() + + Return the numbers that make up this public key. + + :return: A :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicNumbers`. + + +Numbers +~~~~~~~ + +.. class:: DHParameterNumbers(p, g) + + .. versionadded:: 0.8 + + The collection of integers that define a Diffie-Hellman group. + + .. attribute:: p + + :type: int + + The prime modulus value. + + .. attribute:: g + + :type: int + + The generator value. Must be 2 or 5. + +.. class:: DHPrivateNumbers(x, public_numbers) + + .. versionadded:: 0.8 + + The collection of integers that make up a Diffie-Hellman private key. + + .. attribute:: public_numbers + + :type: :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHPublicNumbers` + + The :class:`DHPublicNumbers` which makes up the DH public + key associated with this DH private key. + + .. attribute:: x + + :type: int + + The private value. + + +.. class:: DHPublicNumbers(y, parameter_numbers) + + .. versionadded:: 0.8 + + The collection of integers that make up a Diffie-Hellman public key. + + .. attribute:: parameter_numbers + + :type: :class:`~cryptography.hazmat.primitives.asymmetric.dh.DHParameterNumbers` + + The parameters for this DH group. + + .. attribute:: y + + :type: int + + The public value. + + +.. _`Diffie-Hellman key exchange`: https://en.wikipedia.org/wiki/Diffie%E2%80%93Hellman_key_exchange +.. _`forward secrecy`: https://en.wikipedia.org/wiki/Forward_secrecy diff --git a/docs/hazmat/primitives/asymmetric/dsa.rst b/docs/hazmat/primitives/asymmetric/dsa.rst new file mode 100644 index 0000000..d4c2525 --- /dev/null +++ b/docs/hazmat/primitives/asymmetric/dsa.rst @@ -0,0 +1,462 @@ +.. hazmat:: + +DSA +=== + +.. module:: cryptography.hazmat.primitives.asymmetric.dsa + +`DSA`_ is a `public-key`_ algorithm for signing messages. + +Generation +~~~~~~~~~~ + +.. function:: generate_private_key(key_size, backend) + + .. versionadded:: 0.5 + + Generate a DSA private key from the given key size. This function will + generate a new set of parameters and key in one step. + + :param int key_size: The length of the modulus in bits. It should be + either 1024, 2048 or 3072. For keys generated in 2015 this should + be `at least 2048`_ (See page 41). Note that some applications + (such as SSH) have not yet gained support for larger key sizes + specified in FIPS 186-3 and are still restricted to only the + 1024-bit keys specified in FIPS 186-2. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. + + :return: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if + the provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.DSABackend` + +.. function:: generate_parameters(key_size, backend) + + .. versionadded:: 0.5 + + Generate DSA parameters using the provided ``backend``. + + :param int key_size: The length of :attr:`~DSAParameterNumbers.q`. It + should be either 1024, 2048 or 3072. For keys generated in 2015 this + should be `at least 2048`_ (See page 41). Note that some applications + (such as SSH) have not yet gained support for larger key sizes + specified in FIPS 186-3 and are still restricted to only the + 1024-bit keys specified in FIPS 186-2. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. + + :return: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if + the provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.DSABackend` + +Signing +~~~~~~~ + +Using a :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` +instance. + +.. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import dsa + >>> private_key = dsa.generate_private_key( + ... key_size=1024, + ... backend=default_backend() + ... ) + >>> signer = private_key.signer(hashes.SHA256()) + >>> data = b"this is some data I'd like to sign" + >>> signer.update(data) + >>> signature = signer.finalize() + +There is a shortcut to sign sufficiently short messages directly: + +.. doctest:: + + >>> data = b"this is some data I'd like to sign" + >>> signature = private_key.sign( + ... data, + ... hashes.SHA256() + ... ) + +The ``signature`` is a ``bytes`` object, whose contents is DER encoded as +described in :rfc:`3279`. This can be decoded using +:func:`~cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature`. + +Verification +~~~~~~~~~~~~ + +Verification is performed using a +:class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` instance. +You can get a public key object with +:func:`~cryptography.hazmat.primitives.serialization.load_pem_public_key`, +:func:`~cryptography.hazmat.primitives.serialization.load_der_public_key`, +:meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicNumbers.public_key` +, or +:meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.public_key`. + +.. doctest:: + + >>> public_key = private_key.public_key() + >>> verifier = public_key.verifier(signature, hashes.SHA256()) + >>> verifier.update(data) + >>> verifier.verify() + +There is a shortcut to verify sufficiently short messages directly: + +.. doctest:: + + >>> public_key.verify( + ... signature, + ... data, + ... hashes.SHA256() + ... ) + +``verifier()`` takes the signature in the same format as is returned by +``signer.finalize()``. + +``verify()`` will raise an :class:`~cryptography.exceptions.InvalidSignature` +exception if the signature isn't valid. + +Numbers +~~~~~~~ + +.. class:: DSAParameterNumbers(p, q, g) + + .. versionadded:: 0.5 + + The collection of integers that make up a set of DSA parameters. + + .. attribute:: p + + :type: int + + The public modulus. + + .. attribute:: q + + :type: int + + The sub-group order. + + .. attribute:: g + + :type: int + + The generator. + + .. method:: parameters(backend) + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. + + :returns: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters`. + +.. class:: DSAPublicNumbers(y, parameter_numbers) + + .. versionadded:: 0.5 + + The collection of integers that make up a DSA public key. + + .. attribute:: y + + :type: int + + The public value ``y``. + + .. attribute:: parameter_numbers + + :type: :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameterNumbers` + + The :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameterNumbers` + associated with the public key. + + .. method:: public_key(backend) + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. + + :returns: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`. + +.. class:: DSAPrivateNumbers(x, public_numbers) + + .. versionadded:: 0.5 + + The collection of integers that make up a DSA private key. + + .. warning:: + + Revealing the value of ``x`` will compromise the security of any + cryptographic operations performed. + + .. attribute:: x + + :type: int + + The private value ``x``. + + .. attribute:: public_numbers + + :type: :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicNumbers` + + The :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicNumbers` + associated with the private key. + + .. method:: private_key(backend) + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. + + :returns: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. + +Key interfaces +~~~~~~~~~~~~~~ + +.. class:: DSAParameters + + .. versionadded:: 0.3 + + `DSA`_ parameters. + + .. method:: generate_private_key() + + .. versionadded:: 0.5 + + Generate a DSA private key. This method can be used to generate many + new private keys from a single set of parameters. + + :return: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`. + + +.. class:: DSAParametersWithNumbers + + .. versionadded:: 0.5 + + Extends :class:`DSAParameters`. + + .. method:: parameter_numbers() + + Create a + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameterNumbers` + object. + + :returns: A + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameterNumbers` + instance. + + +.. class:: DSAPrivateKey + + .. versionadded:: 0.3 + + A `DSA`_ private key. + + .. method:: public_key() + + :return: :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` + + An DSA public key object corresponding to the values of the private key. + + .. method:: parameters() + + :return: :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters` + + The DSAParameters object associated with this private key. + + .. method:: signer(algorithm, backend) + + .. versionadded:: 0.4 + + Sign data which can be verified later by others using the public key. + The signature is formatted as DER-encoded bytes, as specified in + :rfc:`3279`. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. + + :returns: + :class:`~cryptography.hazmat.primitives.asymmetric.AsymmetricSignatureContext` + + .. attribute:: key_size + + :type: int + + The bit length of :attr:`~DSAParameterNumbers.q`. + + .. method:: sign(data, algorithm) + + .. versionadded:: 1.5 + .. versionchanged:: 1.6 + :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` + can now be used as an ``algorithm``. + + Sign one block of data which can be verified later by others using the + public key. + + :param bytes data: The message string to sign. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or + :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` + if the ``data`` you want to sign has already been hashed. + + :return bytes: Signature. + + +.. class:: DSAPrivateKeyWithSerialization + + .. versionadded:: 0.8 + + Extends :class:`DSAPrivateKey`. + + .. method:: private_numbers() + + Create a + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateNumbers` + object. + + :returns: A + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateNumbers` + instance. + + .. method:: private_bytes(encoding, format, encryption_algorithm) + + Allows serialization of the key to bytes. Encoding ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`), + format ( + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL` + or + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`) + and encryption algorithm (such as + :class:`~cryptography.hazmat.primitives.serialization.BestAvailableEncryption` + or :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`) + are chosen to define the exact serialization. + + :param encoding: A value from the + :class:`~cryptography.hazmat.primitives.serialization.Encoding` enum. + + :param format: A value from the + :class:`~cryptography.hazmat.primitives.serialization.PrivateFormat` + enum. + + :param encryption_algorithm: An instance of an object conforming to the + :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption` + interface. + + :return bytes: Serialized key. + + +.. class:: DSAPublicKey + + .. versionadded:: 0.3 + + A `DSA`_ public key. + + .. attribute:: key_size + + :type: int + + The bit length of :attr:`~DSAParameterNumbers.q`. + + .. method:: parameters() + + :return: :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAParameters` + + The DSAParameters object associated with this public key. + + .. method:: verifier(signature, algorithm, backend) + + .. versionadded:: 0.4 + + Verify data was signed by the private key associated with this public + key. + + :param bytes signature: The signature to verify. DER encoded as + specified in :rfc:`3279`. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.DSABackend`. + + :returns: + :class:`~cryptography.hazmat.primitives.asymmetric.AsymmetricVerificationContext` + + .. method:: public_numbers() + + Create a + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicNumbers` + object. + + :returns: A + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicNumbers` + instance. + + .. method:: public_bytes(encoding, format) + + Allows serialization of the key to bytes. Encoding ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`) and + format ( + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`) + are chosen to define the exact serialization. + + :param encoding: A value from the + :class:`~cryptography.hazmat.primitives.serialization.Encoding` enum. + + :param format: A value from the + :class:`~cryptography.hazmat.primitives.serialization.PublicFormat` enum. + + :return bytes: Serialized key. + + .. method:: verify(signature, data, algorithm) + + .. versionadded:: 1.5 + .. versionchanged:: 1.6 + :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` + can now be used as an ``algorithm``. + + Verify one block of data was signed by the private key + associated with this public key. + + :param bytes signature: The signature to verify. + + :param bytes data: The message string that was signed. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or + :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` + if the ``data`` you want to sign has already been hashed. + + :raises cryptography.exceptions.InvalidSignature: If the signature does + not validate. + + +.. class:: DSAPublicKeyWithSerialization + + .. versionadded:: 0.8 + + Alias for :class:`DSAPublicKey`. + + +.. _`DSA`: https://en.wikipedia.org/wiki/Digital_Signature_Algorithm +.. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography +.. _`FIPS 186-4`: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +.. _`at least 2048`: http://www.ecrypt.eu.org/ecrypt2/documents/D.SPA.20.pdf diff --git a/docs/hazmat/primitives/asymmetric/ec.rst b/docs/hazmat/primitives/asymmetric/ec.rst new file mode 100644 index 0000000..99abcc6 --- /dev/null +++ b/docs/hazmat/primitives/asymmetric/ec.rst @@ -0,0 +1,648 @@ +.. hazmat:: + +Elliptic curve cryptography +=========================== + +.. module:: cryptography.hazmat.primitives.asymmetric.ec + + +.. function:: generate_private_key(curve, backend) + + .. versionadded:: 0.5 + + Generate a new private key on ``curve`` for use with ``backend``. + + :param curve: An instance of :class:`EllipticCurve`. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. + + :returns: A new instance of :class:`EllipticCurvePrivateKey`. + + +.. function:: derive_private_key(private_value, curve, backend) + + .. versionadded:: 1.6 + + Derive a private key from ``private_value`` on ``curve`` for use with + ``backend``. + + :param int private_value: The secret scalar value. + + :param curve: An instance of :class:`EllipticCurve`. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. + + :returns: A new instance of :class:`EllipticCurvePrivateKey`. + + +Elliptic Curve Signature Algorithms +----------------------------------- + +.. class:: ECDSA(algorithm) + + .. versionadded:: 0.5 + + The ECDSA signature algorithm first standardized in NIST publication + `FIPS 186-3`_, and later in `FIPS 186-4`_. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import ec + >>> private_key = ec.generate_private_key( + ... ec.SECP384R1(), default_backend() + ... ) + >>> signer = private_key.signer(ec.ECDSA(hashes.SHA256())) + >>> signer.update(b"this is some data I'd like") + >>> signer.update(b" to sign") + >>> signature = signer.finalize() + + There is a shortcut to sign sufficiently short messages directly: + + .. doctest:: + + >>> data = b"this is some data I'd like to sign" + >>> signature = private_key.sign( + ... data, + ... ec.ECDSA(hashes.SHA256()) + ... ) + + The ``signature`` is a ``bytes`` object, whose contents is DER encoded as + described in :rfc:`3279`. This can be decoded using + :func:`~cryptography.hazmat.primitives.asymmetric.utils.decode_dss_signature`. + + + Verification requires the public key, the signature itself, the signed data, and knowledge of the hashing algorithm that was used when producing the signature: + + >>> public_key = private_key.public_key() + >>> verifier = public_key.verifier(signature, ec.ECDSA(hashes.SHA256())) + >>> verifier.update(b"this is some data I'd like") + >>> verifier.update(b" to sign") + >>> verifier.verify() + True + + The last call will either return ``True`` or raise an :class:`~cryptography.exceptions.InvalidSignature` exception. + + .. note:: + Although in this case the public key was derived from the private one, in a typical setting you will not possess the private key. The `Key loading`_ section explains how to load the public key from other sources. + + + +.. class:: EllipticCurvePrivateNumbers(private_value, public_numbers) + + .. versionadded:: 0.5 + + The collection of integers that make up an EC private key. + + .. attribute:: public_numbers + + :type: :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicNumbers` + + The :class:`EllipticCurvePublicNumbers` which makes up the EC public + key associated with this EC private key. + + .. attribute:: private_value + + :type: int + + The private value. + + .. method:: private_key(backend) + + Convert a collection of numbers into a private key suitable for doing + actual cryptographic operations. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. + + :returns: A new instance of :class:`EllipticCurvePrivateKey`. + + +.. class:: EllipticCurvePublicNumbers(x, y, curve) + + .. versionadded:: 0.5 + + The collection of integers that make up an EC public key. + + .. attribute:: curve + + :type: :class:`EllipticCurve` + + The elliptic curve for this key. + + .. attribute:: x + + :type: int + + The affine x component of the public point used for verifying. + + .. attribute:: y + + :type: int + + The affine y component of the public point used for verifying. + + .. method:: public_key(backend) + + Convert a collection of numbers into a public key suitable for doing + actual cryptographic operations. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend`. + + :returns: A new instance of :class:`EllipticCurvePublicKey`. + + .. method:: encode_point() + + .. versionadded:: 1.1 + + Encodes an elliptic curve point to a byte string as described in + `SEC 1 v2.0`_ section 2.3.3. This method only supports uncompressed + points. + + :return bytes: The encoded point. + + .. classmethod:: from_encoded_point(curve, data) + + .. versionadded:: 1.1 + + Decodes a byte string as described in `SEC 1 v2.0`_ section 2.3.3 and + returns an :class:`EllipticCurvePublicNumbers`. This method only + supports uncompressed points. + + :param curve: An + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve` + instance. + + :param bytes data: The serialized point byte string. + + :returns: An :class:`EllipticCurvePublicNumbers` instance. + + :raises ValueError: Raised on invalid point type or data length. + + :raises TypeError: Raised when curve is not an + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurve`. + +Elliptic Curve Key Exchange algorithm +------------------------------------- + +.. class:: ECDH() + + .. versionadded:: 1.1 + + The Elliptic Curve Diffie-Hellman Key Exchange algorithm first standardized + in NIST publication `800-56A`_, and later in `800-56Ar2`_. + + For most applications the ``shared_key`` should be passed to a key + derivation function. + + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives.asymmetric import ec + >>> private_key = ec.generate_private_key( + ... ec.SECP384R1(), default_backend() + ... ) + >>> peer_public_key = ec.generate_private_key( + ... ec.SECP384R1(), default_backend() + ... ).public_key() + >>> shared_key = private_key.exchange(ec.ECDH(), peer_public_key) + + ECDHE (or EECDH), the ephemeral form of this exchange, is **strongly + preferred** over simple ECDH and provides `forward secrecy`_ when used. + You must generate a new private key using :func:`generate_private_key` for + each :meth:`~EllipticCurvePrivateKey.exchange` when performing an ECDHE key + exchange. + +Elliptic Curves +--------------- + +Elliptic curves provide equivalent security at much smaller key sizes than +other asymmetric cryptography systems such as RSA or DSA. For many operations +elliptic curves are also significantly faster; `elliptic curve diffie-hellman +is faster than diffie-hellman`_. + +.. note:: + Curves with a size of `less than 224 bits`_ should not be used. You should + strongly consider using curves of at least 224 bits. + +Generally the NIST prime field ("P") curves are significantly faster than the +other types suggested by NIST at both signing and verifying with ECDSA. + +Prime fields also `minimize the number of security concerns for elliptic-curve +cryptography`_. However, there is `some concern`_ that both the prime field and +binary field ("B") NIST curves may have been weakened during their generation. + +Currently `cryptography` only supports NIST curves, none of which are +considered "safe" by the `SafeCurves`_ project run by Daniel J. Bernstein and +Tanja Lange. + +All named curves are instances of :class:`EllipticCurve`. + +.. class:: SECT571K1 + + .. versionadded:: 0.5 + + SECG curve ``sect571k1``. Also called NIST K-571. + + +.. class:: SECT409K1 + + .. versionadded:: 0.5 + + SECG curve ``sect409k1``. Also called NIST K-409. + + +.. class:: SECT283K1 + + .. versionadded:: 0.5 + + SECG curve ``sect283k1``. Also called NIST K-283. + + +.. class:: SECT233K1 + + .. versionadded:: 0.5 + + SECG curve ``sect233k1``. Also called NIST K-233. + + +.. class:: SECT163K1 + + .. versionadded:: 0.5 + + SECG curve ``sect163k1``. Also called NIST K-163. + + +.. class:: SECT571R1 + + .. versionadded:: 0.5 + + SECG curve ``sect571r1``. Also called NIST B-571. + + +.. class:: SECT409R1 + + .. versionadded:: 0.5 + + SECG curve ``sect409r1``. Also called NIST B-409. + + +.. class:: SECT283R1 + + .. versionadded:: 0.5 + + SECG curve ``sect283r1``. Also called NIST B-283. + + +.. class:: SECT233R1 + + .. versionadded:: 0.5 + + SECG curve ``sect233r1``. Also called NIST B-233. + + +.. class:: SECT163R2 + + .. versionadded:: 0.5 + + SECG curve ``sect163r2``. Also called NIST B-163. + + +.. class:: SECP521R1 + + .. versionadded:: 0.5 + + SECG curve ``secp521r1``. Also called NIST P-521. + + +.. class:: SECP384R1 + + .. versionadded:: 0.5 + + SECG curve ``secp384r1``. Also called NIST P-384. + + +.. class:: SECP256R1 + + .. versionadded:: 0.5 + + SECG curve ``secp256r1``. Also called NIST P-256. + + +.. class:: SECT224R1 + + .. versionadded:: 0.5 + + SECG curve ``secp224r1``. Also called NIST P-224. + + +.. class:: SECP192R1 + + .. versionadded:: 0.5 + + SECG curve ``secp192r1``. Also called NIST P-192. + + +.. class:: SECP256K1 + + .. versionadded:: 0.9 + + SECG curve ``secp256k1``. + + +Key Interfaces +~~~~~~~~~~~~~~ + +.. class:: EllipticCurve + + .. versionadded:: 0.5 + + A named elliptic curve. + + .. attribute:: name + + :type: string + + The name of the curve. Usually the name used for the ASN.1 OID such as + ``secp256k1``. + + .. attribute:: key_size + + :type: int + + Size (in bits) of a secret scalar for the curve (as generated by + :func:`generate_private_key`). + + +.. class:: EllipticCurveSignatureAlgorithm + + .. versionadded:: 0.5 + .. versionchanged:: 1.6 + :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` + can now be used as an ``algorithm``. + + A signature algorithm for use with elliptic curve keys. + + .. attribute:: algorithm + + :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or + :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` + + The digest algorithm to be used with the signature scheme. + + +.. class:: EllipticCurvePrivateKey + + .. versionadded:: 0.5 + + An elliptic curve private key for use with an algorithm such as `ECDSA`_ or + `EdDSA`_. + + .. method:: signer(signature_algorithm) + + Sign data which can be verified later by others using the public key. + The signature is formatted as DER-encoded bytes, as specified in + :rfc:`3279`. + + :param signature_algorithm: An instance of + :class:`EllipticCurveSignatureAlgorithm`. + + :returns: + :class:`~cryptography.hazmat.primitives.asymmetric.AsymmetricSignatureContext` + + .. method:: exchange(algorithm, peer_public_key) + + .. versionadded:: 1.1 + + Perform's a key exchange operation using the provided algorithm with + the peer's public key. + + For most applications the result should be passed to a key derivation + function. + + :param algorithm: The key exchange algorithm, currently only + :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDH` is + supported. + :param EllipticCurvePublicKey peer_public_key: The public key for the + peer. + + :returns bytes: A shared key. + + .. method:: public_key() + + :return: :class:`EllipticCurvePublicKey` + + The EllipticCurvePublicKey object for this private key. + + .. method:: sign(data, signature_algorithm) + + .. versionadded:: 1.5 + + Sign one block of data which can be verified later by others using the + public key. + + :param bytes data: The message string to sign. + + :param signature_algorithm: An instance of + :class:`EllipticCurveSignatureAlgorithm`, such as :class:`ECDSA`. + + :return bytes: Signature. + + +.. class:: EllipticCurvePrivateKeyWithSerialization + + .. versionadded:: 0.8 + + Extends :class:`EllipticCurvePrivateKey`. + + .. method:: private_numbers() + + Create a :class:`EllipticCurvePrivateNumbers` object. + + :returns: An :class:`EllipticCurvePrivateNumbers` instance. + + .. method:: private_bytes(encoding, format, encryption_algorithm) + + Allows serialization of the key to bytes. Encoding ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`), + format ( + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL` + or + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`) + and encryption algorithm (such as + :class:`~cryptography.hazmat.primitives.serialization.BestAvailableEncryption` + or :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`) + are chosen to define the exact serialization. + + :param encoding: A value from the + :class:`~cryptography.hazmat.primitives.serialization.Encoding` enum. + + :param format: A value from the + :class:`~cryptography.hazmat.primitives.serialization.PrivateFormat` enum. + + :param encryption_algorithm: An instance of an object conforming to the + :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption` + interface. + + :return bytes: Serialized key. + + +.. class:: EllipticCurvePublicKey + + .. versionadded:: 0.5 + + An elliptic curve public key. + + .. method:: verifier(signature, signature_algorithm) + + Verify data was signed by the private key associated with this public + key. + + :param bytes signature: The signature to verify. DER encoded as + specified in :rfc:`3279`. + + :param signature_algorithm: An instance of + :class:`EllipticCurveSignatureAlgorithm`. + + :returns: + :class:`~cryptography.hazmat.primitives.asymmetric.AsymmetricVerificationContext` + + .. attribute:: curve + + :type: :class:`EllipticCurve` + + The elliptic curve for this key. + + .. method:: public_numbers() + + Create a :class:`EllipticCurvePublicNumbers` object. + + :returns: An :class:`EllipticCurvePublicNumbers` instance. + + .. method:: public_bytes(encoding, format) + + Allows serialization of the key to bytes. Encoding ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`) and + format ( + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo`) + are chosen to define the exact serialization. + + :param encoding: A value from the + :class:`~cryptography.hazmat.primitives.serialization.Encoding` enum. + + :param format: A value from the + :class:`~cryptography.hazmat.primitives.serialization.PublicFormat` enum. + + :return bytes: Serialized key. + + .. method:: verify(signature, data, signature_algorithm) + + .. versionadded:: 1.5 + + Verify one block of data was signed by the private key associated + with this public key. + + :param bytes signature: The signature to verify. + + :param bytes data: The message string that was signed. + + :param signature_algorithm: An instance of + :class:`EllipticCurveSignatureAlgorithm`. + + :raises cryptography.exceptions.InvalidSignature: If the signature does + not validate. + + +.. class:: EllipticCurvePublicKeyWithSerialization + + .. versionadded:: 0.6 + + Alias for :class:`EllipticCurvePublicKey`. + + + +Serialization +~~~~~~~~~~~~~ + +This sample demonstrates how to generate a private key and serialize it. + + +.. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import ec + >>> from cryptography.hazmat.primitives import serialization + + >>> private_key = ec.generate_private_key(ec.SECP384R1(), default_backend()) + + >>> serialized_private = private_key.private_bytes( + ... encoding=serialization.Encoding.PEM, + ... format=serialization.PrivateFormat.PKCS8, + ... encryption_algorithm=serialization.BestAvailableEncryption(b'testpassword') + ... ) + >>> serialized_private.splitlines()[0] + '-----BEGIN ENCRYPTED PRIVATE KEY-----' + +You can also serialize the key without a password, by relying on +:class:`~cryptography.hazmat.primitives.serialization.NoEncryption`. + +The public key is serialized as follows: + + +.. doctest:: + + >>> public_key = private_key.public_key() + >>> serialized_public = public_key.public_bytes( + ... encoding=serialization.Encoding.PEM, + ... format=serialization.PublicFormat.SubjectPublicKeyInfo + ... ) + >>> serialized_public.splitlines()[0] + '-----BEGIN PUBLIC KEY-----' + +This is the part that you would normally share with the rest of the world. + + +Key loading +~~~~~~~~~~~ + +This extends the sample in the previous section, assuming that the variables +``serialized_private`` and ``serialized_public`` contain the respective keys +in PEM format. + +.. doctest:: + + >>> loaded_public_key = serialization.load_pem_public_key( + ... serialized_public, + ... backend=default_backend() + ... ) + + >>> loaded_private_key = serialization.load_pem_private_key( + ... serialized_private, + ... password=b'testpassword', # or password=None, if in plain text + ... backend=default_backend() + ... ) + + +.. _`FIPS 186-3`: http://csrc.nist.gov/publications/fips/fips186-3/fips_186-3.pdf +.. _`FIPS 186-4`: http://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf +.. _`800-56A`: http://csrc.nist.gov/publications/nistpubs/800-56A/SP800-56A_Revision1_Mar08-2007.pdf +.. _`800-56Ar2`: http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf +.. _`some concern`: https://crypto.stackexchange.com/questions/10263/should-we-trust-the-nist-recommended-ecc-parameters +.. _`less than 224 bits`: http://www.ecrypt.eu.org/ecrypt2/documents/D.SPA.20.pdf +.. _`elliptic curve diffie-hellman is faster than diffie-hellman`: http://digitalcommons.unl.edu/cgi/viewcontent.cgi?article=1100&context=cseconfwork +.. _`minimize the number of security concerns for elliptic-curve cryptography`: https://cr.yp.to/ecdh/curve25519-20060209.pdf +.. _`SafeCurves`: https://safecurves.cr.yp.to/ +.. _`ECDSA`: https://en.wikipedia.org/wiki/ECDSA +.. _`EdDSA`: https://en.wikipedia.org/wiki/EdDSA +.. _`forward secrecy`: https://en.wikipedia.org/wiki/Forward_secrecy +.. _`SEC 1 v2.0`: http://www.secg.org/sec1-v2.pdf diff --git a/docs/hazmat/primitives/asymmetric/index.rst b/docs/hazmat/primitives/asymmetric/index.rst new file mode 100644 index 0000000..4242a0b --- /dev/null +++ b/docs/hazmat/primitives/asymmetric/index.rst @@ -0,0 +1,38 @@ +.. hazmat:: + +Asymmetric algorithms +===================== + +Asymmetric cryptography is a branch of cryptography where a secret key can be +divided into two parts, a :term:`public key` and a :term:`private key`. The +public key can be given to anyone, trusted or not, while the private key must +be kept secret (just like the key in symmetric cryptography). + +Asymmetric cryptography has two primary use cases: authentication and +confidentiality. Using asymmetric cryptography, messages can be signed with a +private key, and then anyone with the public key is able to verify that the +message was created by someone possessing the corresponding private key. This +can be combined with a `proof of identity`_ system to know what entity (person +or group) actually owns that private key, providing authentication. + +Encryption with asymmetric cryptography works in a slightly different way from +symmetric encryption. Someone with the public key is able to encrypt a message, +providing confidentiality, and then only the person in possession of the +private key is able to decrypt it. + +Cryptography supports three different sets of asymmetric algorithms: RSA, DSA, +and Elliptic Curve. + +.. toctree:: + :maxdepth: 1 + + dsa + ec + rsa + dh + serialization + interfaces + utils + + +.. _`proof of identity`: https://en.wikipedia.org/wiki/Public-key_infrastructure diff --git a/docs/hazmat/primitives/asymmetric/interfaces.rst b/docs/hazmat/primitives/asymmetric/interfaces.rst new file mode 100644 index 0000000..c4f176c --- /dev/null +++ b/docs/hazmat/primitives/asymmetric/interfaces.rst @@ -0,0 +1,32 @@ +.. hazmat:: + +.. module:: cryptography.hazmat.primitives.asymmetric + +Signature Interfaces +==================== + +.. class:: AsymmetricSignatureContext + + .. versionadded:: 0.2 + + .. method:: update(data) + + :param bytes data: The data you want to sign. + + .. method:: finalize() + + :return bytes signature: The signature. + + +.. class:: AsymmetricVerificationContext + + .. versionadded:: 0.2 + + .. method:: update(data) + + :param bytes data: The data you wish to verify using the signature. + + .. method:: verify() + + :raises cryptography.exceptions.InvalidSignature: If the signature does + not validate. diff --git a/docs/hazmat/primitives/asymmetric/rsa.rst b/docs/hazmat/primitives/asymmetric/rsa.rst new file mode 100644 index 0000000..6cf0e49 --- /dev/null +++ b/docs/hazmat/primitives/asymmetric/rsa.rst @@ -0,0 +1,745 @@ +.. hazmat:: + +RSA +=== + +.. module:: cryptography.hazmat.primitives.asymmetric.rsa + +`RSA`_ is a `public-key`_ algorithm for encrypting and signing messages. + +Generation +~~~~~~~~~~ + +Unlike symmetric cryptography, where the key is typically just a random series +of bytes, RSA keys have a complex internal structure with `specific +mathematical properties`_. + +.. function:: generate_private_key(public_exponent, key_size, backend) + + .. versionadded:: 0.5 + + Generates a new RSA private key using the provided ``backend``. + ``key_size`` describes how many bits long the key should be, larger keys + provide more security, currently ``1024`` and below are considered + breakable, and ``2048`` or ``4096`` are reasonable default key sizes for + new keys. The ``public_exponent`` indicates what one mathematical property + of the key generation will be, ``65537`` should almost always be used. + + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives.asymmetric import rsa + >>> private_key = rsa.generate_private_key( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ) + + :param int public_exponent: The public exponent of the new key. + Usually one of the small Fermat primes 3, 5, 17, 257, 65537. If in + doubt you should `use 65537`_. + + :param int key_size: The length of the modulus in bits. For keys + generated in 2015 it is strongly recommended to be + `at least 2048`_ (See page 41). It must not be less than 512. + Some backends may have additional limitations. + + :param backend: A backend which implements + :class:`~cryptography.hazmat.backends.interfaces.RSABackend`. + + :return: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if + the provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.RSABackend` + +Key loading +~~~~~~~~~~~ + +If you already have an on-disk key in the PEM format (which are recognizable by +the distinctive ``-----BEGIN {format}-----`` and ``-----END {format}-----`` +markers), you can load it: + +.. code-block:: pycon + + >>> from cryptography.hazmat.primitives import serialization + + >>> with open("path/to/key.pem", "rb") as key_file: + ... private_key = serialization.load_pem_private_key( + ... key_file.read(), + ... password=None, + ... backend=default_backend() + ... ) + +Serialized keys may optionally be encrypted on disk using a password. In this +example we loaded an unencrypted key, and therefore we did not provide a +password. If the key is encrypted we can pass a ``bytes`` object as the +``password`` argument. + +There is also support for :func:`loading public keys in the SSH format +`. + +Key serialization +~~~~~~~~~~~~~~~~~ + +If you have a private key that you've loaded or generated which implements the +:class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` +interface you can use +:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes` +to serialize the key. + +.. doctest:: + + >>> from cryptography.hazmat.primitives import serialization + >>> pem = private_key.private_bytes( + ... encoding=serialization.Encoding.PEM, + ... format=serialization.PrivateFormat.PKCS8, + ... encryption_algorithm=serialization.BestAvailableEncryption(b'mypassword') + ... ) + >>> pem.splitlines()[0] + '-----BEGIN ENCRYPTED PRIVATE KEY-----' + +It is also possible to serialize without encryption using +:class:`~cryptography.hazmat.primitives.serialization.NoEncryption`. + +.. doctest:: + + >>> pem = private_key.private_bytes( + ... encoding=serialization.Encoding.PEM, + ... format=serialization.PrivateFormat.TraditionalOpenSSL, + ... encryption_algorithm=serialization.NoEncryption() + ... ) + >>> pem.splitlines()[0] + '-----BEGIN RSA PRIVATE KEY-----' + +For public keys you can use +:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey.public_bytes` +to serialize the key. + +.. doctest:: + + >>> from cryptography.hazmat.primitives import serialization + >>> public_key = private_key.public_key() + >>> pem = public_key.public_bytes( + ... encoding=serialization.Encoding.PEM, + ... format=serialization.PublicFormat.SubjectPublicKeyInfo + ... ) + >>> pem.splitlines()[0] + '-----BEGIN PUBLIC KEY-----' + +Signing +~~~~~~~ + +A private key can be used to sign a message. This allows anyone with the public +key to verify that the message was created by someone who possesses the +corresponding private key. RSA signatures require a specific hash function, and +padding to be used. Here is an example of signing ``message`` using RSA, with a +secure hash function and padding: + +.. doctest:: + + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import padding + + >>> signer = private_key.signer( + ... padding.PSS( + ... mgf=padding.MGF1(hashes.SHA256()), + ... salt_length=padding.PSS.MAX_LENGTH + ... ), + ... hashes.SHA256() + ... ) + >>> message = b"A message I want to sign" + >>> signer.update(message) + >>> signature = signer.finalize() + +There is a shortcut to sign sufficiently short messages directly: + +.. doctest:: + + >>> message = b"A message I want to sign" + >>> signature = private_key.sign( + ... message, + ... padding.PSS( + ... mgf=padding.MGF1(hashes.SHA256()), + ... salt_length=padding.PSS.MAX_LENGTH + ... ), + ... hashes.SHA256() + ... ) + +Valid paddings for signatures are +:class:`~cryptography.hazmat.primitives.asymmetric.padding.PSS` and +:class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`. ``PSS`` +is the recommended choice for any new protocols or applications, ``PKCS1v15`` +should only be used to support legacy protocols. + +Verification +~~~~~~~~~~~~ + +The previous section describes what to do if you have a private key and want to +sign something. If you have a public key, a message, a signature, and the +signing algorithm that was used you can check that the private key associated +with a given public key was used to sign that specific message. You can obtain +a public key to use in verification using +:func:`~cryptography.hazmat.primitives.serialization.load_pem_public_key`, +:func:`~cryptography.hazmat.primitives.serialization.load_der_public_key`, +:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicNumbers.public_key` +, or +:meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.public_key`. + +.. doctest:: + + >>> public_key = private_key.public_key() + >>> verifier = public_key.verifier( + ... signature, + ... padding.PSS( + ... mgf=padding.MGF1(hashes.SHA256()), + ... salt_length=padding.PSS.MAX_LENGTH + ... ), + ... hashes.SHA256() + ... ) + >>> verifier.update(message) + >>> verifier.verify() + +If the signature does not match, ``verify()`` will raise an +:class:`~cryptography.exceptions.InvalidSignature` exception. + +There is a shortcut to verify sufficiently short messages directly: + +.. doctest:: + + >>> public_key.verify( + ... signature, + ... message, + ... padding.PSS( + ... mgf=padding.MGF1(hashes.SHA256()), + ... salt_length=padding.PSS.MAX_LENGTH + ... ), + ... hashes.SHA256() + ... ) + +Encryption +~~~~~~~~~~ + +RSA encryption is interesting because encryption is performed using the +**public** key, meaning anyone can encrypt data. The data is then decrypted +using the **private** key. + +Like signatures, RSA supports encryption with several different padding +options. Here's an example using a secure padding and hash function: + +.. doctest:: + + >>> message = b"encrypted data" + >>> ciphertext = public_key.encrypt( + ... message, + ... padding.OAEP( + ... mgf=padding.MGF1(algorithm=hashes.SHA1()), + ... algorithm=hashes.SHA1(), + ... label=None + ... ) + ... ) + +Valid paddings for encryption are +:class:`~cryptography.hazmat.primitives.asymmetric.padding.OAEP` and +:class:`~cryptography.hazmat.primitives.asymmetric.padding.PKCS1v15`. ``OAEP`` +is the recommended choice for any new protocols or applications, ``PKCS1v15`` +should only be used to support legacy protocols. + + +Decryption +~~~~~~~~~~ + +Once you have an encrypted message, it can be decrypted using the private key: + +.. doctest:: + + >>> plaintext = private_key.decrypt( + ... ciphertext, + ... padding.OAEP( + ... mgf=padding.MGF1(algorithm=hashes.SHA1()), + ... algorithm=hashes.SHA1(), + ... label=None + ... ) + ... ) + >>> plaintext == message + True + +Padding +~~~~~~~ + +.. module:: cryptography.hazmat.primitives.asymmetric.padding + +.. class:: AsymmetricPadding + + .. versionadded:: 0.2 + + .. attribute:: name + +.. class:: PSS(mgf, salt_length) + + .. versionadded:: 0.3 + + .. versionchanged:: 0.4 + Added ``salt_length`` parameter. + + PSS (Probabilistic Signature Scheme) is a signature scheme defined in + :rfc:`3447`. It is more complex than PKCS1 but possesses a `security proof`_. + This is the `recommended padding algorithm`_ for RSA signatures. It cannot + be used with RSA encryption. + + :param mgf: A mask generation function object. At this time the only + supported MGF is :class:`MGF1`. + + :param int salt_length: The length of the salt. It is recommended that this + be set to ``PSS.MAX_LENGTH``. + + .. attribute:: MAX_LENGTH + + Pass this attribute to ``salt_length`` to get the maximum salt length + available. + +.. class:: OAEP(mgf, algorithm, label) + + .. versionadded:: 0.4 + + OAEP (Optimal Asymmetric Encryption Padding) is a padding scheme defined in + :rfc:`3447`. It provides probabilistic encryption and is `proven secure`_ + against several attack types. This is the `recommended padding algorithm`_ + for RSA encryption. It cannot be used with RSA signing. + + :param mgf: A mask generation function object. At this time the only + supported MGF is :class:`MGF1`. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :param bytes label: A label to apply. This is a rarely used field and + should typically be set to ``None`` or ``b""``, which are equivalent. + +.. class:: PKCS1v15() + + .. versionadded:: 0.3 + + PKCS1 v1.5 (also known as simply PKCS1) is a simple padding scheme + developed for use with RSA keys. It is defined in :rfc:`3447`. This padding + can be used for signing and encryption. + + It is not recommended that ``PKCS1v15`` be used for new applications, + :class:`OAEP` should be preferred for encryption and :class:`PSS` should be + preferred for signatures. + + +.. function:: calculate_max_pss_salt_length(key, hash_algorithm) + + .. versionadded:: 1.5 + + :param key: An RSA public or private key. + :param hash_algorithm: A + :class:`cryptography.hazmat.primitives.hashes.HashAlgorithm`. + :returns int: The computed salt length. + + Computes the length of the salt that :class:`PSS` will use if + :data:`PSS.MAX_LENGTH` is used. + + +Mask generation functions +------------------------- + +.. class:: MGF1(algorithm) + + .. versionadded:: 0.3 + + .. versionchanged:: 0.6 + Removed the deprecated ``salt_length`` parameter. + + MGF1 (Mask Generation Function 1) is used as the mask generation function + in :class:`PSS` and :class:`OAEP` padding. It takes a hash algorithm. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + +Numbers +~~~~~~~ + +.. currentmodule:: cryptography.hazmat.primitives.asymmetric.rsa + +These classes hold the constituent components of an RSA key. They are useful +only when more traditional :doc:`/hazmat/primitives/asymmetric/serialization` +is unavailable. + +.. class:: RSAPublicNumbers(e, n) + + .. versionadded:: 0.5 + + The collection of integers that make up an RSA public key. + + .. attribute:: n + + :type: int + + The public modulus. + + .. attribute:: e + + :type: int + + The public exponent. + + .. method:: public_key(backend) + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.RSABackend`. + + :returns: A new instance of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`. + +.. class:: RSAPrivateNumbers(p, q, d, dmp1, dmq1, iqmp, public_numbers) + + .. versionadded:: 0.5 + + The collection of integers that make up an RSA private key. + + .. warning:: + + With the exception of the integers contained in the + :class:`RSAPublicNumbers` all attributes of this class must be kept + secret. Revealing them will compromise the security of any + cryptographic operations performed with a key loaded from them. + + .. attribute:: public_numbers + + :type: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicNumbers` + + The :class:`RSAPublicNumbers` which makes up the RSA public key + associated with this RSA private key. + + .. attribute:: p + + :type: int + + ``p``, one of the two primes composing ``n``. + + .. attribute:: q + + :type: int + + ``q``, one of the two primes composing ``n``. + + .. attribute:: d + + :type: int + + The private exponent. + + .. attribute:: dmp1 + + :type: int + + A `Chinese remainder theorem`_ coefficient used to speed up RSA + operations. Calculated as: d mod (p-1) + + .. attribute:: dmq1 + + :type: int + + A `Chinese remainder theorem`_ coefficient used to speed up RSA + operations. Calculated as: d mod (q-1) + + .. attribute:: iqmp + + :type: int + + A `Chinese remainder theorem`_ coefficient used to speed up RSA + operations. Calculated as: q\ :sup:`-1` mod p + + .. method:: private_key(backend) + + :param backend: A new instance of + :class:`~cryptography.hazmat.backends.interfaces.RSABackend`. + + :returns: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`. + +Handling partial RSA private keys +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you are trying to load RSA private keys yourself you may find that not all +parameters required by ``RSAPrivateNumbers`` are available. In particular the +`Chinese Remainder Theorem`_ (CRT) values ``dmp1``, ``dmq1``, ``iqmp`` may be +missing or present in a different form. For example, `OpenPGP`_ does not include +the ``iqmp``, ``dmp1`` or ``dmq1`` parameters. + +The following functions are provided for users who want to work with keys like +this without having to do the math themselves. + +.. function:: rsa_crt_iqmp(p, q) + + .. versionadded:: 0.4 + + Computes the ``iqmp`` (also known as ``qInv``) parameter from the RSA + primes ``p`` and ``q``. + +.. function:: rsa_crt_dmp1(private_exponent, p) + + .. versionadded:: 0.4 + + Computes the ``dmp1`` parameter from the RSA private exponent (``d``) and + prime ``p``. + +.. function:: rsa_crt_dmq1(private_exponent, q) + + .. versionadded:: 0.4 + + Computes the ``dmq1`` parameter from the RSA private exponent (``d``) and + prime ``q``. + +.. function:: rsa_recover_prime_factors(n, e, d) + + .. versionadded:: 0.8 + + Computes the prime factors ``(p, q)`` given the modulus, public exponent, + and private exponent. + + .. note:: + + When recovering prime factors this algorithm will always return ``p`` + and ``q`` such that ``p > q``. Note: before 1.5, this function always + returned ``p`` and ``q`` such that ``p < q``. It was changed because + libraries commonly require ``p > q``. + + :return: A tuple ``(p, q)`` + + +Key interfaces +~~~~~~~~~~~~~~ + +.. class:: RSAPrivateKey + + .. versionadded:: 0.2 + + An `RSA`_ private key. + + .. method:: signer(padding, algorithm) + + .. versionadded:: 0.3 + + Get signer to sign data which can be verified later by others using + the public key. + + :param padding: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :returns: + :class:`~cryptography.hazmat.primitives.asymmetric.AsymmetricSignatureContext` + + .. method:: decrypt(ciphertext, padding) + + .. versionadded:: 0.4 + + Decrypt data that was encrypted with the public key. + + :param bytes ciphertext: The ciphertext to decrypt. + + :param padding: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. + + :return bytes: Decrypted data. + + .. method:: public_key() + + :return: :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` + + An RSA public key object corresponding to the values of the private key. + + .. attribute:: key_size + + :type: int + + The bit length of the modulus. + + .. method:: sign(data, padding, algorithm) + + .. versionadded:: 1.4 + .. versionchanged:: 1.6 + :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` + can now be used as an ``algorithm``. + + Sign one block of data which can be verified later by others using the + public key. + + :param bytes data: The message string to sign. + + :param padding: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or + :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` + if the ``data`` you want to sign has already been hashed. + + :return bytes: Signature. + + +.. class:: RSAPrivateKeyWithSerialization + + .. versionadded:: 0.8 + + Extends :class:`RSAPrivateKey`. + + .. method:: private_numbers() + + Create a + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateNumbers` + object. + + :returns: An + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateNumbers` + instance. + + .. method:: private_bytes(encoding, format, encryption_algorithm) + + Allows serialization of the key to bytes. Encoding ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`), + format ( + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.TraditionalOpenSSL` + or + :attr:`~cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8`) + and encryption algorithm (such as + :class:`~cryptography.hazmat.primitives.serialization.BestAvailableEncryption` + or :class:`~cryptography.hazmat.primitives.serialization.NoEncryption`) + are chosen to define the exact serialization. + + :param encoding: A value from the + :class:`~cryptography.hazmat.primitives.serialization.Encoding` enum. + + :param format: A value from the + :class:`~cryptography.hazmat.primitives.serialization.PrivateFormat` + enum. + + :param encryption_algorithm: An instance of an object conforming to the + :class:`~cryptography.hazmat.primitives.serialization.KeySerializationEncryption` + interface. + + :return bytes: Serialized key. + + +.. class:: RSAPublicKey + + .. versionadded:: 0.2 + + An `RSA`_ public key. + + .. method:: verifier(signature, padding, algorithm) + + .. versionadded:: 0.3 + + Get verifier to verify data was signed by the private key associated + with this public key. + + :param bytes signature: The signature to verify. + + :param padding: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :returns: + :class:`~cryptography.hazmat.primitives.asymmetric.AsymmetricVerificationContext` + + .. method:: encrypt(plaintext, padding) + + .. versionadded:: 0.4 + + Encrypt data with the public key. + + :param bytes plaintext: The plaintext to encrypt. + + :param padding: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. + + :return bytes: Encrypted data. + + .. attribute:: key_size + + :type: int + + The bit length of the modulus. + + .. method:: public_numbers() + + Create a + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicNumbers` + object. + + :returns: An + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicNumbers` + instance. + + .. method:: public_bytes(encoding, format) + + Allows serialization of the key to bytes. Encoding ( + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.PEM` or + :attr:`~cryptography.hazmat.primitives.serialization.Encoding.DER`) and + format ( + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo` + or + :attr:`~cryptography.hazmat.primitives.serialization.PublicFormat.PKCS1`) + are chosen to define the exact serialization. + + :param encoding: A value from the + :class:`~cryptography.hazmat.primitives.serialization.Encoding` enum. + + :param format: A value from the + :class:`~cryptography.hazmat.primitives.serialization.PublicFormat` enum. + + :return bytes: Serialized key. + + .. method:: verify(signature, data, padding, algorithm) + + .. versionadded:: 1.4 + .. versionchanged:: 1.6 + :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` + can now be used as an ``algorithm``. + + Verify one block of data was signed by the private key + associated with this public key. + + :param bytes signature: The signature to verify. + + :param bytes data: The message string that was signed. + + :param padding: An instance of + :class:`~cryptography.hazmat.primitives.asymmetric.padding.AsymmetricPadding`. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` or + :class:`~cryptography.hazmat.primitives.asymmetric.utils.Prehashed` + if the ``data`` you want to sign has already been hashed. + + :raises cryptography.exceptions.InvalidSignature: If the signature does + not validate. + + +.. class:: RSAPublicKeyWithSerialization + + .. versionadded:: 0.8 + + Alias for :class:`RSAPublicKey`. + + +.. _`RSA`: https://en.wikipedia.org/wiki/RSA_(cryptosystem) +.. _`public-key`: https://en.wikipedia.org/wiki/Public-key_cryptography +.. _`specific mathematical properties`: https://en.wikipedia.org/wiki/RSA_(cryptosystem)#Key_generation +.. _`use 65537`: http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html +.. _`at least 2048`: http://www.ecrypt.eu.org/ecrypt2/documents/D.SPA.20.pdf +.. _`OpenPGP`: https://en.wikipedia.org/wiki/Pretty_Good_Privacy +.. _`Chinese Remainder Theorem`: https://en.wikipedia.org/wiki/RSA_%28cryptosystem%29#Using_the_Chinese_remainder_algorithm +.. _`security proof`: https://eprint.iacr.org/2001/062.pdf +.. _`recommended padding algorithm`: http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html +.. _`proven secure`: https://cseweb.ucsd.edu/~mihir/papers/oae.pdf diff --git a/docs/hazmat/primitives/asymmetric/serialization.rst b/docs/hazmat/primitives/asymmetric/serialization.rst new file mode 100644 index 0000000..f7baf75 --- /dev/null +++ b/docs/hazmat/primitives/asymmetric/serialization.rst @@ -0,0 +1,438 @@ +.. hazmat:: + +Key Serialization +================= + +.. module:: cryptography.hazmat.primitives.serialization + +.. testsetup:: + + import base64 + + pem_data = b""" + -----BEGIN RSA PRIVATE KEY----- + MIICXgIBAAKBgQDn09PV9KPE7Q+N5K5UtNLT1DLl8z/pKM2pP5tXqWx2OsEw00lC + kDHdHESwzS050s/8rtkERKKyusCzCm9+vC1pQzUlmtibfF4PQAQc1pJL6KHqlidg + Hw49atYmnC25CaeXt65pAYXoIacOZ8k5X7FW3Eagex8nG0iMw4ObOtg6CwIDAQAB + AoGBAL31l/4YYN1rNrSZLrQgGyUSGsbLxJHEKolFon95R3O1fzoH117gkstQb4TE + Cwv3jw/JIfBaYUq8tku/AE9D2Jx51x7kYaCuQIMTavKIgkXKfxTQCQDjSEfkvXMW + 4WOIj5sYdSCNbzLbaeFsWG32bSsBTy/sSheDIlCEFnqDuqwBAkEA+wYfJEMDf5nS + VCQd9VKGM4HVeTWBioaWBFCflFdhc1Vb65dsNDp8iIMZgAHC2LEX5dMUmgqXk7AT + lwFlIeW4CwJBAOxsSfuIVMuPKyx1xQ6ebpC7zeVxIOdswcM8ain91MSGDdKZw6pF + ioFh3kUbKHw4yqqHbdRmUDAJ1mcgGJQOxgECQQCmQaGylKfmhWymyd0FtIip6J4I + z4ViyEznwrZOu6kRiEF/QiUqWmpMx/fFrmTsvC5Fy43jkIxgBsiSxRvEXa+NAkB+ + 5m0bhwTEslchKSGZhC6inzuYAQ4BSh4C1mXBnk5bIf0/Ymtk9KiwY8CzZS1o5+7Y + c5LfI/+8mTss5UxsBDYBAkEA6NqhcsNWndIJZiWUU4u+RjFUQXqH8WCyJmEDCNxs + 7SGRS1DTUGX4Y70m9dQpguy6Zg+gpHC+o+ERZR06uEQr+w== + -----END RSA PRIVATE KEY----- + """.strip() + public_pem_data = b""" + -----BEGIN PUBLIC KEY----- + MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDn09PV9KPE7Q+N5K5UtNLT1DLl + 8z/pKM2pP5tXqWx2OsEw00lCkDHdHESwzS050s/8rtkERKKyusCzCm9+vC1pQzUl + mtibfF4PQAQc1pJL6KHqlidgHw49atYmnC25CaeXt65pAYXoIacOZ8k5X7FW3Eag + ex8nG0iMw4ObOtg6CwIDAQAB + -----END PUBLIC KEY----- + """.strip() + der_data = base64.b64decode( + b"MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBALskegl+DrI3Msw5Z63x" + b"nj1rgoPR0KykwBi+jZgAwHv/B0TJyhy6NuEnaf+x442L7lepOqoWQzlUGXyuaSQU9mT/" + b"vHTGZ2xM8QJJaccr4eGho0MU9HePyNCFWjWVrGKpwSEAd6CLlzC0Wiy4kC9IoAUoS/IP" + b"jeyLTQNCddatgcARAgMBAAECgYAA/LlKJgeJUStTcpHgGD6mXjHvnAwWJELQKDP5+tA8" + b"VAQGwBX1G5qzJDGrPGtHQ7DSqdwF4YFZtgTpZmGq1wsAjz3lv6L4XiVsHiIPtP1B4gMx" + b"X9ogxcDzVQ7hyezXPioMAcp7Isus9Csn8HhftcL56BRabn6GvWqbIAy6zJcgEQJBAMlZ" + b"nymKW5/jKth+wkCfqEXlPhGNPO1uq87QZUbYxwdjtSM09J9+HMfH+WXR9ARCOL46DJ0I" + b"JfyjcdmuDDlh9IkCQQDt76up1Tmc7lkb/89IRBu2MudGJPMEf96VCG11nmcXulyk1OLi" + b"TXfO62YpxZbgYrvlrNxEYlSG7WQMztBgA51JAkBU2RhyJ+S+drsaaigvlVgSxCyotszi" + b"/Q0XZMgY18bfPUwanvkqsLkuEv3sw1HB7an9t3aTQdjIIpQad/acw8OJAkEAjvmnCK21" + b"KgTbjQShtQYgNNLPwImxcjG4OYvP4o6l2k9FHlNCZsQwSymOwWkXKYyK5g+CaKFBs7Zw" + b"mXWpJxjk6QJBAInqbm1w3yVfGD9I2mMQi/6oDJQP3pdWU4mU4h4sdDyRgTQLpkD4yypg" + b"jOACt4mTzxifSVT9fT+a79SkT8FFmZE=" + ) + public_der_data = base64.b64decode( + b"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7JHoJfg6yNzLMOWet8Z49a4KD0dCs" + b"pMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkkFPZk/7x0xmdsTPEC" + b"SWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAvSKAFKEvyD43si00DQnXW" + b"rYHAEQIDAQAB" + ) + message = b"" + + def sign_with_rsa_key(key, message): + return b"" + + def sign_with_dsa_key(key, message): + return b"" + +There are several common schemes for serializing asymmetric private and public +keys to bytes. They generally support encryption of private keys and additional +key metadata. + +Many serialization formats support multiple different types of asymmetric keys +and will return an instance of the appropriate type. You should check that +the returned key matches the type your application expects when using these +methods. + + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives.asymmetric import dsa, rsa + >>> from cryptography.hazmat.primitives.serialization import load_pem_private_key + >>> key = load_pem_private_key(pem_data, password=None, backend=default_backend()) + >>> if isinstance(key, rsa.RSAPrivateKey): + ... signature = sign_with_rsa_key(key, message) + ... elif isinstance(key, dsa.DSAPrivateKey): + ... signature = sign_with_dsa_key(key, message) + ... else: + ... raise TypeError + +Key dumping +~~~~~~~~~~~ + +The ``serialization`` module contains functions for loading keys from +``bytes``. To dump a ``key`` object to ``bytes``, you must call the appropriate +bytes on the key object. Documentation for these methods in found in the +:mod:`~cryptography.hazmat.primitives.asymmetric.rsa`, +:mod:`~cryptography.hazmat.primitives.asymmetric.dsa`, and +:mod:`~cryptography.hazmat.primitives.asymmetric.ec` module documentation. + +PEM +~~~ + +PEM is an encapsulation format, meaning keys in it can actually be any of +several different key types. However these are all self-identifying, so you +don't need to worry about this detail. PEM keys are recognizable because they +all begin with ``-----BEGIN {format}-----`` and end with ``-----END +{format}-----``. + +.. note:: + + A PEM block which starts with ``-----BEGIN CERTIFICATE-----`` is not a + public or private key, it's an :doc:`X.509 Certificate `. You + can load it using :func:`~cryptography.x509.load_pem_x509_certificate` and + extract the public key with + :meth:`Certificate.public_key `. + +.. function:: load_pem_private_key(data, password, backend) + + .. versionadded:: 0.6 + + Deserialize a private key from PEM encoded data to one of the supported + asymmetric private key types. + + :param bytes data: The PEM encoded key data. + + :param bytes password: The password to use to decrypt the data. Should + be ``None`` if the private key is not encrypted. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.PEMSerializationBackend`. + + :returns: One of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, + or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + depending on the contents of ``data``. + + :raises ValueError: If the PEM data could not be decrypted or if its + structure could not be decoded successfully. + + :raises TypeError: If a ``password`` was given and the private key was + not encrypted. Or if the key was encrypted but no + password was supplied. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key + is of a type that is not supported by the backend or if the key is + encrypted with a symmetric cipher that is not supported by the backend. + +.. function:: load_pem_public_key(data, backend) + + .. versionadded:: 0.6 + + Deserialize a public key from PEM encoded data to one of the supported + asymmetric public key types. The PEM encoded data is typically a + ``subjectPublicKeyInfo`` payload as specified in :rfc:`5280`. + + .. doctest:: + + >>> from cryptography.hazmat.primitives.serialization import load_pem_public_key + >>> key = load_pem_public_key(public_pem_data, backend=default_backend()) + >>> isinstance(key, rsa.RSAPublicKey) + True + + :param bytes data: The PEM encoded key data. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.PEMSerializationBackend`. + + + :returns: One of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, + or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + depending on the contents of ``data``. + + :raises ValueError: If the PEM data's structure could not be decoded + successfully. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key + is of a type that is not supported by the backend. + +DER +~~~ + +DER is an ASN.1 encoding type. There are no encapsulation boundaries and the +data is binary. DER keys may be in a variety of formats, but as long as you +know whether it is a public or private key the loading functions will handle +the rest. + +.. function:: load_der_private_key(data, password, backend) + + .. versionadded:: 0.8 + + Deserialize a private key from DER encoded data to one of the supported + asymmetric private key types. + + :param bytes data: The DER encoded key data. + + :param bytes password: The password to use to decrypt the data. Should + be ``None`` if the private key is not encrypted. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.DERSerializationBackend`. + + :returns: One of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey`, + or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + depending on the contents of ``data``. + + :raises ValueError: If the DER data could not be decrypted or if its + structure could not be decoded successfully. + + :raises TypeError: If a ``password`` was given and the private key was + not encrypted. Or if the key was encrypted but no + password was supplied. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key is of a type that + is not supported by the backend or if the key is encrypted with a + symmetric cipher that is not supported by the backend. + + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives.asymmetric import rsa + >>> from cryptography.hazmat.primitives.serialization import load_der_private_key + >>> key = load_der_private_key(der_data, password=None, backend=default_backend()) + >>> isinstance(key, rsa.RSAPrivateKey) + True + +.. function:: load_der_public_key(data, backend) + + .. versionadded:: 0.8 + + Deserialize a public key from DER encoded data to one of the supported + asymmetric public key types. The DER encoded data is typically a + ``subjectPublicKeyInfo`` payload as specified in :rfc:`5280`. + + :param bytes data: The DER encoded key data. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.DERSerializationBackend`. + + :returns: One of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, + or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + depending on the contents of ``data``. + + :raises ValueError: If the DER data's structure could not be decoded + successfully. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized key is of a type that + is not supported by the backend. + + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives.asymmetric import rsa + >>> from cryptography.hazmat.primitives.serialization import load_der_public_key + >>> key = load_der_public_key(public_der_data, backend=default_backend()) + >>> isinstance(key, rsa.RSAPublicKey) + True + + +OpenSSH Public Key +~~~~~~~~~~~~~~~~~~ + +The format used by OpenSSH to store public keys, as specified in :rfc:`4253`. + +An example RSA key in OpenSSH format (line breaks added for formatting +purposes):: + + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk + FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll + PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK + vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f + sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy + ///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX + 2MzHvnbv testkey@localhost + +DSA keys look almost identical but begin with ``ssh-dss`` rather than +``ssh-rsa``. ECDSA keys have a slightly different format, they begin with +``ecdsa-sha2-{curve}``. + +.. function:: load_ssh_public_key(data, backend) + + .. versionadded:: 0.7 + + Deserialize a public key from OpenSSH (:rfc:`4253`) encoded data to an + instance of the public key type for the specified backend. + + .. note:: + + Currently Ed25519 keys are not supported. + + :param bytes data: The OpenSSH encoded key data. + + :param backend: A backend which implements + :class:`~cryptography.hazmat.backends.interfaces.RSABackend`, + :class:`~cryptography.hazmat.backends.interfaces.DSABackend`, or + :class:`~cryptography.hazmat.backends.interfaces.EllipticCurveBackend` + depending on the key's type. + + :returns: One of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey`, + or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + depending on the contents of ``data``. + + :raises ValueError: If the OpenSSH data could not be properly decoded or + if the key is not in the proper format. + + :raises cryptography.exceptions.UnsupportedAlgorithm: If the serialized + key is of a type that is not supported. + +Serialization Formats +~~~~~~~~~~~~~~~~~~~~~ + +.. class:: PrivateFormat + + .. versionadded:: 0.8 + + An enumeration for private key formats. Used with the ``private_bytes`` + method available on + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` + , + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` + and + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`. + + .. attribute:: TraditionalOpenSSL + + Frequently known as PKCS#1 format. Still a widely used format, but + generally considered legacy. + + .. attribute:: PKCS8 + + A more modern format for serializing keys which allows for better + encryption. Choose this unless you have explicit legacy compatibility + requirements. + +.. class:: PublicFormat + + .. versionadded:: 0.8 + + An enumeration for public key formats. Used with the ``public_bytes`` + method available on + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization` + , + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization` + , and + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKeyWithSerialization`. + + .. attribute:: SubjectPublicKeyInfo + + This is the typical public key format. It consists of an algorithm + identifier and the public key as a bit string. Choose this unless + you have specific needs. + + .. attribute:: PKCS1 + + Just the public key elements (without the algorithm identifier). This + format is RSA only, but is used by some older systems. + + .. attribute:: OpenSSH + + .. versionadded:: 1.4 + + The public key format used by OpenSSH (e.g. as found in + ``~/.ssh/id_rsa.pub`` or ``~/.ssh/authorized_keys``). + +Serialization Encodings +~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: Encoding + + An enumeration for encoding types. Used with the ``private_bytes`` method + available on + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` + , + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` + and + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization` + as well as ``public_bytes`` on + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKeyWithSerialization` + and + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKeyWithSerialization`. + + .. attribute:: PEM + + .. versionadded:: 0.8 + + For PEM format. This is a base64 format with delimiters. + + .. attribute:: DER + + .. versionadded:: 0.9 + + For DER format. This is a binary format. + + .. attribute:: OpenSSH + + .. versionadded:: 1.4 + + The format used by OpenSSH public keys. This is a text format. + + +Serialization Encryption Types +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: KeySerializationEncryption + + Objects with this interface are usable as encryption types with methods + like ``private_bytes`` available on + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization` + , + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKeyWithSerialization` + and + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKeyWithSerialization`. + All other classes in this section represent the available choices for + encryption and have this interface. They are used with + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKeyWithSerialization.private_bytes`. + +.. class:: BestAvailableEncryption(password) + + Encrypt using the best available encryption for a given key's backend. + This is a curated encryption choice and the algorithm may change over + time. + + :param bytes password: The password to use for encryption. + +.. class:: NoEncryption + + Do not encrypt. diff --git a/docs/hazmat/primitives/asymmetric/utils.rst b/docs/hazmat/primitives/asymmetric/utils.rst new file mode 100644 index 0000000..f46acb2 --- /dev/null +++ b/docs/hazmat/primitives/asymmetric/utils.rst @@ -0,0 +1,88 @@ +.. hazmat:: + +Asymmetric Utilities +==================== + +.. currentmodule:: cryptography.hazmat.primitives.asymmetric.utils + + +.. function:: decode_dss_signature(signature) + + Takes in signatures generated by the DSA/ECDSA signers and returns a + tuple ``(r, s)``. These signatures are ASN.1 encoded ``Dss-Sig-Value`` + sequences (as defined in :rfc:`3279`) + + :param bytes signature: The signature to decode. + + :returns: The decoded tuple ``(r, s)``. + + :raises ValueError: Raised if the signature is malformed. + +.. function:: encode_dss_signature(r, s) + + Creates an ASN.1 encoded ``Dss-Sig-Value`` (as defined in :rfc:`3279`) from + raw ``r`` and ``s`` values. + + :param int r: The raw signature value ``r``. + + :param int s: The raw signature value ``s``. + + :return bytes: The encoded signature. + +.. class:: Prehashed(algorithm) + + .. versionadded:: 1.6 + + ``Prehashed`` can be passed as the ``algorithm`` in the RSA + :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey.sign` + and + :meth:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey.verify` + as well as DSA + :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey.sign` + and + :meth:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey.verify` + methods. + + For elliptic curves it can be passed as the ``algorithm`` in + :class:`~cryptography.hazmat.primitives.asymmetric.ec.ECDSA` and then used + with + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey.sign` + and + :meth:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey.verify` + . + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + .. doctest:: + + >>> import hashlib + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import ( + ... padding, rsa, utils + ... ) + >>> private_key = rsa.generate_private_key( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ) + >>> prehashed_msg = hashlib.sha256(b"A message I want to sign").digest() + >>> signature = private_key.sign( + ... prehashed_msg, + ... padding.PSS( + ... mgf=padding.MGF1(hashes.SHA256()), + ... salt_length=padding.PSS.MAX_LENGTH + ... ), + ... utils.Prehashed(hashes.SHA256()) + ... ) + >>> public_key = private_key.public_key() + >>> public_key.verify( + ... signature, + ... prehashed_msg, + ... padding.PSS( + ... mgf=padding.MGF1(hashes.SHA256()), + ... salt_length=padding.PSS.MAX_LENGTH + ... ), + ... utils.Prehashed(hashes.SHA256()) + ... ) diff --git a/docs/hazmat/primitives/constant-time.rst b/docs/hazmat/primitives/constant-time.rst new file mode 100644 index 0000000..1c1d544 --- /dev/null +++ b/docs/hazmat/primitives/constant-time.rst @@ -0,0 +1,43 @@ +.. hazmat:: + +Constant time functions +======================= + +.. currentmodule:: cryptography.hazmat.primitives.constant_time + +This module contains functions for operating with secret data in a way that +does not leak information about that data through how long it takes to perform +the operation. These functions should be used whenever operating on secret data +along with data that is user supplied. + +An example would be comparing a HMAC signature received from a client to the +one generated by the server code for authentication purposes. + +For more information about this sort of issue, see `Coda Hale's blog post`_ +about the timing attacks on KeyCzar and Java's ``MessageDigest.isEqual()``. + + +.. function:: bytes_eq(a, b) + + Compares ``a`` and ``b`` with one another. If ``a`` and ``b`` have + different lengths, this returns ``False`` immediately. Otherwise it + compares them in a way that takes the same amount of time, regardless of + how many characters are the same between the two. + + .. doctest:: + + >>> from cryptography.hazmat.primitives import constant_time + >>> constant_time.bytes_eq(b"foo", b"foo") + True + >>> constant_time.bytes_eq(b"foo", b"bar") + False + + :param bytes a: The left-hand side. + :param bytes b: The right-hand side. + :returns bool: ``True`` if ``a`` has the same bytes as ``b``, otherwise + ``False``. + :raises TypeError: This exception is raised if ``a`` or ``b`` is not + ``bytes``. + + +.. _`Coda Hale's blog post`: https://codahale.com/a-lesson-in-timing-attacks/ diff --git a/docs/hazmat/primitives/cryptographic-hashes.rst b/docs/hazmat/primitives/cryptographic-hashes.rst new file mode 100644 index 0000000..fd5f35d --- /dev/null +++ b/docs/hazmat/primitives/cryptographic-hashes.rst @@ -0,0 +1,229 @@ +.. hazmat:: + +Message digests +=============== + +.. module:: cryptography.hazmat.primitives.hashes + +.. class:: Hash(algorithm, backend) + + A cryptographic hash function takes an arbitrary block of data and + calculates a fixed-size bit string (a digest), such that different data + results (with a high probability) in different digests. + + This is an implementation of + :class:`~cryptography.hazmat.primitives.hashes.HashContext` meant to + be used with + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + implementations to provide an incremental interface to calculating + various message digests. + + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> digest = hashes.Hash(hashes.SHA256(), backend=default_backend()) + >>> digest.update(b"abc") + >>> digest.update(b"123") + >>> digest.finalize() + 'l\xa1=R\xcap\xc8\x83\xe0\xf0\xbb\x10\x1eBZ\x89\xe8bM\xe5\x1d\xb2\xd29%\x93\xafj\x84\x11\x80\x90' + + If the backend doesn't support the requested ``algorithm`` an + :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be + raised. + + Keep in mind that attacks against cryptographic hashes only get stronger + with time, and that often algorithms that were once thought to be strong, + become broken. Because of this it's important to include a plan for + upgrading the hash algorithm you use over time. For more information, see + `Lifetimes of cryptographic hash functions`_. + + :param algorithm: A + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + instance such as those described in + :ref:`below `. + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.HashBackend` + instance. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.HashBackend` + + .. method:: update(data) + + :param bytes data: The bytes to be hashed. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`. + :raises TypeError: This exception is raised if ``data`` is not ``bytes``. + + .. method:: copy() + + Copy this :class:`Hash` instance, usually so that you may call + :meth:`finalize` to get an intermediate digest value while we continue + to call :meth:`update` on the original instance. + + :return: A new instance of :class:`Hash` that can be updated + and finalized independently of the original instance. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`. + + .. method:: finalize() + + Finalize the current context and return the message digest as bytes. + + After ``finalize`` has been called this object can no longer be used + and :meth:`update`, :meth:`copy`, and :meth:`finalize` will raise an + :class:`~cryptography.exceptions.AlreadyFinalized` exception. + + :return bytes: The message digest as bytes. + + +.. _cryptographic-hash-algorithms: + +SHA-1 +~~~~~ + +.. attention:: + + NIST has deprecated SHA-1 in favor of the SHA-2 variants. New applications + are strongly suggested to use SHA-2 over SHA-1. + +.. class:: SHA1() + + SHA-1 is a cryptographic hash function standardized by NIST. It produces an + 160-bit message digest. Cryptanalysis of SHA-1 has demonstrated that it is + vulnerable to practical collision attacks, though no actual collisions are + publicly known. + +SHA-2 family +~~~~~~~~~~~~ + +.. class:: SHA224() + + SHA-224 is a cryptographic hash function from the SHA-2 family and is + standardized by NIST. It produces a 224-bit message digest. + +.. class:: SHA256() + + SHA-256 is a cryptographic hash function from the SHA-2 family and is + standardized by NIST. It produces a 256-bit message digest. + +.. class:: SHA384() + + SHA-384 is a cryptographic hash function from the SHA-2 family and is + standardized by NIST. It produces a 384-bit message digest. + +.. class:: SHA512() + + SHA-512 is a cryptographic hash function from the SHA-2 family and is + standardized by NIST. It produces a 512-bit message digest. + +BLAKE2 +~~~~~~ + +`BLAKE2`_ is a cryptographic hash function specified in :rfc:`7693`. BLAKE2's +design makes it immune to `length-extension attacks`_, an advantage over the +SHA-family of hashes. + +.. note:: + + While the RFC specifies keying, personalization, and salting features, + these are not supported at this time due to limitations in OpenSSL 1.1.0. + +.. class:: BLAKE2b(digest_size) + + BLAKE2b is optimized for 64-bit platforms and produces an 1 to 64-byte + message digest. + + :param int digest_size: The desired size of the hash output in bytes. Only + ``64`` is supported at this time. + + :raises ValueError: If the ``digest_size`` is invalid. + +.. class:: BLAKE2s(digest_size) + + BLAKE2s is optimized for 8 to 32-bit platforms and produces a + 1 to 32-byte message digest. + + :param int digest_size: The desired size of the hash output in bytes. Only + ``32`` is supported at this time. + + :raises ValueError: If the ``digest_size`` is invalid. + +RIPEMD160 +~~~~~~~~~ + +.. class:: RIPEMD160() + + RIPEMD160 is a cryptographic hash function that is part of ISO/IEC + 10118-3:2004. It produces a 160-bit message digest. + +Whirlpool +~~~~~~~~~ + +.. class:: Whirlpool() + + Whirlpool is a cryptographic hash function that is part of ISO/IEC + 10118-3:2004. It produces a 512-bit message digest. + +MD5 +~~~ + +.. warning:: + + MD5 is a deprecated hash algorithm that has practical known collision + attacks. You are strongly discouraged from using it. Existing applications + should strongly consider moving away. + +.. class:: MD5() + + MD5 is a deprecated cryptographic hash function. It produces a 128-bit + message digest and has practical known collision attacks. + + +Interfaces +~~~~~~~~~~ + +.. class:: HashAlgorithm + + .. attribute:: name + + :type: str + + The standard name for the hash algorithm, for example: ``"sha256"`` or + ``"whirlpool"``. + + .. attribute:: digest_size + + :type: int + + The size of the resulting digest in bytes. + + .. attribute:: block_size + + :type: int + + The internal block size of the hash algorithm in bytes. + + +.. class:: HashContext + + .. attribute:: algorithm + + A :class:`HashAlgorithm` that will be used by this context. + + .. method:: update(data) + + :param bytes data: The data you want to hash. + + .. method:: finalize() + + :return: The final digest as bytes. + + .. method:: copy() + + :return: A :class:`HashContext` that is a copy of the current context. + + +.. _`Lifetimes of cryptographic hash functions`: http://valerieaurora.org/hash.html +.. _`BLAKE2`: https://blake2.net +.. _`length-extension attacks`: https://en.wikipedia.org/wiki/Length_extension_attack diff --git a/docs/hazmat/primitives/index.rst b/docs/hazmat/primitives/index.rst new file mode 100644 index 0000000..cf27622 --- /dev/null +++ b/docs/hazmat/primitives/index.rst @@ -0,0 +1,18 @@ +.. hazmat:: + +Primitives +========== + +.. toctree:: + :maxdepth: 1 + + cryptographic-hashes + mac/index + symmetric-encryption + padding + key-derivation-functions + keywrap + asymmetric/index + constant-time + interfaces + twofactor diff --git a/docs/hazmat/primitives/interfaces.rst b/docs/hazmat/primitives/interfaces.rst new file mode 100644 index 0000000..d60fe18 --- /dev/null +++ b/docs/hazmat/primitives/interfaces.rst @@ -0,0 +1,80 @@ +.. hazmat:: + +.. module:: cryptography.hazmat.primitives.interfaces + +Interfaces +========== + + +``cryptography`` uses `Abstract Base Classes`_ as interfaces to describe the +properties and methods of most primitive constructs. Backends may also use +this information to influence their operation. Interfaces should also be used +to document argument and return types. + +.. _`Abstract Base Classes`: https://docs.python.org/3/library/abc.html + + +Asymmetric interfaces +--------------------- + +In 0.8 the asymmetric signature and verification interfaces were moved to the +:mod:`cryptography.hazmat.primitives.asymmetric` module. + +In 0.8 the asymmetric padding interface was moved to the +:mod:`cryptography.hazmat.primitives.asymmetric.padding` module. + +DSA +~~~ + +In 0.8 the DSA key interfaces were moved to the +:mod:`cryptography.hazmat.primitives.asymmetric.dsa` module. + + +RSA +~~~ + +In 0.8 the RSA key interfaces were moved to the +:mod:`cryptography.hazmat.primitives.asymmetric.rsa` module. + + +Elliptic Curve +~~~~~~~~~~~~~~ + +In 0.8 the EC key interfaces were moved to the +:mod:`cryptography.hazmat.primitives.asymmetric.ec` module. + + +Key derivation functions +------------------------ + +In 0.8 the key derivation function interface was moved to the +:mod:`cryptography.hazmat.primitives.kdf` module. + + +.. class:: MACContext + + .. versionadded:: 0.7 + + .. method:: update(data) + + :param bytes data: The data you want to authenticate. + + .. method:: finalize() + + :return: The message authentication code. + + .. method:: copy() + + :return: A + :class:`~cryptography.hazmat.primitives.interfaces.MACContext` that + is a copy of the current context. + + .. method:: verify(signature) + + :param bytes signature: The signature to verify. + + :raises cryptography.exceptions.InvalidSignature: This is raised when + the provided signature does not match the expected signature. + + +.. _`CMAC`: https://en.wikipedia.org/wiki/CMAC diff --git a/docs/hazmat/primitives/key-derivation-functions.rst b/docs/hazmat/primitives/key-derivation-functions.rst new file mode 100644 index 0000000..607eeb2 --- /dev/null +++ b/docs/hazmat/primitives/key-derivation-functions.rst @@ -0,0 +1,909 @@ +.. hazmat:: + +Key derivation functions +======================== + +.. module:: cryptography.hazmat.primitives.kdf + +Key derivation functions derive bytes suitable for cryptographic operations +from passwords or other data sources using a pseudo-random function (PRF). +Different KDFs are suitable for different tasks such as: + +* Cryptographic key derivation + + Deriving a key suitable for use as input to an encryption algorithm. + Typically this means taking a password and running it through an algorithm + such as :class:`~cryptography.hazmat.primitives.kdf.pbkdf2.PBKDF2HMAC` or + :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF`. + This process is typically known as `key stretching`_. + +* Password storage + + When storing passwords you want to use an algorithm that is computationally + intensive. Legitimate users will only need to compute it once (for example, + taking the user's password, running it through the KDF, then comparing it + to the stored value), while attackers will need to do it billions of times. + Ideal password storage KDFs will be demanding on both computational and + memory resources. + +.. currentmodule:: cryptography.hazmat.primitives.kdf.pbkdf2 + +.. class:: PBKDF2HMAC(algorithm, length, salt, iterations, backend) + + .. versionadded:: 0.2 + + `PBKDF2`_ (Password Based Key Derivation Function 2) is typically used for + deriving a cryptographic key from a password. It may also be used for + key storage, but an alternate key storage KDF such as + :class:`~cryptography.hazmat.primitives.kdf.scrypt.Scrypt` is generally + considered a better solution. + + This class conforms to the + :class:`~cryptography.hazmat.primitives.kdf.KeyDerivationFunction` + interface. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> salt = os.urandom(16) + >>> # derive + >>> kdf = PBKDF2HMAC( + ... algorithm=hashes.SHA256(), + ... length=32, + ... salt=salt, + ... iterations=100000, + ... backend=backend + ... ) + >>> key = kdf.derive(b"my great password") + >>> # verify + >>> kdf = PBKDF2HMAC( + ... algorithm=hashes.SHA256(), + ... length=32, + ... salt=salt, + ... iterations=100000, + ... backend=backend + ... ) + >>> kdf.verify(b"my great password", key) + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + :param int length: The desired length of the derived key. Maximum is + (2\ :sup:`32` - 1) * ``algorithm.digest_size``. + :param bytes salt: A salt. `NIST SP 800-132`_ recommends 128-bits or + longer. + :param int iterations: The number of iterations to perform of the hash + function. This can be used to control the length of time the operation + takes. Higher numbers help mitigate brute force attacks against derived + keys. See OWASP's `Password Storage Cheat Sheet`_ for more + detailed recommendations if you intend to use this for password storage. + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.PBKDF2HMACBackend` + + :raises TypeError: This exception is raised if ``salt`` is not ``bytes``. + + .. method:: derive(key_material) + + :param bytes key_material: The input key material. For PBKDF2 this + should be a password. + :return bytes: the derived key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + :raises TypeError: This exception is raised if ``key_material`` is not + ``bytes``. + + This generates and returns a new key from the supplied password. + + .. method:: verify(key_material, expected_key) + + :param bytes key_material: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param bytes expected_key: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. This can be used for + checking whether the password a user provides matches the stored derived + key. + + +.. currentmodule:: cryptography.hazmat.primitives.kdf.hkdf + +.. class:: HKDF(algorithm, length, salt, info, backend) + + .. versionadded:: 0.2 + + `HKDF`_ (HMAC-based Extract-and-Expand Key Derivation Function) is suitable + for deriving keys of a fixed size used for other cryptographic operations. + + .. warning:: + + HKDF should not be used for password storage. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.kdf.hkdf import HKDF + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> salt = os.urandom(16) + >>> info = b"hkdf-example" + >>> hkdf = HKDF( + ... algorithm=hashes.SHA256(), + ... length=32, + ... salt=salt, + ... info=info, + ... backend=backend + ... ) + >>> key = hkdf.derive(b"input key") + >>> hkdf = HKDF( + ... algorithm=hashes.SHA256(), + ... length=32, + ... salt=salt, + ... info=info, + ... backend=backend + ... ) + >>> hkdf.verify(b"input key", key) + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :param int length: The desired length of the derived key. Maximum is + ``255 * (algorithm.digest_size // 8)``. + + :param bytes salt: A salt. Randomizes the KDF's output. Optional, but + highly recommended. Ideally as many bits of entropy as the security + level of the hash: often that means cryptographically random and as + long as the hash output. Worse (shorter, less entropy) salt values can + still meaningfully contribute to security. May be reused. Does not have + to be secret, but may cause stronger security guarantees if secret; see + :rfc:`5869` and the `HKDF paper`_ for more details. If ``None`` is + explicitly passed a default salt of ``algorithm.digest_size // 8`` null + bytes will be used. + + :param bytes info: Application specific context information. If ``None`` + is explicitly passed an empty byte string will be used. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + + :raises TypeError: This exception is raised if ``salt`` or ``info`` is not + ``bytes``. + + .. method:: derive(key_material) + + :param bytes key_material: The input key material. + :return bytes: The derived key. + :raises TypeError: This exception is raised if ``key_material`` is not + ``bytes``. + + Derives a new key from the input key material by performing both the + extract and expand operations. + + .. method:: verify(key_material, expected_key) + + :param bytes key_material: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param bytes expected_key: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. + + +.. class:: HKDFExpand(algorithm, length, info, backend) + + .. versionadded:: 0.5 + + HKDF consists of two stages, extract and expand. This class exposes an + expand only version of HKDF that is suitable when the key material is + already cryptographically strong. + + .. warning:: + + HKDFExpand should only be used if the key material is + cryptographically strong. You should use + :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF` if + you are unsure. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.kdf.hkdf import HKDFExpand + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> info = b"hkdf-example" + >>> key_material = os.urandom(16) + >>> hkdf = HKDFExpand( + ... algorithm=hashes.SHA256(), + ... length=32, + ... info=info, + ... backend=backend + ... ) + >>> key = hkdf.derive(key_material) + >>> hkdf = HKDFExpand( + ... algorithm=hashes.SHA256(), + ... length=32, + ... info=info, + ... backend=backend + ... ) + >>> hkdf.verify(key_material, key) + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :param int length: The desired length of the derived key. Maximum is + ``255 * (algorithm.digest_size // 8)``. + + :param bytes info: Application specific context information. If ``None`` + is explicitly passed an empty byte string will be used. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + :raises TypeError: This is raised if the provided ``info`` is a unicode object + :raises TypeError: This exception is raised if ``info`` is not ``bytes``. + + .. method:: derive(key_material) + + :param bytes key_material: The input key material. + :return bytes: The derived key. + + :raises TypeError: This is raised if the provided ``key_material`` is + a unicode object + :raises TypeError: This exception is raised if ``key_material`` is not + ``bytes``. + + Derives a new key from the input key material by performing both the + extract and expand operations. + + .. method:: verify(key_material, expected_key) + + :param bytes key_material: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param bytes expected_key: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + :raises TypeError: This is raised if the provided ``key_material`` is + a unicode object + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. + +.. currentmodule:: cryptography.hazmat.primitives.kdf.concatkdf + +.. class:: ConcatKDFHash(algorithm, length, otherinfo, backend) + + .. versionadded:: 1.0 + + ConcatKDFHash (Concatenation Key Derivation Function) is defined by the + NIST Special Publication `NIST SP 800-56Ar2`_ document, to be used to + derive keys for use after a Key Exchange negotiation operation. + + .. warning:: + + ConcatKDFHash should not be used for password storage. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> otherinfo = b"concatkdf-example" + >>> ckdf = ConcatKDFHash( + ... algorithm=hashes.SHA256(), + ... length=256, + ... otherinfo=otherinfo, + ... backend=backend + ... ) + >>> key = ckdf.derive(b"input key") + >>> ckdf = ConcatKDFHash( + ... algorithm=hashes.SHA256(), + ... length=256, + ... otherinfo=otherinfo, + ... backend=backend + ... ) + >>> ckdf.verify(b"input key", key) + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :param int length: The desired length of the derived key in bytes. + Maximum is ``hashlen * (2^32 -1)``. + + :param bytes otherinfo: Application specific context information. + If ``None`` is explicitly passed an empty byte string will be used. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.HashBackend`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised + if the provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.HashBackend` + + :raises TypeError: This exception is raised if ``otherinfo`` is not + ``bytes``. + + .. method:: derive(key_material) + + :param bytes key_material: The input key material. + :return bytes: The derived key. + :raises TypeError: This exception is raised if ``key_material`` is + not ``bytes``. + + Derives a new key from the input key material. + + .. method:: verify(key_material, expected_key) + + :param bytes key_material: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param bytes expected_key: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. + + +.. class:: ConcatKDFHMAC(algorithm, length, salt, otherinfo, backend) + + .. versionadded:: 1.0 + + Similar to ConcatKFDHash but uses an HMAC function instead. + + .. warning:: + + ConcatKDFHMAC should not be used for password storage. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHMAC + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> salt = os.urandom(16) + >>> otherinfo = b"concatkdf-example" + >>> ckdf = ConcatKDFHMAC( + ... algorithm=hashes.SHA256(), + ... length=256, + ... salt=salt, + ... otherinfo=otherinfo, + ... backend=backend + ... ) + >>> key = ckdf.derive(b"input key") + >>> ckdf = ConcatKDFHMAC( + ... algorithm=hashes.SHA256(), + ... length=256, + ... salt=salt, + ... otherinfo=otherinfo, + ... backend=backend + ... ) + >>> ckdf.verify(b"input key", key) + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :param int length: The desired length of the derived key in bytes. Maximum + is ``hashlen * (2^32 -1)``. + + :param bytes salt: A salt. Randomizes the KDF's output. Optional, but + highly recommended. Ideally as many bits of entropy as the security + level of the hash: often that means cryptographically random and as + long as the hash output. Does not have to be secret, but may cause + stronger security guarantees if secret; If ``None`` is explicitly + passed a default salt of ``algorithm.block_size`` null bytes will be + used. + + :param bytes otherinfo: Application specific context information. + If ``None`` is explicitly passed an empty byte string will be used. + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + + :raises TypeError: This exception is raised if ``salt`` or ``otherinfo`` + is not ``bytes``. + + .. method:: derive(key_material) + + :param bytes key_material: The input key material. + :return bytes: The derived key. + :raises TypeError: This exception is raised if ``key_material`` is not + ``bytes``. + + Derives a new key from the input key material. + + .. method:: verify(key_material, expected_key) + + :param bytes key_material: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param bytes expected_key: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. + +.. currentmodule:: cryptography.hazmat.primitives.kdf.x963kdf + +.. class:: X963KDF(algorithm, length, otherinfo, backend) + + .. versionadded:: 1.1 + + X963KDF (ANSI X9.63 Key Derivation Function) is defined by ANSI + in the `ANSI X9.63:2001`_ document, to be used to derive keys for use + after a Key Exchange negotiation operation. + + SECG in `SEC 1 v2.0`_ recommends that + :class:`~cryptography.hazmat.primitives.kdf.concatkdf.ConcatKDFHash` be + used for new projects. This KDF should only be used for backwards + compatibility with pre-existing protocols. + + + .. warning:: + + X963KDF should not be used for password storage. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> sharedinfo = b"ANSI X9.63 Example" + >>> xkdf = X963KDF( + ... algorithm=hashes.SHA256(), + ... length=256, + ... sharedinfo=sharedinfo, + ... backend=backend + ... ) + >>> key = xkdf.derive(b"input key") + >>> xkdf = X963KDF( + ... algorithm=hashes.SHA256(), + ... length=256, + ... sharedinfo=sharedinfo, + ... backend=backend + ... ) + >>> xkdf.verify(b"input key", key) + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :param int length: The desired length of the derived key in bytes. + Maximum is ``hashlen * (2^32 -1)``. + + :param bytes sharedinfo: Application specific context information. + If ``None`` is explicitly passed an empty byte string will be used. + + :param backend: A cryptography backend + :class:`~cryptography.hazmat.backends.interfaces.HashBackend` + instance. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised + if the provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.HashBackend` + + :raises TypeError: This exception is raised if ``sharedinfo`` is not + ``bytes``. + + .. method:: derive(key_material) + + :param bytes key_material: The input key material. + :return bytes: The derived key. + :raises TypeError: This exception is raised if ``key_material`` is + not ``bytes``. + + Derives a new key from the input key material. + + .. method:: verify(key_material, expected_key) + + :param bytes key_material: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param bytes expected_key: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. + + +.. currentmodule:: cryptography.hazmat.primitives.kdf.kbkdf + +.. class:: KBKDFHMAC(algorithm, mode, length, rlen, llen, location,\ + label, context, fixed, backend) + + .. versionadded:: 1.4 + + KBKDF (Key Based Key Derivation Function) is defined by the + `NIST SP 800-108`_ document, to be used to derive additional + keys from a key that has been established through an automated + key-establishment scheme. + + .. warning:: + + KBKDFHMAC should not be used for password storage. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.kdf.kbkdf import ( + ... CounterLocation, KBKDFHMAC, Mode + ... ) + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> label = b"KBKDF HMAC Label" + >>> context = b"KBKDF HMAC Context" + >>> kdf = KBKDFHMAC( + ... algorithm=hashes.SHA256(), + ... mode=Mode.CounterMode, + ... length=256, + ... rlen=4, + ... llen=4, + ... location=CounterLocation.BeforeFixed, + ... label=label, + ... context=context, + ... fixed=None, + ... backend=backend + ... ) + >>> key = kdf.derive(b"input key") + >>> kdf = KBKDFHMAC( + ... algorithm=hashes.SHA256(), + ... mode=Mode.CounterMode, + ... length=256, + ... rlen=4, + ... llen=4, + ... location=CounterLocation.BeforeFixed, + ... label=label, + ... context=context, + ... fixed=None, + ... backend=backend + ... ) + >>> kdf.verify(b"input key", key) + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm`. + + :param mode: The desired mode of the PRF. A value from the + :class:`~cryptography.hazmat.primitives.kdf.kbkdf.Mode` enum. + + :param int length: The desired length of the derived key in bytes. + + :param int rlen: An integer that indicates the length of the binary + representation of the counter in bytes. + + :param int llen: An integer that indicates the binary + representation of the ``length`` in bytes. + + :param location: The desired location of the counter. A value from the + :class:`~cryptography.hazmat.primitives.kdf.kbkdf.CounterLocation` enum. + + :param bytes label: Application specific label information. If ``None`` + is explicitly passed an empty byte string will be used. + + :param bytes context: Application specific context information. If ``None`` + is explicitly passed an empty byte string will be used. + + :param bytes fixed: Instead of specifying ``label`` and ``context`` you + may supply your own fixed data. If ``fixed`` is specified, ``label`` + and ``context`` is ignored. + + :param backend: A cryptography backend + :class:`~cryptography.hazmat.backends.interfaces.HashBackend` + instance. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised + if the provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.HashBackend` + + :raises TypeError: This exception is raised if ``label`` or ``context`` + is not ``bytes``. Also raised if ``rlen`` or ``llen`` is not ``int``. + + :raises ValueError: This exception is raised if ``rlen`` or ``llen`` + is greater than 4 or less than 1. This exception is also raised if + you specify a ``label`` or ``context`` and ``fixed``. + + .. method:: derive(key_material) + + :param bytes key_material: The input key material. + :return bytes: The derived key. + :raises TypeError: This exception is raised if ``key_material`` is + not ``bytes``. + + Derives a new key from the input key material. + + .. method:: verify(key_material, expected_key) + + :param bytes key_material: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param bytes expected_key: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. + +.. class:: Mode + + An enumeration for the key based key derivative modes. + + .. attribute:: CounterMode + + The output of the PRF is computed with a counter + as the iteration variable. + +.. class:: CounterLocation + + An enumeration for the key based key derivative counter location. + + .. attribute:: BeforeFixed + + The counter iteration variable will be concatenated before + the fixed input data. + + .. attribute:: AfterFixed + + The counter iteration variable will be concatenated after + the fixed input data. + +.. currentmodule:: cryptography.hazmat.primitives.kdf.scrypt + +.. class:: Scrypt(salt, length, n, r, p, backend) + + .. versionadded:: 1.6 + + Scrypt is a KDF designed for password storage by Colin Percival to be + resistant against hardware-assisted attackers by having a tunable memory + cost. It is described in :rfc:`7914`. + + This class conforms to the + :class:`~cryptography.hazmat.primitives.kdf.KeyDerivationFunction` + interface. + + .. code-block:: python + + >>> import os + >>> from cryptography.hazmat.primitives.kdf.scrypt import Scrypt + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> salt = os.urandom(16) + >>> # derive + >>> kdf = Scrypt( + ... salt=salt, + ... length=64, + ... n=2**14, + ... r=8, + ... p=1, + ... backend=backend + ... ) + >>> key = kdf.derive(b"my great password") + >>> # verify + >>> kdf = Scrypt( + ... salt=salt, + ... length=64, + ... n=2**14, + ... r=8, + ... p=1, + ... backend=backend + ... ) + >>> kdf.verify(b"my great password", key) + + :param bytes salt: A salt. + :param int length: The desired length of the derived key. + :param int n: CPU/Memory cost parameter. It must be larger than 1 and be a + power of 2. + :param int r: Block size parameter. + :param int p: Parallelization parameter. + + The computational and memory cost of Scrypt can be adjusted by manipulating + the 3 parameters: ``n``, ``r``, and ``p``. In general, the memory cost of + Scrypt is affected by the values of both ``n`` and ``r``, while ``n`` also + determines the number of iterations performed. ``p`` increases the + computational cost without affecting memory usage. A more in-depth + explanation of the 3 parameters can be found `here`_. + + :rfc:`7914` `recommends`_ values of ``r=8`` and ``p=1`` while scaling ``n`` + to a number appropriate for your system. `The scrypt paper`_ suggests a + minimum value of ``n=2**14`` for interactive logins (t < 100ms), or + ``n=2**20`` for more sensitive files (t < 5s). + + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.ScryptBackend`. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.ScryptBackend` + + :raises TypeError: This exception is raised if ``salt`` is not ``bytes``. + :raises ValueError: This exception is raised if ``n`` is less than 2, if + ``n`` is not a power of 2, if ``r`` is less than 1 or if ``p`` is less + than 1. + + .. method:: derive(key_material) + + :param bytes key_material: The input key material. + :return bytes: the derived key. + :raises TypeError: This exception is raised if ``key_material`` is not + ``bytes``. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This generates and returns a new key from the supplied password. + + .. method:: verify(key_material, expected_key) + + :param bytes key_material: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param bytes expected_key: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. This can be used for + checking whether the password a user provides matches the stored derived + key. + +Interface +~~~~~~~~~ + +.. currentmodule:: cryptography.hazmat.primitives.kdf + +.. class:: KeyDerivationFunction + + .. versionadded:: 0.2 + + .. method:: derive(key_material) + + :param bytes key_material: The input key material. Depending on what + key derivation function you are using this + could be either random bytes, or a user + supplied password. + :return: The new key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This generates and returns a new key from the supplied key material. + + .. method:: verify(key_material, expected_key) + + :param bytes key_material: The input key material. This is the same as + ``key_material`` in :meth:`derive`. + :param bytes expected_key: The expected result of deriving a new key, + this is the same as the return value of + :meth:`derive`. + :raises cryptography.exceptions.InvalidKey: This is raised when the + derived key does not match + the expected key. + :raises cryptography.exceptions.AlreadyFinalized: This is raised when + :meth:`derive` or + :meth:`verify` is + called more than + once. + + This checks whether deriving a new key from the supplied + ``key_material`` generates the same key as the ``expected_key``, and + raises an exception if they do not match. This can be used for + something like checking whether a user's password attempt matches the + stored derived key. + + +.. _`NIST SP 800-132`: http://csrc.nist.gov/publications/nistpubs/800-132/nist-sp800-132.pdf +.. _`NIST SP 800-108`: http://csrc.nist.gov/publications/nistpubs/800-108/sp800-108.pdf +.. _`NIST SP 800-56Ar2`: http://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-56Ar2.pdf +.. _`ANSI X9.63:2001`: https://webstore.ansi.org +.. _`SEC 1 v2.0`: http://www.secg.org/sec1-v2.pdf +.. _`Password Storage Cheat Sheet`: https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet +.. _`PBKDF2`: https://en.wikipedia.org/wiki/PBKDF2 +.. _`key stretching`: https://en.wikipedia.org/wiki/Key_stretching +.. _`HKDF`: https://en.wikipedia.org/wiki/HKDF +.. _`HKDF paper`: https://eprint.iacr.org/2010/264 +.. _`here`: https://stackoverflow.com/a/30308723/1170681 +.. _`recommends`: https://tools.ietf.org/html/rfc7914#section-2 +.. _`The scrypt paper`: https://www.tarsnap.com/scrypt/scrypt.pdf diff --git a/docs/hazmat/primitives/keywrap.rst b/docs/hazmat/primitives/keywrap.rst new file mode 100644 index 0000000..95cae8d --- /dev/null +++ b/docs/hazmat/primitives/keywrap.rst @@ -0,0 +1,59 @@ +.. hazmat:: + +.. module:: cryptography.hazmat.primitives.keywrap + +Key wrapping +============ + +Key wrapping is a cryptographic construct that uses symmetric encryption to +encapsulate key material. Key wrapping algorithms are occasionally utilized +to protect keys at rest or transmit them over insecure networks. Many of the +protections offered by key wrapping are also offered by using authenticated +:doc:`symmetric encryption `. + +.. function:: aes_key_wrap(wrapping_key, key_to_wrap, backend) + + .. versionadded:: 1.1 + + This function performs AES key wrap (without padding) as specified in + :rfc:`3394`. + + :param bytes wrapping_key: The wrapping key. + + :param bytes key_to_wrap: The key to wrap. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` + instance that supports + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES`. + + :return bytes: The wrapped key as bytes. + +.. function:: aes_key_unwrap(wrapping_key, wrapped_key, backend) + + .. versionadded:: 1.1 + + This function performs AES key unwrap (without padding) as specified in + :rfc:`3394`. + + :param bytes wrapping_key: The wrapping key. + + :param bytes wrapped_key: The wrapped key. + + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` + instance that supports + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES`. + + :return bytes: The unwrapped key as bytes. + + :raises cryptography.hazmat.primitives.keywrap.InvalidUnwrap: This is + raised if the key is not successfully unwrapped. + +Exceptions +~~~~~~~~~~ + +.. class:: InvalidUnwrap + + This is raised when a wrapped key fails to unwrap. It can be caused by a + corrupted or invalid wrapped key or an invalid wrapping key. diff --git a/docs/hazmat/primitives/mac/cmac.rst b/docs/hazmat/primitives/mac/cmac.rst new file mode 100644 index 0000000..e170db3 --- /dev/null +++ b/docs/hazmat/primitives/mac/cmac.rst @@ -0,0 +1,110 @@ +.. hazmat:: + +Cipher-based message authentication code +======================================== + +.. currentmodule:: cryptography.hazmat.primitives.cmac + +.. testsetup:: + + import binascii + key = binascii.unhexlify(b"0" * 32) + +`Cipher-based message authentication codes`_ (or CMACs) are a tool for +calculating message authentication codes using a block cipher coupled with a +secret key. You can use an CMAC to verify both the integrity and authenticity +of a message. + +A subset of CMAC with the AES-128 algorithm is described in :rfc:`4493`. + +.. class:: CMAC(algorithm, backend) + + .. versionadded:: 0.4 + + CMAC objects take a + :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm` instance. + + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import cmac + >>> from cryptography.hazmat.primitives.ciphers import algorithms + >>> c = cmac.CMAC(algorithms.AES(key), backend=default_backend()) + >>> c.update(b"message to authenticate") + >>> c.finalize() + 'CT\x1d\xc8\x0e\x15\xbe4e\xdb\xb6\x84\xca\xd9Xk' + + If the backend doesn't support the requested ``algorithm`` an + :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be + raised. + + If ``algorithm`` isn't a + :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm` + instance then ``TypeError`` will be raised. + + To check that a given signature is correct use the :meth:`verify` method. + You will receive an exception if the signature is wrong: + + .. doctest:: + + >>> c = cmac.CMAC(algorithms.AES(key), backend=default_backend()) + >>> c.update(b"message to authenticate") + >>> c.verify(b"an incorrect signature") + Traceback (most recent call last): + ... + cryptography.exceptions.InvalidSignature: Signature did not match digest. + + :param algorithm: An instance of + :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm`. + :param backend: An instance of + :class:`~cryptography.hazmat.backends.interfaces.CMACBackend`. + :raises TypeError: This is raised if the provided ``algorithm`` is not an instance of + :class:`~cryptography.hazmat.primitives.ciphers.BlockCipherAlgorithm` + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.CMACBackend` + + .. method:: update(data) + + :param bytes data: The bytes to hash and authenticate. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` + :raises TypeError: This exception is raised if ``data`` is not ``bytes``. + + .. method:: copy() + + Copy this :class:`CMAC` instance, usually so that we may call + :meth:`finalize` to get an intermediate value while we continue + to call :meth:`update` on the original instance. + + :return: A new instance of :class:`CMAC` that can be updated + and finalized independently of the original instance. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` + + .. method:: verify(signature) + + Finalize the current context and securely compare the MAC to + ``signature``. + + :param bytes signature: The bytes to compare the current CMAC + against. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` + :raises cryptography.exceptions.InvalidSignature: If signature does not + match digest + :raises TypeError: This exception is raised if ``signature`` is not + ``bytes``. + + .. method:: finalize() + + Finalize the current context and return the message authentication code + as bytes. + + After ``finalize`` has been called this object can no longer be used + and :meth:`update`, :meth:`copy`, :meth:`verify` and :meth:`finalize` + will raise an :class:`~cryptography.exceptions.AlreadyFinalized` + exception. + + :return bytes: The message authentication code as bytes. + :raises cryptography.exceptions.AlreadyFinalized: + + +.. _`Cipher-based message authentication codes`: https://en.wikipedia.org/wiki/CMAC diff --git a/docs/hazmat/primitives/mac/hmac.rst b/docs/hazmat/primitives/mac/hmac.rst new file mode 100644 index 0000000..e00c4de --- /dev/null +++ b/docs/hazmat/primitives/mac/hmac.rst @@ -0,0 +1,109 @@ +.. hazmat:: + +Hash-based message authentication codes +======================================= + +.. currentmodule:: cryptography.hazmat.primitives.hmac + +.. testsetup:: + + import binascii + key = binascii.unhexlify(b"0" * 32) + +Hash-based message authentication codes (or HMACs) are a tool for calculating +message authentication codes using a cryptographic hash function coupled with a +secret key. You can use an HMAC to verify both the integrity and authenticity +of a message. + +.. class:: HMAC(key, algorithm, backend) + + HMAC objects take a ``key`` and a + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` instance. + The ``key`` should be :doc:`randomly generated bytes ` and + is recommended to be equal in length to the ``digest_size`` of the hash + function chosen. You must keep the ``key`` secret. + + This is an implementation of :rfc:`2104`. + + .. doctest:: + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes, hmac + >>> h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend()) + >>> h.update(b"message to hash") + >>> h.finalize() + '#F\xdaI\x8b"e\xc4\xf1\xbb\x9a\x8fc\xff\xf5\xdex.\xbc\xcd/+\x8a\x86\x1d\x84\'\xc3\xa6\x1d\xd8J' + + If the backend doesn't support the requested ``algorithm`` an + :class:`~cryptography.exceptions.UnsupportedAlgorithm` exception will be + raised. + + If ``algorithm`` isn't a + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` instance + then ``TypeError`` will be raised. + + To check that a given signature is correct use the :meth:`verify` method. + You will receive an exception if the signature is wrong: + + .. doctest:: + + >>> h = hmac.HMAC(key, hashes.SHA256(), backend=default_backend()) + >>> h.update(b"message to hash") + >>> h.verify(b"an incorrect signature") + Traceback (most recent call last): + ... + cryptography.exceptions.InvalidSignature: Signature did not match digest. + + :param bytes key: Secret key as ``bytes``. + :param algorithm: An + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + instance such as those described in + :ref:`Cryptographic Hashes `. + :param backend: An + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + instance. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + + .. method:: update(msg) + + :param bytes msg: The bytes to hash and authenticate. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` + :raises TypeError: This exception is raised if ``msg`` is not ``bytes``. + + .. method:: copy() + + Copy this :class:`HMAC` instance, usually so that we may call + :meth:`finalize` to get an intermediate digest value while we continue + to call :meth:`update` on the original instance. + + :return: A new instance of :class:`HMAC` that can be updated + and finalized independently of the original instance. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` + + .. method:: verify(signature) + + Finalize the current context and securely compare digest to + ``signature``. + + :param bytes signature: The bytes to compare the current digest + against. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` + :raises cryptography.exceptions.InvalidSignature: If signature does not + match digest + :raises TypeError: This exception is raised if ``signature`` is not + ``bytes``. + + .. method:: finalize() + + Finalize the current context and return the message digest as bytes. + + After ``finalize`` has been called this object can no longer be used + and :meth:`update`, :meth:`copy`, :meth:`verify` and :meth:`finalize` + will raise an :class:`~cryptography.exceptions.AlreadyFinalized` + exception. + + :return bytes: The message digest as bytes. + :raises cryptography.exceptions.AlreadyFinalized: diff --git a/docs/hazmat/primitives/mac/index.rst b/docs/hazmat/primitives/mac/index.rst new file mode 100644 index 0000000..05db708 --- /dev/null +++ b/docs/hazmat/primitives/mac/index.rst @@ -0,0 +1,18 @@ +.. hazmat:: + +Message authentication codes +============================ + +While cryptography supports both the CMAC and HMAC algorithms, we strongly +recommend that HMAC should be used unless you have a good reason otherwise. + +For more information on why HMAC is preferred, see `Use cases for CMAC vs. +HMAC?`_ + +.. _`Use cases for CMAC vs. HMAC?`: https://crypto.stackexchange.com/questions/15721/use-cases-for-cmac-vs-hmac + +.. toctree:: + :maxdepth: 1 + + cmac + hmac diff --git a/docs/hazmat/primitives/padding.rst b/docs/hazmat/primitives/padding.rst new file mode 100644 index 0000000..916b9bc --- /dev/null +++ b/docs/hazmat/primitives/padding.rst @@ -0,0 +1,129 @@ +.. hazmat:: + +Padding +======= + +.. module:: cryptography.hazmat.primitives.padding + +Padding is a way to take data that may or may not be a multiple of the block +size for a cipher and extend it out so that it is. This is required for many +block cipher modes as they require the data to be encrypted to be an exact +multiple of the block size. + + +.. class:: PKCS7(block_size) + + PKCS7 padding is a generalization of PKCS5 padding (also known as standard + padding). PKCS7 padding works by appending ``N`` bytes with the value of + ``chr(N)``, where ``N`` is the number of bytes required to make the final + block of data the same size as the block size. A simple example of padding + is: + + .. doctest:: + + >>> from cryptography.hazmat.primitives import padding + >>> padder = padding.PKCS7(128).padder() + >>> padded_data = padder.update(b"11111111111111112222222222") + >>> padded_data + '1111111111111111' + >>> padded_data += padder.finalize() + >>> padded_data + '11111111111111112222222222\x06\x06\x06\x06\x06\x06' + >>> unpadder = padding.PKCS7(128).unpadder() + >>> data = unpadder.update(padded_data) + >>> data + '1111111111111111' + >>> data + unpadder.finalize() + '11111111111111112222222222' + + :param block_size: The size of the block in bits that the data is being + padded to. + :raises ValueError: Raised if block size is not a multiple of 8 or is not + between 0 and 2040 inclusive. + + .. method:: padder() + + :returns: A padding + :class:`~cryptography.hazmat.primitives.padding.PaddingContext` + instance. + + .. method:: unpadder() + + :returns: An unpadding + :class:`~cryptography.hazmat.primitives.padding.PaddingContext` + instance. + + +.. class:: ANSIX923(block_size) + + .. versionadded:: 1.3 + + `ANSI X.923`_ padding works by appending ``N-1`` bytes with the value of + ``0`` and a last byte with the value of ``chr(N)``, where ``N`` is the + number of bytes required to make the final block of data the same size as + the block size. A simple example of padding is: + + .. doctest:: + + >>> padder = padding.ANSIX923(128).padder() + >>> padded_data = padder.update(b"11111111111111112222222222") + >>> padded_data + '1111111111111111' + >>> padded_data += padder.finalize() + >>> padded_data + '11111111111111112222222222\x00\x00\x00\x00\x00\x06' + >>> unpadder = padding.ANSIX923(128).unpadder() + >>> data = unpadder.update(padded_data) + >>> data + '1111111111111111' + >>> data + unpadder.finalize() + '11111111111111112222222222' + + :param block_size: The size of the block in bits that the data is being + padded to. + :raises ValueError: Raised if block size is not a multiple of 8 or is not + between 0 and 2040 inclusive. + + .. method:: padder() + + :returns: A padding + :class:`~cryptography.hazmat.primitives.padding.PaddingContext` + instance. + + .. method:: unpadder() + + :returns: An unpadding + :class:`~cryptography.hazmat.primitives.padding.PaddingContext` + instance. + + +.. class:: PaddingContext + + When calling ``padder()`` or ``unpadder()`` the result will conform to the + ``PaddingContext`` interface. You can then call ``update(data)`` with data + until you have fed everything into the context. Once that is done call + ``finalize()`` to finish the operation and obtain the remainder of the + data. + + .. method:: update(data) + + :param bytes data: The data you wish to pass into the context. + :return bytes: Returns the data that was padded or unpadded. + :raises TypeError: Raised if data is not bytes. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize`. + :raises TypeError: This exception is raised if ``data`` is not ``bytes``. + + .. method:: finalize() + + Finalize the current context and return the rest of the data. + + After ``finalize`` has been called this object can no longer be used; + :meth:`update` and :meth:`finalize` will raise an + :class:`~cryptography.exceptions.AlreadyFinalized` exception. + + :return bytes: Returns the remainder of the data. + :raises TypeError: Raised if data is not bytes. + :raises ValueError: When trying to remove padding from incorrectly + padded data. + +.. _`ANSI X.923`: https://en.wikipedia.org/wiki/Padding_%28cryptography%29#ANSI_X.923 diff --git a/docs/hazmat/primitives/symmetric-encryption.rst b/docs/hazmat/primitives/symmetric-encryption.rst new file mode 100644 index 0000000..24b2c04 --- /dev/null +++ b/docs/hazmat/primitives/symmetric-encryption.rst @@ -0,0 +1,616 @@ +.. hazmat:: /fernet + + +Symmetric encryption +==================== + +.. module:: cryptography.hazmat.primitives.ciphers + +Symmetric encryption is a way to `encrypt`_ or hide the contents of material +where the sender and receiver both use the same secret key. Note that symmetric +encryption is **not** sufficient for most applications because it only +provides secrecy but not authenticity. That means an attacker can't see the +message but an attacker can create bogus messages and force the application to +decrypt them. + +For this reason it is **strongly** recommended to combine encryption with a +message authentication code, such as :doc:`HMAC `, +in an "encrypt-then-MAC" formulation as `described by Colin Percival`_. + +.. class:: Cipher(algorithm, mode, backend) + + Cipher objects combine an algorithm such as + :class:`~cryptography.hazmat.primitives.ciphers.algorithms.AES` with a + mode like + :class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` or + :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR`. A simple + example of encrypting and then decrypting content with AES is: + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + >>> from cryptography.hazmat.backends import default_backend + >>> backend = default_backend() + >>> key = os.urandom(32) + >>> iv = os.urandom(16) + >>> cipher = Cipher(algorithms.AES(key), modes.CBC(iv), backend=backend) + >>> encryptor = cipher.encryptor() + >>> ct = encryptor.update(b"a secret message") + encryptor.finalize() + >>> decryptor = cipher.decryptor() + >>> decryptor.update(ct) + decryptor.finalize() + 'a secret message' + + :param algorithms: A + :class:`~cryptography.hazmat.primitives.ciphers.CipherAlgorithm` + instance such as those described + :ref:`below `. + :param mode: A :class:`~cryptography.hazmat.primitives.ciphers.modes.Mode` + instance such as those described + :ref:`below `. + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` + instance. + + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.CipherBackend` + + .. method:: encryptor() + + :return: An encrypting + :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` + instance. + + If the backend doesn't support the requested combination of ``cipher`` + and ``mode`` an :class:`~cryptography.exceptions.UnsupportedAlgorithm` + exception will be raised. + + .. method:: decryptor() + + :return: A decrypting + :class:`~cryptography.hazmat.primitives.ciphers.CipherContext` + instance. + + If the backend doesn't support the requested combination of ``cipher`` + and ``mode`` an :class:`~cryptography.exceptions.UnsupportedAlgorithm` + exception will be raised. + +.. _symmetric-encryption-algorithms: + +Algorithms +~~~~~~~~~~ + +.. currentmodule:: cryptography.hazmat.primitives.ciphers.algorithms + +.. class:: AES(key) + + AES (Advanced Encryption Standard) is a block cipher standardized by NIST. + AES is both fast, and cryptographically strong. It is a good default + choice for encryption. + + :param bytes key: The secret key. This must be kept secret. Either ``128``, + ``192``, or ``256`` bits long. + +.. class:: Camellia(key) + + Camellia is a block cipher approved for use by `CRYPTREC`_ and ISO/IEC. + It is considered to have comparable security and performance to AES but + is not as widely studied or deployed. + + :param bytes key: The secret key. This must be kept secret. Either ``128``, + ``192``, or ``256`` bits long. + +.. class:: TripleDES(key) + + Triple DES (Data Encryption Standard), sometimes referred to as 3DES, is a + block cipher standardized by NIST. Triple DES has known crypto-analytic + flaws, however none of them currently enable a practical attack. + Nonetheless, Triple DES is not recommended for new applications because it + is incredibly slow; old applications should consider moving away from it. + + :param bytes key: The secret key. This must be kept secret. Either ``64``, + ``128``, or ``192`` bits long. DES only uses ``56``, ``112``, or ``168`` + bits of the key as there is a parity byte in each component of the key. + Some writing refers to there being up to three separate keys that are each + ``56`` bits long, they can simply be concatenated to produce the full key. + +.. class:: CAST5(key) + + .. versionadded:: 0.2 + + CAST5 (also known as CAST-128) is a block cipher approved for use in the + Canadian government by the `Communications Security Establishment`_. It is + a variable key length cipher and supports keys from 40-128 bits in length. + + :param bytes key: The secret key, This must be kept secret. 40 to 128 bits + in length in increments of 8 bits. + +.. class:: SEED(key) + + .. versionadded:: 0.4 + + SEED is a block cipher developed by the Korea Information Security Agency + (KISA). It is defined in :rfc:`4269` and is used broadly throughout South + Korean industry, but rarely found elsewhere. + + :param bytes key: The secret key. This must be kept secret. ``128`` bits in + length. + +Weak ciphers +------------ + +.. warning:: + + These ciphers are considered weak for a variety of reasons. New + applications should avoid their use and existing applications should + strongly consider migrating away. + +.. class:: Blowfish(key) + + Blowfish is a block cipher developed by Bruce Schneier. It is known to be + susceptible to attacks when using weak keys. The author has recommended + that users of Blowfish move to newer algorithms such as :class:`AES`. + + :param bytes key: The secret key. This must be kept secret. 32 to 448 bits + in length in increments of 8 bits. + +.. class:: ARC4(key) + + ARC4 (Alleged RC4) is a stream cipher with serious weaknesses in its + initial stream output. Its use is strongly discouraged. ARC4 does not use + mode constructions. + + :param bytes key: The secret key. This must be kept secret. Either ``40``, + ``56``, ``64``, ``80``, ``128``, ``192``, or ``256`` bits in length. + + .. doctest:: + + >>> from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + >>> from cryptography.hazmat.backends import default_backend + >>> algorithm = algorithms.ARC4(key) + >>> cipher = Cipher(algorithm, mode=None, backend=default_backend()) + >>> encryptor = cipher.encryptor() + >>> ct = encryptor.update(b"a secret message") + >>> decryptor = cipher.decryptor() + >>> decryptor.update(ct) + 'a secret message' + +.. class:: IDEA(key) + + IDEA (`International Data Encryption Algorithm`_) is a block cipher created + in 1991. It is an optional component of the `OpenPGP`_ standard. This cipher + is susceptible to attacks when using weak keys. It is recommended that you + do not use this cipher for new applications. + + :param bytes key: The secret key. This must be kept secret. ``128`` bits in + length. + + +.. _symmetric-encryption-modes: + +Modes +~~~~~ + +.. module:: cryptography.hazmat.primitives.ciphers.modes + +.. class:: CBC(initialization_vector) + + CBC (Cipher Block Chaining) is a mode of operation for block ciphers. It is + considered cryptographically strong. + + **Padding is required when using this mode.** + + :param bytes initialization_vector: Must be :doc:`random bytes + `. They do not need to be kept secret and they can be + included in a transmitted message. Must be the same number of bytes as + the ``block_size`` of the cipher. Each time something is encrypted a + new ``initialization_vector`` should be generated. Do not reuse an + ``initialization_vector`` with a given ``key``, and particularly do not + use a constant ``initialization_vector``. + + A good construction looks like: + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.primitives.ciphers.modes import CBC + >>> iv = os.urandom(16) + >>> mode = CBC(iv) + + While the following is bad and will leak information: + + .. doctest:: + + >>> from cryptography.hazmat.primitives.ciphers.modes import CBC + >>> iv = "a" * 16 + >>> mode = CBC(iv) + + +.. class:: CTR(nonce) + + .. warning:: + + Counter mode is not recommended for use with block ciphers that have a + block size of less than 128-bits. + + CTR (Counter) is a mode of operation for block ciphers. It is considered + cryptographically strong. It transforms a block cipher into a stream + cipher. + + **This mode does not require padding.** + + :param bytes nonce: Should be unique, a :term:`nonce`. It is + critical to never reuse a ``nonce`` with a given key. Any reuse of a + nonce with the same key compromises the security of every message + encrypted with that key. Must be the same number of bytes as the + ``block_size`` of the cipher with a given key. The nonce does not need + to be kept secret and may be included with the ciphertext. + +.. class:: OFB(initialization_vector) + + OFB (Output Feedback) is a mode of operation for block ciphers. It + transforms a block cipher into a stream cipher. + + **This mode does not require padding.** + + :param bytes initialization_vector: Must be :doc:`random bytes + `. They do not need to be kept secret and they can be + included in a transmitted message. Must be the same number of bytes as + the ``block_size`` of the cipher. Do not reuse an + ``initialization_vector`` with a given ``key``. + +.. class:: CFB(initialization_vector) + + CFB (Cipher Feedback) is a mode of operation for block ciphers. It + transforms a block cipher into a stream cipher. + + **This mode does not require padding.** + + :param bytes initialization_vector: Must be :doc:`random bytes + `. They do not need to be kept secret and they can be + included in a transmitted message. Must be the same number of bytes as + the ``block_size`` of the cipher. Do not reuse an + ``initialization_vector`` with a given ``key``. + +.. class:: CFB8(initialization_vector) + + CFB (Cipher Feedback) is a mode of operation for block ciphers. It + transforms a block cipher into a stream cipher. The CFB8 variant uses an + 8-bit shift register. + + **This mode does not require padding.** + + :param bytes initialization_vector: Must be :doc:`random bytes + `. They do not need to be kept secret and they can be + included in a transmitted message. Must be the same number of bytes as + the ``block_size`` of the cipher. Do not reuse an + ``initialization_vector`` with a given ``key``. + +.. class:: GCM(initialization_vector, tag=None, min_tag_length=16) + + .. danger:: + + When using this mode you **must** not use the decrypted data until + :meth:`~cryptography.hazmat.primitives.ciphers.CipherContext.finalize` + has been called. GCM provides **no** guarantees of ciphertext integrity + until decryption is complete. + + GCM (Galois Counter Mode) is a mode of operation for block ciphers. An + AEAD (authenticated encryption with additional data) mode is a type of + block cipher mode that simultaneously encrypts the message as well as + authenticating it. Additional unencrypted data may also be authenticated. + Additional means of verifying integrity such as + :doc:`HMAC ` are not necessary. + + **This mode does not require padding.** + + :param bytes initialization_vector: Must be unique, a :term:`nonce`. + They do not need to be kept secret and they can be included in a + transmitted message. NIST `recommends a 96-bit IV length`_ for + performance critical situations but it can be up to 2\ :sup:`64` - 1 + bits. Do not reuse an ``initialization_vector`` with a given ``key``. + + .. note:: + + Cryptography will generate a 128-bit tag when finalizing encryption. + You can shorten a tag by truncating it to the desired length but this + is **not recommended** as it lowers the security margins of the + authentication (`NIST SP-800-38D`_ recommends 96-bits or greater). + Applications wishing to allow truncation must pass the + ``min_tag_length`` parameter. + + .. versionchanged:: 0.5 + + The ``min_tag_length`` parameter was added in ``0.5``, previously + truncation down to ``4`` bytes was always allowed. + + :param bytes tag: The tag bytes to verify during decryption. When + encrypting this must be ``None``. + + :param bytes min_tag_length: The minimum length ``tag`` must be. By default + this is ``16``, meaning tag truncation is not allowed. Allowing tag + truncation is strongly discouraged for most applications. + + :raises ValueError: This is raised if ``len(tag) < min_tag_length``. + + An example of securely encrypting and decrypting data with ``AES`` in the + ``GCM`` mode looks like: + + .. testcode:: + + import os + + from cryptography.hazmat.primitives.ciphers import ( + Cipher, algorithms, modes + ) + + def encrypt(key, plaintext, associated_data): + # Generate a random 96-bit IV. + iv = os.urandom(12) + + # Construct an AES-GCM Cipher object with the given key and a + # randomly generated IV. + encryptor = Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=default_backend() + ).encryptor() + + # associated_data will be authenticated but not encrypted, + # it must also be passed in on decryption. + encryptor.authenticate_additional_data(associated_data) + + # Encrypt the plaintext and get the associated ciphertext. + # GCM does not require padding. + ciphertext = encryptor.update(plaintext) + encryptor.finalize() + + return (iv, ciphertext, encryptor.tag) + + def decrypt(key, associated_data, iv, ciphertext, tag): + # Construct a Cipher object, with the key, iv, and additionally the + # GCM tag used for authenticating the message. + decryptor = Cipher( + algorithms.AES(key), + modes.GCM(iv, tag), + backend=default_backend() + ).decryptor() + + # We put associated_data back in or the tag will fail to verify + # when we finalize the decryptor. + decryptor.authenticate_additional_data(associated_data) + + # Decryption gets us the authenticated plaintext. + # If the tag does not match an InvalidTag exception will be raised. + return decryptor.update(ciphertext) + decryptor.finalize() + + iv, ciphertext, tag = encrypt( + key, + b"a secret message!", + b"authenticated but not encrypted payload" + ) + + print(decrypt( + key, + b"authenticated but not encrypted payload", + iv, + ciphertext, + tag + )) + + .. testoutput:: + + a secret message! + + +Insecure modes +-------------- + +.. warning:: + + These modes are insecure. New applications should never make use of them, + and existing applications should strongly consider migrating away. + + +.. class:: ECB() + + ECB (Electronic Code Book) is the simplest mode of operation for block + ciphers. Each block of data is encrypted in the same way. This means + identical plaintext blocks will always result in identical ciphertext + blocks, which can leave `significant patterns in the output`_. + + **Padding is required when using this mode.** + +Interfaces +---------- + +.. currentmodule:: cryptography.hazmat.primitives.ciphers + +.. class:: CipherContext + + When calling ``encryptor()`` or ``decryptor()`` on a ``Cipher`` object + the result will conform to the ``CipherContext`` interface. You can then + call ``update(data)`` with data until you have fed everything into the + context. Once that is done call ``finalize()`` to finish the operation and + obtain the remainder of the data. + + Block ciphers require that the plaintext or ciphertext always be a multiple + of their block size. Because of that **padding** is sometimes required to + make a message the correct size. ``CipherContext`` will not automatically + apply any padding; you'll need to add your own. For block ciphers the + recommended padding is + :class:`~cryptography.hazmat.primitives.padding.PKCS7`. If you are using a + stream cipher mode (such as + :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR`) you don't have + to worry about this. + + .. method:: update(data) + + :param bytes data: The data you wish to pass into the context. + :return bytes: Returns the data that was encrypted or decrypted. + :raises cryptography.exceptions.AlreadyFinalized: See :meth:`finalize` + + When the ``Cipher`` was constructed in a mode that turns it into a + stream cipher (e.g. + :class:`~cryptography.hazmat.primitives.ciphers.modes.CTR`), this will + return bytes immediately, however in other modes it will return chunks + whose size is determined by the cipher's block size. + + .. method:: finalize() + + :return bytes: Returns the remainder of the data. + :raises ValueError: This is raised when the data provided isn't + a multiple of the algorithm's block size. + + Once ``finalize`` is called this object can no longer be used and + :meth:`update` and :meth:`finalize` will raise an + :class:`~cryptography.exceptions.AlreadyFinalized` exception. + +.. class:: AEADCipherContext + + When calling ``encryptor`` or ``decryptor`` on a ``Cipher`` object + with an AEAD mode (e.g. + :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM`) the result will + conform to the ``AEADCipherContext`` and ``CipherContext`` interfaces. If + it is an encryption context it will additionally be an + ``AEADEncryptionContext`` instance. ``AEADCipherContext`` contains an + additional method :meth:`authenticate_additional_data` for adding + additional authenticated but unencrypted data (see note below). You should + call this before calls to ``update``. When you are done call ``finalize`` + to finish the operation. + + .. note:: + + In AEAD modes all data passed to ``update()`` will be both encrypted + and authenticated. Do not pass encrypted data to the + ``authenticate_additional_data()`` method. It is meant solely for + additional data you may want to authenticate but leave unencrypted. + + .. method:: authenticate_additional_data(data) + + :param bytes data: Any data you wish to authenticate but not encrypt. + :raises: :class:`~cryptography.exceptions.AlreadyFinalized` + +.. class:: AEADEncryptionContext + + When creating an encryption context using ``encryptor`` on a ``Cipher`` + object with an AEAD mode such as + :class:`~cryptography.hazmat.primitives.ciphers.modes.GCM` an object + conforming to both the ``AEADEncryptionContext`` and ``AEADCipherContext`` + interfaces will be returned. This interface provides one + additional attribute ``tag``. ``tag`` can only be obtained after + ``finalize`` has been called. + + .. attribute:: tag + + :return bytes: Returns the tag value as bytes. + :raises: :class:`~cryptography.exceptions.NotYetFinalized` if called + before the context is finalized. + +.. class:: CipherAlgorithm + + A named symmetric encryption algorithm. + + .. attribute:: name + + :type: str + + The standard name for the mode, for example, "AES", "Camellia", or + "Blowfish". + + .. attribute:: key_size + + :type: int + + The number of bits in the key being used. + + +.. class:: BlockCipherAlgorithm + + A block cipher algorithm. + + .. attribute:: block_size + + :type: int + + The number of bits in a block. + +Interfaces used by the symmetric cipher modes described in +:ref:`Symmetric Encryption Modes `. + +.. currentmodule:: cryptography.hazmat.primitives.ciphers.modes + +.. class:: Mode + + A named cipher mode. + + .. attribute:: name + + :type: str + + This should be the standard shorthand name for the mode, for example + Cipher-Block Chaining mode is "CBC". + + The name may be used by a backend to influence the operation of a + cipher in conjunction with the algorithm's name. + + .. method:: validate_for_algorithm(algorithm) + + :param cryptography.hazmat.primitives.ciphers.CipherAlgorithm algorithm: + + Checks that the combination of this mode with the provided algorithm + meets any necessary invariants. This should raise an exception if they + are not met. + + For example, the + :class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` mode uses + this method to check that the provided initialization vector's length + matches the block size of the algorithm. + + +.. class:: ModeWithInitializationVector + + A cipher mode with an initialization vector. + + .. attribute:: initialization_vector + + :type: bytes + + Exact requirements of the initialization are described by the + documentation of individual modes. + + +.. class:: ModeWithNonce + + A cipher mode with a nonce. + + .. attribute:: nonce + + :type: bytes + + Exact requirements of the nonce are described by the documentation of + individual modes. + + +.. class:: ModeWithAuthenticationTag + + A cipher mode with an authentication tag. + + .. attribute:: tag + + :type: bytes + + Exact requirements of the tag are described by the documentation of + individual modes. + + + +.. _`described by Colin Percival`: http://www.daemonology.net/blog/2009-06-11-cryptographic-right-answers.html +.. _`recommends a 96-bit IV length`: http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/gcm/gcm-spec.pdf +.. _`NIST SP-800-38D`: http://csrc.nist.gov/publications/nistpubs/800-38D/SP-800-38D.pdf +.. _`Communications Security Establishment`: https://www.cse-cst.gc.ca +.. _`encrypt`: https://ssd.eff.org/en/module/what-encryption +.. _`CRYPTREC`: https://www.cryptrec.go.jp/english/ +.. _`significant patterns in the output`: https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Electronic_Codebook_.28ECB.29 +.. _`International Data Encryption Algorithm`: https://en.wikipedia.org/wiki/International_Data_Encryption_Algorithm +.. _`OpenPGP`: http://openpgp.org diff --git a/docs/hazmat/primitives/twofactor.rst b/docs/hazmat/primitives/twofactor.rst new file mode 100644 index 0000000..9f11332 --- /dev/null +++ b/docs/hazmat/primitives/twofactor.rst @@ -0,0 +1,244 @@ +.. hazmat:: + +Two-factor authentication +========================= + +.. currentmodule:: cryptography.hazmat.primitives.twofactor + +This module contains algorithms related to two-factor authentication. + +Currently, it contains an algorithm for generating and verifying +one time password values based on Hash-based message authentication +codes (HMAC). + +.. class:: InvalidToken + + This is raised when the verify method of a one time password function's + computed token does not match the expected token. + +.. currentmodule:: cryptography.hazmat.primitives.twofactor.hotp + +.. class:: HOTP(key, length, algorithm, backend, enforce_key_length=True) + + .. versionadded:: 0.3 + + HOTP objects take a ``key``, ``length`` and ``algorithm`` parameter. The + ``key`` should be :doc:`randomly generated bytes ` and is + recommended to be 160 bits in length. The ``length`` parameter controls the + length of the generated one time password and must be >= 6 and <= 8. + + This is an implementation of :rfc:`4226`. + + .. doctest:: + + >>> import os + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives.twofactor.hotp import HOTP + >>> from cryptography.hazmat.primitives.hashes import SHA1 + >>> key = os.urandom(20) + >>> hotp = HOTP(key, 6, SHA1(), backend=default_backend()) + >>> hotp_value = hotp.generate(0) + >>> hotp.verify(hotp_value, 0) + + :param bytes key: Per-user secret key. This value must be kept secret + and be at least 128 bits. It is recommended that the + key be 160 bits. + :param int length: Length of generated one time password as ``int``. + :param cryptography.hazmat.primitives.hashes.HashAlgorithm algorithm: A + :class:`~cryptography.hazmat.primitives.hashes` + instance. + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + instance. + :param enforce_key_length: A boolean flag defaulting to True that toggles + whether a minimum key length of 128 bits is enforced. This exists to + work around the fact that as documented in `Issue #2915`_, the + Google Authenticator PAM module by default generates 80 bit keys. If + this flag is set to False, the application develop should implement + additional checks of the key length before passing it into + :class:`~cryptography.hazmat.primitives.twofactor.hotp.HOTP`. + + .. versionadded:: 1.5 + + :raises ValueError: This is raised if the provided ``key`` is shorter than + 128 bits or if the ``length`` parameter is not 6, 7 or 8. + :raises TypeError: This is raised if the provided ``algorithm`` is not + :class:`~cryptography.hazmat.primitives.hashes.SHA1()`, + :class:`~cryptography.hazmat.primitives.hashes.SHA256()` or + :class:`~cryptography.hazmat.primitives.hashes.SHA512()` or if the + ``length`` parameter is not an integer. + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + + .. method:: generate(counter) + + :param int counter: The counter value used to generate the one time + password. + :return bytes: A one time password value. + + .. method:: verify(hotp, counter) + + :param bytes hotp: The one time password value to validate. + :param int counter: The counter value to validate against. + :raises cryptography.hazmat.primitives.twofactor.InvalidToken: This + is raised when the supplied HOTP does not match the expected HOTP. + + .. method:: get_provisioning_uri(account_name, counter, issuer) + + .. versionadded:: 1.0 + + :param account_name: The display name of account, such as + ``'Alice Smith'`` or ``'alice@example.com'``. + :type account_name: :term:`text` + :param issuer: The optional display name of issuer. This is typically + the provider or service the user wants to access using the OTP + token. + :type issuer: :term:`text` or `None` + :param int counter: The current value of counter. + :return: A URI string. + +Throttling +~~~~~~~~~~ + +Due to the fact that the HOTP algorithm generates rather short tokens that are +6 - 8 digits long, brute force attacks are possible. It is highly recommended +that the server that validates the token implement a throttling scheme that +locks out the account for a period of time after a number of failed attempts. +The number of allowed attempts should be as low as possible while still +ensuring that usability is not significantly impacted. + +Re-synchronization of the counter +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The server's counter value should only be incremented on a successful HOTP +authentication. However, the counter on the client is incremented every time a +new HOTP value is requested. This can lead to the counter value being out of +synchronization between the client and server. + +Due to this, it is highly recommended that the server sets a look-ahead window +that allows the server to calculate the next ``x`` HOTP values and check them +against the supplied HOTP value. This can be accomplished with something +similar to the following code. + +.. code-block:: python + + def verify(hotp, counter, look_ahead): + assert look_ahead >= 0 + correct_counter = None + + otp = HOTP(key, 6, default_backend()) + for count in range(counter, counter + look_ahead): + try: + otp.verify(hotp, count) + correct_counter = count + except InvalidToken: + pass + + return correct_counter + +.. currentmodule:: cryptography.hazmat.primitives.twofactor.totp + +.. class:: TOTP(key, length, algorithm, time_step, backend, enforce_key_length=True) + + TOTP objects take a ``key``, ``length``, ``algorithm`` and ``time_step`` + parameter. The ``key`` should be :doc:`randomly generated bytes + ` and is recommended to be as long as your hash function's + output (e.g 256-bit for SHA256). The ``length`` parameter controls the + length of the generated one time password and must be >= 6 and <= 8. + + This is an implementation of :rfc:`6238`. + + .. doctest:: + + >>> import os + >>> import time + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives.twofactor.totp import TOTP + >>> from cryptography.hazmat.primitives.hashes import SHA1 + >>> key = os.urandom(20) + >>> totp = TOTP(key, 8, SHA1(), 30, backend=default_backend()) + >>> time_value = time.time() + >>> totp_value = totp.generate(time_value) + >>> totp.verify(totp_value, time_value) + + :param bytes key: Per-user secret key. This value must be kept secret + and be at least 128 bits. It is recommended that the + key be 160 bits. + :param int length: Length of generated one time password as ``int``. + :param cryptography.hazmat.primitives.hashes.HashAlgorithm algorithm: A + :class:`~cryptography.hazmat.primitives.hashes` + instance. + :param int time_step: The time step size. The recommended size is 30. + :param backend: A + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + instance. + :param enforce_key_length: A boolean flag defaulting to True that toggles + whether a minimum key length of 128 bits is enforced. This exists to + work around the fact that as documented in `Issue #2915`_, the + Google Authenticator PAM module by default generates 80 bit keys. If + this flag is set to False, the application develop should implement + additional checks of the key length before passing it into + :class:`~cryptography.hazmat.primitives.twofactor.totp.TOTP`. + + .. versionadded:: 1.5 + :raises ValueError: This is raised if the provided ``key`` is shorter than + 128 bits or if the ``length`` parameter is not 6, 7 or 8. + :raises TypeError: This is raised if the provided ``algorithm`` is not + :class:`~cryptography.hazmat.primitives.hashes.SHA1()`, + :class:`~cryptography.hazmat.primitives.hashes.SHA256()` or + :class:`~cryptography.hazmat.primitives.hashes.SHA512()` or if the + ``length`` parameter is not an integer. + :raises cryptography.exceptions.UnsupportedAlgorithm: This is raised if the + provided ``backend`` does not implement + :class:`~cryptography.hazmat.backends.interfaces.HMACBackend` + + .. method:: generate(time) + + :param int time: The time value used to generate the one time password. + :return bytes: A one time password value. + + .. method:: verify(totp, time) + + :param bytes totp: The one time password value to validate. + :param int time: The time value to validate against. + :raises cryptography.hazmat.primitives.twofactor.InvalidToken: This + is raised when the supplied TOTP does not match the expected TOTP. + + .. method:: get_provisioning_uri(account_name, issuer) + + .. versionadded:: 1.0 + + :param account_name: The display name of account, such as + ``'Alice Smith'`` or ``'alice@example.com'``. + :type: :term:`text` + :param issuer: The optional display name of issuer. This is typically + the provider or service the user wants to access using the OTP + token. + :type issuer: :term:`text` or `None` + :return: A URI string. + +Provisioning URI +~~~~~~~~~~~~~~~~ + +The provisioning URI of HOTP and TOTP is not actual the part of RFC 4226 and +RFC 6238, but a `spec of Google Authenticator`_. It is widely supported by web +sites and mobile applications which are using Two-Factor authentication. + +For generating a provisioning URI, you could use the ``get_provisioning_uri`` +method of HOTP/TOTP instances. + +.. code-block:: python + + counter = 5 + account_name = 'alice@example.com' + issuer_name = 'Example Inc' + + hotp_uri = hotp.get_provisioning_uri(account_name, counter, issuer_name) + totp_uri = totp.get_provisioning_uri(account_name, issuer_name) + +A common usage is encoding the provisioning URI into QR code and guiding users +to scan it with Two-Factor authentication applications in their mobile devices. + +.. _`spec of Google Authenticator`: https://github.com/google/google-authenticator/wiki/Key-Uri-Format +.. _`Issue #2915`: https://github.com/pyca/cryptography/issues/2915 diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..5c26a75 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,104 @@ +Welcome to ``cryptography`` +=========================== + +``cryptography`` is a Python library which exposes cryptographic recipes and +primitives. Our goal is for it to be your "cryptographic standard library". If +you are interested in learning more about the field of cryptography, we +recommend `Crypto 101, by Laurens Van Houtven`_. + +Installation +------------ +You can install ``cryptography`` with ``pip``: + +.. code-block:: console + + $ pip install cryptography + +See :doc:`Installation ` for more information. + +Why a new crypto library for Python? +------------------------------------ + +If you've done cryptographic work in Python before, you've probably seen some +other libraries in Python, such as *M2Crypto*, *PyCrypto*, or *PyOpenSSL*. In +building ``cryptography`` we wanted to address a few issues we observed in the +existing libraries: + +* Lack of PyPy and Python 3 support. +* Lack of maintenance. +* Use of poor implementations of algorithms (i.e. ones with known side-channel + attacks). +* Lack of high level, "Cryptography for humans", APIs. +* Absence of algorithms such as + :class:`AES-GCM ` and + :class:`~cryptography.hazmat.primitives.kdf.hkdf.HKDF`. +* Poor introspectability, and thus poor testability. +* Extremely error prone APIs, and bad defaults. + + +.. _cryptography-layout: + +Layout +------ + +``cryptography`` is broadly divided into two levels. One with safe +cryptographic recipes, "cryptography for humans" if you will. These are safe +and easy to use and don't require developers to make many decisions. + +The other level is low-level cryptographic primitives. These are often +dangerous and can be used incorrectly. They require making decisions and having +an in-depth knowledge of the cryptographic concepts at work. Because of the +potential danger in working at this level, this is referred to as the +"hazardous materials" or "hazmat" layer. These live in the +``cryptography.hazmat`` package, and their documentation will always contain an +admonition at the top. + +We recommend using the recipes layer whenever possible, and falling back to the +hazmat layer only when necessary. + +The recipes layer +~~~~~~~~~~~~~~~~~ + +.. toctree:: + :maxdepth: 2 + + fernet + x509/index + random-numbers + exceptions + faq + glossary + +The hazardous materials layer +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. toctree:: + :maxdepth: 2 + + hazmat/primitives/index + hazmat/backends/index + hazmat/bindings/index + +The ``cryptography`` open source project +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. toctree:: + :maxdepth: 2 + + installation + development/index + security + limitations + api-stability + doing-a-release + changelog + community + + +.. note:: + + ``cryptography`` has not been subjected to an external audit of its code or + documentation. If you're interested in discussing an audit please + :doc:`get in touch `. + +.. _`Crypto 101, by Laurens Van Houtven`: https://www.crypto101.io/ diff --git a/docs/installation.rst b/docs/installation.rst new file mode 100644 index 0000000..9ad9a3c --- /dev/null +++ b/docs/installation.rst @@ -0,0 +1,277 @@ +Installation +============ + +You can install ``cryptography`` with ``pip``: + +.. code-block:: console + + $ pip install cryptography + +Supported platforms +------------------- + +Currently we test ``cryptography`` on Python 2.6, 2.7, 3.3, 3.4, 3.5, and PyPy +2.6+ on these operating systems. + +* x86-64 CentOS 7.x, 6.4 +* x86-64 FreeBSD 10 +* OS X 10.12 Sierra, 10.11 El Capitan, 10.10 Yosemite, 10.9 Mavericks, + 10.8 Mountain Lion, and 10.7 Lion +* x86-64 Ubuntu 12.04 LTS and Ubuntu 14.04 LTS +* x86-64 Debian Wheezy (7.x), Jessie (8.x), and Debian Sid (unstable) +* 32-bit and 64-bit Python on 64-bit Windows Server 2012 + +.. warning:: + Python 2.6 is no longer supported by the Python core team. A future version + of cryptography will drop support for this version. + +We test compiling with ``clang`` as well as ``gcc`` and use the following +OpenSSL releases: + +* ``OpenSSL 1.0.0-fips`` (``RHEL/CentOS 6.4``) +* ``OpenSSL 1.0.1`` +* ``OpenSSL 1.0.1e-fips`` (``RHEL/CentOS 7``) +* ``OpenSSL 1.0.1j-freebsd`` +* ``OpenSSL 1.0.1f`` +* ``OpenSSL 1.0.2-latest`` +* ``OpenSSL 1.1.0`` + +.. warning:: + Cryptography 1.7 has dropped support for OpenSSL 1.0.0, see the + :doc:`FAQ ` for more details + +Building cryptography on Windows +-------------------------------- + +The wheel package on Windows is a statically linked build (as of 0.5) so all +dependencies are included. To install ``cryptography``, you will typically +just run + +.. code-block:: console + + $ pip install cryptography + +If you prefer to compile it yourself you'll need to have OpenSSL installed. +You can compile OpenSSL yourself as well or use the binaries we build for our +release infrastructure (`openssl-release`_). Be sure to download the proper +version for your architecture and Python (2010 works for Python 2.6, 2.7, 3.3, +and 3.4 while 2015 is required for 3.5). Wherever you place your copy +of OpenSSL you'll need to set the ``LIB`` and ``INCLUDE`` environment variables +to include the proper locations. For example: + +.. code-block:: console + + C:\> \path\to\vcvarsall.bat x86_amd64 + C:\> set LIB=C:\OpenSSL-win64\lib;%LIB% + C:\> set INCLUDE=C:\OpenSSL-win64\include;%INCLUDE% + C:\> pip install cryptography + +If you need to rebuild ``cryptography`` for any reason be sure to clear the +local `wheel cache`_. + +.. _build-on-linux: + +Building cryptography on Linux +------------------------------ + +``cryptography`` should build very easily on Linux provided you have a C +compiler, headers for Python (if you're not using ``pypy``), and headers for +the OpenSSL and ``libffi`` libraries available on your system. + +For Debian and Ubuntu, the following command will ensure that the required +dependencies are installed: + +.. code-block:: console + + $ sudo apt-get install build-essential libssl-dev libffi-dev python-dev + +For Fedora and RHEL-derivatives, the following command will ensure that the +required dependencies are installed: + +.. code-block:: console + + $ sudo yum install gcc libffi-devel python-devel openssl-devel + +You should now be able to build and install cryptography with the usual + +.. code-block:: console + + $ pip install cryptography + + +Using your own OpenSSL on Linux +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Python links to OpenSSL for its own purposes and this can sometimes cause +problems when you wish to use a different version of OpenSSL with cryptography. +If you want to use cryptography with your own build of OpenSSL you will need to +make sure that the build is configured correctly so that your version of +OpenSSL doesn't conflict with Python's. + +The options you need to add allow the linker to identify every symbol correctly +even when multiple versions of the library are linked into the same program. If +you are using your distribution's source packages these will probably be +patched in for you already, otherwise you'll need to use options something like +this when configuring OpenSSL: + +.. code-block:: console + + $ ./config -Wl,--version-script=openssl.ld -Wl,-Bsymbolic-functions -fPIC shared + +You'll also need to generate your own ``openssl.ld`` file. For example:: + + OPENSSL_1.0.1F_CUSTOM { + global: + *; + }; + +You should replace the version string on the first line as appropriate for your +build. + +Static Wheels +~~~~~~~~~~~~~ + +Cryptography ships statically-linked wheels for OS X and Windows, ensuring that +these platforms can always use the most-recent OpenSSL, regardless of what is +shipped by default on those platforms. As a result of various difficulties +around Linux binary linking, Cryptography cannot do the same on Linux. + +However, you can build your own statically-linked wheels that will work on your +own systems. This will allow you to continue to use relatively old Linux +distributions (such as LTS releases), while making sure you have the most +recent OpenSSL available to your Python programs. + +To do so, you should find yourself a machine that is as similar as possible to +your target environment (e.g. your production environment): for example, spin +up a new cloud server running your target Linux distribution. On this machine, +install the Cryptography dependencies as mentioned in :ref:`build-on-linux`. +Please also make sure you have `virtualenv`_ installed: this should be +available from your system package manager. + +Then, paste the following into a shell script. You'll need to populate the +``OPENSSL_VERSION`` variable. To do that, visit `openssl.org`_ and find the +latest non-FIPS release version number, then set the string appropriately. For +example, for OpenSSL 1.0.2d, use ``OPENSSL_VERSION="1.0.2d"``. + +When this shell script is complete, you'll find a collection of wheel files in +a directory called ``wheelhouse``. These wheels can be installed by a +sufficiently-recent version of ``pip``. The Cryptography wheel in this +directory contains a statically-linked OpenSSL binding, which ensures that you +have access to the most-recent OpenSSL releases without corrupting your system +dependencies. + +.. code-block:: console + + set -e + + OPENSSL_VERSION="VERSIONGOESHERE" + CWD=$(pwd) + + virtualenv env + . env/bin/activate + pip install -U setuptools + pip install -U wheel pip + curl -O https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz + tar xvf openssl-${OPENSSL_VERSION}.tar.gz + cd openssl-${OPENSSL_VERSION} + ./config no-shared no-ssl2 -fPIC --prefix=${CWD}/openssl + make && make install + cd .. + CFLAGS="-I${CWD}/openssl/include" LDFLAGS="-L${CWD}/openssl/lib" pip wheel --no-use-wheel cryptography + +Building cryptography on OS X +----------------------------- + +.. note:: + + If installation gives a ``fatal error: 'openssl/aes.h' file not found`` + see the :doc:`FAQ ` for information about how to fix this issue. + +The wheel package on OS X is a statically linked build (as of 1.0.1) so for +users with pip 8 or above you only need one step: + +.. code-block:: console + + $ pip install cryptography + +If you want to build cryptography yourself or are on an older OS X version, +cryptography requires the presence of a C compiler, development headers, and +the proper libraries. On OS X much of this is provided by Apple's Xcode +development tools. To install the Xcode command line tools (on OS X 10.9+) +open a terminal window and run: + +.. code-block:: console + + $ xcode-select --install + +This will install a compiler (clang) along with (most of) the required +development headers. + +You'll also need OpenSSL, which you can obtain from `Homebrew`_ or `MacPorts`_. +Cryptography does **not** support Apple's deprecated OpenSSL distribution. + +To build cryptography and dynamically link it: + +`Homebrew`_ + +.. code-block:: console + + $ brew install openssl + $ env LDFLAGS="-L$(brew --prefix openssl)/lib" CFLAGS="-I$(brew --prefix openssl)/include" pip install cryptography + +`MacPorts`_: + +.. code-block:: console + + $ sudo port install openssl + $ env LDFLAGS="-L/opt/local/lib" CFLAGS="-I/opt/local/include" pip install cryptography + +You can also build cryptography statically: + +`Homebrew`_ + +.. code-block:: console + + $ brew install openssl + $ env CRYPTOGRAPHY_OSX_NO_LINK_FLAGS=1 LDFLAGS="$(brew --prefix openssl)/lib/libssl.a $(brew --prefix openssl)/lib/libcrypto.a" CFLAGS="-I$(brew --prefix openssl)/include" pip install cryptography + +`MacPorts`_: + +.. code-block:: console + + $ sudo port install openssl + $ env CRYPTOGRAPHY_OSX_NO_LINK_FLAGS=1 LDFLAGS="/opt/local/lib/libssl.a /opt/local/lib/libcrypto.a" CFLAGS="-I/opt/local/include" pip install cryptography + +If you need to rebuild ``cryptography`` for any reason be sure to clear the +local `wheel cache`_. + +Building cryptography with conda +-------------------------------- + +Because of a bug in conda, attempting to install cryptography out of the box +will result in an error. This can be resolved by setting the library path +environment variable for your platform. + +On OS X: + +.. code-block:: console + + $ env DYLD_LIBRARY_PATH="$HOME/anaconda/lib" pip install cryptography + +and on Linux: + +.. code-block:: console + + $ env LD_LIBRARY_PATH="$HOME/anaconda/lib" pip install cryptography + +You will need to set this variable every time you start Python. For more +information, consult `Greg Wilson's blog post`_ on the subject. + + +.. _`Homebrew`: http://brew.sh +.. _`MacPorts`: https://www.macports.org +.. _`openssl-release`: https://jenkins.cryptography.io/job/openssl-release/ +.. _`Greg Wilson's blog post`: https://software-carpentry.org/blog/2014/04/mr-biczo-was-right.html +.. _virtualenv: https://virtualenv.pypa.io/en/latest/ +.. _openssl.org: https://www.openssl.org/source/ +.. _`wheel cache`: https://pip.pypa.io/en/stable/reference/pip_install/#caching diff --git a/docs/limitations.rst b/docs/limitations.rst new file mode 100644 index 0000000..503bdfe --- /dev/null +++ b/docs/limitations.rst @@ -0,0 +1,19 @@ +Known security limitations +-------------------------- + +Lack of secure memory wiping +============================ + +`Memory wiping`_ is used to protect secret data or key material from attackers +with access to uninitialized memory. This can be either because the attacker +has some kind of local user access or because of how other software uses +uninitialized memory. + +Python exposes no API for us to implement this reliably and as such almost all +software in Python is potentially vulnerable to this attack. The +`CERT secure coding guidelines`_ assesses this issue as "Severity: medium, +Likelihood: unlikely, Remediation Cost: expensive to repair" and we do not +consider this a high risk for most users. + +.. _`Memory wiping`: https://blogs.msdn.microsoft.com/oldnewthing/20130529-00/?p=4223/ +.. _`CERT secure coding guidelines`: https://www.securecoding.cert.org/confluence/display/c/MEM03-C.+Clear+sensitive+information+stored+in+reusable+resources diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..905288d --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,190 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\Cryptography.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\Cryptography.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +:end diff --git a/docs/random-numbers.rst b/docs/random-numbers.rst new file mode 100644 index 0000000..8c6b9fd --- /dev/null +++ b/docs/random-numbers.rst @@ -0,0 +1,32 @@ +Random number generation +======================== + +When generating random data for use in cryptographic operations, such as an +initialization vector for encryption in +:class:`~cryptography.hazmat.primitives.ciphers.modes.CBC` mode, you do not +want to use the standard :mod:`random` module APIs. This is because they do not +provide a cryptographically secure random number generator, which can result in +major security issues depending on the algorithms in use. + +Therefore, it is our recommendation to `always use your operating system's +provided random number generator`_, which is available as :func:`os.urandom`. +For example, if you need 16 bytes of random data for an initialization vector, +you can obtain them with: + +.. doctest:: + + >>> import os + >>> iv = os.urandom(16) + +This will use ``/dev/urandom`` on UNIX platforms, and ``CryptGenRandom`` on +Windows. + +If you need your random number as an integer (for example, for +:meth:`~cryptography.x509.CertificateBuilder.serial_number`), you can use +``int.from_bytes`` to convert the result of ``os.urandom``: + +.. code-block:: pycon + + >>> serial = int.from_bytes(os.urandom(20), byteorder="big") + +.. _`always use your operating system's provided random number generator`: https://sockpuppet.org/blog/2014/02/25/safely-generate-random-numbers/ diff --git a/docs/security.rst b/docs/security.rst new file mode 100644 index 0000000..1cc1273 --- /dev/null +++ b/docs/security.rst @@ -0,0 +1,115 @@ +Security +======== + +We take the security of ``cryptography`` seriously. The following are a set of +policies we have adopted to ensure that security issues are addressed in a +timely fashion. + +What is a security issue? +------------------------- + +Anytime it's possible to write code using ``cryptography``'s public API which +does not provide the guarantees that a reasonable developer would expect it to +based on our documentation. + +That's a bit academic, but basically it means the scope of what we consider a +vulnerability is broad, and we do not require a proof of concept or even a +specific exploit, merely a reasonable threat model under which ``cryptography`` +could be attacked. + +To give a few examples of things we would consider security issues: + +* If a recipe, such as Fernet, made it easy for a user to bypass + confidentiality or integrity with the public API (e.g. if the API let a user + reuse nonces). +* If, under any circumstances, we used a CSPRNG which wasn't fork-safe. +* If ``cryptography`` used an API in an underlying C library and failed to + handle error conditions safely. + +Examples of things we wouldn't consider security issues: + +* Offering ECB mode for symmetric encryption in the *Hazmat* layer. Though ECB + is critically weak, it is documented as being weak in our documentation. +* Using a variable time comparison somewhere, if it's not possible to + articulate any particular program in which this would result in problematic + information disclosure. + +In general, if you're unsure, we request that you to default to treating things +as security issues and handling them sensitively, the worst thing that can +happen is that we'll ask you to file a bug issue. + +Reporting a security issue +-------------------------- + +We ask that you do not report security issues to our normal GitHub issue +tracker. + +If you believe you've identified a security issue with ``cryptography``, please +report it to ``alex.gaynor@gmail.com``. Messages may be optionally encrypted +with PGP using key fingerprint +``F7FC 698F AAE2 D2EF BECD E98E D1B3 ADC0 E023 8CA6`` (this public key is +available from most commonly-used key servers). + +Once you've submitted an issue via email, you should receive an acknowledgment +within 48 hours, and depending on the action to be taken, you may receive +further follow-up emails. + +Supported Versions +------------------ + +At any given time, we will provide security support for the `master`_ branch +as well as the most recent release. + +New releases for OpenSSL updates +-------------------------------- + +As of version 0.5, ``cryptography`` statically links OpenSSL on Windows, and as +of version 1.0.1 on OS X, to ease installation. Due to this, ``cryptography`` +will release a new version whenever OpenSSL has a security or bug fix release to +avoid shipping insecure software. + +Like all our other releases, this will be announced on the mailing list and we +strongly recommend that you upgrade as soon as possible. + +Disclosure Process +------------------ + +Our process for taking a security issue from private discussion to public +disclosure involves multiple steps. + +Approximately one week before full public disclosure, we will send advance +notification of the issue to a list of people and organizations, primarily +composed of operating-system vendors and other distributors of +``cryptography``. This notification will consist of an email message +containing: + +* A full description of the issue and the affected versions of + ``cryptography``. +* The steps we will be taking to remedy the issue. +* The patches, if any, that will be applied to ``cryptography``. +* The date on which the ``cryptography`` team will apply these patches, issue + new releases, and publicly disclose the issue. + +Simultaneously, the reporter of the issue will receive notification of the date +on which we plan to take the issue public. + +On the day of disclosure, we will take the following steps: + +* Apply the relevant patches to the ``cryptography`` repository. The commit + messages for these patches will indicate that they are for security issues, + but will not describe the issue in any detail; instead, they will warn of + upcoming disclosure. +* Issue the relevant releases. +* Post a notice to the cryptography mailing list that describes the issue in + detail, point to the new release and crediting the reporter of the issue. + +If a reported issue is believed to be particularly time-sensitive – due to a +known exploit in the wild, for example – the time between advance notification +and public disclosure may be shortened considerably. + +The list of people and organizations who receives advanced notification of +security issues is not and will not be made public. This list generally +consists of high profile downstream distributors and is entirely at the +discretion of the ``cryptography`` team. + +.. _`master`: https://github.com/pyca/cryptography diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt new file mode 100644 index 0000000..13d5e07 --- /dev/null +++ b/docs/spelling_wordlist.txt @@ -0,0 +1,94 @@ +affine +Authenticator +backend +Backends +backends +bcrypt +Blowfish +boolean +Botan +Capitan +Changelog +ciphertext +committer +committers +conda +Cryptanalysis +crypto +cryptographic +cryptographically +Debian +decrypt +decrypted +decrypting +DER +deserialize +deserialized +Deserialization +Diffie +Diffie +disambiguating +Django +Docstrings +El +Encodings +endian +fallback +Fernet +fernet +FIPS +Google +hazmat +hostname +indistinguishability +initialisms +interoperable +introspectability +invariants +iOS +iterable +Koblitz +Lange +logins +metadata +Mozilla +multi +namespace +namespaces +macOS +naïve +Nonces +nonces +online +paddings +Parallelization +personalization +pickleable +plaintext +pre +preprocessor +preprocessors +pseudorandom +pyOpenSSL +relicensed +responder +runtime +Schneier +scrypt +serializer +Serializers +SHA +Solaris +syscall +Tanja +testability +tunable +Ubuntu +unencrypted +unpadded +unpadding +verifier +Verifier +Verisign +wildcard +Xcode diff --git a/docs/x509/index.rst b/docs/x509/index.rst new file mode 100644 index 0000000..2e3aa74 --- /dev/null +++ b/docs/x509/index.rst @@ -0,0 +1,15 @@ +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 + + tutorial + 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/reference.rst b/docs/x509/reference.rst new file mode 100644 index 0000000..68e1f73 --- /dev/null +++ b/docs/x509/reference.rst @@ -0,0 +1,2697 @@ +X.509 Reference +=============== + +.. currentmodule:: cryptography.x509 + +.. testsetup:: + + pem_crl_data = b""" + -----BEGIN X509 CRL----- + MIIBtDCBnQIBAjANBgkqhkiG9w0BAQsFADAnMQswCQYDVQQGEwJVUzEYMBYGA1UE + AwwPY3J5cHRvZ3JhcGh5LmlvGA8yMDE1MDEwMTAwMDAwMFoYDzIwMTYwMTAxMDAw + MDAwWjA+MDwCAQAYDzIwMTUwMTAxMDAwMDAwWjAmMBgGA1UdGAQRGA8yMDE1MDEw + MTAwMDAwMFowCgYDVR0VBAMKAQEwDQYJKoZIhvcNAQELBQADggEBABRA4ww50Lz5 + zk1j2+aluC4HPHqb7o06h4pTDcCGeXUKXIGeP5ntGGmIoxa26sNoLeOr8+5b43Gf + yWraHertllOwaOpNFEe+YZFaE9femtoDbf+GLMvRx/0wDfd3KxPoXnXKMXb2d1w4 + RCLgmkYx6JyvS+5ciuLQVIKC+l7jwIUeZFLJMUJ8msM4pFYoGameeZmtjMbd/TNg + cVBfmZxNMHuLladJxvSo2esARo0TYPhYsgrREKoHwhpzSxdynjn4bOVkILfguwsN + qtEEMZFEv5Kb0GqRp2+Iagv2S6dg9JGvxVdsoGjaB6EbYSZ3Psx4aODasIn11uwo + X4B9vUQNXqc= + -----END X509 CRL----- + """.strip() + + pem_req_data = b""" + -----BEGIN CERTIFICATE REQUEST----- + MIIC0zCCAbsCAQAwWTELMAkGA1UEBhMCVVMxETAPBgNVBAgMCElsbGlub2lzMRAw + DgYDVQQHDAdDaGljYWdvMREwDwYDVQQKDAhyNTA5IExMQzESMBAGA1UEAwwJaGVs + bG8uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqhZx+Mo9VRd9 + vsnWWa6NBCws21rZ0+1B/JGgB4hDsZS7iDE4Bj5z4idheFRtl8bBbdjPknq7BfoF + 8v15Zq/Zv7i2xMSDL+LUrTBZezRd4bRTGqCm6YJ5EYkhqdcqeZleHCFImguHoq1J + Fh0+kObQrTHXw3ZP57a3o1IvyIUA3nNoCBL0QQhwBXaDXOojMKNR+bqB5ve8GS1y + Elr0AM/+cJsfaIahNQUgFKx3Eu3GeEOMKYOAG1lycgdQdmTUybLrT3U7vkClTseM + xHg1r5En7ALjONIhqRuq3rddYahrP8HXozb3zUy3cJ7P6IeaosuvNzvMXOX9P6HD + Ha9urDAJ1wIDAQABoDUwMwYJKoZIhvcNAQkOMSYwJDAiBgNVHREEGzAZggl3b3Js + ZC5jb22CDHdoYXRldmVyLmNvbTANBgkqhkiG9w0BAQUFAAOCAQEAS4Ro6h+z52SK + YSLCYARpnEu/rmh4jdqndt8naqcNb6uLx9mlKZ2W9on9XDjnSdQD9q+ZP5aZfESw + R0+rJhW9ZrNa/g1pt6M24ihclHYDAxYMWxT1z/TXXGM3TmZZ6gfYlNE1kkBuODHa + UYsR/1Ht1E1EsmmUimt2n+zQR2K8T9Coa+boaUW/GsTEuz1aaJAkj5ZvTDiIhRG4 + AOCqFZOLAQmCCNgJnnspD9hDz/Ons085LF5wnYjN4/Nsk5tS6AGs3xjZ3jPoOGGn + 82WQ9m4dBGoVDZXsobVTaN592JEYwN5iu72zRn7Einb4V4H5y3yD2dD4yWPlt4pk + 5wFkeYsZEA== + -----END CERTIFICATE REQUEST----- + """.strip() + + pem_data = b""" + -----BEGIN CERTIFICATE----- + MIIDfDCCAmSgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBFMQswCQYDVQQGEwJVUzEf + MB0GA1UEChMWVGVzdCBDZXJ0aWZpY2F0ZXMgMjAxMTEVMBMGA1UEAxMMVHJ1c3Qg + QW5jaG9yMB4XDTEwMDEwMTA4MzAwMFoXDTMwMTIzMTA4MzAwMFowQDELMAkGA1UE + BhMCVVMxHzAdBgNVBAoTFlRlc3QgQ2VydGlmaWNhdGVzIDIwMTExEDAOBgNVBAMT + B0dvb2QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCQWJpHYo37 + Xfb7oJSPe+WvfTlzIG21WQ7MyMbGtK/m8mejCzR6c+f/pJhEH/OcDSMsXq8h5kXa + BGqWK+vSwD/Pzp5OYGptXmGPcthDtAwlrafkGOS4GqIJ8+k9XGKs+vQUXJKsOk47 + RuzD6PZupq4s16xaLVqYbUC26UcY08GpnoLNHJZS/EmXw1ZZ3d4YZjNlpIpWFNHn + UGmdiGKXUPX/9H0fVjIAaQwjnGAbpgyCumWgzIwPpX+ElFOUr3z7BoVnFKhIXze+ + VmQGSWxZxvWDUN90Ul0tLEpLgk3OVxUB4VUGuf15OJOpgo1xibINPmWt14Vda2N9 + yrNKloJGZNqLAgMBAAGjfDB6MB8GA1UdIwQYMBaAFOR9X9FclYYILAWuvnW2ZafZ + XahmMB0GA1UdDgQWBBRYAYQkG7wrUpRKPaUQchRR9a86yTAOBgNVHQ8BAf8EBAMC + AQYwFwYDVR0gBBAwDjAMBgpghkgBZQMCATABMA8GA1UdEwEB/wQFMAMBAf8wDQYJ + KoZIhvcNAQELBQADggEBADWHlxbmdTXNwBL/llwhQqwnazK7CC2WsXBBqgNPWj7m + tvQ+aLG8/50Qc2Sun7o2VnwF9D18UUe8Gj3uPUYH+oSI1vDdyKcjmMbKRU4rk0eo + 3UHNDXwqIVc9CQS9smyV+x1HCwL4TTrq+LXLKx/qVij0Yqk+UJfAtrg2jnYKXsCu + FMBQQnWCGrwa1g1TphRp/RmYHnMynYFmZrXtzFz+U9XEA7C+gPq4kqDI/iVfIT1s + 6lBtdB50lrDVwl2oYfAvW/6sC2se2QleZidUmrziVNP4oEeXINokU6T6p//HM1FG + QYw2jOvpKcKtWCSAnegEbgsGYzATKjmPJPJ0npHFqzM= + -----END CERTIFICATE----- + """.strip() + + cryptography_cert_pem = b""" + -----BEGIN CERTIFICATE----- + MIIFvTCCBKWgAwIBAgICPyAwDQYJKoZIhvcNAQELBQAwRzELMAkGA1UEBhMCVVMx + FjAUBgNVBAoTDUdlb1RydXN0IEluYy4xIDAeBgNVBAMTF1JhcGlkU1NMIFNIQTI1 + NiBDQSAtIEczMB4XDTE0MTAxNTEyMDkzMloXDTE4MTExNjAxMTUwM1owgZcxEzAR + BgNVBAsTCkdUNDg3NDI5NjUxMTAvBgNVBAsTKFNlZSB3d3cucmFwaWRzc2wuY29t + L3Jlc291cmNlcy9jcHMgKGMpMTQxLzAtBgNVBAsTJkRvbWFpbiBDb250cm9sIFZh + bGlkYXRlZCAtIFJhcGlkU1NMKFIpMRwwGgYDVQQDExN3d3cuY3J5cHRvZ3JhcGh5 + LmlvMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAom/FebKJIot7Sp3s + itG1sicpe3thCssjI+g1JDAS7I3GLVNmbms1DOdIIqwf01gZkzzXBN2+9sOnyRaR + PPfCe1jTr3dk2y6rPE559vPa1nZQkhlzlhMhlPyjaT+S7g4Tio4qV2sCBZU01DZJ + CaksfohN+5BNVWoJzTbOcrHOEJ+M8B484KlBCiSxqf9cyNQKru4W3bHaCVNVJ8eu + 6i6KyhzLa0L7yK3LXwwXVs583C0/vwFhccGWsFODqD/9xHUzsBIshE8HKjdjDi7Y + 3BFQzVUQFjBB50NSZfAA/jcdt1blxJouc7z9T8Oklh+V5DDBowgAsrT4b6Z2Fq6/ + r7D1GqivLK/ypUQmxq2WXWAUBb/Q6xHgxASxI4Br+CByIUQJsm8L2jzc7k+mF4hW + ltAIUkbo8fGiVnat0505YJgxWEDKOLc4Gda6d/7GVd5AvKrz242bUqeaWo6e4MTx + diku2Ma3rhdcr044Qvfh9hGyjqNjvhWY/I+VRWgihU7JrYvgwFdJqsQ5eiKT4OHi + gsejvWwkZzDtiQ+aQTrzM1FsY2swJBJsLSX4ofohlVRlIJCn/ME+XErj553431Lu + YQ5SzMd3nXzN78Vj6qzTfMUUY72UoT1/AcFiUMobgIqrrmwuNxfrkbVE2b6Bga74 + FsJX63prvrJ41kuHK/16RQBM7fcCAwEAAaOCAWAwggFcMB8GA1UdIwQYMBaAFMOc + 8/zTRgg0u85Gf6B8W/PiCMtZMFcGCCsGAQUFBwEBBEswSTAfBggrBgEFBQcwAYYT + aHR0cDovL2d2LnN5bWNkLmNvbTAmBggrBgEFBQcwAoYaaHR0cDovL2d2LnN5bWNi + LmNvbS9ndi5jcnQwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMB + BggrBgEFBQcDAjAvBgNVHREEKDAmghN3d3cuY3J5cHRvZ3JhcGh5Lmlvgg9jcnlw + dG9ncmFwaHkuaW8wKwYDVR0fBCQwIjAgoB6gHIYaaHR0cDovL2d2LnN5bWNiLmNv + bS9ndi5jcmwwDAYDVR0TAQH/BAIwADBFBgNVHSAEPjA8MDoGCmCGSAGG+EUBBzYw + LDAqBggrBgEFBQcCARYeaHR0cHM6Ly93d3cucmFwaWRzc2wuY29tL2xlZ2FsMA0G + CSqGSIb3DQEBCwUAA4IBAQAzIYO2jx7h17FBT74tJ2zbV9OKqGb7QF8y3wUtP4xc + dH80vprI/Cfji8s86kr77aAvAqjDjaVjHn7UzebhSUivvRPmfzRgyWBacomnXTSt + Xlt2dp2nDQuwGyK2vB7dMfKnQAkxwq1sYUXznB8i0IhhCAoXp01QGPKq51YoIlnF + 7DRMk6iEaL1SJbkIrLsCQyZFDf0xtfW9DqXugMMLoxeCsBhZJQzNyS2ryirrv9LH + aK3+6IZjrcyy9bkpz/gzJucyhU+75c4My/mnRCrtItRbCQuiI5pd5poDowm+HH9i + GVI9+0lAFwxOUnOnwsoI40iOoxjLMGB+CgFLKCGUcWxP + -----END CERTIFICATE----- + """.strip() + +Loading Certificates +~~~~~~~~~~~~~~~~~~~~ + +.. function:: load_pem_x509_certificate(data, backend) + + .. versionadded:: 0.7 + + Deserialize a certificate from PEM encoded data. PEM certificates are + base64 decoded and have delimiters that look like + ``-----BEGIN CERTIFICATE-----``. + + :param bytes data: The PEM encoded certificate data. + + :param backend: A backend supporting the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :returns: An instance of :class:`~cryptography.x509.Certificate`. + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> cert = x509.load_pem_x509_certificate(pem_data, default_backend()) + >>> cert.serial_number + 2 + +.. function:: load_der_x509_certificate(data, backend) + + .. versionadded:: 0.7 + + Deserialize a certificate from DER encoded data. DER is a binary format + and is commonly found in files with the ``.cer`` extension (although file + extensions are not a guarantee of encoding type). + + :param bytes data: The DER encoded certificate data. + + :param backend: A backend supporting the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :returns: An instance of :class:`~cryptography.x509.Certificate`. + +Loading Certificate Revocation Lists +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. function:: load_pem_x509_crl(data, backend) + + .. versionadded:: 1.1 + + Deserialize a certificate revocation list (CRL) from PEM encoded data. PEM + requests are base64 decoded and have delimiters that look like + ``-----BEGIN X509 CRL-----``. + + :param bytes data: The PEM encoded request data. + + :param backend: A backend supporting the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :returns: An instance of + :class:`~cryptography.x509.CertificateRevocationList`. + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> crl = x509.load_pem_x509_crl(pem_crl_data, default_backend()) + >>> isinstance(crl.signature_hash_algorithm, hashes.SHA256) + True + +.. function:: load_der_x509_crl(data, backend) + + .. versionadded:: 1.1 + + Deserialize a certificate revocation list (CRL) from DER encoded data. DER + is a binary format. + + :param bytes data: The DER encoded request data. + + :param backend: A backend supporting the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :returns: An instance of + :class:`~cryptography.x509.CertificateRevocationList`. + +Loading Certificate Signing Requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. function:: load_pem_x509_csr(data, backend) + + .. versionadded:: 0.9 + + Deserialize a certificate signing request (CSR) from PEM encoded data. PEM + requests are base64 decoded and have delimiters that look like + ``-----BEGIN CERTIFICATE REQUEST-----``. This format is also known as + PKCS#10. + + :param bytes data: The PEM encoded request data. + + :param backend: A backend supporting the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :returns: An instance of + :class:`~cryptography.x509.CertificateSigningRequest`. + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> csr = x509.load_pem_x509_csr(pem_req_data, default_backend()) + >>> isinstance(csr.signature_hash_algorithm, hashes.SHA1) + True + +.. function:: load_der_x509_csr(data, backend) + + .. versionadded:: 0.9 + + Deserialize a certificate signing request (CSR) from DER encoded data. DER + is a binary format and is not commonly used with CSRs. + + :param bytes data: The DER encoded request data. + + :param backend: A backend supporting the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :returns: An instance of + :class:`~cryptography.x509.CertificateSigningRequest`. + +X.509 Certificate Object +~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: Certificate + + .. versionadded:: 0.7 + + .. attribute:: version + + :type: :class:`~cryptography.x509.Version` + + The certificate version as an enumeration. Version 3 certificates are + the latest version and also the only type you should see in practice. + + :raises cryptography.x509.InvalidVersion: If the version in the + certificate is not a known + :class:`X.509 version `. + + .. doctest:: + + >>> cert.version + + + .. method:: fingerprint(algorithm) + + :param algorithm: The + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + that will be used to generate the fingerprint. + + :return bytes: The fingerprint using the supplied hash algorithm, as + bytes. + + .. doctest:: + + >>> from cryptography.hazmat.primitives import hashes + >>> cert.fingerprint(hashes.SHA256()) + '\x86\xd2\x187Gc\xfc\xe7}[+E9\x8d\xb4\x8f\x10\xe5S\xda\x18u\xbe}a\x03\x08[\xac\xa04?' + + .. attribute:: serial_number + + :type: int + + The serial as a Python integer. + + .. doctest:: + + >>> cert.serial_number + 2 + + .. method:: public_key() + + The public key associated with the certificate. + + :returns: + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + + .. doctest:: + + >>> from cryptography.hazmat.primitives.asymmetric import rsa + >>> public_key = cert.public_key() + >>> isinstance(public_key, rsa.RSAPublicKey) + True + + .. attribute:: not_valid_before + + :type: :class:`datetime.datetime` + + A naïve datetime representing the beginning of the validity period for + the certificate in UTC. This value is inclusive. + + .. doctest:: + + >>> cert.not_valid_before + datetime.datetime(2010, 1, 1, 8, 30) + + .. attribute:: not_valid_after + + :type: :class:`datetime.datetime` + + A naïve datetime representing the end of the validity period for the + certificate in UTC. This value is inclusive. + + .. doctest:: + + >>> cert.not_valid_after + datetime.datetime(2030, 12, 31, 8, 30) + + .. attribute:: issuer + + .. versionadded:: 0.8 + + :type: :class:`Name` + + The :class:`Name` of the issuer. + + .. attribute:: subject + + .. versionadded:: 0.8 + + :type: :class:`Name` + + The :class:`Name` of the subject. + + .. attribute:: signature_hash_algorithm + + :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + + Returns the + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` which + was used in signing this certificate. + + .. doctest:: + + >>> from cryptography.hazmat.primitives import hashes + >>> isinstance(cert.signature_hash_algorithm, hashes.SHA256) + True + + .. attribute:: signature_algorithm_oid + + .. versionadded:: 1.6 + + :type: :class:`ObjectIdentifier` + + Returns the :class:`ObjectIdentifier` of the signature algorithm used + to sign the certificate. This will be one of the OIDs from + :class:`~cryptography.x509.oid.SignatureAlgorithmOID`. + + + .. doctest:: + + >>> cert.signature_algorithm_oid + + + .. attribute:: extensions + + :type: :class:`Extensions` + + The extensions encoded in the certificate. + + :raises cryptography.x509.DuplicateExtension: If more than one + extension of the same type is found within the certificate. + + :raises cryptography.x509.UnsupportedExtension: If the certificate + contains an extension that is not supported. + + :raises cryptography.x509.UnsupportedGeneralNameType: If an extension + contains a general name that is not supported. + + :raises UnicodeError: If an extension contains IDNA encoding that is + invalid or not compliant with IDNA 2008. + + .. doctest:: + + >>> for ext in cert.extensions: + ... print(ext) + , critical=False, value=)> + , critical=False, value=)> + , critical=True, value=)> + , critical=False, value=, policy_qualifiers=None)>])>)> + , critical=True, value=)> + + .. attribute:: signature + + .. versionadded:: 1.2 + + :type: bytes + + The bytes of the certificate's signature. + + .. attribute:: tbs_certificate_bytes + + .. versionadded:: 1.2 + + :type: bytes + + The DER encoded bytes payload (as defined by :rfc:`5280`) that is hashed + and then signed by the private key of the certificate's issuer. This + data may be used to validate a signature, but use extreme caution as + certificate validation is a complex problem that involves much more + than just signature checks. + + .. method:: public_bytes(encoding) + + .. versionadded:: 1.0 + + :param encoding: The + :class:`~cryptography.hazmat.primitives.serialization.Encoding` + that will be used to serialize the certificate. + + :return bytes: The data that can be written to a file or sent + over the network to be verified by clients. + +X.509 CRL (Certificate Revocation List) Object +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: CertificateRevocationList + + .. versionadded:: 1.0 + + A CertificateRevocationList is an object representing a list of revoked + certificates. The object is iterable and will yield the RevokedCertificate + objects stored in this CRL. + + .. doctest:: + + >>> len(crl) + 1 + >>> revoked_certificate = crl[0] + >>> type(revoked_certificate) + + >>> for r in crl: + ... print(r.serial_number) + 0 + + .. method:: fingerprint(algorithm) + + :param algorithm: The + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + that will be used to generate the fingerprint. + + :return bytes: The fingerprint using the supplied hash algorithm, as + bytes. + + .. doctest:: + + >>> from cryptography.hazmat.primitives import hashes + >>> crl.fingerprint(hashes.SHA256()) + 'e\xcf.\xc4:\x83?1\xdc\xf3\xfc\x95\xd7\xb3\x87\xb3\x8e\xf8\xb93!\x87\x07\x9d\x1b\xb4!\xb9\xe4W\xf4\x1f' + + .. attribute:: signature_hash_algorithm + + :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + + Returns the + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` which + was used in signing this CRL. + + .. doctest:: + + >>> from cryptography.hazmat.primitives import hashes + >>> isinstance(crl.signature_hash_algorithm, hashes.SHA256) + True + + .. attribute:: signature_algorithm_oid + + .. versionadded:: 1.6 + + :type: :class:`ObjectIdentifier` + + Returns the :class:`ObjectIdentifier` of the signature algorithm used + to sign the CRL. This will be one of the OIDs from + :class:`~cryptography.x509.oid.SignatureAlgorithmOID`. + + .. doctest:: + + >>> crl.signature_algorithm_oid + + + .. attribute:: issuer + + :type: :class:`Name` + + The :class:`Name` of the issuer. + + .. doctest:: + + >>> crl.issuer + , value=u'US')>, , value=u'cryptography.io')>])> + + .. attribute:: next_update + + :type: :class:`datetime.datetime` + + A naïve datetime representing when the next update to this CRL is + expected. + + .. doctest:: + + >>> crl.next_update + datetime.datetime(2016, 1, 1, 0, 0) + + .. attribute:: last_update + + :type: :class:`datetime.datetime` + + A naïve datetime representing when the this CRL was last updated. + + .. doctest:: + + >>> crl.last_update + datetime.datetime(2015, 1, 1, 0, 0) + + .. attribute:: extensions + + :type: :class:`Extensions` + + The extensions encoded in the CRL. + + .. attribute:: signature + + .. versionadded:: 1.2 + + :type: bytes + + The bytes of the CRL's signature. + + .. attribute:: tbs_certlist_bytes + + .. versionadded:: 1.2 + + :type: bytes + + The DER encoded bytes payload (as defined by :rfc:`5280`) that is hashed + and then signed by the private key of the CRL's issuer. This data may be + used to validate a signature, but use extreme caution as CRL validation + is a complex problem that involves much more than just signature checks. + + .. method:: public_bytes(encoding) + + .. versionadded:: 1.2 + + :param encoding: The + :class:`~cryptography.hazmat.primitives.serialization.Encoding` + that will be used to serialize the certificate revocation list. + + :return bytes: The data that can be written to a file or sent + over the network and used as part of a certificate verification + process. + + +X.509 Certificate Builder +~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: CertificateBuilder + + .. versionadded:: 1.0 + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import rsa + >>> from cryptography.x509.oid import NameOID + >>> import datetime + >>> one_day = datetime.timedelta(1, 0, 0) + >>> private_key = rsa.generate_private_key( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ) + >>> public_key = private_key.public_key() + >>> builder = x509.CertificateBuilder() + >>> builder = builder.subject_name(x509.Name([ + ... x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ... ])) + >>> builder = builder.issuer_name(x509.Name([ + ... x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ... ])) + >>> builder = builder.not_valid_before(datetime.datetime.today() - one_day) + >>> builder = builder.not_valid_after(datetime.datetime(2018, 8, 2)) + >>> builder = builder.serial_number(x509.random_serial_number()) + >>> builder = builder.public_key(public_key) + >>> builder = builder.add_extension( + ... x509.BasicConstraints(ca=False, path_length=None), critical=True, + ... ) + >>> certificate = builder.sign( + ... private_key=private_key, algorithm=hashes.SHA256(), + ... backend=default_backend() + ... ) + >>> isinstance(certificate, x509.Certificate) + True + + .. method:: issuer_name(name) + + Sets the issuer's distinguished name. + + :param name: The :class:`~cryptography.x509.Name` that describes the + issuer (CA). + + .. method:: subject_name(name) + + Sets the subject's distinguished name. + + :param name: The :class:`~cryptography.x509.Name` that describes the + subject. + + .. method:: public_key(public_key) + + Sets the subject's public key. + + :param public_key: The subject's public key. This can be one of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + + .. method:: serial_number(serial_number) + + Sets the certificate's serial number (an integer). The CA's policy + determines how it attributes serial numbers to certificates. This + number must uniquely identify the certificate given the issuer. + `CABForum Guidelines`_ require entropy in the serial number + to provide protection against hash collision attacks. For more + information on secure random number generation, see + :doc:`/random-numbers`. + + :param serial_number: Integer number that will be used by the CA to + identify this certificate (most notably during certificate + revocation checking). Users should consider using + :func:`~cryptography.x509.random_serial_number` when possible. + + .. method:: not_valid_before(time) + + Sets the certificate's activation time. This is the time from which + clients can start trusting the certificate. It may be different from + the time at which the certificate was created. + + :param time: The :class:`datetime.datetime` object (in UTC) that marks the + activation time for the certificate. The certificate may not be + trusted clients if it is used before this time. + + .. method:: not_valid_after(time) + + Sets the certificate's expiration time. This is the time from which + clients should no longer trust the certificate. The CA's policy will + determine how long the certificate should remain in use. + + :param time: The :class:`datetime.datetime` object (in UTC) that marks the + expiration time for the certificate. The certificate may not be + trusted clients if it is used after this time. + + .. method:: add_extension(extension, critical) + + Adds an X.509 extension to the certificate. + + :param extension: An extension conforming to the + :class:`~cryptography.x509.ExtensionType` interface. + + :param critical: Set to ``True`` if the extension must be understood and + handled by whoever reads the certificate. + + .. method:: sign(private_key, algorithm, backend) + + Sign the certificate using the CA's private key. + + :param private_key: The + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + that will be used to sign the certificate. + + :param algorithm: The + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that + will be used to generate the signature. + + :param backend: Backend that will be used to build the certificate. + Must support the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :returns: :class:`~cryptography.x509.Certificate` + + +X.509 CSR (Certificate Signing Request) Object +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: CertificateSigningRequest + + .. versionadded:: 0.9 + + .. method:: public_key() + + The public key associated with the request. + + :returns: + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey` + + .. doctest:: + + >>> from cryptography.hazmat.primitives.asymmetric import rsa + >>> public_key = csr.public_key() + >>> isinstance(public_key, rsa.RSAPublicKey) + True + + .. attribute:: subject + + :type: :class:`Name` + + The :class:`Name` of the subject. + + .. attribute:: signature_hash_algorithm + + :type: :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + + Returns the + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` which + was used in signing this request. + + .. doctest:: + + >>> from cryptography.hazmat.primitives import hashes + >>> isinstance(csr.signature_hash_algorithm, hashes.SHA1) + True + + .. attribute:: signature_algorithm_oid + + .. versionadded:: 1.6 + + :type: :class:`ObjectIdentifier` + + Returns the :class:`ObjectIdentifier` of the signature algorithm used + to sign the request. This will be one of the OIDs from + :class:`~cryptography.x509.oid.SignatureAlgorithmOID`. + + .. doctest:: + + >>> csr.signature_algorithm_oid + + + .. attribute:: extensions + + :type: :class:`Extensions` + + The extensions encoded in the certificate signing request. + + :raises cryptography.x509.DuplicateExtension: If more than one + extension of the same type is found within the certificate signing request. + + :raises cryptography.x509.UnsupportedExtension: If the certificate signing request + contains an extension that is not supported. + + :raises cryptography.x509.UnsupportedGeneralNameType: If an extension + contains a general name that is not supported. + + :raises UnicodeError: If an extension contains IDNA encoding that is + invalid or not compliant with IDNA 2008. + + + .. method:: public_bytes(encoding) + + .. versionadded:: 1.0 + + :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. + + .. attribute:: signature + + .. versionadded:: 1.2 + + :type: bytes + + The bytes of the certificate signing request's signature. + + .. attribute:: tbs_certrequest_bytes + + .. versionadded:: 1.2 + + :type: bytes + + The DER encoded bytes payload (as defined by :rfc:`2986`) that is + hashed and then signed by the private key (corresponding to the public + key embedded in the CSR). This data may be used to validate the CSR + signature. + + .. attribute:: is_signature_valid + + .. versionadded:: 1.3 + + Returns True if the CSR signature is correct, False otherwise. + +X.509 Certificate Revocation List Builder +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: CertificateRevocationListBuilder + + .. versionadded:: 1.2 + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import rsa + >>> from cryptography.x509.oid import NameOID + >>> import datetime + >>> one_day = datetime.timedelta(1, 0, 0) + >>> private_key = rsa.generate_private_key( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ) + >>> builder = x509.CertificateRevocationListBuilder() + >>> builder = builder.issuer_name(x509.Name([ + ... x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io CA'), + ... ])) + >>> builder = builder.last_update(datetime.datetime.today()) + >>> builder = builder.next_update(datetime.datetime.today() + one_day) + >>> revoked_cert = x509.RevokedCertificateBuilder().serial_number( + ... 333 + ... ).revocation_date( + ... datetime.datetime.today() + ... ).build(default_backend()) + >>> builder = builder.add_revoked_certificate(revoked_cert) + >>> crl = builder.sign( + ... private_key=private_key, algorithm=hashes.SHA256(), + ... backend=default_backend() + ... ) + >>> len(crl) + 1 + + .. method:: issuer_name(name) + + Sets the issuer's distinguished name. + + :param name: The :class:`~cryptography.x509.Name` that describes the + issuer (CA). + + .. method:: last_update(time) + + Sets this CRL's activation time. This is the time from which + clients can start trusting this CRL. It may be different from + the time at which this CRL was created. This is also known as the + ``thisUpdate`` time. + + :param time: The :class:`datetime.datetime` object (in UTC) that marks + the activation time for this CRL. The CRL may not be trusted if it + is used before this time. + + .. method:: next_update(time) + + Sets this CRL's next update time. This is the time by which + a new CRL will be issued. The CA is allowed to issue a new CRL before + this date, however clients are not required to check for it. + + :param time: The :class:`datetime.datetime` object (in UTC) that marks + the next update time for this CRL. + + .. method:: add_extension(extension, critical) + + Adds an X.509 extension to this CRL. + + :param extension: An extension with the + :class:`~cryptography.x509.ExtensionType` interface. + + :param critical: Set to ``True`` if the extension must be understood and + handled by whoever reads the CRL. + + .. method:: add_revoked_certificate(revoked_certificate) + + Adds a revoked certificate to this CRL. + + :param revoked_certificate: An instance of + :class:`~cryptography.x509.RevokedCertificate`. These can be + obtained from an existing CRL or created with + :class:`~cryptography.x509.RevokedCertificateBuilder`. + + .. method:: sign(private_key, algorithm, backend) + + Sign this CRL using the CA's private key. + + :param private_key: The + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + that will be used to sign the certificate. + + :param algorithm: The + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` that + will be used to generate the signature. + + :param backend: Backend that will be used to build the CRL. + Must support the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :returns: :class:`~cryptography.x509.CertificateRevocationList` + +X.509 Revoked Certificate Object +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: RevokedCertificate + + .. versionadded:: 1.0 + + .. attribute:: serial_number + + :type: :class:`int` + + An integer representing the serial number of the revoked certificate. + + .. doctest:: + + >>> revoked_certificate.serial_number + 0 + + .. attribute:: revocation_date + + :type: :class:`datetime.datetime` + + A naïve datetime representing the date this certificates was revoked. + + .. doctest:: + + >>> revoked_certificate.revocation_date + datetime.datetime(2015, 1, 1, 0, 0) + + .. attribute:: extensions + + :type: :class:`Extensions` + + The extensions encoded in the revoked certificate. + + .. doctest:: + + >>> for ext in revoked_certificate.extensions: + ... print(ext) + , critical=False, value=)> + , critical=False, value=)> + +X.509 Revoked Certificate Builder +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: RevokedCertificateBuilder + + This class is used to create :class:`~cryptography.x509.RevokedCertificate` + objects that can be used with the + :class:`~cryptography.x509.CertificateRevocationListBuilder`. + + .. versionadded:: 1.2 + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> import datetime + >>> builder = x509.RevokedCertificateBuilder() + >>> builder = builder.revocation_date(datetime.datetime.today()) + >>> builder = builder.serial_number(3333) + >>> revoked_certificate = builder.build(default_backend()) + >>> isinstance(revoked_certificate, x509.RevokedCertificate) + True + + .. method:: serial_number(serial_number) + + Sets the revoked certificate's serial number. + + :param serial_number: Integer number that is used to identify the + revoked certificate. + + .. method:: revocation_date(time) + + Sets the certificate's revocation date. + + :param time: The :class:`datetime.datetime` object (in UTC) that marks the + revocation time for the certificate. + + .. method:: add_extension(extension, critical) + + Adds an X.509 extension to this revoked certificate. + + :param extension: An instance of one of the + :ref:`CRL entry extensions `. + + :param critical: Set to ``True`` if the extension must be understood and + handled. + + .. method:: build(backend) + + Create a revoked certificate object using the provided backend. + + :param backend: Backend that will be used to build the revoked + certificate. Must support the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :returns: :class:`~cryptography.x509.RevokedCertificate` + +X.509 CSR (Certificate Signing Request) Builder Object +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. class:: CertificateSigningRequestBuilder + + .. versionadded:: 1.0 + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> from cryptography.hazmat.primitives.asymmetric import rsa + >>> from cryptography.x509.oid import NameOID + >>> private_key = rsa.generate_private_key( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ) + >>> builder = x509.CertificateSigningRequestBuilder() + >>> builder = builder.subject_name(x509.Name([ + ... x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ... ])) + >>> builder = builder.add_extension( + ... x509.BasicConstraints(ca=False, path_length=None), critical=True, + ... ) + >>> request = builder.sign( + ... private_key, hashes.SHA256(), default_backend() + ... ) + >>> isinstance(request, x509.CertificateSigningRequest) + True + + .. method:: subject_name(name) + + :param name: The :class:`~cryptography.x509.Name` of the certificate + subject. + :returns: A new + :class:`~cryptography.x509.CertificateSigningRequestBuilder`. + + .. method:: add_extension(extension, critical) + + :param extension: An extension conforming to the + :class:`~cryptography.x509.ExtensionType` interface. + :param critical: Set to `True` if the extension must be understood and + handled by whoever reads the certificate. + :returns: A new + :class:`~cryptography.x509.CertificateSigningRequestBuilder`. + + .. method:: sign(private_key, algorithm, backend) + + :param backend: Backend that will be used to sign the request. + Must support the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :param private_key: The + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPrivateKey`, + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPrivateKey` or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePrivateKey` + that will be used to sign the request. When the request is + signed by a certificate authority, the private key's associated + public key will be stored in the resulting certificate. + + :param algorithm: The + :class:`~cryptography.hazmat.primitives.hashes.HashAlgorithm` + that will be used to generate the request signature. + + :returns: A new + :class:`~cryptography.x509.CertificateSigningRequest`. + + +.. class:: Name + + .. versionadded:: 0.8 + + An X509 Name is an ordered list of attributes. The object is iterable to + get every attribute or you can use :meth:`Name.get_attributes_for_oid` to + obtain the specific type you want. Names are sometimes represented as a + slash or comma delimited string (e.g. ``/CN=mydomain.com/O=My Org/C=US`` or + ``CN=mydomain.com, O=My Org, C=US``). + + Technically, a Name is a list of *sets* of attributes, called *Relative + Distinguished Names* or *RDNs*, although multi-valued RDNs are rarely + encountered. The iteration order of values within a multi-valued RDN is + undefined. If you need to handle multi-valued RDNs, the ``rdns`` property + gives access to an ordered list of :class:`RelativeDistinguishedName` + objects. + + A Name can be initialized with an iterable of :class:`NameAttribute` (the + common case where each RDN has a single attribute) or an iterable of + :class:`RelativeDistinguishedName` objects (in the rare case of + multi-valued RDNs). + + .. doctest:: + + >>> len(cert.subject) + 3 + >>> for attribute in cert.subject: + ... print(attribute) + , value=u'US')> + , value=u'Test Certificates 2011')> + , value=u'Good CA')> + + .. attribute:: rdns + + .. versionadded:: 1.6 + + :type: list of :class:`RelativeDistinguishedName` + + .. method:: get_attributes_for_oid(oid) + + :param oid: An :class:`ObjectIdentifier` instance. + + :returns: A list of :class:`NameAttribute` instances that match the + OID provided. If nothing matches an empty list will be returned. + + .. doctest:: + + >>> cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME) + [, value=u'Good CA')>] + + .. method:: public_bytes(backend) + + .. versionadded:: 1.6 + + :param backend: A backend supporting the + :class:`~cryptography.hazmat.backends.interfaces.X509Backend` + interface. + + :return bytes: The DER encoded name. + +.. class:: Version + + .. versionadded:: 0.7 + + An enumeration for X.509 versions. + + .. attribute:: v1 + + For version 1 X.509 certificates. + + .. attribute:: v3 + + For version 3 X.509 certificates. + +.. class:: NameAttribute + + .. versionadded:: 0.8 + + An X.509 name consists of a list of :class:`RelativeDistinguishedName` + instances, which consist of a set of :class:`NameAttribute` instances. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + The attribute OID. + + .. attribute:: value + + :type: :term:`text` + + The value of the attribute. + + +.. class:: RelativeDistinguishedName(attributes) + + .. versionadded:: 1.6 + + A relative distinguished name is a non-empty set of name attributes. The + object is iterable to get every attribute. + + .. method:: get_attributes_for_oid(oid) + + :param oid: An :class:`ObjectIdentifier` instance. + + :returns: A list of :class:`NameAttribute` instances that match the OID + provided. The list should contain zero or one values. + + +.. class:: ObjectIdentifier + + .. versionadded:: 0.8 + + Object identifiers (frequently seen abbreviated as OID) identify the type + of a value (see: :class:`NameAttribute`). + + .. attribute:: dotted_string + + :type: :class:`str` + + The dotted string value of the OID (e.g. ``"2.5.4.3"``) + +.. _general_name_classes: + +General Name Classes +~~~~~~~~~~~~~~~~~~~~ + +.. class:: GeneralName + + .. versionadded:: 0.9 + + This is the generic interface that all the following classes are registered + against. + +.. class:: RFC822Name(value) + + .. versionadded:: 0.9 + + This corresponds to an email address. For example, ``user@example.com``. + + .. attribute:: value + + :type: :term:`text` + +.. class:: DNSName(value) + + .. versionadded:: 0.9 + + This corresponds to a domain name. For example, ``cryptography.io``. + + .. attribute:: value + + :type: :term:`text` + +.. class:: DirectoryName(value) + + .. versionadded:: 0.9 + + This corresponds to a directory name. + + .. attribute:: value + + :type: :class:`Name` + +.. class:: UniformResourceIdentifier(value) + + .. versionadded:: 0.9 + + This corresponds to a uniform resource identifier. For example, + ``https://cryptography.io``. The URI is parsed and IDNA decoded (see + :rfc:`5895`). + + .. note:: + + URIs that do not contain ``://`` in them will not be decoded. + + .. attribute:: value + + :type: :term:`text` + +.. class:: IPAddress(value) + + .. versionadded:: 0.9 + + This corresponds to an IP address. + + .. attribute:: value + + :type: :class:`~ipaddress.IPv4Address`, + :class:`~ipaddress.IPv6Address`, :class:`~ipaddress.IPv4Network`, + or :class:`~ipaddress.IPv6Network`. + +.. class:: RegisteredID(value) + + .. versionadded:: 0.9 + + This corresponds to a registered ID. + + .. attribute:: value + + :type: :class:`ObjectIdentifier` + +.. class:: OtherName(type_id, value) + + .. versionadded:: 1.0 + + This corresponds to an ``otherName.`` An ``otherName`` has a type identifier and a value represented in binary DER format. + + .. attribute:: type_id + + :type: :class:`ObjectIdentifier` + + .. attribute:: value + + :type: `bytes` + +X.509 Extensions +~~~~~~~~~~~~~~~~ + +.. class:: Extensions + + .. versionadded:: 0.9 + + An X.509 Extensions instance is an ordered list of extensions. The object + is iterable to get every extension. + + .. method:: get_extension_for_oid(oid) + + :param oid: An :class:`ObjectIdentifier` instance. + + :returns: An instance of the extension class. + + :raises cryptography.x509.ExtensionNotFound: If the certificate does + not have the extension requested. + + .. doctest:: + + >>> from cryptography.x509.oid import ExtensionOID + >>> cert.extensions.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS) + , critical=True, value=)> + + .. method:: get_extension_for_class(extclass) + + .. versionadded:: 1.1 + + :param extclass: An extension class. + + :returns: An instance of the extension class. + + :raises cryptography.x509.ExtensionNotFound: If the certificate does + not have the extension requested. + + .. doctest:: + + >>> from cryptography import x509 + >>> cert.extensions.get_extension_for_class(x509.BasicConstraints) + , critical=True, value=)> + +.. class:: Extension + + .. versionadded:: 0.9 + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + One of the :class:`~cryptography.x509.oid.ExtensionOID` OIDs. + + .. attribute:: critical + + :type: bool + + Determines whether a given extension is critical or not. :rfc:`5280` + requires that "A certificate-using system MUST reject the certificate + if it encounters a critical extension it does not recognize or a + critical extension that contains information that it cannot process". + + .. attribute:: value + + Returns an instance of the extension type corresponding to the OID. + +.. class:: ExtensionType + + .. versionadded:: 1.0 + + This is the interface against which all the following extension types are + registered. + +.. class:: KeyUsage(digital_signature, content_commitment, key_encipherment, data_encipherment, key_agreement, key_cert_sign, crl_sign, encipher_only, decipher_only) + + .. versionadded:: 0.9 + + The key usage extension defines the purpose of the key contained in the + certificate. The usage restriction might be employed when a key that could + be used for more than one operation is to be restricted. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :attr:`~cryptography.x509.oid.ExtensionOID.KEY_USAGE`. + + .. attribute:: digital_signature + + :type: bool + + This purpose is set to true when the subject public key is used for verifying + digital signatures, other than signatures on certificates + (``key_cert_sign``) and CRLs (``crl_sign``). + + .. attribute:: content_commitment + + :type: bool + + This purpose is set to true when the subject public key is used for verifying + digital signatures, other than signatures on certificates + (``key_cert_sign``) and CRLs (``crl_sign``). It is used to provide a + non-repudiation service that protects against the signing entity + falsely denying some action. In the case of later conflict, a + reliable third party may determine the authenticity of the signed + data. This was called ``non_repudiation`` in older revisions of the + X.509 specification. + + .. attribute:: key_encipherment + + :type: bool + + This purpose is set to true when the subject public key is used for + enciphering private or secret keys. + + .. attribute:: data_encipherment + + :type: bool + + This purpose is set to true when the subject public key is used for + directly enciphering raw user data without the use of an intermediate + symmetric cipher. + + .. attribute:: key_agreement + + :type: bool + + This purpose is set to true when the subject public key is used for key + agreement. For example, when a Diffie-Hellman key is to be used for + key management, then this purpose is set to true. + + .. attribute:: key_cert_sign + + :type: bool + + This purpose is set to true when the subject public key is used for + verifying signatures on public key certificates. If this purpose is set + to true then ``ca`` must be true in the :class:`BasicConstraints` + extension. + + .. attribute:: crl_sign + + :type: bool + + This purpose is set to true when the subject public key is used for + verifying signatures on certificate revocation lists. + + .. attribute:: encipher_only + + :type: bool + + When this purposes is set to true and the ``key_agreement`` purpose is + also set, the subject public key may be used only for enciphering data + while performing key agreement. + + :raises ValueError: This is raised if accessed when ``key_agreement`` + is false. + + .. attribute:: decipher_only + + :type: bool + + When this purposes is set to true and the ``key_agreement`` purpose is + also set, the subject public key may be used only for deciphering data + while performing key agreement. + + :raises ValueError: This is raised if accessed when ``key_agreement`` + is false. + + +.. class:: BasicConstraints(ca, path_length) + + .. versionadded:: 0.9 + + Basic constraints is an X.509 extension type that defines whether a given + certificate is allowed to sign additional certificates and what path + length restrictions may exist. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :attr:`~cryptography.x509.oid.ExtensionOID.BASIC_CONSTRAINTS`. + + .. attribute:: ca + + :type: bool + + Whether the certificate can sign certificates. + + .. attribute:: path_length + + :type: int or None + + The maximum path length for certificates subordinate to this + certificate. This attribute only has meaning if ``ca`` is true. + If ``ca`` is true then a path length of None means there's no + restriction on the number of subordinate CAs in the certificate chain. + If it is zero or greater then it defines the maximum length for a + subordinate CA's certificate chain. For example, a ``path_length`` of 1 + means the certificate can sign a subordinate CA, but the subordinate CA + is not allowed to create subordinates with ``ca`` set to true. + +.. class:: ExtendedKeyUsage(usages) + + .. versionadded:: 0.9 + + This extension indicates one or more purposes for which the certified + public key may be used, in addition to or in place of the basic + purposes indicated in the key usage extension. The object is + iterable to obtain the list of + :class:`~cryptography.x509.oid.ExtendedKeyUsageOID` OIDs present. + + :param list usages: A list of + :class:`~cryptography.x509.oid.ExtendedKeyUsageOID` OIDs. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :attr:`~cryptography.x509.oid.ExtensionOID.EXTENDED_KEY_USAGE`. + + +.. class:: OCSPNoCheck() + + .. versionadded:: 1.0 + + This presence of this extension indicates that an OCSP client can trust a + responder for the lifetime of the responder's certificate. CAs issuing + such a certificate should realize that a compromise of the responder's key + is as serious as the compromise of a CA key used to sign CRLs, at least for + the validity period of this certificate. CA's may choose to issue this type + of certificate with a very short lifetime and renew it frequently. This + extension is only relevant when the certificate is an authorized OCSP + responder. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :attr:`~cryptography.x509.oid.ExtensionOID.OCSP_NO_CHECK`. + +.. class:: NameConstraints(permitted_subtrees, excluded_subtrees) + + .. versionadded:: 1.0 + + The name constraints extension, which only has meaning in a CA certificate, + defines a name space within which all subject names in certificates issued + beneath the CA certificate must (or must not) be in. For specific details + on the way this extension should be processed see :rfc:`5280`. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns :attr:`~cryptography.x509.oid.ExtensionOID.NAME_CONSTRAINTS`. + + .. attribute:: permitted_subtrees + + :type: list of :class:`GeneralName` objects or None + + The set of permitted name patterns. If a name matches this and an + element in ``excluded_subtrees`` it is invalid. At least one of + ``permitted_subtrees`` and ``excluded_subtrees`` will be non-None. + + .. attribute:: excluded_subtrees + + :type: list of :class:`GeneralName` objects or None + + Any name matching a restriction in the ``excluded_subtrees`` field is + invalid regardless of information appearing in the + ``permitted_subtrees``. At least one of ``permitted_subtrees`` and + ``excluded_subtrees`` will be non-None. + +.. class:: AuthorityKeyIdentifier(key_identifier, authority_cert_issuer, authority_cert_serial_number) + + .. versionadded:: 0.9 + + The authority key identifier extension provides a means of identifying the + public key corresponding to the private key used to sign a certificate. + This extension is typically used to assist in determining the appropriate + certificate chain. For more information about generation and use of this + extension see `RFC 5280 section 4.2.1.1`_. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.ExtensionOID.AUTHORITY_KEY_IDENTIFIER`. + + .. attribute:: key_identifier + + :type: bytes + + A value derived from the public key used to verify the certificate's + signature. + + .. attribute:: authority_cert_issuer + + :type: :class:`Name` or None + + The :class:`Name` of the issuer's issuer. + + .. attribute:: authority_cert_serial_number + + :type: int or None + + The serial number of the issuer's issuer. + + .. classmethod:: from_issuer_public_key(public_key) + + .. versionadded:: 1.0 + + .. note:: + + This method should be used if the issuer certificate does not + contain a :class:`~cryptography.x509.SubjectKeyIdentifier`. + Otherwise, use + :meth:`~cryptography.x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier`. + + Creates a new AuthorityKeyIdentifier instance using the public key + provided to generate the appropriate digest. This should be the + **issuer's public key**. The resulting object will contain + :attr:`~cryptography.x509.AuthorityKeyIdentifier.key_identifier`, but + :attr:`~cryptography.x509.AuthorityKeyIdentifier.authority_cert_issuer` + and + :attr:`~cryptography.x509.AuthorityKeyIdentifier.authority_cert_serial_number` + will be None. + The generated ``key_identifier`` is the SHA1 hash of the ``subjectPublicKey`` + ASN.1 bit string. This is the first recommendation in :rfc:`5280` + section 4.2.1.2. + + :param public_key: One of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` + , + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` + , or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`. + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> issuer_cert = x509.load_pem_x509_certificate(pem_data, default_backend()) + >>> x509.AuthorityKeyIdentifier.from_issuer_public_key(issuer_cert.public_key()) + + + .. classmethod:: from_issuer_subject_key_identifier(ski) + + .. versionadded:: 1.3 + + .. note:: + This method should be used if the issuer certificate contains a + :class:`~cryptography.x509.SubjectKeyIdentifier`. Otherwise, use + :meth:`~cryptography.x509.AuthorityKeyIdentifier.from_issuer_public_key`. + + Creates a new AuthorityKeyIdentifier instance using the + SubjectKeyIdentifier from the issuer certificate. The resulting object + will contain + :attr:`~cryptography.x509.AuthorityKeyIdentifier.key_identifier`, but + :attr:`~cryptography.x509.AuthorityKeyIdentifier.authority_cert_issuer` + and + :attr:`~cryptography.x509.AuthorityKeyIdentifier.authority_cert_serial_number` + will be None. + + :param ski: The + :class:`~cryptography.x509.SubjectKeyIdentifier` from the issuer + certificate. + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> issuer_cert = x509.load_pem_x509_certificate(pem_data, default_backend()) + >>> ski = issuer_cert.extensions.get_extension_for_class(x509.SubjectKeyIdentifier) + >>> x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier(ski) + + +.. class:: SubjectKeyIdentifier(digest) + + .. versionadded:: 0.9 + + The subject key identifier extension provides a means of identifying + certificates that contain a particular public key. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.ExtensionOID.SUBJECT_KEY_IDENTIFIER`. + + .. attribute:: digest + + :type: bytes + + The binary value of the identifier. + + .. classmethod:: from_public_key(public_key) + + .. versionadded:: 1.0 + + Creates a new SubjectKeyIdentifier instance using the public key + provided to generate the appropriate digest. This should be the public + key that is in the certificate. The generated digest is the SHA1 hash + of the ``subjectPublicKey`` ASN.1 bit string. This is the first + recommendation in :rfc:`5280` section 4.2.1.2. + + :param public_key: One of + :class:`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey` + , + :class:`~cryptography.hazmat.primitives.asymmetric.dsa.DSAPublicKey` + , or + :class:`~cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey`. + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> csr = x509.load_pem_x509_csr(pem_req_data, default_backend()) + >>> x509.SubjectKeyIdentifier.from_public_key(csr.public_key()) + + +.. class:: SubjectAlternativeName(general_names) + + .. versionadded:: 0.9 + + Subject alternative name is an X.509 extension that provides a list of + :ref:`general name ` instances that provide a set + of identities for which the certificate is valid. The object is iterable to + get every element. + + :param list general_names: A list of :class:`GeneralName` instances. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME`. + + .. method:: get_values_for_type(type) + + :param type: A :class:`GeneralName` instance. This is one of the + :ref:`general name classes `. + + :returns: A list of values extracted from the matched general names. + The type of the returned values depends on the :class:`GeneralName`. + + .. doctest:: + + >>> from cryptography import x509 + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import hashes + >>> cert = x509.load_pem_x509_certificate(cryptography_cert_pem, default_backend()) + >>> # Get the subjectAltName extension from the certificate + >>> ext = cert.extensions.get_extension_for_oid(ExtensionOID.SUBJECT_ALTERNATIVE_NAME) + >>> # Get the dNSName entries from the SAN extension + >>> ext.value.get_values_for_type(x509.DNSName) + [u'www.cryptography.io', u'cryptography.io'] + + +.. class:: IssuerAlternativeName(general_names) + + .. versionadded:: 1.0 + + Issuer alternative name is an X.509 extension that provides a list of + :ref:`general name ` instances that provide a set + of identities for the certificate issuer. The object is iterable to + get every element. + + :param list general_names: A list of :class:`GeneralName` instances. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.ExtensionOID.ISSUER_ALTERNATIVE_NAME`. + + .. method:: get_values_for_type(type) + + :param type: A :class:`GeneralName` instance. This is one of the + :ref:`general name classes `. + + :returns: A list of values extracted from the matched general names. + + +.. class:: AuthorityInformationAccess(descriptions) + + .. versionadded:: 0.9 + + The authority information access extension indicates how to access + information and services for the issuer of the certificate in which + the extension appears. Information and services may include online + validation services (such as OCSP) and issuer data. It is an iterable, + containing one or more :class:`~cryptography.x509.AccessDescription` + instances. + + :param list descriptions: A list of :class:`AccessDescription` objects. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.ExtensionOID.AUTHORITY_INFORMATION_ACCESS`. + + +.. class:: AccessDescription(access_method, access_location) + + .. versionadded:: 0.9 + + .. attribute:: access_method + + :type: :class:`ObjectIdentifier` + + The access method defines what the ``access_location`` means. It must + be either + :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.OCSP` or + :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.CA_ISSUERS`. + If it is + :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.OCSP` + the access location will be where to obtain OCSP + information for the certificate. If it is + :attr:`~cryptography.x509.oid.AuthorityInformationAccessOID.CA_ISSUERS` + the access location will provide additional information about the + issuing certificate. + + .. attribute:: access_location + + :type: :class:`GeneralName` + + Where to access the information defined by the access method. + +.. class:: CRLDistributionPoints(distribution_points) + + .. versionadded:: 0.9 + + The CRL distribution points extension identifies how CRL information is + obtained. It is an iterable, containing one or more + :class:`DistributionPoint` instances. + + :param list distribution_points: A list of :class:`DistributionPoint` + instances. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.ExtensionOID.CRL_DISTRIBUTION_POINTS`. + +.. class:: DistributionPoint(full_name, relative_name, reasons, crl_issuer) + + .. versionadded:: 0.9 + + .. attribute:: full_name + + :type: list of :class:`GeneralName` instances or None + + This field describes methods to retrieve the CRL. At most one of + ``full_name`` or ``relative_name`` will be non-None. + + .. attribute:: relative_name + + :type: :class:`RelativeDistinguishedName` or None + + This field describes methods to retrieve the CRL relative to the CRL + issuer. At most one of ``full_name`` or ``relative_name`` will be + non-None. + + .. versionchanged:: 1.6 + Changed from :class:`Name` to :class:`RelativeDistinguishedName`. + + .. attribute:: crl_issuer + + :type: list of :class:`GeneralName` instances or None + + Information about the issuer of the CRL. + + .. attribute:: reasons + + :type: frozenset of :class:`ReasonFlags` or None + + The reasons a given distribution point may be used for when performing + revocation checks. + +.. class:: ReasonFlags + + .. versionadded:: 0.9 + + An enumeration for CRL reasons. + + .. attribute:: unspecified + + It is unspecified why the certificate was revoked. This reason cannot + be used as a reason flag in a :class:`DistributionPoint`. + + .. attribute:: key_compromise + + This reason indicates that the private key was compromised. + + .. attribute:: ca_compromise + + This reason indicates that the CA issuing the certificate was + compromised. + + .. attribute:: affiliation_changed + + This reason indicates that the subject's name or other information has + changed. + + .. attribute:: superseded + + This reason indicates that a certificate has been superseded. + + .. attribute:: cessation_of_operation + + This reason indicates that the certificate is no longer required. + + .. attribute:: certificate_hold + + This reason indicates that the certificate is on hold. + + .. attribute:: privilege_withdrawn + + This reason indicates that the privilege granted by this certificate + have been withdrawn. + + .. attribute:: aa_compromise + + When an attribute authority has been compromised. + + .. attribute:: remove_from_crl + + This reason indicates that the certificate was on hold and should be + removed from the CRL. This reason cannot be used as a reason flag + in a :class:`DistributionPoint`. + +.. class:: InhibitAnyPolicy(skip_certs) + + .. versionadded:: 1.0 + + The inhibit ``anyPolicy`` extension indicates that the special OID + :attr:`~cryptography.x509.oid.CertificatePoliciesOID.ANY_POLICY`, is not + considered an explicit match for other :class:`CertificatePolicies` except + when it appears in an intermediate self-issued CA certificate. The value + indicates the number of additional non-self-issued certificates that may + appear in the path before + :attr:`~cryptography.x509.oid.CertificatePoliciesOID.ANY_POLICY` is no + longer permitted. For example, a value of one indicates that + :attr:`~cryptography.x509.oid.CertificatePoliciesOID.ANY_POLICY` may be + processed in certificates issued by the subject of this certificate, but + not in additional certificates in the path. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.ExtensionOID.INHIBIT_ANY_POLICY`. + + .. attribute:: skip_certs + + :type: int + +.. class:: PolicyConstraints + + .. versionadded:: 1.3 + + The policy constraints extension is used to inhibit policy mapping or + require that each certificate in a chain contain an acceptable policy + identifier. For more information about the use of this extension see + :rfc:`5280`. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns :attr:`~cryptography.x509.oid.ExtensionOID.POLICY_CONSTRAINTS`. + + .. attribute:: require_explicit_policy + + :type: int or None + + If this field is not None, the value indicates the number of additional + certificates that may appear in the chain before an explicit policy is + required for the entire path. When an explicit policy is required, it + is necessary for all certificates in the chain to contain an acceptable + policy identifier in the certificate policies extension. An + acceptable policy identifier is the identifier of a policy required + by the user of the certification path or the identifier of a policy + that has been declared equivalent through policy mapping. + + .. attribute:: inhibit_policy_mapping + + :type: int or None + + If this field is not None, the value indicates the number of additional + certificates that may appear in the chain before policy mapping is no + longer permitted. For example, a value of one indicates that policy + mapping may be processed in certificates issued by the subject of this + certificate, but not in additional certificates in the chain. + +.. class:: CRLNumber(crl_number) + + .. versionadded:: 1.2 + + The CRL number is a CRL extension that conveys a monotonically increasing + sequence number for a given CRL scope and CRL issuer. This extension allows + users to easily determine when a particular CRL supersedes another CRL. + :rfc:`5280` requires that this extension be present in conforming CRLs. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.ExtensionOID.CRL_NUMBER`. + + .. attribute:: crl_number + + :type: int + +.. class:: UnrecognizedExtension + + .. versionadded:: 1.2 + + A generic extension class used to hold the raw value of **non-critical** + extensions that ``cryptography`` does not know how to parse. Extensions + marked critical will raise + :class:`~cryptography.x509.UnsupportedExtension`. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns the OID associated with this extension. + + .. attribute:: value + + :type: byte + + Returns the DER encoded bytes payload of the extension. + +.. class:: CertificatePolicies(policies) + + .. versionadded:: 0.9 + + The certificate policies extension is an iterable, containing one or more + :class:`PolicyInformation` instances. + + :param list policies: A list of :class:`PolicyInformation` instances. + + .. attribute:: oid + + .. versionadded:: 1.0 + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.ExtensionOID.CERTIFICATE_POLICIES`. + +Certificate Policies Classes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These classes may be present within a :class:`CertificatePolicies` instance. + +.. class:: PolicyInformation(policy_identifier, policy_qualifiers) + + .. versionadded:: 0.9 + + Contains a policy identifier and an optional list of qualifiers. + + .. attribute:: policy_identifier + + :type: :class:`ObjectIdentifier` + + .. attribute:: policy_qualifiers + + :type: list + + A list consisting of :term:`text` and/or :class:`UserNotice` objects. + If the value is text it is a pointer to the practice statement + published by the certificate authority. If it is a user notice it is + meant for display to the relying party when the certificate is + used. + +.. class:: UserNotice(notice_reference, explicit_text) + + .. versionadded:: 0.9 + + User notices are intended for display to a relying party when a certificate + is used. In practice, few if any UIs expose this data and it is a rarely + encoded component. + + .. attribute:: notice_reference + + :type: :class:`NoticeReference` or None + + The notice reference field names an organization and identifies, + by number, a particular statement prepared by that organization. + + .. attribute:: explicit_text + + This field includes an arbitrary textual statement directly in the + certificate. + + :type: :term:`text` + +.. class:: NoticeReference(organization, notice_numbers) + + Notice reference can name an organization and provide information about + notices related to the certificate. For example, it might identify the + organization name and notice number 1. Application software could + have a notice file containing the current set of notices for the named + organization; the application would then extract the notice text from the + file and display it. In practice this is rarely seen. + + .. versionadded:: 0.9 + + .. attribute:: organization + + :type: :term:`text` + + .. attribute:: notice_numbers + + :type: list + + A list of integers. + +.. _crl_entry_extensions: + +CRL Entry Extensions +~~~~~~~~~~~~~~~~~~~~ + +These extensions are only valid within a :class:`RevokedCertificate` object. + +.. class:: CertificateIssuer(general_names) + + .. versionadded:: 1.2 + + The certificate issuer is an extension that is only valid inside + :class:`~cryptography.x509.RevokedCertificate` objects. If the + ``indirectCRL`` property of the parent CRL's IssuingDistributionPoint + extension is set, then this extension identifies the certificate issuer + associated with the revoked certificate. The object is iterable to get + every element. + + :param list general_names: A list of :class:`GeneralName` instances. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.CRLEntryExtensionOID.CERTIFICATE_ISSUER`. + + .. method:: get_values_for_type(type) + + :param type: A :class:`GeneralName` instance. This is one of the + :ref:`general name classes `. + + :returns: A list of values extracted from the matched general names. + The type of the returned values depends on the :class:`GeneralName`. + +.. class:: CRLReason(reason) + + .. versionadded:: 1.2 + + CRL reason (also known as ``reasonCode``) is an extension that is only + valid inside :class:`~cryptography.x509.RevokedCertificate` objects. It + identifies a reason for the certificate revocation. + + :param reason: A value from the + :class:`~cryptography.x509.oid.CRLEntryExtensionOID` enum. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.CRLEntryExtensionOID.CRL_REASON`. + + .. attribute:: reason + + :type: An element from :class:`~cryptography.x509.ReasonFlags` + +.. class:: InvalidityDate(invalidity_date) + + .. versionadded:: 1.2 + + Invalidity date is an extension that is only valid inside + :class:`~cryptography.x509.RevokedCertificate` objects. It provides + the date on which it is known or suspected that the private key was + compromised or that the certificate otherwise became invalid. + This date may be earlier than the revocation date in the CRL entry, + which is the date at which the CA processed the revocation. + + :param invalidity_date: The :class:`datetime.datetime` when it is known + or suspected that the private key was compromised. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns + :attr:`~cryptography.x509.oid.CRLEntryExtensionOID.INVALIDITY_DATE`. + + .. attribute:: invalidity_date + + :type: :class:`datetime.datetime` + + +Object Identifiers +~~~~~~~~~~~~~~~~~~ + +X.509 elements are frequently identified by :class:`ObjectIdentifier` +instances. The following common OIDs are available as constants. + +.. currentmodule:: cryptography.x509.oid + +.. class:: NameOID + + These OIDs are typically seen in X.509 names. + + .. versionadded:: 1.0 + + .. attribute:: COMMON_NAME + + Corresponds to the dotted string ``"2.5.4.3"``. Historically the domain + name would be encoded here for server certificates. :rfc:`2818` + deprecates this practice and names of that type should now be located + in a :class:`~cryptography.x509.SubjectAlternativeName` extension. + + .. attribute:: COUNTRY_NAME + + Corresponds to the dotted string ``"2.5.4.6"``. + + .. attribute:: LOCALITY_NAME + + Corresponds to the dotted string ``"2.5.4.7"``. + + .. attribute:: STATE_OR_PROVINCE_NAME + + Corresponds to the dotted string ``"2.5.4.8"``. + + .. attribute:: STREET_ADDRESS + + .. versionadded:: 1.6 + + Corresponds to the dotted string ``"2.5.4.9"``. + + .. attribute:: ORGANIZATION_NAME + + Corresponds to the dotted string ``"2.5.4.10"``. + + .. attribute:: ORGANIZATIONAL_UNIT_NAME + + Corresponds to the dotted string ``"2.5.4.11"``. + + .. attribute:: SERIAL_NUMBER + + Corresponds to the dotted string ``"2.5.4.5"``. This is distinct from + the serial number of the certificate itself (which can be obtained with + :func:`~cryptography.x509.Certificate.serial_number`). + + .. attribute:: SURNAME + + Corresponds to the dotted string ``"2.5.4.4"``. + + .. attribute:: GIVEN_NAME + + Corresponds to the dotted string ``"2.5.4.42"``. + + .. attribute:: TITLE + + Corresponds to the dotted string ``"2.5.4.12"``. + + .. attribute:: GENERATION_QUALIFIER + + Corresponds to the dotted string ``"2.5.4.44"``. + + .. attribute:: X500_UNIQUE_IDENTIFIER + + .. versionadded:: 1.6 + + Corresponds to the dotted string ``"2.5.4.45"``. + + .. attribute:: DN_QUALIFIER + + Corresponds to the dotted string ``"2.5.4.46"``. This specifies + disambiguating information to add to the relative distinguished name of an + entry. See :rfc:`2256`. + + .. attribute:: PSEUDONYM + + Corresponds to the dotted string ``"2.5.4.65"``. + + .. attribute:: USER_ID + + .. versionadded:: 1.6 + + Corresponds to the dotted string ``"0.9.2342.19200300.100.1.1"``. + + .. attribute:: DOMAIN_COMPONENT + + Corresponds to the dotted string ``"0.9.2342.19200300.100.1.25"``. A string + holding one component of a domain name. See :rfc:`4519`. + + .. attribute:: EMAIL_ADDRESS + + Corresponds to the dotted string ``"1.2.840.113549.1.9.1"``. + + .. attribute:: JURISDICTION_COUNTRY_NAME + + Corresponds to the dotted string ``"1.3.6.1.4.1.311.60.2.1.3"``. + + .. attribute:: JURISDICTION_LOCALITY_NAME + + Corresponds to the dotted string ``"1.3.6.1.4.1.311.60.2.1.1"``. + + .. attribute:: JURISDICTION_STATE_OR_PROVINCE_NAME + + Corresponds to the dotted string ``"1.3.6.1.4.1.311.60.2.1.2"``. + + .. attribute:: BUSINESS_CATEGORY + + Corresponds to the dotted string ``"2.5.4.15"``. + + .. attribute:: POSTAL_ADDRESS + + .. versionadded:: 1.6 + + Corresponds to the dotted string ``"2.5.4.16"``. + + .. attribute:: POSTAL_CODE + + .. versionadded:: 1.6 + + Corresponds to the dotted string ``"2.5.4.17"``. + + +.. class:: SignatureAlgorithmOID + + .. versionadded:: 1.0 + + .. attribute:: RSA_WITH_MD5 + + Corresponds to the dotted string ``"1.2.840.113549.1.1.4"``. This is + an MD5 digest signed by an RSA key. + + .. attribute:: RSA_WITH_SHA1 + + Corresponds to the dotted string ``"1.2.840.113549.1.1.5"``. This is + a SHA1 digest signed by an RSA key. + + .. attribute:: RSA_WITH_SHA224 + + Corresponds to the dotted string ``"1.2.840.113549.1.1.14"``. This is + a SHA224 digest signed by an RSA key. + + .. attribute:: RSA_WITH_SHA256 + + Corresponds to the dotted string ``"1.2.840.113549.1.1.11"``. This is + a SHA256 digest signed by an RSA key. + + .. attribute:: RSA_WITH_SHA384 + + Corresponds to the dotted string ``"1.2.840.113549.1.1.12"``. This is + a SHA384 digest signed by an RSA key. + + .. attribute:: RSA_WITH_SHA512 + + Corresponds to the dotted string ``"1.2.840.113549.1.1.13"``. This is + a SHA512 digest signed by an RSA key. + + .. attribute:: ECDSA_WITH_SHA1 + + Corresponds to the dotted string ``"1.2.840.10045.4.1"``. This is a SHA1 + digest signed by an ECDSA key. + + .. attribute:: ECDSA_WITH_SHA224 + + Corresponds to the dotted string ``"1.2.840.10045.4.3.1"``. This is + a SHA224 digest signed by an ECDSA key. + + .. attribute:: ECDSA_WITH_SHA256 + + Corresponds to the dotted string ``"1.2.840.10045.4.3.2"``. This is + a SHA256 digest signed by an ECDSA key. + + .. attribute:: ECDSA_WITH_SHA384 + + Corresponds to the dotted string ``"1.2.840.10045.4.3.3"``. This is + a SHA384 digest signed by an ECDSA key. + + .. attribute:: ECDSA_WITH_SHA512 + + Corresponds to the dotted string ``"1.2.840.10045.4.3.4"``. This is + a SHA512 digest signed by an ECDSA key. + + .. attribute:: DSA_WITH_SHA1 + + Corresponds to the dotted string ``"1.2.840.10040.4.3"``. This is + a SHA1 digest signed by a DSA key. + + .. attribute:: DSA_WITH_SHA224 + + Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.1"``. This is + a SHA224 digest signed by a DSA key. + + .. attribute:: DSA_WITH_SHA256 + + Corresponds to the dotted string ``"2.16.840.1.101.3.4.3.2"``. This is + a SHA256 digest signed by a DSA key. + + +.. class:: ExtendedKeyUsageOID + + .. versionadded:: 1.0 + + .. attribute:: SERVER_AUTH + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.1"``. This is used + to denote that a certificate may be used for TLS web server + authentication. + + .. attribute:: CLIENT_AUTH + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.2"``. This is used + to denote that a certificate may be used for TLS web client + authentication. + + .. attribute:: CODE_SIGNING + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.3"``. This is used + to denote that a certificate may be used for code signing. + + .. attribute:: EMAIL_PROTECTION + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.4"``. This is used + to denote that a certificate may be used for email protection. + + .. attribute:: TIME_STAMPING + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.8"``. This is used + to denote that a certificate may be used for time stamping. + + .. attribute:: OCSP_SIGNING + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.3.9"``. This is used + to denote that a certificate may be used for signing OCSP responses. + + +.. class:: AuthorityInformationAccessOID + + .. versionadded:: 1.0 + + .. attribute:: OCSP + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.1"``. Used as the + identifier for OCSP data in + :class:`~cryptography.x509.AccessDescription` objects. + + .. attribute:: CA_ISSUERS + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.2"``. Used as the + identifier for CA issuer data in + :class:`~cryptography.x509.AccessDescription` objects. + + +.. class:: CertificatePoliciesOID + + .. versionadded:: 1.0 + + .. attribute:: CPS_QUALIFIER + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.2.1"``. + + .. attribute:: CPS_USER_NOTICE + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.2.2"``. + + .. attribute:: ANY_POLICY + + Corresponds to the dotted string ``"2.5.29.32.0"``. + + +.. class:: ExtensionOID + + .. versionadded:: 1.0 + + .. attribute:: BASIC_CONSTRAINTS + + Corresponds to the dotted string ``"2.5.29.19"``. The identifier for the + :class:`~cryptography.x509.BasicConstraints` extension type. + + .. attribute:: KEY_USAGE + + Corresponds to the dotted string ``"2.5.29.15"``. The identifier for the + :class:`~cryptography.x509.KeyUsage` extension type. + + .. attribute:: SUBJECT_ALTERNATIVE_NAME + + Corresponds to the dotted string ``"2.5.29.17"``. The identifier for the + :class:`~cryptography.x509.SubjectAlternativeName` extension type. + + .. attribute:: ISSUER_ALTERNATIVE_NAME + + Corresponds to the dotted string ``"2.5.29.18"``. The identifier for the + :class:`~cryptography.x509.IssuerAlternativeName` extension type. + + .. attribute:: SUBJECT_KEY_IDENTIFIER + + Corresponds to the dotted string ``"2.5.29.14"``. The identifier for the + :class:`~cryptography.x509.SubjectKeyIdentifier` extension type. + + .. attribute:: NAME_CONSTRAINTS + + Corresponds to the dotted string ``"2.5.29.30"``. The identifier for the + :class:`~cryptography.x509.NameConstraints` extension type. + + .. attribute:: CRL_DISTRIBUTION_POINTS + + Corresponds to the dotted string ``"2.5.29.31"``. The identifier for the + :class:`~cryptography.x509.CRLDistributionPoints` extension type. + + .. attribute:: CERTIFICATE_POLICIES + + Corresponds to the dotted string ``"2.5.29.32"``. The identifier for the + :class:`~cryptography.x509.CertificatePolicies` extension type. + + .. attribute:: AUTHORITY_KEY_IDENTIFIER + + Corresponds to the dotted string ``"2.5.29.35"``. The identifier for the + :class:`~cryptography.x509.AuthorityKeyIdentifier` extension type. + + .. attribute:: EXTENDED_KEY_USAGE + + Corresponds to the dotted string ``"2.5.29.37"``. The identifier for the + :class:`~cryptography.x509.ExtendedKeyUsage` extension type. + + .. attribute:: AUTHORITY_INFORMATION_ACCESS + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.1.1"``. The identifier + for the :class:`~cryptography.x509.AuthorityInformationAccess` extension + type. + + .. attribute:: INHIBIT_ANY_POLICY + + Corresponds to the dotted string ``"2.5.29.54"``. The identifier + for the :class:`~cryptography.x509.InhibitAnyPolicy` extension type. + + .. attribute:: OCSP_NO_CHECK + + Corresponds to the dotted string ``"1.3.6.1.5.5.7.48.1.5"``. The + identifier for the :class:`~cryptography.x509.OCSPNoCheck` extension + type. + + .. attribute:: CRL_NUMBER + + Corresponds to the dotted string ``"2.5.29.20"``. The identifier for + the ``CRLNumber`` extension type. This extension only has meaning + for certificate revocation lists. + + .. attribute:: POLICY_CONSTRAINTS + + Corresponds to the dotted string ``"2.5.29.36"``. The identifier for the + :class:`~cryptography.x509.PolicyConstraints` extension type. + + +.. class:: CRLEntryExtensionOID + + .. versionadded:: 1.2 + + .. attribute:: CERTIFICATE_ISSUER + + Corresponds to the dotted string ``"2.5.29.29"``. + + .. attribute:: CRL_REASON + + Corresponds to the dotted string ``"2.5.29.21"``. + + .. attribute:: INVALIDITY_DATE + + Corresponds to the dotted string ``"2.5.29.24"``. + +Helper Functions +~~~~~~~~~~~~~~~~ +.. currentmodule:: cryptography.x509 + +.. function:: random_serial_number() + + .. versionadded:: 1.6 + + Generates a random serial number suitable for use when constructing + certificates. + +Exceptions +~~~~~~~~~~ +.. currentmodule:: cryptography.x509 + +.. class:: InvalidVersion + + This is raised when an X.509 certificate has an invalid version number. + + .. attribute:: parsed_version + + :type: int + + Returns the raw version that was parsed from the certificate. + +.. class:: DuplicateExtension + + This is raised when more than one X.509 extension of the same type is + found within a certificate. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns the OID. + +.. class:: UnsupportedExtension + + This is raised when a certificate contains an unsupported extension type + that is marked ``critical``. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns the OID. + +.. class:: ExtensionNotFound + + This is raised when calling :meth:`Extensions.get_extension_for_oid` with + an extension OID that is not present in the certificate. + + .. attribute:: oid + + :type: :class:`ObjectIdentifier` + + Returns the OID. + +.. class:: UnsupportedGeneralNameType + + This is raised when a certificate contains an unsupported general name + type in an extension. + + .. attribute:: type + + :type: int + + The integer value of the unsupported type. The complete list of + types can be found in `RFC 5280 section 4.2.1.6`_. + + +.. _`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 +.. _`CABForum Guidelines`: https://cabforum.org/baseline-requirements-documents/ diff --git a/docs/x509/tutorial.rst b/docs/x509/tutorial.rst new file mode 100644 index 0000000..143cdfc --- /dev/null +++ b/docs/x509/tutorial.rst @@ -0,0 +1,154 @@ +Tutorial +======== + +X.509 certificates are used to authenticate clients and servers. The most +common use case is for web servers using HTTPS. + +Creating a Certificate Signing Request (CSR) +-------------------------------------------- + +When obtaining a certificate from a certificate authority (CA), the usual +flow is: + +1. You generate a private/public key pair. +2. You create a request for a certificate, which is signed by your key (to + prove that you own that key). +3. You give your CSR to a CA (but *not* the private key). +4. The CA validates that you own the resource (e.g. domain) you want a + certificate for. +5. The CA gives you a certificate, signed by them, which identifies your public + key, and the resource you are authenticated for. +6. You configure your server to use that certificate, combined with your + private key, to server traffic. + +If you want to obtain a certificate from a typical commercial CA, here's how. +First, you'll need to generate a private key, we'll generate an RSA key (these +are the most common types of keys on the web right now): + +.. code-block:: pycon + + >>> from cryptography.hazmat.backends import default_backend + >>> from cryptography.hazmat.primitives import serialization + >>> from cryptography.hazmat.primitives.asymmetric import rsa + >>> # Generate our key + >>> key = rsa.generate_private_key( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ) + >>> # Write our key to disk for safe keeping + >>> with open("path/to/store/key.pem", "wb") as f: + ... f.write(key.private_bytes( + ... encoding=serialization.Encoding.PEM, + ... format=serialization.PrivateFormat.TraditionalOpenSSL, + ... encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"), + ... )) + +If you've already generated a key you can load it with +:func:`~cryptography.hazmat.primitives.serialization.load_pem_private_key`. + +Next we need to generate a certificate signing request. A typical CSR contains +a few details: + +* Information about our public key (including a signature of the entire body). +* Information about who *we* are. +* Information about what domains this certificate is for. + +.. code-block:: pycon + + >>> from cryptography import x509 + >>> from cryptography.x509.oid import NameOID + >>> from cryptography.hazmat.primitives import hashes + >>> # Generate a CSR + >>> csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([ + ... # Provide various details about who we are. + ... x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + ... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"CA"), + ... x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"), + ... x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Company"), + ... x509.NameAttribute(NameOID.COMMON_NAME, u"mysite.com"), + ... ])).add_extension( + ... x509.SubjectAlternativeName([ + ... # Describe what sites we want this certificate for. + ... x509.DNSName(u"mysite.com"), + ... x509.DNSName(u"www.mysite.com"), + ... x509.DNSName(u"subdomain.mysite.com"), + ... ]), + ... critical=False, + ... # Sign the CSR with our private key. + ... ).sign(key, hashes.SHA256(), default_backend()) + >>> # Write our CSR out to disk. + >>> with open("path/to/csr.pem", "wb") as f: + ... f.write(csr.public_bytes(serialization.Encoding.PEM)) + +Now we can give our CSR to a CA, who will give a certificate to us in return. + +Creating a self-signed certificate +---------------------------------- + +While most of the time you want a certificate that has been *signed* by someone +else (i.e. a certificate authority), so that trust is established, sometimes +you want to create a self-signed certificate. Self-signed certificates are not +issued by a certificate authority, but instead they are signed by the private +key corresponding to the public key they embed. + +This means that other people don't trust these certificates, but it also means +they can be issued very easily. In general the only use case for a self-signed +certificate is local testing, where you don't need anyone else to trust your +certificate. + +Like generating a CSR, we start with creating a new private key: + +.. code-block:: pycon + + >>> # Generate our key + >>> key = rsa.generate_private_key( + ... public_exponent=65537, + ... key_size=2048, + ... backend=default_backend() + ... ) + >>> # Write our key to disk for safe keeping + >>> with open("path/to/store/key.pem", "wb") as f: + ... f.write(key.private_bytes( + ... encoding=serialization.Encoding.PEM, + ... format=serialization.PrivateFormat.TraditionalOpenSSL, + ... encryption_algorithm=serialization.BestAvailableEncryption(b"passphrase"), + ... )) + +Then we generate the certificate itself: + +.. code-block:: pycon + + >>> # Various details about who we are. For a self-signed certificate the + >>> # subject and issuer are always the same. + >>> subject = issuer = x509.Name([ + ... x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + ... x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"CA"), + ... x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"), + ... x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Company"), + ... x509.NameAttribute(NameOID.COMMON_NAME, u"mysite.com"), + ... ]) + >>> cert = x509.CertificateBuilder().subject_name( + ... subject + ... ).issuer_name( + ... issuer + ... ).public_key( + ... private_key.public_key() + ... ).serial_number( + ... x509.random_serial_number() + ... ).not_valid_before( + ... datetime.datetime.utcnow() + ... ).not_valid_after( + ... # Our certificate will be valid for 10 days + ... datetime.datetime.utcnow() + datetime.timedelta(days=10) + ... ).add_extension( + ... x509.SubjectAlternativeName([x509.DNSName(u"localhost")]), + ... critical=False, + ... # Sign our certificate with our private key + ... ).sign(private_key, hashes.SHA256(), default_backend()) + >>> # Write our certificate out to disk. + >>> with open("path/to/certificate.pem", "wb") as f: + ... f.write(cert.public_bytes(serialization.Encoding.PEM)) + +And now we have a private key and certificate that can be used for local +testing. diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..861a9f5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c5a99be --- /dev/null +++ b/setup.py @@ -0,0 +1,335 @@ +#!/usr/bin/env python + +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import os +import platform +import subprocess +import sys +from distutils.command.build import build + +import pkg_resources + +from setuptools import find_packages, setup +from setuptools.command.install import install +from setuptools.command.test import test + + +base_dir = os.path.dirname(__file__) +src_dir = os.path.join(base_dir, "src") + +# When executing the setup.py, we need to be able to import ourselves, this +# means that we need to add the src/ directory to the sys.path. +sys.path.insert(0, src_dir) + +about = {} +with open(os.path.join(src_dir, "cryptography", "__about__.py")) as f: + exec(f.read(), about) + + +VECTORS_DEPENDENCY = "cryptography_vectors=={0}".format(about['__version__']) + +requirements = [ + "idna>=2.0", + "pyasn1>=0.1.8", + "six>=1.4.1", + "setuptools>=11.3", +] +setup_requirements = [] + +if sys.version_info < (3, 4): + requirements.append("enum34") + +if sys.version_info < (3, 3): + requirements.append("ipaddress") + +if platform.python_implementation() == "PyPy": + if sys.pypy_version_info < (2, 6): + raise RuntimeError( + "cryptography 1.0 is not compatible with PyPy < 2.6. Please " + "upgrade PyPy to use this library." + ) +else: + requirements.append("cffi>=1.4.1") + setup_requirements.append("cffi>=1.4.1") + +test_requirements = [ + "pytest>=2.9.0", + "pretend", + "iso8601", + "pyasn1_modules", + "pytz", +] +if sys.version_info[:2] > (2, 6): + test_requirements.append("hypothesis>=1.11.4") + + +# If there's no vectors locally that probably means we are in a tarball and +# need to go and get the matching vectors package from PyPi +if not os.path.exists(os.path.join(base_dir, "vectors/setup.py")): + test_requirements.append(VECTORS_DEPENDENCY) + + +def cc_is_available(): + return sys.platform == "darwin" and list(map( + int, platform.mac_ver()[0].split("."))) >= [10, 8, 0] + + +backends = [ + "openssl = cryptography.hazmat.backends.openssl:backend" +] + +if cc_is_available(): + backends.append( + "commoncrypto = cryptography.hazmat.backends.commoncrypto:backend", + ) + + +class PyTest(test): + def finalize_options(self): + test.finalize_options(self) + self.test_args = [] + self.test_suite = True + + # This means there's a vectors/ folder with the package in here. + # cd into it, install the vectors package and then refresh sys.path + if VECTORS_DEPENDENCY not in test_requirements: + subprocess.check_call( + [sys.executable, "setup.py", "install"], cwd="vectors" + ) + pkg_resources.get_distribution("cryptography_vectors").activate() + + def run_tests(self): + # Import here because in module scope the eggs are not loaded. + import pytest + test_args = [os.path.join(base_dir, "tests")] + errno = pytest.main(test_args) + sys.exit(errno) + + +def keywords_with_side_effects(argv): + """ + Get a dictionary with setup keywords that (can) have side effects. + + :param argv: A list of strings with command line arguments. + :returns: A dictionary with keyword arguments for the ``setup()`` function. + + This setup.py script uses the setuptools 'setup_requires' feature because + this is required by the cffi package to compile extension modules. The + purpose of ``keywords_with_side_effects()`` is to avoid triggering the cffi + build process as a result of setup.py invocations that don't need the cffi + module to be built (setup.py serves the dual purpose of exposing package + metadata). + + All of the options listed by ``python setup.py --help`` that print + information should be recognized here. The commands ``clean``, + ``egg_info``, ``register``, ``sdist`` and ``upload`` are also recognized. + Any combination of these options and commands is also supported. + + This function was originally based on the `setup.py script`_ of SciPy (see + also the discussion in `pip issue #25`_). + + .. _pip issue #25: https://github.com/pypa/pip/issues/25 + .. _setup.py script: https://github.com/scipy/scipy/blob/master/setup.py + """ + no_setup_requires_arguments = ( + '-h', '--help', + '-n', '--dry-run', + '-q', '--quiet', + '-v', '--verbose', + '-V', '--version', + '--author', + '--author-email', + '--classifiers', + '--contact', + '--contact-email', + '--description', + '--egg-base', + '--fullname', + '--help-commands', + '--keywords', + '--licence', + '--license', + '--long-description', + '--maintainer', + '--maintainer-email', + '--name', + '--no-user-cfg', + '--obsoletes', + '--platforms', + '--provides', + '--requires', + '--url', + 'clean', + 'egg_info', + 'register', + 'sdist', + 'upload', + ) + + def is_short_option(argument): + """Check whether a command line argument is a short option.""" + return len(argument) >= 2 and argument[0] == '-' and argument[1] != '-' + + def expand_short_options(argument): + """Expand combined short options into canonical short options.""" + return ('-' + char for char in argument[1:]) + + def argument_without_setup_requirements(argv, i): + """Check whether a command line argument needs setup requirements.""" + if argv[i] in no_setup_requires_arguments: + # Simple case: An argument which is either an option or a command + # which doesn't need setup requirements. + return True + elif (is_short_option(argv[i]) and + all(option in no_setup_requires_arguments + for option in expand_short_options(argv[i]))): + # Not so simple case: Combined short options none of which need + # setup requirements. + return True + elif argv[i - 1:i] == ['--egg-base']: + # Tricky case: --egg-info takes an argument which should not make + # us use setup_requires (defeating the purpose of this code). + return True + else: + return False + + if all(argument_without_setup_requirements(argv, i) + for i in range(1, len(argv))): + return { + "cmdclass": { + "build": DummyBuild, + "install": DummyInstall, + "test": DummyPyTest, + } + } + else: + cffi_modules = [ + "src/_cffi_src/build_openssl.py:ffi", + "src/_cffi_src/build_constant_time.py:ffi", + "src/_cffi_src/build_padding.py:ffi", + ] + if cc_is_available(): + cffi_modules.append("src/_cffi_src/build_commoncrypto.py:ffi") + + return { + "setup_requires": setup_requirements, + "cmdclass": { + "test": PyTest, + }, + "cffi_modules": cffi_modules + } + + +setup_requires_error = ("Requested setup command that needs 'setup_requires' " + "while command line arguments implied a side effect " + "free command or option.") + + +class DummyBuild(build): + """ + This class makes it very obvious when ``keywords_with_side_effects()`` has + incorrectly interpreted the command line arguments to ``setup.py build`` as + one of the 'side effect free' commands or options. + """ + + def run(self): + raise RuntimeError(setup_requires_error) + + +class DummyInstall(install): + """ + This class makes it very obvious when ``keywords_with_side_effects()`` has + incorrectly interpreted the command line arguments to ``setup.py install`` + as one of the 'side effect free' commands or options. + """ + + def run(self): + raise RuntimeError(setup_requires_error) + + +class DummyPyTest(test): + """ + This class makes it very obvious when ``keywords_with_side_effects()`` has + incorrectly interpreted the command line arguments to ``setup.py test`` as + one of the 'side effect free' commands or options. + """ + + def run_tests(self): + raise RuntimeError(setup_requires_error) + + +with open(os.path.join(base_dir, "README.rst")) as f: + long_description = f.read() + + +setup( + name=about["__title__"], + version=about["__version__"], + + description=about["__summary__"], + long_description=long_description, + license=about["__license__"], + url=about["__uri__"], + + author=about["__author__"], + author_email=about["__email__"], + + classifiers=[ + "Intended Audience :: Developers", + "License :: OSI Approved :: Apache Software License", + "License :: OSI Approved :: BSD License", + "Natural Language :: English", + "Operating System :: MacOS :: MacOS X", + "Operating System :: POSIX", + "Operating System :: POSIX :: BSD", + "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", + "Programming Language :: Python", + "Programming Language :: Python :: 2", + "Programming Language :: Python :: 2.6", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", + "Topic :: Security :: Cryptography", + ], + + package_dir={"": "src"}, + packages=find_packages(where="src", exclude=["_cffi_src", "_cffi_src.*"]), + include_package_data=True, + + install_requires=requirements, + tests_require=test_requirements, + extras_require={ + "test": test_requirements, + "docstest": [ + "doc8", + "pyenchant", + "readme_renderer >= 16.0", + "sphinx", + "sphinx_rtd_theme", + "sphinxcontrib-spelling", + ], + "pep8test": [ + "flake8", + "flake8-import-order", + "pep8-naming", + ], + }, + + # for cffi + zip_safe=False, + ext_package="cryptography.hazmat.bindings", + entry_points={ + "cryptography.backends": backends, + }, + **keywords_with_side_effects(sys.argv) +) diff --git a/src/_cffi_src/__init__.py b/src/_cffi_src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/_cffi_src/build_commoncrypto.py b/src/_cffi_src/build_commoncrypto.py new file mode 100644 index 0000000..373f6f7 --- /dev/null +++ b/src/_cffi_src/build_commoncrypto.py @@ -0,0 +1,33 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from _cffi_src.utils import build_ffi_for_binding + + +ffi = build_ffi_for_binding( + module_name="_commoncrypto", + module_prefix="_cffi_src.commoncrypto.", + modules=[ + "cf", + "common_digest", + "common_hmac", + "common_key_derivation", + "common_cryptor", + "common_symmetric_key_wrap", + "seccertificate", + "secimport", + "secitem", + "seckey", + "seckeychain", + "secpolicy", + "sectransform", + "sectrust", + "secure_transport", + ], + extra_link_args=[ + "-framework", "Security", "-framework", "CoreFoundation" + ], +) diff --git a/src/_cffi_src/build_constant_time.py b/src/_cffi_src/build_constant_time.py new file mode 100644 index 0000000..7a11f7b --- /dev/null +++ b/src/_cffi_src/build_constant_time.py @@ -0,0 +1,27 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import os + +from _cffi_src.utils import build_ffi, compiler_type, extra_link_args + + +with open(os.path.join( + os.path.dirname(__file__), "hazmat_src/constant_time.h" +)) as f: + types = f.read() + +with open(os.path.join( + os.path.dirname(__file__), "hazmat_src/constant_time.c" +)) as f: + functions = f.read() + +ffi = build_ffi( + module_name="_constant_time", + cdef_source=types, + verify_source=functions, + extra_link_args=extra_link_args(compiler_type()), +) diff --git a/src/_cffi_src/build_openssl.py b/src/_cffi_src/build_openssl.py new file mode 100644 index 0000000..416e1b3 --- /dev/null +++ b/src/_cffi_src/build_openssl.py @@ -0,0 +1,86 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import os +import sys + +from _cffi_src.utils import ( + build_ffi_for_binding, compiler_type, extra_link_args +) + + +def _get_openssl_libraries(platform): + # OpenSSL goes by a different library name on different operating systems. + if platform == "darwin": + return _osx_libraries( + os.environ.get("CRYPTOGRAPHY_OSX_NO_LINK_FLAGS") + ) + elif platform == "win32": + if compiler_type() == "msvc": + libs = ["libeay32", "ssleay32"] + else: + libs = ["ssl", "crypto"] + return libs + ["advapi32", "crypt32", "gdi32", "user32", "ws2_32"] + else: + # In some circumstances, the order in which these libs are + # specified on the linker command-line is significant; + # libssl must come before libcrypto + # (http://marc.info/?l=openssl-users&m=135361825921871) + return ["ssl", "crypto"] + + +def _osx_libraries(build_static): + # For building statically we don't want to pass the -lssl or -lcrypto flags + if build_static == "1": + return [] + else: + return ["ssl", "crypto"] + + +ffi = build_ffi_for_binding( + module_name="_openssl", + module_prefix="_cffi_src.openssl.", + modules=[ + # This goes first so we can define some cryptography-wide symbols. + "cryptography", + + "aes", + "asn1", + "bignum", + "bio", + "cmac", + "cms", + "conf", + "crypto", + "dh", + "dsa", + "ec", + "ecdh", + "ecdsa", + "engine", + "err", + "evp", + "hmac", + "nid", + "objects", + "ocsp", + "opensslv", + "osrandom_engine", + "pem", + "pkcs12", + "rand", + "rsa", + "ssl", + "x509", + "x509name", + "x509v3", + "x509_vfy", + "pkcs7", + "callbacks", + ], + libraries=_get_openssl_libraries(sys.platform), + extra_link_args=extra_link_args(compiler_type()), +) diff --git a/src/_cffi_src/build_padding.py b/src/_cffi_src/build_padding.py new file mode 100644 index 0000000..4c5096a --- /dev/null +++ b/src/_cffi_src/build_padding.py @@ -0,0 +1,27 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import os + +from _cffi_src.utils import build_ffi, compiler_type, extra_link_args + + +with open(os.path.join( + os.path.dirname(__file__), "hazmat_src/padding.h" +)) as f: + types = f.read() + +with open(os.path.join( + os.path.dirname(__file__), "hazmat_src/padding.c" +)) as f: + functions = f.read() + +ffi = build_ffi( + module_name="_padding", + cdef_source=types, + verify_source=functions, + extra_link_args=extra_link_args(compiler_type()), +) diff --git a/src/_cffi_src/commoncrypto/__init__.py b/src/_cffi_src/commoncrypto/__init__.py new file mode 100644 index 0000000..4b54088 --- /dev/null +++ b/src/_cffi_src/commoncrypto/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function diff --git a/src/_cffi_src/commoncrypto/cf.py b/src/_cffi_src/commoncrypto/cf.py new file mode 100644 index 0000000..02e58d9 --- /dev/null +++ b/src/_cffi_src/commoncrypto/cf.py @@ -0,0 +1,113 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef bool Boolean; +typedef signed long OSStatus; +typedef unsigned char UInt8; +typedef uint32_t UInt32; + +typedef const void * CFAllocatorRef; +const CFAllocatorRef kCFAllocatorDefault; +typedef ... *CFDataRef; +typedef signed long long CFIndex; +typedef ... *CFStringRef; +typedef ... *CFArrayRef; +typedef ... *CFMutableArrayRef; +typedef ... *CFBooleanRef; +typedef ... *CFErrorRef; +typedef ... *CFNumberRef; +typedef ... *CFTypeRef; +typedef ... *CFDictionaryRef; +typedef ... *CFMutableDictionaryRef; +typedef struct { + ...; +} CFDictionaryKeyCallBacks; +typedef struct { + ...; +} CFDictionaryValueCallBacks; +typedef struct { + ...; +} CFRange; +typedef struct { + ...; +} CFArrayCallBacks; + +typedef UInt32 CFStringEncoding; +enum { + kCFStringEncodingASCII = 0x0600 +}; + +enum { + kCFNumberSInt8Type = 1, + kCFNumberSInt16Type = 2, + kCFNumberSInt32Type = 3, + kCFNumberSInt64Type = 4, + kCFNumberFloat32Type = 5, + kCFNumberFloat64Type = 6, + kCFNumberCharType = 7, + kCFNumberShortType = 8, + kCFNumberIntType = 9, + kCFNumberLongType = 10, + kCFNumberLongLongType = 11, + kCFNumberFloatType = 12, + kCFNumberDoubleType = 13, + kCFNumberCFIndexType = 14, + kCFNumberNSIntegerType = 15, + kCFNumberCGFloatType = 16, + kCFNumberMaxType = 16 +}; +typedef int CFNumberType; + +const CFDictionaryKeyCallBacks kCFTypeDictionaryKeyCallBacks; +const CFDictionaryValueCallBacks kCFTypeDictionaryValueCallBacks; + +const CFArrayCallBacks kCFTypeArrayCallBacks; + +const CFBooleanRef kCFBooleanTrue; +const CFBooleanRef kCFBooleanFalse; +""" + +FUNCTIONS = """ +CFDataRef CFDataCreate(CFAllocatorRef, const UInt8 *, CFIndex); +CFStringRef CFStringCreateWithCString(CFAllocatorRef, const char *, + CFStringEncoding); +CFDictionaryRef CFDictionaryCreate(CFAllocatorRef, const void **, + const void **, CFIndex, + const CFDictionaryKeyCallBacks *, + const CFDictionaryValueCallBacks *); +CFMutableDictionaryRef CFDictionaryCreateMutable( + CFAllocatorRef, + CFIndex, + const CFDictionaryKeyCallBacks *, + const CFDictionaryValueCallBacks * +); +void CFDictionarySetValue(CFMutableDictionaryRef, const void *, const void *); +CFIndex CFArrayGetCount(CFArrayRef); +const void *CFArrayGetValueAtIndex(CFArrayRef, CFIndex); +CFIndex CFDataGetLength(CFDataRef); +void CFDataGetBytes(CFDataRef, CFRange, UInt8 *); +CFRange CFRangeMake(CFIndex, CFIndex); +void CFShow(CFTypeRef); +Boolean CFBooleanGetValue(CFBooleanRef); +CFNumberRef CFNumberCreate(CFAllocatorRef, CFNumberType, const void *); +void CFRelease(CFTypeRef); +CFTypeRef CFRetain(CFTypeRef); + +CFMutableArrayRef CFArrayCreateMutable(CFAllocatorRef, CFIndex, + const CFArrayCallBacks *); +void CFArrayAppendValue(CFMutableArrayRef, const void *); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/commoncrypto/common_cryptor.py b/src/_cffi_src/commoncrypto/common_cryptor.py new file mode 100644 index 0000000..15c253e --- /dev/null +++ b/src/_cffi_src/commoncrypto/common_cryptor.py @@ -0,0 +1,99 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +enum { + kCCAlgorithmAES128 = 0, + kCCAlgorithmDES, + kCCAlgorithm3DES, + kCCAlgorithmCAST, + kCCAlgorithmRC4, + kCCAlgorithmRC2, + kCCAlgorithmBlowfish +}; +typedef uint32_t CCAlgorithm; +enum { + kCCSuccess = 0, + kCCParamError = -4300, + kCCBufferTooSmall = -4301, + kCCMemoryFailure = -4302, + kCCAlignmentError = -4303, + kCCDecodeError = -4304, + kCCUnimplemented = -4305 +}; +typedef int32_t CCCryptorStatus; +typedef uint32_t CCOptions; +enum { + kCCEncrypt = 0, + kCCDecrypt, +}; +typedef uint32_t CCOperation; +typedef ... *CCCryptorRef; + +enum { + kCCModeOptionCTR_LE = 0x0001, + kCCModeOptionCTR_BE = 0x0002 +}; + +typedef uint32_t CCModeOptions; + +enum { + kCCModeECB = 1, + kCCModeCBC = 2, + kCCModeCFB = 3, + kCCModeCTR = 4, + kCCModeF8 = 5, + kCCModeLRW = 6, + kCCModeOFB = 7, + kCCModeXTS = 8, + kCCModeRC4 = 9, + kCCModeCFB8 = 10, + kCCModeGCM = 11 +}; +typedef uint32_t CCMode; +enum { + ccNoPadding = 0, + ccPKCS7Padding = 1, +}; +typedef uint32_t CCPadding; +""" + +FUNCTIONS = """ +CCCryptorStatus CCCryptorCreateWithMode(CCOperation, CCMode, CCAlgorithm, + CCPadding, const void *, const void *, + size_t, const void *, size_t, int, + CCModeOptions, CCCryptorRef *); +CCCryptorStatus CCCryptorCreate(CCOperation, CCAlgorithm, CCOptions, + const void *, size_t, const void *, + CCCryptorRef *); +CCCryptorStatus CCCryptorUpdate(CCCryptorRef, const void *, size_t, void *, + size_t, size_t *); +CCCryptorStatus CCCryptorFinal(CCCryptorRef, void *, size_t, size_t *); +CCCryptorStatus CCCryptorRelease(CCCryptorRef); + +CCCryptorStatus CCCryptorGCMAddIV(CCCryptorRef, const void *, size_t); +CCCryptorStatus CCCryptorGCMAddAAD(CCCryptorRef, const void *, size_t); +CCCryptorStatus CCCryptorGCMEncrypt(CCCryptorRef, const void *, size_t, + void *); +CCCryptorStatus CCCryptorGCMDecrypt(CCCryptorRef, const void *, size_t, + void *); +CCCryptorStatus CCCryptorGCMFinal(CCCryptorRef, const void *, size_t *); +CCCryptorStatus CCCryptorGCMReset(CCCryptorRef); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +/* Not defined in the public header */ +enum { + kCCModeGCM = 11 +}; +""" diff --git a/src/_cffi_src/commoncrypto/common_digest.py b/src/_cffi_src/commoncrypto/common_digest.py new file mode 100644 index 0000000..e40e729 --- /dev/null +++ b/src/_cffi_src/commoncrypto/common_digest.py @@ -0,0 +1,58 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef uint32_t CC_LONG; +typedef uint64_t CC_LONG64; +typedef struct CC_MD5state_st { + ...; +} CC_MD5_CTX; +typedef struct CC_SHA1state_st { + ...; +} CC_SHA1_CTX; +typedef struct CC_SHA256state_st { + ...; +} CC_SHA256_CTX; +typedef struct CC_SHA512state_st { + ...; +} CC_SHA512_CTX; +""" + +FUNCTIONS = """ +int CC_MD5_Init(CC_MD5_CTX *); +int CC_MD5_Update(CC_MD5_CTX *, const void *, CC_LONG); +int CC_MD5_Final(unsigned char *, CC_MD5_CTX *); + +int CC_SHA1_Init(CC_SHA1_CTX *); +int CC_SHA1_Update(CC_SHA1_CTX *, const void *, CC_LONG); +int CC_SHA1_Final(unsigned char *, CC_SHA1_CTX *); + +int CC_SHA224_Init(CC_SHA256_CTX *); +int CC_SHA224_Update(CC_SHA256_CTX *, const void *, CC_LONG); +int CC_SHA224_Final(unsigned char *, CC_SHA256_CTX *); + +int CC_SHA256_Init(CC_SHA256_CTX *); +int CC_SHA256_Update(CC_SHA256_CTX *, const void *, CC_LONG); +int CC_SHA256_Final(unsigned char *, CC_SHA256_CTX *); + +int CC_SHA384_Init(CC_SHA512_CTX *); +int CC_SHA384_Update(CC_SHA512_CTX *, const void *, CC_LONG); +int CC_SHA384_Final(unsigned char *, CC_SHA512_CTX *); + +int CC_SHA512_Init(CC_SHA512_CTX *); +int CC_SHA512_Update(CC_SHA512_CTX *, const void *, CC_LONG); +int CC_SHA512_Final(unsigned char *, CC_SHA512_CTX *); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/commoncrypto/common_hmac.py b/src/_cffi_src/commoncrypto/common_hmac.py new file mode 100644 index 0000000..721cff5 --- /dev/null +++ b/src/_cffi_src/commoncrypto/common_hmac.py @@ -0,0 +1,37 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef struct { + ...; +} CCHmacContext; +enum { + kCCHmacAlgSHA1, + kCCHmacAlgMD5, + kCCHmacAlgSHA256, + kCCHmacAlgSHA384, + kCCHmacAlgSHA512, + kCCHmacAlgSHA224 +}; +typedef uint32_t CCHmacAlgorithm; +""" + +FUNCTIONS = """ +void CCHmacInit(CCHmacContext *, CCHmacAlgorithm, const void *, size_t); +void CCHmacUpdate(CCHmacContext *, const void *, size_t); +void CCHmacFinal(CCHmacContext *, void *); + +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/commoncrypto/common_key_derivation.py b/src/_cffi_src/commoncrypto/common_key_derivation.py new file mode 100644 index 0000000..13ae580 --- /dev/null +++ b/src/_cffi_src/commoncrypto/common_key_derivation.py @@ -0,0 +1,39 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +enum { + kCCPBKDF2 = 2, +}; +typedef uint32_t CCPBKDFAlgorithm; +enum { + kCCPRFHmacAlgSHA1 = 1, + kCCPRFHmacAlgSHA224 = 2, + kCCPRFHmacAlgSHA256 = 3, + kCCPRFHmacAlgSHA384 = 4, + kCCPRFHmacAlgSHA512 = 5, +}; +typedef uint32_t CCPseudoRandomAlgorithm; +typedef unsigned int uint; +""" + +FUNCTIONS = """ +int CCKeyDerivationPBKDF(CCPBKDFAlgorithm, const char *, size_t, + const uint8_t *, size_t, CCPseudoRandomAlgorithm, + uint, uint8_t *, size_t); +uint CCCalibratePBKDF(CCPBKDFAlgorithm, size_t, size_t, + CCPseudoRandomAlgorithm, size_t, uint32_t); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/commoncrypto/common_symmetric_key_wrap.py b/src/_cffi_src/commoncrypto/common_symmetric_key_wrap.py new file mode 100644 index 0000000..9c7d42f --- /dev/null +++ b/src/_cffi_src/commoncrypto/common_symmetric_key_wrap.py @@ -0,0 +1,35 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +enum { + kCCWRAPAES = 1, +}; + +typedef uint32_t CCWrappingAlgorithm; +""" + +FUNCTIONS = """ +int CCSymmetricKeyWrap(CCWrappingAlgorithm, const uint8_t *, const size_t, + const uint8_t *, size_t, const uint8_t *, size_t, + uint8_t *, size_t *); +int CCSymmetricKeyUnwrap(CCWrappingAlgorithm algorithm, const uint8_t *, + const size_t, const uint8_t *, size_t, + const uint8_t *, size_t, uint8_t *, size_t *); +size_t CCSymmetricWrappedSize(CCWrappingAlgorithm, size_t); +size_t CCSymmetricUnwrappedSize(CCWrappingAlgorithm, size_t); + +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/commoncrypto/seccertificate.py b/src/_cffi_src/commoncrypto/seccertificate.py new file mode 100644 index 0000000..2b54b0e --- /dev/null +++ b/src/_cffi_src/commoncrypto/seccertificate.py @@ -0,0 +1,23 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... *SecCertificateRef; +""" + +FUNCTIONS = """ +SecCertificateRef SecCertificateCreateWithData(CFAllocatorRef, CFDataRef); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/commoncrypto/secimport.py b/src/_cffi_src/commoncrypto/secimport.py new file mode 100644 index 0000000..8165759 --- /dev/null +++ b/src/_cffi_src/commoncrypto/secimport.py @@ -0,0 +1,86 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... *SecAccessRef; + +CFStringRef kSecImportExportPassphrase; +CFStringRef kSecImportExportKeychain; +CFStringRef kSecImportExportAccess; + +typedef uint32_t SecExternalItemType; +enum { + kSecItemTypeUnknown, + kSecItemTypePrivateKey, + kSecItemTypePublicKey, + kSecItemTypeSessionKey, + kSecItemTypeCertificate, + kSecItemTypeAggregate +}; + + +typedef uint32_t SecExternalFormat; +enum { + kSecFormatUnknown = 0, + kSecFormatOpenSSL, + kSecFormatSSH, + kSecFormatBSAFE, + kSecFormatRawKey, + kSecFormatWrappedPKCS8, + kSecFormatWrappedOpenSSL, + kSecFormatWrappedSSH, + kSecFormatWrappedLSH, + kSecFormatX509Cert, + kSecFormatPEMSequence, + kSecFormatPKCS7, + kSecFormatPKCS12, + kSecFormatNetscapeCertSequence, + kSecFormatSSHv2 +}; + +typedef uint32_t SecItemImportExportFlags; +enum { + kSecKeyImportOnlyOne = 0x00000001, + kSecKeySecurePassphrase = 0x00000002, + kSecKeyNoAccessControl = 0x00000004 +}; +typedef uint32_t SecKeyImportExportFlags; + +typedef struct { + /* for import and export */ + uint32_t version; + SecKeyImportExportFlags flags; + CFTypeRef passphrase; + CFStringRef alertTitle; + CFStringRef alertPrompt; + + /* for import only */ + SecAccessRef accessRef; + CFArrayRef keyUsage; + + CFArrayRef keyAttributes; +} SecItemImportExportKeyParameters; +""" + +FUNCTIONS = """ +OSStatus SecItemImport(CFDataRef, CFStringRef, SecExternalFormat *, + SecExternalItemType *, SecItemImportExportFlags, + const SecItemImportExportKeyParameters *, + SecKeychainRef, CFArrayRef *); +OSStatus SecPKCS12Import(CFDataRef, CFDictionaryRef, CFArrayRef *); +OSStatus SecItemExport(CFTypeRef, SecExternalFormat, SecItemImportExportFlags, + const SecItemImportExportKeyParameters *, CFDataRef *); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/commoncrypto/secitem.py b/src/_cffi_src/commoncrypto/secitem.py new file mode 100644 index 0000000..19bfe76 --- /dev/null +++ b/src/_cffi_src/commoncrypto/secitem.py @@ -0,0 +1,27 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +const CFTypeRef kSecAttrKeyType; +const CFTypeRef kSecAttrKeySizeInBits; +const CFTypeRef kSecAttrIsPermanent; +const CFTypeRef kSecAttrKeyTypeRSA; +const CFTypeRef kSecAttrKeyTypeDSA; +const CFTypeRef kSecUseKeychain; +""" + +FUNCTIONS = """ +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/commoncrypto/seckey.py b/src/_cffi_src/commoncrypto/seckey.py new file mode 100644 index 0000000..99fe61e --- /dev/null +++ b/src/_cffi_src/commoncrypto/seckey.py @@ -0,0 +1,24 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... *SecKeyRef; +""" + +FUNCTIONS = """ +OSStatus SecKeyGeneratePair(CFDictionaryRef, SecKeyRef *, SecKeyRef *); +size_t SecKeyGetBlockSize(SecKeyRef); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/commoncrypto/seckeychain.py b/src/_cffi_src/commoncrypto/seckeychain.py new file mode 100644 index 0000000..221a87c --- /dev/null +++ b/src/_cffi_src/commoncrypto/seckeychain.py @@ -0,0 +1,25 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... *SecKeychainRef; +""" + +FUNCTIONS = """ +OSStatus SecKeychainCreate(const char *, UInt32, const void *, Boolean, + SecAccessRef, SecKeychainRef *); +OSStatus SecKeychainDelete(SecKeychainRef); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/commoncrypto/secpolicy.py b/src/_cffi_src/commoncrypto/secpolicy.py new file mode 100644 index 0000000..e132cfa --- /dev/null +++ b/src/_cffi_src/commoncrypto/secpolicy.py @@ -0,0 +1,23 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... *SecPolicyRef; +""" + +FUNCTIONS = """ +SecPolicyRef SecPolicyCreateSSL(Boolean, CFStringRef); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/commoncrypto/sectransform.py b/src/_cffi_src/commoncrypto/sectransform.py new file mode 100644 index 0000000..0403746 --- /dev/null +++ b/src/_cffi_src/commoncrypto/sectransform.py @@ -0,0 +1,68 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +#include +#include +""" + +TYPES = """ +typedef ... *SecTransformRef; + +CFStringRef kSecImportExportPassphrase; +CFStringRef kSecImportExportKeychain; +CFStringRef kSecImportExportAccess; + +CFStringRef kSecEncryptionMode; +CFStringRef kSecEncryptKey; +CFStringRef kSecIVKey; +CFStringRef kSecModeCBCKey; +CFStringRef kSecModeCFBKey; +CFStringRef kSecModeECBKey; +CFStringRef kSecModeNoneKey; +CFStringRef kSecModeOFBKey; +CFStringRef kSecOAEPEncodingParametersAttributeName; +CFStringRef kSecPaddingKey; +CFStringRef kSecPaddingNoneKey; +CFStringRef kSecPaddingOAEPKey; +CFStringRef kSecPaddingPKCS1Key; +CFStringRef kSecPaddingPKCS5Key; +CFStringRef kSecPaddingPKCS7Key; + +const CFStringRef kSecTransformInputAttributeName; +const CFStringRef kSecTransformOutputAttributeName; +const CFStringRef kSecTransformDebugAttributeName; +const CFStringRef kSecTransformTransformName; +const CFStringRef kSecTransformAbortAttributeName; + +CFStringRef kSecInputIsAttributeName; +CFStringRef kSecInputIsPlainText; +CFStringRef kSecInputIsDigest; +CFStringRef kSecInputIsRaw; + +const CFStringRef kSecDigestTypeAttribute; +const CFStringRef kSecDigestLengthAttribute; +const CFStringRef kSecDigestMD5; +const CFStringRef kSecDigestSHA1; +const CFStringRef kSecDigestSHA2; +""" + +FUNCTIONS = """ +Boolean SecTransformSetAttribute(SecTransformRef, CFStringRef, CFTypeRef, + CFErrorRef *); +SecTransformRef SecDecryptTransformCreate(SecKeyRef, CFErrorRef *); +SecTransformRef SecEncryptTransformCreate(SecKeyRef, CFErrorRef *); +SecTransformRef SecVerifyTransformCreate(SecKeyRef, CFDataRef, CFErrorRef *); +SecTransformRef SecSignTransformCreate(SecKeyRef, CFErrorRef *) ; +CFTypeRef SecTransformExecute(SecTransformRef, CFErrorRef *); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/commoncrypto/sectrust.py b/src/_cffi_src/commoncrypto/sectrust.py new file mode 100644 index 0000000..842c36c --- /dev/null +++ b/src/_cffi_src/commoncrypto/sectrust.py @@ -0,0 +1,39 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... *SecTrustRef; +typedef uint32_t SecTrustResultType; + +enum { + kSecTrustResultInvalid, + kSecTrustResultProceed, + kSecTrustResultDeny, + kSecTrustResultUnspecified, + kSecTrustResultRecoverableTrustFailure, + kSecTrustResultFatalTrustFailure, + kSecTrustResultOtherError +}; +""" + +FUNCTIONS = """ +OSStatus SecTrustEvaluate(SecTrustRef, SecTrustResultType *); +OSStatus SecTrustCopyAnchorCertificates(CFArrayRef *); +""" + +MACROS = """ +/* The first argument changed from CFArrayRef to CFTypeRef in 10.8, so this + * has to go here for compatibility. + */ +OSStatus SecTrustCreateWithCertificates(CFTypeRef, CFTypeRef, SecTrustRef *); +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/commoncrypto/secure_transport.py b/src/_cffi_src/commoncrypto/secure_transport.py new file mode 100644 index 0000000..0ad1b0b --- /dev/null +++ b/src/_cffi_src/commoncrypto/secure_transport.py @@ -0,0 +1,308 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... *SSLContextRef; +typedef const void *SSLConnectionRef; + +typedef enum { + kSSLSessionOptionBreakOnServerAuth, + kSSLSessionOptionBreakOnCertRequested, +} SSLSessionOption; + +typedef enum { + kNeverAuthenticate, + kAlwaysAuthenticate, + kTryAuthenticate +} SSLAuthenticate; + +typedef enum { + kSSLIdle, + kSSLHandshake, + kSSLConnected, + kSSLClosed, + kSSLAborted +} SSLSessionState; + +typedef enum { + kSSLProtocolUnknown = 0, + kSSLProtocol3 = 2, + kTLSProtocol1 = 4, + /* DEPRECATED on iOS */ + kSSLProtocol2 = 1, + kSSLProtocol3Only = 3, + kTLSProtocol1Only = 5, + kSSLProtocolAll = 6, +} SSLProtocol; + +typedef UInt32 SSLCipherSuite; +enum { + SSL_NULL_WITH_NULL_NULL = 0x0000, + SSL_RSA_WITH_NULL_MD5 = 0x0001, + SSL_RSA_WITH_NULL_SHA = 0x0002, + SSL_RSA_EXPORT_WITH_RC4_40_MD5 = 0x0003, + SSL_RSA_WITH_RC4_128_MD5 = 0x0004, + SSL_RSA_WITH_RC4_128_SHA = 0x0005, + SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5 = 0x0006, + SSL_RSA_WITH_IDEA_CBC_SHA = 0x0007, + SSL_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0008, + SSL_RSA_WITH_DES_CBC_SHA = 0x0009, + SSL_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A, + SSL_DH_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x000B, + SSL_DH_DSS_WITH_DES_CBC_SHA = 0x000C, + SSL_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x000D, + SSL_DH_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x000E, + SSL_DH_RSA_WITH_DES_CBC_SHA = 0x000F, + SSL_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x0010, + SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA = 0x0011, + SSL_DHE_DSS_WITH_DES_CBC_SHA = 0x0012, + SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013, + SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA = 0x0014, + SSL_DHE_RSA_WITH_DES_CBC_SHA = 0x0015, + SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016, + SSL_DH_anon_EXPORT_WITH_RC4_40_MD5 = 0x0017, + SSL_DH_anon_WITH_RC4_128_MD5 = 0x0018, + SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA = 0x0019, + SSL_DH_anon_WITH_DES_CBC_SHA = 0x001A, + SSL_DH_anon_WITH_3DES_EDE_CBC_SHA = 0x001B, + SSL_FORTEZZA_DMS_WITH_NULL_SHA = 0x001C, + SSL_FORTEZZA_DMS_WITH_FORTEZZA_CBC_SHA =0x001D, + + /* TLS addenda using AES, per RFC 3268 */ + TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F, + TLS_DH_DSS_WITH_AES_128_CBC_SHA = 0x0030, + TLS_DH_RSA_WITH_AES_128_CBC_SHA = 0x0031, + TLS_DHE_DSS_WITH_AES_128_CBC_SHA = 0x0032, + TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033, + TLS_DH_anon_WITH_AES_128_CBC_SHA = 0x0034, + TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035, + TLS_DH_DSS_WITH_AES_256_CBC_SHA = 0x0036, + TLS_DH_RSA_WITH_AES_256_CBC_SHA = 0x0037, + TLS_DHE_DSS_WITH_AES_256_CBC_SHA = 0x0038, + TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039, + TLS_DH_anon_WITH_AES_256_CBC_SHA = 0x003A, + + /* ECDSA addenda, RFC 4492 */ + TLS_ECDH_ECDSA_WITH_NULL_SHA = 0xC001, + TLS_ECDH_ECDSA_WITH_RC4_128_SHA = 0xC002, + TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC003, + TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = 0xC004, + TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xC005, + TLS_ECDHE_ECDSA_WITH_NULL_SHA = 0xC006, + TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = 0xC007, + TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC008, + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A, + TLS_ECDH_RSA_WITH_NULL_SHA = 0xC00B, + TLS_ECDH_RSA_WITH_RC4_128_SHA = 0xC00C, + TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = 0xC00D, + TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = 0xC00E, + TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = 0xC00F, + TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010, + TLS_ECDHE_RSA_WITH_RC4_128_SHA = 0xC011, + TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xC012, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014, + TLS_ECDH_anon_WITH_NULL_SHA = 0xC015, + TLS_ECDH_anon_WITH_RC4_128_SHA = 0xC016, + TLS_ECDH_anon_WITH_3DES_EDE_CBC_SHA = 0xC017, + TLS_ECDH_anon_WITH_AES_128_CBC_SHA = 0xC018, + TLS_ECDH_anon_WITH_AES_256_CBC_SHA = 0xC019, + + /* TLS 1.2 addenda, RFC 5246 */ + /* Initial state. */ + TLS_NULL_WITH_NULL_NULL = 0x0000, + + /* Server provided RSA certificate for key exchange. */ + TLS_RSA_WITH_NULL_MD5 = 0x0001, + TLS_RSA_WITH_NULL_SHA = 0x0002, + TLS_RSA_WITH_RC4_128_MD5 = 0x0004, + TLS_RSA_WITH_RC4_128_SHA = 0x0005, + TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A, + TLS_RSA_WITH_NULL_SHA256 = 0x003B, + TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C, + TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D, + + /* Server-authenticated (and optionally client-authenticated) + Diffie-Hellman. */ + TLS_DH_DSS_WITH_3DES_EDE_CBC_SHA = 0x000D, + TLS_DH_RSA_WITH_3DES_EDE_CBC_SHA = 0x0010, + TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA = 0x0013, + TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016, + TLS_DH_DSS_WITH_AES_128_CBC_SHA256 = 0x003E, + TLS_DH_RSA_WITH_AES_128_CBC_SHA256 = 0x003F, + TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 = 0x0040, + TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067, + TLS_DH_DSS_WITH_AES_256_CBC_SHA256 = 0x0068, + TLS_DH_RSA_WITH_AES_256_CBC_SHA256 = 0x0069, + TLS_DHE_DSS_WITH_AES_256_CBC_SHA256 = 0x006A, + TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B, + + /* Completely anonymous Diffie-Hellman */ + TLS_DH_anon_WITH_RC4_128_MD5 = 0x0018, + TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = 0x001B, + TLS_DH_anon_WITH_AES_128_CBC_SHA256 = 0x006C, + TLS_DH_anon_WITH_AES_256_CBC_SHA256 = 0x006D, + + /* Addenda from rfc 5288 AES Galois Counter Mode (GCM) Cipher Suites + for TLS. */ + TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C, + TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D, + TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E, + TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F, + TLS_DH_RSA_WITH_AES_128_GCM_SHA256 = 0x00A0, + TLS_DH_RSA_WITH_AES_256_GCM_SHA384 = 0x00A1, + TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 = 0x00A2, + TLS_DHE_DSS_WITH_AES_256_GCM_SHA384 = 0x00A3, + TLS_DH_DSS_WITH_AES_128_GCM_SHA256 = 0x00A4, + TLS_DH_DSS_WITH_AES_256_GCM_SHA384 = 0x00A5, + TLS_DH_anon_WITH_AES_128_GCM_SHA256 = 0x00A6, + TLS_DH_anon_WITH_AES_256_GCM_SHA384 = 0x00A7, + + /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with + HMAC SHA-256/384. */ + TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023, + TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024, + TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC025, + TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC026, + TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027, + TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028, + TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 = 0xC029, + TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 = 0xC02A, + + /* Addenda from rfc 5289 Elliptic Curve Cipher Suites with + SHA-256/384 and AES Galois Counter Mode (GCM) */ + TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B, + TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C, + TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02D, + TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02E, + TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F, + TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030, + TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = 0xC031, + TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = 0xC032, + + /* RFC 5746 - Secure Renegotiation */ + TLS_EMPTY_RENEGOTIATION_INFO_SCSV = 0x00FF, + + /* + * Tags for SSL 2 cipher kinds that are not specified + * for SSL 3. + */ + SSL_RSA_WITH_RC2_CBC_MD5 = 0xFF80, + SSL_RSA_WITH_IDEA_CBC_MD5 = 0xFF81, + SSL_RSA_WITH_DES_CBC_MD5 = 0xFF82, + SSL_RSA_WITH_3DES_EDE_CBC_MD5 = 0xFF83, + SSL_NO_SUCH_CIPHERSUITE = 0xFFFF +}; + +typedef enum { + kSSLClientCertNone, + kSSLClientCertRequested, + kSSLClientCertSent, + kSSLClientCertRejected +} SSLClientCertificateState; + +enum { + errSSLProtocol = -9800, + errSSLNegotiation = -9801, + errSSLFatalAlert = -9802, + errSSLWouldBlock = -9803, + errSSLSessionNotFound = -9804, + errSSLClosedGraceful = -9805, + errSSLClosedAbort = -9806, + errSSLXCertChainInvalid = -9807, + errSSLBadCert = -9808, + errSSLCrypto = -9809, + errSSLInternal = -9810, + errSSLModuleAttach = -9811, + errSSLUnknownRootCert = -9812, + errSSLNoRootCert = -9813, + errSSLCertExpired = -9814, + errSSLCertNotYetValid = -9815, + errSSLClosedNoNotify = -9816, + errSSLBufferOverflow = -9817, + errSSLBadCipherSuite = -9818, + errSSLPeerUnexpectedMsg = -9819, + errSSLPeerBadRecordMac = -9820, + errSSLPeerDecryptionFail = -9821, + errSSLPeerRecordOverflow = -9822, + errSSLPeerDecompressFail = -9823, + errSSLPeerHandshakeFail = -9824, + errSSLPeerBadCert = -9825, + errSSLPeerUnsupportedCert = -9826, + errSSLPeerCertRevoked = -9827, + errSSLPeerCertExpired = -9828, + errSSLPeerCertUnknown = -9829, + errSSLIllegalParam = -9830, + errSSLPeerUnknownCA = -9831, + errSSLPeerAccessDenied = -9832, + errSSLPeerDecodeError = -9833, + errSSLPeerDecryptError = -9834, + errSSLPeerExportRestriction = -9835, + errSSLPeerProtocolVersion = -9836, + errSSLPeerInsufficientSecurity = -9837, + errSSLPeerInternalError = -9838, + errSSLPeerUserCancelled = -9839, + errSSLPeerNoRenegotiation = -9840, + errSSLServerAuthCompleted = -9841, + errSSLClientCertRequested = -9842, + errSSLHostNameMismatch = -9843, + errSSLConnectionRefused = -9844, + errSSLDecryptionFail = -9845, + errSSLBadRecordMac = -9846, + errSSLRecordOverflow = -9847, + errSSLBadConfiguration = -9848, + errSSLLast = -9849 /* end of range, to be deleted */ +}; +""" + +FUNCTIONS = """ +OSStatus SSLSetConnection(SSLContextRef, SSLConnectionRef); +OSStatus SSLGetConnection(SSLContextRef, SSLConnectionRef *); +OSStatus SSLSetSessionOption(SSLContextRef, SSLSessionOption, Boolean); +OSStatus SSLSetClientSideAuthenticate(SSLContextRef, SSLAuthenticate); + +OSStatus SSLHandshake(SSLContextRef); +OSStatus SSLGetSessionState(SSLContextRef, SSLSessionState *); +OSStatus SSLGetNegotiatedProtocolVersion(SSLContextRef, SSLProtocol *); +OSStatus SSLSetPeerID(SSLContextRef, const void *, size_t); +OSStatus SSLGetPeerID(SSLContextRef, const void **, size_t *); +OSStatus SSLGetBufferedReadSize(SSLContextRef, size_t *); +OSStatus SSLRead(SSLContextRef, void *, size_t, size_t *); +OSStatus SSLWrite(SSLContextRef, const void *, size_t, size_t *); +OSStatus SSLClose(SSLContextRef); + +OSStatus SSLGetNumberSupportedCiphers(SSLContextRef, size_t *); +OSStatus SSLGetSupportedCiphers(SSLContextRef, SSLCipherSuite *, size_t *); +OSStatus SSLSetEnabledCiphers(SSLContextRef, const SSLCipherSuite *, size_t); +OSStatus SSLGetNumberEnabledCiphers(SSLContextRef, size_t *); +OSStatus SSLGetEnabledCiphers(SSLContextRef, SSLCipherSuite *, size_t *); +OSStatus SSLGetNegotiatedCipher(SSLContextRef, SSLCipherSuite *); +OSStatus SSLSetDiffieHellmanParams(SSLContextRef, const void *, size_t); +OSStatus SSLGetDiffieHellmanParams(SSLContextRef, const void **, size_t *); + +OSStatus SSLSetCertificateAuthorities(SSLContextRef, CFTypeRef, Boolean); +OSStatus SSLCopyCertificateAuthorities(SSLContextRef, CFArrayRef *); +OSStatus SSLCopyDistinguishedNames(SSLContextRef, CFArrayRef *); +OSStatus SSLSetCertificate(SSLContextRef, CFArrayRef); +OSStatus SSLGetClientCertificateState(SSLContextRef, + SSLClientCertificateState *); +OSStatus SSLCopyPeerTrust(SSLContextRef, SecTrustRef *trust); + +OSStatus SSLSetPeerDomainName(SSLContextRef, const char *, size_t); +OSStatus SSLGetPeerDomainNameLength(SSLContextRef, size_t *); +OSStatus SSLGetPeerDomainName(SSLContextRef, char *, size_t *); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/hazmat_src/constant_time.c b/src/_cffi_src/hazmat_src/constant_time.c new file mode 100644 index 0000000..0a48fe8 --- /dev/null +++ b/src/_cffi_src/hazmat_src/constant_time.c @@ -0,0 +1,22 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this +// repository for complete details. + +uint8_t Cryptography_constant_time_bytes_eq(uint8_t *a, size_t len_a, + uint8_t *b, size_t len_b) { + size_t i = 0; + uint8_t mismatch = 0; + if (len_a != len_b) { + return 0; + } + for (i = 0; i < len_a; i++) { + mismatch |= a[i] ^ b[i]; + } + + /* Make sure any bits set are copied to the lowest bit */ + mismatch |= mismatch >> 4; + mismatch |= mismatch >> 2; + mismatch |= mismatch >> 1; + /* Now check the low bit to see if it's set */ + return (mismatch & 1) == 0; +} diff --git a/src/_cffi_src/hazmat_src/constant_time.h b/src/_cffi_src/hazmat_src/constant_time.h new file mode 100644 index 0000000..593479f --- /dev/null +++ b/src/_cffi_src/hazmat_src/constant_time.h @@ -0,0 +1,6 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this +// repository for complete details. + +uint8_t Cryptography_constant_time_bytes_eq(uint8_t *, size_t, uint8_t *, + size_t); diff --git a/src/_cffi_src/hazmat_src/padding.c b/src/_cffi_src/hazmat_src/padding.c new file mode 100644 index 0000000..a6e05de --- /dev/null +++ b/src/_cffi_src/hazmat_src/padding.c @@ -0,0 +1,65 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this +// repository for complete details. + +/* Returns the value of the input with the most-significant-bit copied to all + of the bits. */ +static uint16_t Cryptography_DUPLICATE_MSB_TO_ALL(uint16_t a) { + return (1 - (a >> (sizeof(uint16_t) * 8 - 1))) - 1; +} + +/* This returns 0xFFFF if a < b else 0x0000, but does so in a constant time + fashion */ +static uint16_t Cryptography_constant_time_lt(uint16_t a, uint16_t b) { + a -= b; + return Cryptography_DUPLICATE_MSB_TO_ALL(a); +} + +uint8_t Cryptography_check_pkcs7_padding(const uint8_t *data, + uint16_t block_len) { + uint16_t i; + uint16_t pad_size = data[block_len - 1]; + uint16_t mismatch = 0; + for (i = 0; i < block_len; i++) { + unsigned int mask = Cryptography_constant_time_lt(i, pad_size); + uint16_t b = data[block_len - 1 - i]; + mismatch |= (mask & (pad_size ^ b)); + } + + /* Check to make sure the pad_size was within the valid range. */ + mismatch |= ~Cryptography_constant_time_lt(0, pad_size); + mismatch |= Cryptography_constant_time_lt(block_len, pad_size); + + /* Make sure any bits set are copied to the lowest bit */ + mismatch |= mismatch >> 8; + mismatch |= mismatch >> 4; + mismatch |= mismatch >> 2; + mismatch |= mismatch >> 1; + /* Now check the low bit to see if it's set */ + return (mismatch & 1) == 0; +} + +uint8_t Cryptography_check_ansix923_padding(const uint8_t *data, + uint16_t block_len) { + uint16_t i; + uint16_t pad_size = data[block_len - 1]; + uint16_t mismatch = 0; + /* Skip the first one with the pad size */ + for (i = 1; i < block_len; i++) { + unsigned int mask = Cryptography_constant_time_lt(i, pad_size); + uint16_t b = data[block_len - 1 - i]; + mismatch |= (mask & b); + } + + /* Check to make sure the pad_size was within the valid range. */ + mismatch |= ~Cryptography_constant_time_lt(0, pad_size); + mismatch |= Cryptography_constant_time_lt(block_len, pad_size); + + /* Make sure any bits set are copied to the lowest bit */ + mismatch |= mismatch >> 8; + mismatch |= mismatch >> 4; + mismatch |= mismatch >> 2; + mismatch |= mismatch >> 1; + /* Now check the low bit to see if it's set */ + return (mismatch & 1) == 0; +} diff --git a/src/_cffi_src/hazmat_src/padding.h b/src/_cffi_src/hazmat_src/padding.h new file mode 100644 index 0000000..fb023c1 --- /dev/null +++ b/src/_cffi_src/hazmat_src/padding.h @@ -0,0 +1,6 @@ +// This file is dual licensed under the terms of the Apache License, Version +// 2.0, and the BSD License. See the LICENSE file in the root of this +// repository for complete details. + +uint8_t Cryptography_check_pkcs7_padding(const uint8_t *, uint8_t); +uint8_t Cryptography_check_ansix923_padding(const uint8_t *, uint8_t); diff --git a/src/_cffi_src/openssl/__init__.py b/src/_cffi_src/openssl/__init__.py new file mode 100644 index 0000000..4b54088 --- /dev/null +++ b/src/_cffi_src/openssl/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function diff --git a/src/_cffi_src/openssl/aes.py b/src/_cffi_src/openssl/aes.py new file mode 100644 index 0000000..c54b636 --- /dev/null +++ b/src/_cffi_src/openssl/aes.py @@ -0,0 +1,50 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +static const int Cryptography_HAS_AES_WRAP; +static const int Cryptography_HAS_AES_CTR128_ENCRYPT; + +struct aes_key_st { + ...; +}; +typedef struct aes_key_st AES_KEY; +""" + +FUNCTIONS = """ +int AES_set_encrypt_key(const unsigned char *, const int, AES_KEY *); +int AES_set_decrypt_key(const unsigned char *, const int, AES_KEY *); + +int AES_wrap_key(AES_KEY *, const unsigned char *, unsigned char *, + const unsigned char *, unsigned int); +int AES_unwrap_key(AES_KEY *, const unsigned char *, unsigned char *, + const unsigned char *, unsigned int); +""" + +MACROS = """ +/* The ctr128_encrypt function is only useful in 1.0.0. We can use EVP for + this in 1.0.1+. */ +void AES_ctr128_encrypt(const unsigned char *, unsigned char *, + size_t, const AES_KEY *, unsigned char[], + unsigned char[], unsigned int *); +""" + +CUSTOMIZATIONS = """ +static const long Cryptography_HAS_AES_WRAP = 1; +#if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER && !defined(LIBRESSL_VERSION_NUMBER) +static const int Cryptography_HAS_AES_CTR128_ENCRYPT = 0; +void (*AES_ctr128_encrypt)(const unsigned char *, unsigned char *, + size_t, const AES_KEY *, + unsigned char[], unsigned char[], + unsigned int *) = NULL; +#else +static const int Cryptography_HAS_AES_CTR128_ENCRYPT = 1; +#endif +""" diff --git a/src/_cffi_src/openssl/asn1.py b/src/_cffi_src/openssl/asn1.py new file mode 100644 index 0000000..4afca3d --- /dev/null +++ b/src/_cffi_src/openssl/asn1.py @@ -0,0 +1,164 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef int... time_t; + +typedef int ASN1_BOOLEAN; +typedef ... ASN1_INTEGER; + +struct asn1_string_st { + int length; + int type; + unsigned char *data; + long flags; +}; + +typedef struct asn1_string_st ASN1_OCTET_STRING; +typedef struct asn1_string_st ASN1_IA5STRING; +typedef struct asn1_string_st ASN1_BIT_STRING; +typedef struct asn1_string_st ASN1_TIME; +typedef ... ASN1_OBJECT; +typedef struct asn1_string_st ASN1_STRING; +typedef struct asn1_string_st ASN1_UTF8STRING; +typedef ... ASN1_TYPE; +typedef ... ASN1_GENERALIZEDTIME; +typedef ... ASN1_ENUMERATED; +typedef ... ASN1_ITEM; +typedef ... ASN1_VALUE; + +typedef ... ASN1_ITEM_EXP; + +typedef ... ASN1_UTCTIME; + +static const int V_ASN1_GENERALIZEDTIME; + +static const int MBSTRING_FLAG; +static const int MBSTRING_ASC; +static const int MBSTRING_BMP; +static const int MBSTRING_UTF8; +static const int MBSTRING_UNIV; +""" + +FUNCTIONS = """ +ASN1_OBJECT *ASN1_OBJECT_new(void); +void ASN1_OBJECT_free(ASN1_OBJECT *); + +/* ASN1 OBJECT IDENTIFIER */ +ASN1_OBJECT *d2i_ASN1_OBJECT(ASN1_OBJECT **, const unsigned char **, long); + +/* ASN1 STRING */ +ASN1_STRING *ASN1_STRING_new(void); +ASN1_STRING *ASN1_STRING_type_new(int); +void ASN1_STRING_free(ASN1_STRING *); +unsigned char *ASN1_STRING_data(ASN1_STRING *); +int ASN1_STRING_set(ASN1_STRING *, const void *, int); + +/* ASN1 OCTET STRING */ +ASN1_OCTET_STRING *ASN1_OCTET_STRING_new(void); +void ASN1_OCTET_STRING_free(ASN1_OCTET_STRING *); +int ASN1_OCTET_STRING_set(ASN1_OCTET_STRING *, const unsigned char *, int); + +/* ASN1 IA5STRING */ +ASN1_IA5STRING *ASN1_IA5STRING_new(void); + +/* ASN1 INTEGER */ +ASN1_INTEGER *ASN1_INTEGER_new(void); +void ASN1_INTEGER_free(ASN1_INTEGER *); +int ASN1_INTEGER_set(ASN1_INTEGER *, long); + +/* ASN1 TIME */ +ASN1_TIME *ASN1_TIME_new(void); +void ASN1_TIME_free(ASN1_TIME *); +ASN1_GENERALIZEDTIME *ASN1_TIME_to_generalizedtime(ASN1_TIME *, + ASN1_GENERALIZEDTIME **); +ASN1_TIME *ASN1_TIME_set(ASN1_TIME *, time_t); + +/* ASN1 UTCTIME */ +ASN1_UTCTIME *ASN1_UTCTIME_new(void); +void ASN1_UTCTIME_free(ASN1_UTCTIME *); +int ASN1_UTCTIME_cmp_time_t(const ASN1_UTCTIME *, time_t); +ASN1_UTCTIME *ASN1_UTCTIME_set(ASN1_UTCTIME *, time_t); + +/* ASN1 GENERALIZEDTIME */ +int ASN1_GENERALIZEDTIME_set_string(ASN1_GENERALIZEDTIME *, const char *); +ASN1_GENERALIZEDTIME *ASN1_GENERALIZEDTIME_set(ASN1_GENERALIZEDTIME *, time_t); +void ASN1_GENERALIZEDTIME_free(ASN1_GENERALIZEDTIME *); +int i2d_ASN1_GENERALIZEDTIME(ASN1_GENERALIZEDTIME *, unsigned char **); + +/* ASN1 ENUMERATED */ +ASN1_ENUMERATED *ASN1_ENUMERATED_new(void); +void ASN1_ENUMERATED_free(ASN1_ENUMERATED *); +int ASN1_ENUMERATED_set(ASN1_ENUMERATED *, long); +int i2d_ASN1_ENUMERATED(ASN1_ENUMERATED *, unsigned char **); + +ASN1_VALUE *ASN1_item_d2i(ASN1_VALUE **, const unsigned char **, long, + const ASN1_ITEM *); +int ASN1_BIT_STRING_set_bit(ASN1_BIT_STRING *, int, int); +""" + +MACROS = """ +/* These became const ASN1_* in 1.1.0 */ +int i2d_ASN1_OBJECT(ASN1_OBJECT *, unsigned char **); +int ASN1_STRING_type(ASN1_STRING *); +int ASN1_STRING_to_UTF8(unsigned char **, ASN1_STRING *); +long ASN1_ENUMERATED_get(ASN1_ENUMERATED *); +int i2a_ASN1_INTEGER(BIO *, ASN1_INTEGER *); + +ASN1_UTF8STRING *ASN1_UTF8STRING_new(void); +void ASN1_UTF8STRING_free(ASN1_UTF8STRING *); + +ASN1_BIT_STRING *ASN1_BIT_STRING_new(void); +void ASN1_BIT_STRING_free(ASN1_BIT_STRING *); +int i2d_ASN1_BIT_STRING(ASN1_BIT_STRING *, unsigned char **); +int i2d_ASN1_OCTET_STRING(ASN1_OCTET_STRING *, unsigned char **); +int i2d_ASN1_INTEGER(ASN1_INTEGER *, unsigned char **); +/* This is not a macro, but is const on some versions of OpenSSL */ +int ASN1_BIT_STRING_get_bit(ASN1_BIT_STRING *, int); +ASN1_TIME *M_ASN1_TIME_dup(void *); +const ASN1_ITEM *ASN1_ITEM_ptr(ASN1_ITEM_EXP *); + +/* These aren't macros these arguments are all const X on openssl > 1.0.x */ + +int ASN1_TIME_print(BIO *, ASN1_TIME *); +int ASN1_STRING_length(ASN1_STRING *); +ASN1_STRING *ASN1_STRING_dup(ASN1_STRING *); +int ASN1_STRING_cmp(ASN1_STRING *, ASN1_STRING *); +int ASN1_UTCTIME_print(BIO *, ASN1_UTCTIME *); + +ASN1_OCTET_STRING *ASN1_OCTET_STRING_dup(ASN1_OCTET_STRING *); +int ASN1_OCTET_STRING_cmp(ASN1_OCTET_STRING *, ASN1_OCTET_STRING *); + +ASN1_INTEGER *ASN1_INTEGER_dup(ASN1_INTEGER *); +int ASN1_INTEGER_cmp(ASN1_INTEGER *, ASN1_INTEGER *); +long ASN1_INTEGER_get(ASN1_INTEGER *); + +BIGNUM *ASN1_INTEGER_to_BN(ASN1_INTEGER *, BIGNUM *); +ASN1_INTEGER *BN_to_ASN1_INTEGER(BIGNUM *, ASN1_INTEGER *); + +/* These isn't a macro the arg is const on openssl 1.0.2+ */ +int ASN1_GENERALIZEDTIME_check(ASN1_GENERALIZEDTIME *); +int ASN1_UTCTIME_check(ASN1_UTCTIME *); + +/* Not a macro, const on openssl 1.0 */ +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 = """ +/* This macro is removed in 1.1.0. We re-add it if required to support + pyOpenSSL versions older than whatever resolves + https://github.com/pyca/pyopenssl/issues/431 */ +#if !defined(M_ASN1_TIME_dup) +#define M_ASN1_TIME_dup(a) (ASN1_TIME *)ASN1_STRING_dup((const ASN1_STRING *)a) +#endif +""" diff --git a/src/_cffi_src/openssl/bignum.py b/src/_cffi_src/openssl/bignum.py new file mode 100644 index 0000000..455afdc --- /dev/null +++ b/src/_cffi_src/openssl/bignum.py @@ -0,0 +1,88 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... BN_CTX; +typedef ... BIGNUM; +typedef int... BN_ULONG; +""" + +FUNCTIONS = """ +BIGNUM *BN_new(void); +void BN_free(BIGNUM *); + +BN_CTX *BN_CTX_new(void); +void BN_CTX_free(BN_CTX *); + +void BN_CTX_start(BN_CTX *); +BIGNUM *BN_CTX_get(BN_CTX *); +void BN_CTX_end(BN_CTX *); + +BIGNUM *BN_copy(BIGNUM *, const BIGNUM *); +BIGNUM *BN_dup(const BIGNUM *); + +int BN_set_word(BIGNUM *, BN_ULONG); +BN_ULONG BN_get_word(const BIGNUM *); + +const BIGNUM *BN_value_one(void); + +char *BN_bn2hex(const BIGNUM *); +int BN_hex2bn(BIGNUM **, const char *); +int BN_dec2bn(BIGNUM **, const char *); + +int BN_bn2bin(const BIGNUM *, unsigned char *); +BIGNUM *BN_bin2bn(const unsigned char *, int, BIGNUM *); + +int BN_num_bits(const BIGNUM *); + +int BN_cmp(const BIGNUM *, const BIGNUM *); +int BN_add(BIGNUM *, const BIGNUM *, const BIGNUM *); +int BN_sub(BIGNUM *, const BIGNUM *, const BIGNUM *); +int BN_mul(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); +int BN_sqr(BIGNUM *, const BIGNUM *, BN_CTX *); +int BN_div(BIGNUM *, BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); +int BN_nnmod(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); +int BN_mod_add(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, + BN_CTX *); +int BN_mod_sub(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, + BN_CTX *); +int BN_mod_mul(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, + BN_CTX *); +int BN_mod_sqr(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); +int BN_exp(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); +int BN_mod_exp(BIGNUM *, const BIGNUM *, const BIGNUM *, const BIGNUM *, + BN_CTX *); +int BN_gcd(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); +BIGNUM *BN_mod_inverse(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); + +int BN_set_bit(BIGNUM *, int); +int BN_clear_bit(BIGNUM *, int); + +int BN_is_bit_set(const BIGNUM *, int); + +int BN_mask_bits(BIGNUM *, int); +""" + +MACROS = """ +int BN_num_bytes(const BIGNUM *); + +int BN_zero(BIGNUM *); +int BN_one(BIGNUM *); +int BN_mod(BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); + +int BN_lshift(BIGNUM *, const BIGNUM *, int); +int BN_lshift1(BIGNUM *, BIGNUM *); + +int BN_rshift(BIGNUM *, BIGNUM *, int); +int BN_rshift1(BIGNUM *, BIGNUM *); +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/openssl/bio.py b/src/_cffi_src/openssl/bio.py new file mode 100644 index 0000000..a134d9b --- /dev/null +++ b/src/_cffi_src/openssl/bio.py @@ -0,0 +1,147 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef struct bio_st BIO; +typedef void bio_info_cb(BIO *, int, const char *, int, long, long); +typedef ... bio_st; +typedef ... BIO_METHOD; +typedef ... BUF_MEM; + +static const int BIO_TYPE_MEM; +static const int BIO_TYPE_FILE; +static const int BIO_TYPE_FD; +static const int BIO_TYPE_SOCKET; +static const int BIO_TYPE_CONNECT; +static const int BIO_TYPE_ACCEPT; +static const int BIO_TYPE_NULL; +static const int BIO_CLOSE; +static const int BIO_NOCLOSE; +static const int BIO_TYPE_SOURCE_SINK; +static const int BIO_CTRL_RESET; +static const int BIO_CTRL_EOF; +static const int BIO_CTRL_SET; +static const int BIO_CTRL_SET_CLOSE; +static const int BIO_CTRL_FLUSH; +static const int BIO_CTRL_DUP; +static const int BIO_CTRL_GET_CLOSE; +static const int BIO_CTRL_INFO; +static const int BIO_CTRL_GET; +static const int BIO_CTRL_PENDING; +static const int BIO_CTRL_WPENDING; +static const int BIO_C_FILE_SEEK; +static const int BIO_C_FILE_TELL; +static const int BIO_TYPE_NONE; +static const int BIO_TYPE_NBIO_TEST; +static const int BIO_TYPE_BIO; +static const int BIO_TYPE_DESCRIPTOR; +static const int BIO_FLAGS_READ; +static const int BIO_FLAGS_WRITE; +static const int BIO_FLAGS_IO_SPECIAL; +static const int BIO_FLAGS_RWS; +static const int BIO_FLAGS_SHOULD_RETRY; +static const int BIO_TYPE_NULL_FILTER; +static const int BIO_TYPE_SSL; +static const int BIO_TYPE_MD; +static const int BIO_TYPE_BUFFER; +static const int BIO_TYPE_CIPHER; +static const int BIO_TYPE_BASE64; +static const int BIO_TYPE_FILTER; +""" + +FUNCTIONS = """ +int BIO_free(BIO *); +void BIO_vfree(BIO *); +void BIO_free_all(BIO *); +BIO *BIO_push(BIO *, BIO *); +BIO *BIO_pop(BIO *); +BIO *BIO_next(BIO *); +BIO *BIO_find_type(BIO *, int); +BIO *BIO_new_file(const char *, const char *); +BIO *BIO_new_fp(FILE *, int); +BIO *BIO_new_fd(int, int); +BIO *BIO_new_socket(int, int); +long BIO_ctrl(BIO *, int, long, void *); +long BIO_callback_ctrl( + BIO *, + int, + void (*)(struct bio_st *, int, const char *, int, long, long) +); +long BIO_int_ctrl(BIO *, int, long, int); +size_t BIO_ctrl_pending(BIO *); +size_t BIO_ctrl_wpending(BIO *); +int BIO_read(BIO *, void *, int); +int BIO_gets(BIO *, char *, int); +int BIO_write(BIO *, const void *, int); +int BIO_puts(BIO *, const char *); +int BIO_method_type(const BIO *); +""" + +MACROS = """ +/* Added in 1.1.0 */ +int BIO_up_ref(BIO *); + +/* These added const to BIO_METHOD in 1.1.0 */ +BIO *BIO_new(BIO_METHOD *); +BIO_METHOD *BIO_s_mem(void); +BIO_METHOD *BIO_s_file(void); +BIO_METHOD *BIO_s_fd(void); +BIO_METHOD *BIO_s_socket(void); +BIO_METHOD *BIO_s_null(void); +BIO_METHOD *BIO_f_null(void); +BIO_METHOD *BIO_f_buffer(void); +/* BIO_new_mem_buf became const void * in 1.0.2g */ +BIO *BIO_new_mem_buf(void *, int); +long BIO_set_fd(BIO *, long, int); +long BIO_get_fd(BIO *, char *); +long BIO_set_mem_eof_return(BIO *, int); +long BIO_get_mem_data(BIO *, char **); +long BIO_set_mem_buf(BIO *, BUF_MEM *, int); +long BIO_get_mem_ptr(BIO *, BUF_MEM **); +long BIO_set_fp(BIO *, FILE *, int); +long BIO_get_fp(BIO *, FILE **); +long BIO_read_filename(BIO *, char *); +long BIO_write_filename(BIO *, char *); +long BIO_append_filename(BIO *, char *); +long BIO_rw_filename(BIO *, char *); +int BIO_should_read(BIO *); +int BIO_should_write(BIO *); +int BIO_should_io_special(BIO *); +int BIO_retry_type(BIO *); +int BIO_should_retry(BIO *); +int BIO_reset(BIO *); +int BIO_seek(BIO *, int); +int BIO_tell(BIO *); +int BIO_flush(BIO *); +int BIO_eof(BIO *); +int BIO_set_close(BIO *,long); +int BIO_get_close(BIO *); +int BIO_pending(BIO *); +int BIO_wpending(BIO *); +int BIO_get_info_callback(BIO *, bio_info_cb **); +int BIO_set_info_callback(BIO *, bio_info_cb *); +long BIO_get_buffer_num_lines(BIO *); +long BIO_set_read_buffer_size(BIO *, long); +long BIO_set_write_buffer_size(BIO *, long); +long BIO_set_buffer_size(BIO *, long); +long BIO_set_buffer_read_data(BIO *, void *, long); +long BIO_set_nbio(BIO *, long); +void BIO_set_retry_read(BIO *); +void BIO_clear_retry_flags(BIO *); +""" + +CUSTOMIZATIONS = """ +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110PRE4 || defined(LIBRESSL_VERSION_NUMBER) +int BIO_up_ref(BIO *b) { + CRYPTO_add(&b->references, 1, CRYPTO_LOCK_BIO); + return 1; +} +#endif +""" diff --git a/src/_cffi_src/openssl/callbacks.py b/src/_cffi_src/openssl/callbacks.py new file mode 100644 index 0000000..4a6b4d3 --- /dev/null +++ b/src/_cffi_src/openssl/callbacks.py @@ -0,0 +1,124 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import sys + +import cffi + +INCLUDES = """ +#include +#include +#include +#include + +#include +""" + +TYPES = """ +static const long Cryptography_STATIC_CALLBACKS; + +/* crypto.h + * CRYPTO_set_locking_callback + * void (*cb)(int mode, int type, const char *file, int line) + */ +extern "Python" void Cryptography_locking_cb(int, int, const char *, int); + +/* pem.h + * int pem_password_cb(char *buf, int size, int rwflag, void *userdata); + */ +extern "Python" int Cryptography_pem_password_cb(char *, int, int, void *); + +/* rand.h + * int (*bytes)(unsigned char *buf, int num); + * int (*status)(void); + */ +extern "Python" int Cryptography_rand_bytes(unsigned char *, int); +extern "Python" int Cryptography_rand_status(void); +""" + +FUNCTIONS = """ +int _setup_ssl_threads(void); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +static const long Cryptography_STATIC_CALLBACKS = 1; +""" + +if cffi.__version_info__ < (1, 4, 0) or sys.version_info >= (3, 5): + # backwards compatibility for old cffi version on PyPy + # and Python >=3.5 (https://github.com/pyca/cryptography/issues/2970) + TYPES = "static const long Cryptography_STATIC_CALLBACKS;" + CUSTOMIZATIONS = """static const long Cryptography_STATIC_CALLBACKS = 0; +""" + +CUSTOMIZATIONS += """ +/* This code is derived from the locking code found in the Python _ssl module's + locking callback for OpenSSL. + + Copyright 2001-2016 Python Software Foundation; All Rights Reserved. +*/ + +static unsigned int _ssl_locks_count = 0; +static PyThread_type_lock *_ssl_locks = NULL; + +static void _ssl_thread_locking_function(int mode, int n, const char *file, + int line) { + /* this function is needed to perform locking on shared data + structures. (Note that OpenSSL uses a number of global data + structures that will be implicitly shared whenever multiple + threads use OpenSSL.) Multi-threaded applications will + crash at random if it is not set. + + locking_function() must be able to handle up to + CRYPTO_num_locks() different mutex locks. It sets the n-th + lock if mode & CRYPTO_LOCK, and releases it otherwise. + + file and line are the file number of the function setting the + lock. They can be useful for debugging. + */ + + if ((_ssl_locks == NULL) || + (n < 0) || ((unsigned)n >= _ssl_locks_count)) { + return; + } + + if (mode & CRYPTO_LOCK) { + PyThread_acquire_lock(_ssl_locks[n], 1); + } else { + PyThread_release_lock(_ssl_locks[n]); + } +} + +int _setup_ssl_threads(void) { + unsigned int i; + + if (_ssl_locks == NULL) { + _ssl_locks_count = CRYPTO_num_locks(); + _ssl_locks = PyMem_New(PyThread_type_lock, _ssl_locks_count); + if (_ssl_locks == NULL) { + PyErr_NoMemory(); + return 0; + } + memset(_ssl_locks, 0, sizeof(PyThread_type_lock) * _ssl_locks_count); + for (i = 0; i < _ssl_locks_count; i++) { + _ssl_locks[i] = PyThread_allocate_lock(); + if (_ssl_locks[i] == NULL) { + unsigned int j; + for (j = 0; j < i; j++) { + PyThread_free_lock(_ssl_locks[j]); + } + PyMem_Free(_ssl_locks); + return 0; + } + } + CRYPTO_set_locking_callback(_ssl_thread_locking_function); + } + return 1; +} +""" diff --git a/src/_cffi_src/openssl/cmac.py b/src/_cffi_src/openssl/cmac.py new file mode 100644 index 0000000..67abc85 --- /dev/null +++ b/src/_cffi_src/openssl/cmac.py @@ -0,0 +1,44 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#if !defined(OPENSSL_NO_CMAC) && CRYPTOGRAPHY_OPENSSL_101_OR_GREATER +#include +#endif +""" + +TYPES = """ +static const int Cryptography_HAS_CMAC; +typedef ... CMAC_CTX; +""" + +FUNCTIONS = """ +""" + +MACROS = """ +CMAC_CTX *CMAC_CTX_new(void); +int CMAC_Init(CMAC_CTX *, const void *, size_t, const EVP_CIPHER *, ENGINE *); +int CMAC_Update(CMAC_CTX *, const void *, size_t); +int CMAC_Final(CMAC_CTX *, unsigned char *, size_t *); +int CMAC_CTX_copy(CMAC_CTX *, const CMAC_CTX *); +void CMAC_CTX_free(CMAC_CTX *); +""" + +CUSTOMIZATIONS = """ +#if !defined(OPENSSL_NO_CMAC) && CRYPTOGRAPHY_OPENSSL_101_OR_GREATER +static const long Cryptography_HAS_CMAC = 1; +#else +static const long Cryptography_HAS_CMAC = 0; +typedef void CMAC_CTX; +CMAC_CTX *(*CMAC_CTX_new)(void) = NULL; +int (*CMAC_Init)(CMAC_CTX *, const void *, size_t, const EVP_CIPHER *, + ENGINE *) = NULL; +int (*CMAC_Update)(CMAC_CTX *, const void *, size_t) = NULL; +int (*CMAC_Final)(CMAC_CTX *, unsigned char *, size_t *) = NULL; +int (*CMAC_CTX_copy)(CMAC_CTX *, const CMAC_CTX *) = NULL; +void (*CMAC_CTX_free)(CMAC_CTX *) = NULL; +#endif +""" diff --git a/src/_cffi_src/openssl/cms.py b/src/_cffi_src/openssl/cms.py new file mode 100644 index 0000000..fb87bd3 --- /dev/null +++ b/src/_cffi_src/openssl/cms.py @@ -0,0 +1,116 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#if !defined(OPENSSL_NO_CMS) +#include +#endif +""" + +TYPES = """ +static const long Cryptography_HAS_CMS; +static const long Cryptography_HAS_CMS_BIO_FUNCTIONS; + +typedef ... CMS_ContentInfo; +typedef ... CMS_SignerInfo; +typedef ... CMS_CertificateChoices; +typedef ... CMS_RevocationInfoChoice; +typedef ... CMS_RecipientInfo; +typedef ... CMS_ReceiptRequest; +typedef ... CMS_Receipt; + +static const int CMS_TEXT; +static const int CMS_NOCERTS; +static const int CMS_NO_CONTENT_VERIFY; +static const int CMS_NO_ATTR_VERIFY; +static const int CMS_NOSIGS; +static const int CMS_NOINTERN; +static const int CMS_NO_SIGNER_CERT_VERIFY; +static const int CMS_NOVERIFY; +static const int CMS_DETACHED; +static const int CMS_BINARY; +static const int CMS_NOATTR; +static const int CMS_NOSMIMECAP; +static const int CMS_NOOLDMIMETYPE; +static const int CMS_CRLFEOL; +static const int CMS_STREAM; +static const int CMS_NOCRL; +static const int CMS_PARTIAL; +static const int CMS_REUSE_DIGEST; +static const int CMS_USE_KEYID; +static const int CMS_DEBUG_DECRYPT; +""" + +FUNCTIONS = """ +""" + +MACROS = """ +BIO *BIO_new_CMS(BIO *, CMS_ContentInfo *); +int i2d_CMS_bio_stream(BIO *, CMS_ContentInfo *, BIO *, int); +int PEM_write_bio_CMS_stream(BIO *, CMS_ContentInfo *, BIO *, int); +int CMS_final(CMS_ContentInfo *, BIO *, BIO *, unsigned int); +CMS_ContentInfo *CMS_sign(X509 *, EVP_PKEY *, Cryptography_STACK_OF_X509 *, + BIO *, unsigned int); +int CMS_verify(CMS_ContentInfo *, Cryptography_STACK_OF_X509 *, X509_STORE *, + BIO *, BIO *, unsigned int); +CMS_ContentInfo *CMS_encrypt(Cryptography_STACK_OF_X509 *, BIO *, + const EVP_CIPHER *, unsigned int); +int CMS_decrypt(CMS_ContentInfo *, EVP_PKEY *, X509 *, BIO *, BIO *, + unsigned int); +CMS_SignerInfo *CMS_add1_signer(CMS_ContentInfo *, X509 *, EVP_PKEY *, + const EVP_MD *, unsigned int); +""" + +CUSTOMIZATIONS = """ +#if !defined(OPENSSL_NO_CMS) +static const long Cryptography_HAS_CMS = 1; +static const long Cryptography_HAS_CMS_BIO_FUNCTIONS = 1; +#else +static const long Cryptography_HAS_CMS = 0; +static const long Cryptography_HAS_CMS_BIO_FUNCTIONS = 0; +typedef void CMS_ContentInfo; +typedef void CMS_SignerInfo; +typedef void CMS_CertificateChoices; +typedef void CMS_RevocationInfoChoice; +typedef void CMS_RecipientInfo; +typedef void CMS_ReceiptRequest; +typedef void CMS_Receipt; +const long CMS_TEXT = 0; +const long CMS_NOCERTS = 0; +const long CMS_NO_CONTENT_VERIFY = 0; +const long CMS_NO_ATTR_VERIFY = 0; +const long CMS_NOSIGS = 0; +const long CMS_NOINTERN = 0; +const long CMS_NO_SIGNER_CERT_VERIFY = 0; +const long CMS_NOVERIFY = 0; +const long CMS_DETACHED = 0; +const long CMS_BINARY = 0; +const long CMS_NOATTR = 0; +const long CMS_NOSMIMECAP = 0; +const long CMS_NOOLDMIMETYPE = 0; +const long CMS_CRLFEOL = 0; +const long CMS_STREAM = 0; +const long CMS_NOCRL = 0; +const long CMS_PARTIAL = 0; +const long CMS_REUSE_DIGEST = 0; +const long CMS_USE_KEYID = 0; +const long CMS_DEBUG_DECRYPT = 0; +BIO *(*BIO_new_CMS)(BIO *, CMS_ContentInfo *) = NULL; +int (*i2d_CMS_bio_stream)(BIO *, CMS_ContentInfo *, BIO *, int) = NULL; +int (*PEM_write_bio_CMS_stream)(BIO *, CMS_ContentInfo *, BIO *, int) = NULL; +int (*CMS_final)(CMS_ContentInfo *, BIO *, BIO *, unsigned int) = NULL; +CMS_ContentInfo *(*CMS_sign)(X509 *, EVP_PKEY *, Cryptography_STACK_OF_X509 *, + BIO *, unsigned int) = NULL; +int (*CMS_verify)(CMS_ContentInfo *, Cryptography_STACK_OF_X509 *, + X509_STORE *, BIO *, BIO *, unsigned int) = NULL; +CMS_ContentInfo *(*CMS_encrypt)(Cryptography_STACK_OF_X509 *, BIO *, + const EVP_CIPHER *, unsigned int) = NULL; +int (*CMS_decrypt)(CMS_ContentInfo *, EVP_PKEY *, X509 *, BIO *, BIO *, + unsigned int) = NULL; +CMS_SignerInfo *(*CMS_add1_signer)(CMS_ContentInfo *, X509 *, EVP_PKEY *, + const EVP_MD *, unsigned int) = NULL; +#endif +""" diff --git a/src/_cffi_src/openssl/conf.py b/src/_cffi_src/openssl/conf.py new file mode 100644 index 0000000..ad21476 --- /dev/null +++ b/src/_cffi_src/openssl/conf.py @@ -0,0 +1,24 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +""" + +FUNCTIONS = """ +void OPENSSL_config(const char *); +""" + +MACROS = """ +/* This is a macro in 1.1.0 */ +void OPENSSL_no_config(void); +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/openssl/crypto.py b/src/_cffi_src/openssl/crypto.py new file mode 100644 index 0000000..e33a354 --- /dev/null +++ b/src/_cffi_src/openssl/crypto.py @@ -0,0 +1,105 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +static const long Cryptography_HAS_LOCKING_CALLBACKS; + +static const int SSLEAY_VERSION; +static const int SSLEAY_CFLAGS; +static const int SSLEAY_PLATFORM; +static const int SSLEAY_DIR; +static const int SSLEAY_BUILT_ON; +static const int OPENSSL_VERSION; +static const int OPENSSL_CFLAGS; +static const int OPENSSL_BUILT_ON; +static const int OPENSSL_PLATFORM; +static const int OPENSSL_DIR; +static const int CRYPTO_MEM_CHECK_ON; +static const int CRYPTO_MEM_CHECK_OFF; +static const int CRYPTO_MEM_CHECK_ENABLE; +static const int CRYPTO_MEM_CHECK_DISABLE; +static const int CRYPTO_LOCK; +static const int CRYPTO_UNLOCK; +static const int CRYPTO_READ; +static const int CRYPTO_LOCK_SSL; +""" + +FUNCTIONS = """ +int CRYPTO_mem_ctrl(int); +""" + +MACROS = """ +/* CRYPTO_cleanup_all_ex_data became a macro in 1.1.0 */ +void CRYPTO_cleanup_all_ex_data(void); + +/* as of 1.1.0 OpenSSL does its own locking *angelic chorus*. These functions + have become macros that are no ops */ +int CRYPTO_num_locks(void); +void CRYPTO_set_locking_callback(void(*)(int, int, const char *, int)); +void (*CRYPTO_get_locking_callback(void))(int, int, const char *, int); + +/* SSLeay was removed in 1.1.0 */ +unsigned long SSLeay(void); +const char *SSLeay_version(int); +/* these functions were added to replace the SSLeay functions in 1.1.0 */ +unsigned long OpenSSL_version_num(void); +const char *OpenSSL_version(int); + +/* this is a macro in 1.1.0 */ +void *OPENSSL_malloc(size_t); +void OPENSSL_free(void *); + +/* This was removed in 1.1.0 */ +void CRYPTO_lock(int, int, const char *, int); +""" + +CUSTOMIZATIONS = """ +/* In 1.1.0 SSLeay has finally been retired. We bidirectionally define the + values so you can use either one. This is so we can use the new function + names no matter what OpenSSL we're running on, but users on older pyOpenSSL + releases won't see issues if they're running OpenSSL 1.1.0 */ +#if !defined(SSLEAY_VERSION) +# define SSLeay OpenSSL_version_num +# define SSLeay_version OpenSSL_version +# define SSLEAY_VERSION_NUMBER OPENSSL_VERSION_NUMBER +# define SSLEAY_VERSION OPENSSL_VERSION +# define SSLEAY_CFLAGS OPENSSL_CFLAGS +# define SSLEAY_BUILT_ON OPENSSL_BUILT_ON +# define SSLEAY_PLATFORM OPENSSL_PLATFORM +# define SSLEAY_DIR OPENSSL_DIR +#endif +#if !defined(OPENSSL_VERSION) +# define OpenSSL_version_num SSLeay +# define OpenSSL_version SSLeay_version +# define OPENSSL_VERSION SSLEAY_VERSION +# define OPENSSL_CFLAGS SSLEAY_CFLAGS +# define OPENSSL_BUILT_ON SSLEAY_BUILT_ON +# define OPENSSL_PLATFORM SSLEAY_PLATFORM +# define OPENSSL_DIR SSLEAY_DIR +#endif +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 || defined(LIBRESSL_VERSION_NUMBER) +static const long Cryptography_HAS_LOCKING_CALLBACKS = 1; +#else +static const long Cryptography_HAS_LOCKING_CALLBACKS = 0; +#if !defined(CRYPTO_LOCK) +static const long CRYPTO_LOCK = 0; +#endif +#if !defined(CRYPTO_UNLOCK) +static const long CRYPTO_UNLOCK = 0; +#endif +#if !defined(CRYPTO_READ) +static const long CRYPTO_READ = 0; +#endif +#if !defined(CRYPTO_LOCK_SSL) +static const long CRYPTO_LOCK_SSL = 0; +#endif +void (*CRYPTO_lock)(int, int, const char *, int) = NULL; +#endif +""" diff --git a/src/_cffi_src/openssl/cryptography.py b/src/_cffi_src/openssl/cryptography.py new file mode 100644 index 0000000..373f830 --- /dev/null +++ b/src/_cffi_src/openssl/cryptography.py @@ -0,0 +1,72 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +/* + LibreSSL removed e_os2.h from the public headers so we'll only include it + if we're using vanilla OpenSSL. +*/ +#if !defined(LIBRESSL_VERSION_NUMBER) +#include +#endif +#if defined(_WIN32) +#include +#endif + +#define CRYPTOGRAPHY_OPENSSL_101_OR_GREATER \ + (OPENSSL_VERSION_NUMBER >= 0x10001000) +#define CRYPTOGRAPHY_OPENSSL_102_OR_GREATER \ + (OPENSSL_VERSION_NUMBER >= 0x10002000) +#define CRYPTOGRAPHY_OPENSSL_102BETA2_OR_GREATER \ + (OPENSSL_VERSION_NUMBER >= 0x10002002) +#define CRYPTOGRAPHY_OPENSSL_110_OR_GREATER \ + (OPENSSL_VERSION_NUMBER >= 0x10100000) + +#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_101 \ + (OPENSSL_VERSION_NUMBER < 0x10001000) +#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_102 \ + (OPENSSL_VERSION_NUMBER < 0x10002000) +#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_102BETA3 \ + (OPENSSL_VERSION_NUMBER < 0x10002003) +#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_102I \ + (OPENSSL_VERSION_NUMBER < 0x1000209fL) +#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 \ + (OPENSSL_VERSION_NUMBER < 0x10100000) +#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_110PRE4 \ + (OPENSSL_VERSION_NUMBER < 0x10100004) +#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_110PRE5 \ + (OPENSSL_VERSION_NUMBER < 0x10100005) +#define CRYPTOGRAPHY_OPENSSL_LESS_THAN_110PRE6 \ + (OPENSSL_VERSION_NUMBER < 0x10100006) + +#if defined(LIBRESSL_VERSION_NUMBER) +#define CRYPTOGRAPHY_IS_LIBRESSL 1 +#else +#define CRYPTOGRAPHY_IS_LIBRESSL 0 +#endif +""" + +TYPES = """ +static const int CRYPTOGRAPHY_OPENSSL_101_OR_GREATER; + +static const int CRYPTOGRAPHY_OPENSSL_110_OR_GREATER; + +static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_101; + +static const int CRYPTOGRAPHY_OPENSSL_LESS_THAN_102I; + +static const int CRYPTOGRAPHY_IS_LIBRESSL; +""" + +FUNCTIONS = """ +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/openssl/dh.py b/src/_cffi_src/openssl/dh.py new file mode 100644 index 0000000..9b776b9 --- /dev/null +++ b/src/_cffi_src/openssl/dh.py @@ -0,0 +1,115 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... DH; +""" + +FUNCTIONS = """ +DH *DH_new(void); +void DH_free(DH *); +int DH_size(const DH *); +int DH_check(const DH *, int *); +int DH_check_pub_key(const DH *, const BIGNUM *, int *); +int DH_generate_key(DH *); +int DH_compute_key(unsigned char *, const BIGNUM *, DH *); +int DH_set_ex_data(DH *, int, void *); +void *DH_get_ex_data(DH *, int); +DH *d2i_DHparams(DH **, const unsigned char **, long); +int i2d_DHparams(const DH *, unsigned char **); +int DHparams_print_fp(FILE *, const DH *); +int DHparams_print(BIO *, const DH *); +DH *DHparams_dup(DH *); + +/* added in 1.1.0 when the DH struct was opaqued */ +void DH_get0_pqg(const DH *, const BIGNUM **, const BIGNUM **, + const BIGNUM **); +int DH_set0_pqg(DH *, BIGNUM *, BIGNUM *, BIGNUM *); +void DH_get0_key(const DH *, const BIGNUM **, const BIGNUM **); +int DH_set0_key(DH *, BIGNUM *, BIGNUM *); +""" + +MACROS = """ +int DH_generate_parameters_ex(DH *, int, int, BN_GENCB *); +""" + +CUSTOMIZATIONS = """ +/* These functions were added in OpenSSL 1.1.0-pre5 (beta2) */ +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110PRE5 || defined(LIBRESSL_VERSION_NUMBER) +void DH_get0_pqg(const DH *dh, + const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) +{ + if (p != NULL) + *p = dh->p; + if (q != NULL) + *q = dh->q; + if (g != NULL) + *g = dh->g; +} + +int DH_set0_pqg(DH *dh, BIGNUM *p, BIGNUM *q, BIGNUM *g) +{ + /* If the fields p and g in d are NULL, the corresponding input + * parameters MUST be non-NULL. q may remain NULL. + */ + if ((dh->p == NULL && p == NULL) + || (dh->g == NULL && g == NULL)) + return 0; + + if (p != NULL) { + BN_free(dh->p); + dh->p = p; + } + if (q != NULL) { + BN_free(dh->q); + dh->q = q; + } + if (g != NULL) { + BN_free(dh->g); + dh->g = g; + } + + if (q != NULL) { + dh->length = BN_num_bits(q); + } + + return 1; +} + +void DH_get0_key(const DH *dh, const BIGNUM **pub_key, const BIGNUM **priv_key) +{ + if (pub_key != NULL) + *pub_key = dh->pub_key; + if (priv_key != NULL) + *priv_key = dh->priv_key; +} + +int DH_set0_key(DH *dh, BIGNUM *pub_key, BIGNUM *priv_key) +{ + /* If the field pub_key in dh is NULL, the corresponding input + * parameters MUST be non-NULL. The priv_key field may + * be left NULL. + */ + if (dh->pub_key == NULL && pub_key == NULL) + return 0; + + if (pub_key != NULL) { + BN_free(dh->pub_key); + dh->pub_key = pub_key; + } + if (priv_key != NULL) { + BN_free(dh->priv_key); + dh->priv_key = priv_key; + } + + return 1; +} +#endif +""" diff --git a/src/_cffi_src/openssl/dsa.py b/src/_cffi_src/openssl/dsa.py new file mode 100644 index 0000000..5970e2f --- /dev/null +++ b/src/_cffi_src/openssl/dsa.py @@ -0,0 +1,106 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... DSA; +""" + +FUNCTIONS = """ +int DSA_generate_key(DSA *); +DSA *DSA_new(void); +void DSA_free(DSA *); +DSA *DSAparams_dup(DSA *); +int DSA_size(const DSA *); +int DSA_sign(int, const unsigned char *, int, unsigned char *, unsigned int *, + DSA *); +int DSA_verify(int, const unsigned char *, int, const unsigned char *, int, + DSA *); + +/* added in 1.1.0 to access the opaque struct */ +void DSA_get0_pqg(const DSA *, const BIGNUM **, const BIGNUM **, + const BIGNUM **); +int DSA_set0_pqg(DSA *, BIGNUM *, BIGNUM *, BIGNUM *); +void DSA_get0_key(const DSA *, const BIGNUM **, const BIGNUM **); +int DSA_set0_key(DSA *, BIGNUM *, BIGNUM *); +""" + +MACROS = """ +int DSA_generate_parameters_ex(DSA *, int, unsigned char *, int, + int *, unsigned long *, BN_GENCB *); +""" + +CUSTOMIZATIONS = """ +/* These functions were added in OpenSSL 1.1.0-pre5 (beta2) */ +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110PRE5 || defined(LIBRESSL_VERSION_NUMBER) +void DSA_get0_pqg(const DSA *d, + const BIGNUM **p, const BIGNUM **q, const BIGNUM **g) +{ + if (p != NULL) + *p = d->p; + if (q != NULL) + *q = d->q; + if (g != NULL) + *g = d->g; +} +int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) +{ + /* If the fields p, q and g in d are NULL, the corresponding input + * parameters MUST be non-NULL. + */ + if ((d->p == NULL && p == NULL) + || (d->q == NULL && q == NULL) + || (d->g == NULL && g == NULL)) + return 0; + + if (p != NULL) { + BN_free(d->p); + d->p = p; + } + if (q != NULL) { + BN_free(d->q); + d->q = q; + } + if (g != NULL) { + BN_free(d->g); + d->g = g; + } + + return 1; +} +void DSA_get0_key(const DSA *d, + const BIGNUM **pub_key, const BIGNUM **priv_key) +{ + if (pub_key != NULL) + *pub_key = d->pub_key; + if (priv_key != NULL) + *priv_key = d->priv_key; +} +int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) +{ + /* If the field pub_key in d is NULL, the corresponding input + * parameters MUST be non-NULL. The priv_key field may + * be left NULL. + */ + if (d->pub_key == NULL && pub_key == NULL) + return 0; + + if (pub_key != NULL) { + BN_free(d->pub_key); + d->pub_key = pub_key; + } + if (priv_key != NULL) { + BN_free(d->priv_key); + d->priv_key = priv_key; + } + + return 1; +} +#endif +""" diff --git a/src/_cffi_src/openssl/ec.py b/src/_cffi_src/openssl/ec.py new file mode 100644 index 0000000..0853a72 --- /dev/null +++ b/src/_cffi_src/openssl/ec.py @@ -0,0 +1,381 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#ifndef OPENSSL_NO_EC +#include +#endif + +#include +""" + +TYPES = """ +static const int Cryptography_HAS_EC; +static const int Cryptography_HAS_EC_1_0_1; +static const int Cryptography_HAS_EC2M; +static const int Cryptography_HAS_EC_1_0_2; + +static const int OPENSSL_EC_NAMED_CURVE; + +typedef ... EC_KEY; +typedef ... EC_GROUP; +typedef ... EC_POINT; +typedef ... EC_METHOD; +typedef struct { + int nid; + const char *comment; +} EC_builtin_curve; +typedef enum { + POINT_CONVERSION_COMPRESSED, + POINT_CONVERSION_UNCOMPRESSED, + POINT_CONVERSION_HYBRID, + ... +} point_conversion_form_t; +""" + +FUNCTIONS = """ +""" + +MACROS = """ +EC_GROUP *EC_GROUP_new(const EC_METHOD *); +void EC_GROUP_free(EC_GROUP *); +void EC_GROUP_clear_free(EC_GROUP *); + +EC_GROUP *EC_GROUP_new_curve_GFp( + const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); +EC_GROUP *EC_GROUP_new_curve_GF2m( + const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); +EC_GROUP *EC_GROUP_new_by_curve_name(int); + +int EC_GROUP_set_curve_GFp( + EC_GROUP *, const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); +int EC_GROUP_get_curve_GFp( + const EC_GROUP *, BIGNUM *, BIGNUM *, BIGNUM *, BN_CTX *); +int EC_GROUP_set_curve_GF2m( + EC_GROUP *, const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); +int EC_GROUP_get_curve_GF2m( + const EC_GROUP *, BIGNUM *, BIGNUM *, BIGNUM *, BN_CTX *); + +int EC_GROUP_get_degree(const EC_GROUP *); +void EC_GROUP_set_asn1_flag(EC_GROUP *, int); +void EC_GROUP_set_point_conversion_form(EC_GROUP *, point_conversion_form_t); + +const EC_METHOD *EC_GROUP_method_of(const EC_GROUP *); +const EC_POINT *EC_GROUP_get0_generator(const EC_GROUP *); +int EC_GROUP_get_curve_name(const EC_GROUP *); + +size_t EC_get_builtin_curves(EC_builtin_curve *, size_t); + +EC_KEY *EC_KEY_new(void); +void EC_KEY_free(EC_KEY *); + +int EC_KEY_get_flags(const EC_KEY *); +void EC_KEY_set_flags(EC_KEY *, int); +void EC_KEY_clear_flags(EC_KEY *, int); +EC_KEY *EC_KEY_new_by_curve_name(int); +EC_KEY *EC_KEY_copy(EC_KEY *, EC_KEY *); +EC_KEY *EC_KEY_dup(EC_KEY *); +int EC_KEY_up_ref(EC_KEY *); +const EC_GROUP *EC_KEY_get0_group(const EC_KEY *); +int EC_GROUP_get_order(const EC_GROUP *, BIGNUM *, BN_CTX *); +int EC_KEY_set_group(EC_KEY *, const EC_GROUP *); +const BIGNUM *EC_KEY_get0_private_key(const EC_KEY *); +int EC_KEY_set_private_key(EC_KEY *, const BIGNUM *); +const EC_POINT *EC_KEY_get0_public_key(const EC_KEY *); +int EC_KEY_set_public_key(EC_KEY *, const EC_POINT *); +unsigned int EC_KEY_get_enc_flags(const EC_KEY *); +void EC_KEY_set_enc_flags(EC_KEY *eckey, unsigned int); +point_conversion_form_t EC_KEY_get_conv_form(const EC_KEY *); +void EC_KEY_set_conv_form(EC_KEY *, point_conversion_form_t); +void EC_KEY_set_asn1_flag(EC_KEY *, int); +int EC_KEY_precompute_mult(EC_KEY *, BN_CTX *); +int EC_KEY_generate_key(EC_KEY *); +int EC_KEY_check_key(const EC_KEY *); +int EC_KEY_set_public_key_affine_coordinates(EC_KEY *, BIGNUM *, BIGNUM *); + +EC_POINT *EC_POINT_new(const EC_GROUP *); +void EC_POINT_free(EC_POINT *); +void EC_POINT_clear_free(EC_POINT *); +int EC_POINT_copy(EC_POINT *, const EC_POINT *); +EC_POINT *EC_POINT_dup(const EC_POINT *, const EC_GROUP *); +const EC_METHOD *EC_POINT_method_of(const EC_POINT *); + +int EC_POINT_set_to_infinity(const EC_GROUP *, EC_POINT *); + +int EC_POINT_set_Jprojective_coordinates_GFp(const EC_GROUP *, EC_POINT *, + const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); + +int EC_POINT_get_Jprojective_coordinates_GFp(const EC_GROUP *, + const EC_POINT *, BIGNUM *, BIGNUM *, BIGNUM *, BN_CTX *); + +int EC_POINT_set_affine_coordinates_GFp(const EC_GROUP *, EC_POINT *, + const BIGNUM *, const BIGNUM *, BN_CTX *); + +int EC_POINT_get_affine_coordinates_GFp(const EC_GROUP *, + const EC_POINT *, BIGNUM *, BIGNUM *, BN_CTX *); + +int EC_POINT_set_compressed_coordinates_GFp(const EC_GROUP *, EC_POINT *, + const BIGNUM *, int, BN_CTX *); + +int EC_POINT_set_affine_coordinates_GF2m(const EC_GROUP *, EC_POINT *, + const BIGNUM *, const BIGNUM *, BN_CTX *); + +int EC_POINT_get_affine_coordinates_GF2m(const EC_GROUP *, + const EC_POINT *, BIGNUM *, BIGNUM *, BN_CTX *); + +int EC_POINT_set_compressed_coordinates_GF2m(const EC_GROUP *, EC_POINT *, + const BIGNUM *, int, BN_CTX *); + +size_t EC_POINT_point2oct(const EC_GROUP *, const EC_POINT *, + point_conversion_form_t, + unsigned char *, size_t, BN_CTX *); + +int EC_POINT_oct2point(const EC_GROUP *, EC_POINT *, + const unsigned char *, size_t, BN_CTX *); + +BIGNUM *EC_POINT_point2bn(const EC_GROUP *, const EC_POINT *, + point_conversion_form_t form, BIGNUM *, BN_CTX *); + +EC_POINT *EC_POINT_bn2point(const EC_GROUP *, const BIGNUM *, + EC_POINT *, BN_CTX *); + +char *EC_POINT_point2hex(const EC_GROUP *, const EC_POINT *, + point_conversion_form_t form, BN_CTX *); + +EC_POINT *EC_POINT_hex2point(const EC_GROUP *, const char *, + EC_POINT *, BN_CTX *); + +int EC_POINT_add(const EC_GROUP *, EC_POINT *, const EC_POINT *, + const EC_POINT *, BN_CTX *); + +int EC_POINT_dbl(const EC_GROUP *, EC_POINT *, const EC_POINT *, BN_CTX *); +int EC_POINT_invert(const EC_GROUP *, EC_POINT *, BN_CTX *); +int EC_POINT_is_at_infinity(const EC_GROUP *, const EC_POINT *); +int EC_POINT_is_on_curve(const EC_GROUP *, const EC_POINT *, BN_CTX *); + +int EC_POINT_cmp( + const EC_GROUP *, const EC_POINT *, const EC_POINT *, BN_CTX *); + +int EC_POINT_make_affine(const EC_GROUP *, EC_POINT *, BN_CTX *); +int EC_POINTs_make_affine(const EC_GROUP *, size_t, EC_POINT *[], BN_CTX *); + +int EC_POINTs_mul( + const EC_GROUP *, EC_POINT *, const BIGNUM *, + size_t, const EC_POINT *[], const BIGNUM *[], BN_CTX *); + +int EC_POINT_mul(const EC_GROUP *, EC_POINT *, const BIGNUM *, + const EC_POINT *, const BIGNUM *, BN_CTX *); + +int EC_GROUP_precompute_mult(EC_GROUP *, BN_CTX *); +int EC_GROUP_have_precompute_mult(const EC_GROUP *); + +const EC_METHOD *EC_GFp_simple_method(); +const EC_METHOD *EC_GFp_mont_method(); +const EC_METHOD *EC_GFp_nist_method(); + +const EC_METHOD *EC_GF2m_simple_method(); + +int EC_METHOD_get_field_type(const EC_METHOD *); + +const char *EC_curve_nid2nist(int); +""" + +CUSTOMIZATIONS = """ +#ifdef OPENSSL_NO_EC +static const long Cryptography_HAS_EC = 0; + +typedef void EC_KEY; +typedef void EC_GROUP; +typedef void EC_POINT; +typedef void EC_METHOD; +typedef struct { + int nid; + const char *comment; +} EC_builtin_curve; +typedef enum { + POINT_CONVERSION_COMPRESSED, + POINT_CONVERSION_UNCOMPRESSED, + POINT_CONVERSION_HYBRID, +} point_conversion_form_t; + +static const int OPENSSL_EC_NAMED_CURVE = 0; + +void (*EC_KEY_free)(EC_KEY *) = NULL; +EC_KEY *(*EC_KEY_new)(void) = NULL; +size_t (*EC_get_builtin_curves)(EC_builtin_curve *, size_t) = NULL; +EC_KEY *(*EC_KEY_new_by_curve_name)(int) = NULL; +EC_KEY *(*EC_KEY_copy)(EC_KEY *, const EC_KEY *) = NULL; +EC_KEY *(*EC_KEY_dup)(const EC_KEY *) = NULL; +int (*EC_KEY_up_ref)(EC_KEY *) = NULL; +const EC_GROUP *(*EC_KEY_get0_group)(const EC_KEY *) = NULL; +int (*EC_GROUP_get_order)(const EC_GROUP *, BIGNUM *, BN_CTX *) = NULL; +int (*EC_KEY_set_group)(EC_KEY *, const EC_GROUP *) = NULL; +void (*EC_GROUP_set_asn1_flag)(EC_GROUP *, int) = NULL; +void (*EC_GROUP_set_point_conversion_form)(EC_GROUP *, + point_conversion_form_t) = NULL; +const BIGNUM *(*EC_KEY_get0_private_key)(const EC_KEY *) = NULL; +int (*EC_KEY_set_private_key)(EC_KEY *, const BIGNUM *) = NULL; +const EC_POINT *(*EC_KEY_get0_public_key)(const EC_KEY *) = NULL; +int (*EC_KEY_set_public_key)(EC_KEY *, const EC_POINT *) = NULL; +unsigned int (*EC_KEY_get_enc_flags)(const EC_KEY *) = NULL; +void (*EC_KEY_set_enc_flags)(EC_KEY *eckey, unsigned int) = NULL; +point_conversion_form_t (*EC_KEY_get_conv_form)(const EC_KEY *) = NULL; +void (*EC_KEY_set_conv_form)(EC_KEY *, point_conversion_form_t) = NULL; +void (*EC_KEY_set_asn1_flag)(EC_KEY *, int) = NULL; +int (*EC_KEY_precompute_mult)(EC_KEY *, BN_CTX *) = NULL; +int (*EC_KEY_generate_key)(EC_KEY *) = NULL; +int (*EC_KEY_check_key)(const EC_KEY *) = NULL; + +EC_GROUP *(*EC_GROUP_new)(const EC_METHOD *); +void (*EC_GROUP_free)(EC_GROUP *); +void (*EC_GROUP_clear_free)(EC_GROUP *); + +EC_GROUP *(*EC_GROUP_new_curve_GFp)( + const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); + +EC_GROUP *(*EC_GROUP_new_by_curve_name)(int); + +int (*EC_GROUP_set_curve_GFp)( + EC_GROUP *, const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); + +int (*EC_GROUP_get_curve_GFp)( + const EC_GROUP *, BIGNUM *, BIGNUM *, BIGNUM *, BN_CTX *); + +int (*EC_GROUP_get_degree)(const EC_GROUP *) = NULL; + +const EC_METHOD *(*EC_GROUP_method_of)(const EC_GROUP *) = NULL; +const EC_POINT *(*EC_GROUP_get0_generator)(const EC_GROUP *) = NULL; +int (*EC_GROUP_get_curve_name)(const EC_GROUP *) = NULL; + +EC_POINT *(*EC_POINT_new)(const EC_GROUP *) = NULL; +void (*EC_POINT_free)(EC_POINT *) = NULL; +void (*EC_POINT_clear_free)(EC_POINT *) = NULL; +int (*EC_POINT_copy)(EC_POINT *, const EC_POINT *) = NULL; +EC_POINT *(*EC_POINT_dup)(const EC_POINT *, const EC_GROUP *) = NULL; +const EC_METHOD *(*EC_POINT_method_of)(const EC_POINT *) = NULL; +int (*EC_POINT_set_to_infinity)(const EC_GROUP *, EC_POINT *) = NULL; +int (*EC_POINT_set_Jprojective_coordinates_GFp)(const EC_GROUP *, EC_POINT *, + const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *) = NULL; + +int (*EC_POINT_get_Jprojective_coordinates_GFp)(const EC_GROUP *, + const EC_POINT *, BIGNUM *, BIGNUM *, BIGNUM *, BN_CTX *) = NULL; + +int (*EC_POINT_set_affine_coordinates_GFp)(const EC_GROUP *, EC_POINT *, + const BIGNUM *, const BIGNUM *, BN_CTX *) = NULL; + +int (*EC_POINT_get_affine_coordinates_GFp)(const EC_GROUP *, + const EC_POINT *, BIGNUM *, BIGNUM *, BN_CTX *) = NULL; + +int (*EC_POINT_set_compressed_coordinates_GFp)(const EC_GROUP *, EC_POINT *, + const BIGNUM *, int, BN_CTX *) = NULL; + +size_t (*EC_POINT_point2oct)(const EC_GROUP *, const EC_POINT *, + point_conversion_form_t, + unsigned char *, size_t, BN_CTX *) = NULL; + +int (*EC_POINT_oct2point)(const EC_GROUP *, EC_POINT *, + const unsigned char *, size_t, BN_CTX *) = NULL; + +BIGNUM *(*EC_POINT_point2bn)(const EC_GROUP *, const EC_POINT *, + point_conversion_form_t form, BIGNUM *, BN_CTX *) = NULL; + +EC_POINT *(*EC_POINT_bn2point)(const EC_GROUP *, const BIGNUM *, + EC_POINT *, BN_CTX *) = NULL; + +char *(*EC_POINT_point2hex)(const EC_GROUP *, const EC_POINT *, + point_conversion_form_t form, BN_CTX *) = NULL; + +EC_POINT *(*EC_POINT_hex2point)(const EC_GROUP *, const char *, + EC_POINT *, BN_CTX *) = NULL; + +int (*EC_POINT_add)(const EC_GROUP *, EC_POINT *, const EC_POINT *, + const EC_POINT *, BN_CTX *) = NULL; + +int (*EC_POINT_dbl)(const EC_GROUP *, EC_POINT *, const EC_POINT *, + BN_CTX *) = NULL; + +int (*EC_POINT_invert)(const EC_GROUP *, EC_POINT *, BN_CTX *) = NULL; +int (*EC_POINT_is_at_infinity)(const EC_GROUP *, const EC_POINT *) = NULL; + +int (*EC_POINT_is_on_curve)(const EC_GROUP *, const EC_POINT *, + BN_CTX *) = NULL; + +int (*EC_POINT_cmp)( + const EC_GROUP *, const EC_POINT *, const EC_POINT *, BN_CTX *) = NULL; + +int (*EC_POINT_make_affine)(const EC_GROUP *, EC_POINT *, BN_CTX *) = NULL; + +int (*EC_POINTs_make_affine)(const EC_GROUP *, size_t, EC_POINT *[], + BN_CTX *) = NULL; + +int (*EC_POINTs_mul)( + const EC_GROUP *, EC_POINT *, const BIGNUM *, + size_t, const EC_POINT *[], const BIGNUM *[], BN_CTX *) = NULL; + +int (*EC_POINT_mul)(const EC_GROUP *, EC_POINT *, const BIGNUM *, + const EC_POINT *, const BIGNUM *, BN_CTX *) = NULL; + +int (*EC_GROUP_precompute_mult)(EC_GROUP *, BN_CTX *) = NULL; +int (*EC_GROUP_have_precompute_mult)(const EC_GROUP *) = NULL; + +const EC_METHOD *(*EC_GFp_simple_method)() = NULL; +const EC_METHOD *(*EC_GFp_mont_method)() = NULL; +const EC_METHOD *(*EC_GFp_nist_method)() = NULL; + +int (*EC_METHOD_get_field_type)(const EC_METHOD *) = NULL; + +#else +static const long Cryptography_HAS_EC = 1; +#endif + +#if defined(OPENSSL_NO_EC) || CRYPTOGRAPHY_OPENSSL_LESS_THAN_101 +static const long Cryptography_HAS_EC_1_0_1 = 0; + +int (*EC_KEY_get_flags)(const EC_KEY *) = NULL; +void (*EC_KEY_set_flags)(EC_KEY *, int) = NULL; +void (*EC_KEY_clear_flags)(EC_KEY *, int) = NULL; + +int (*EC_KEY_set_public_key_affine_coordinates)( + EC_KEY *, BIGNUM *, BIGNUM *) = NULL; +#else +static const long Cryptography_HAS_EC_1_0_1 = 1; +#endif + + +#if defined(OPENSSL_NO_EC) || defined(OPENSSL_NO_EC2M) +static const long Cryptography_HAS_EC2M = 0; + +const EC_METHOD *(*EC_GF2m_simple_method)() = NULL; + +int (*EC_POINT_set_affine_coordinates_GF2m)(const EC_GROUP *, EC_POINT *, + const BIGNUM *, const BIGNUM *, BN_CTX *) = NULL; + +int (*EC_POINT_get_affine_coordinates_GF2m)(const EC_GROUP *, + const EC_POINT *, BIGNUM *, BIGNUM *, BN_CTX *) = NULL; + +int (*EC_POINT_set_compressed_coordinates_GF2m)(const EC_GROUP *, EC_POINT *, + const BIGNUM *, int, BN_CTX *) = NULL; + +int (*EC_GROUP_set_curve_GF2m)( + EC_GROUP *, const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); + +int (*EC_GROUP_get_curve_GF2m)( + const EC_GROUP *, BIGNUM *, BIGNUM *, BIGNUM *, BN_CTX *); + +EC_GROUP *(*EC_GROUP_new_curve_GF2m)( + const BIGNUM *, const BIGNUM *, const BIGNUM *, BN_CTX *); +#else +static const long Cryptography_HAS_EC2M = 1; +#endif + +#if defined(OPENSSL_NO_EC) || CRYPTOGRAPHY_OPENSSL_LESS_THAN_102 || \ + defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20020002L +static const long Cryptography_HAS_EC_1_0_2 = 0; +const char *(*EC_curve_nid2nist)(int) = NULL; +#else +static const long Cryptography_HAS_EC_1_0_2 = 1; +#endif +""" diff --git a/src/_cffi_src/openssl/ecdh.py b/src/_cffi_src/openssl/ecdh.py new file mode 100644 index 0000000..2dd5fd7 --- /dev/null +++ b/src/_cffi_src/openssl/ecdh.py @@ -0,0 +1,45 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#ifndef OPENSSL_NO_ECDH +#include +#endif +""" + +TYPES = """ +static const int Cryptography_HAS_ECDH; +static const int Cryptography_HAS_SET_ECDH_AUTO; +""" + +FUNCTIONS = """ +""" + +MACROS = """ +int ECDH_compute_key(void *, size_t, const EC_POINT *, EC_KEY *, + void *(*)(const void *, size_t, void *, size_t *)); +int SSL_CTX_set_ecdh_auto(SSL_CTX *, int); +""" + +CUSTOMIZATIONS = """ +#ifdef OPENSSL_NO_ECDH +static const long Cryptography_HAS_ECDH = 0; + +int (*ECDH_compute_key)(void *, size_t, const EC_POINT *, EC_KEY *, + void *(*)(const void *, size_t, void *, + size_t *)) = NULL; + +#else +static const long Cryptography_HAS_ECDH = 1; +#endif + +#ifndef SSL_CTX_set_ecdh_auto +static const long Cryptography_HAS_SET_ECDH_AUTO = 0; +int (*SSL_CTX_set_ecdh_auto)(SSL_CTX *, int) = NULL; +#else +static const long Cryptography_HAS_SET_ECDH_AUTO = 1; +#endif +""" diff --git a/src/_cffi_src/openssl/ecdsa.py b/src/_cffi_src/openssl/ecdsa.py new file mode 100644 index 0000000..f3e9fba --- /dev/null +++ b/src/_cffi_src/openssl/ecdsa.py @@ -0,0 +1,78 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#ifndef OPENSSL_NO_ECDSA +#include +#endif +""" + +TYPES = """ +static const int Cryptography_HAS_ECDSA; + +typedef ... ECDSA_SIG; + +typedef ... CRYPTO_EX_new; +typedef ... CRYPTO_EX_dup; +typedef ... CRYPTO_EX_free; +""" + +FUNCTIONS = """ +""" + +MACROS = """ +ECDSA_SIG *ECDSA_SIG_new(); +void ECDSA_SIG_free(ECDSA_SIG *); +int i2d_ECDSA_SIG(const ECDSA_SIG *, unsigned char **); +ECDSA_SIG *d2i_ECDSA_SIG(ECDSA_SIG **s, const unsigned char **, long); +ECDSA_SIG *ECDSA_do_sign(const unsigned char *, int, EC_KEY *); +ECDSA_SIG *ECDSA_do_sign_ex(const unsigned char *, int, const BIGNUM *, + const BIGNUM *, EC_KEY *); +int ECDSA_do_verify(const unsigned char *, int, const ECDSA_SIG *, EC_KEY *); +int ECDSA_sign_setup(EC_KEY *, BN_CTX *, BIGNUM **, BIGNUM **); +int ECDSA_sign(int, const unsigned char *, int, unsigned char *, + unsigned int *, EC_KEY *); +int ECDSA_sign_ex(int, const unsigned char *, int dgstlen, unsigned char *, + unsigned int *, const BIGNUM *, const BIGNUM *, EC_KEY *); +int ECDSA_verify(int, const unsigned char *, int, const unsigned char *, int, + EC_KEY *); +int ECDSA_size(const EC_KEY *); + +""" + +CUSTOMIZATIONS = """ +#ifdef OPENSSL_NO_ECDSA +static const long Cryptography_HAS_ECDSA = 0; + +typedef struct { + BIGNUM *r; + BIGNUM *s; +} ECDSA_SIG; + +ECDSA_SIG* (*ECDSA_SIG_new)() = NULL; +void (*ECDSA_SIG_free)(ECDSA_SIG *) = NULL; +int (*i2d_ECDSA_SIG)(const ECDSA_SIG *, unsigned char **) = NULL; +ECDSA_SIG* (*d2i_ECDSA_SIG)(ECDSA_SIG **s, const unsigned char **, + long) = NULL; +ECDSA_SIG* (*ECDSA_do_sign)(const unsigned char *, int, EC_KEY *eckey) = NULL; +ECDSA_SIG* (*ECDSA_do_sign_ex)(const unsigned char *, int, const BIGNUM *, + const BIGNUM *, EC_KEY *) = NULL; +int (*ECDSA_do_verify)(const unsigned char *, int, const ECDSA_SIG *, + EC_KEY *) = NULL; +int (*ECDSA_sign_setup)(EC_KEY *, BN_CTX *, BIGNUM **, BIGNUM **) = NULL; +int (*ECDSA_sign)(int, const unsigned char *, int, unsigned char *, + unsigned int *, EC_KEY *) = NULL; +int (*ECDSA_sign_ex)(int, const unsigned char *, int dgstlen, unsigned char *, + unsigned int *, const BIGNUM *, const BIGNUM *, + EC_KEY *) = NULL; +int (*ECDSA_verify)(int, const unsigned char *, int, const unsigned char *, + int, EC_KEY *) = NULL; +int (*ECDSA_size)(const EC_KEY *) = NULL; + +#else +static const long Cryptography_HAS_ECDSA = 1; +#endif +""" diff --git a/src/_cffi_src/openssl/engine.py b/src/_cffi_src/openssl/engine.py new file mode 100644 index 0000000..8547768 --- /dev/null +++ b/src/_cffi_src/openssl/engine.py @@ -0,0 +1,146 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +static const long Cryptography_HAS_ENGINE_CRYPTODEV; + +typedef ... ENGINE; +typedef ... RSA_METHOD; +typedef ... DSA_METHOD; +typedef ... DH_METHOD; +typedef struct { + int (*bytes)(unsigned char *, int); + int (*pseudorand)(unsigned char *, int); + int (*status)(); + ...; +} RAND_METHOD; +typedef int (*ENGINE_GEN_INT_FUNC_PTR)(ENGINE *); +typedef ... *ENGINE_CTRL_FUNC_PTR; +typedef ... *ENGINE_LOAD_KEY_PTR; +typedef ... *ENGINE_CIPHERS_PTR; +typedef ... *ENGINE_DIGESTS_PTR; +typedef ... ENGINE_CMD_DEFN; +typedef ... UI_METHOD; + +static const unsigned int ENGINE_METHOD_RSA; +static const unsigned int ENGINE_METHOD_DSA; +static const unsigned int ENGINE_METHOD_RAND; +static const unsigned int ENGINE_METHOD_CIPHERS; +static const unsigned int ENGINE_METHOD_DIGESTS; +static const unsigned int ENGINE_METHOD_ALL; +static const unsigned int ENGINE_METHOD_NONE; + +static const int ENGINE_R_CONFLICTING_ENGINE_ID; +""" + +FUNCTIONS = """ +ENGINE *ENGINE_get_first(void); +ENGINE *ENGINE_get_last(void); +ENGINE *ENGINE_get_next(ENGINE *); +ENGINE *ENGINE_get_prev(ENGINE *); +int ENGINE_add(ENGINE *); +int ENGINE_remove(ENGINE *); +ENGINE *ENGINE_by_id(const char *); +int ENGINE_init(ENGINE *); +int ENGINE_finish(ENGINE *); +void ENGINE_load_builtin_engines(void); +ENGINE *ENGINE_get_default_RSA(void); +ENGINE *ENGINE_get_default_DSA(void); +ENGINE *ENGINE_get_default_DH(void); +ENGINE *ENGINE_get_default_RAND(void); +ENGINE *ENGINE_get_cipher_engine(int); +ENGINE *ENGINE_get_digest_engine(int); +int ENGINE_set_default_RSA(ENGINE *); +int ENGINE_set_default_DSA(ENGINE *); +int ENGINE_set_default_DH(ENGINE *); +int ENGINE_set_default_RAND(ENGINE *); +int ENGINE_set_default_ciphers(ENGINE *); +int ENGINE_set_default_digests(ENGINE *); +int ENGINE_set_default_string(ENGINE *, const char *); +int ENGINE_set_default(ENGINE *, unsigned int); +unsigned int ENGINE_get_table_flags(void); +void ENGINE_set_table_flags(unsigned int); +int ENGINE_register_RSA(ENGINE *); +void ENGINE_unregister_RSA(ENGINE *); +void ENGINE_register_all_RSA(void); +int ENGINE_register_DSA(ENGINE *); +void ENGINE_unregister_DSA(ENGINE *); +void ENGINE_register_all_DSA(void); +int ENGINE_register_DH(ENGINE *); +void ENGINE_unregister_DH(ENGINE *); +void ENGINE_register_all_DH(void); +int ENGINE_register_RAND(ENGINE *); +void ENGINE_unregister_RAND(ENGINE *); +void ENGINE_register_all_RAND(void); +int ENGINE_register_ciphers(ENGINE *); +void ENGINE_unregister_ciphers(ENGINE *); +void ENGINE_register_all_ciphers(void); +int ENGINE_register_digests(ENGINE *); +void ENGINE_unregister_digests(ENGINE *); +void ENGINE_register_all_digests(void); +int ENGINE_register_complete(ENGINE *); +int ENGINE_register_all_complete(void); +int ENGINE_ctrl(ENGINE *, int, long, void *, void (*)(void)); +int ENGINE_cmd_is_executable(ENGINE *, int); +int ENGINE_ctrl_cmd(ENGINE *, const char *, long, void *, void (*)(void), int); +int ENGINE_ctrl_cmd_string(ENGINE *, const char *, const char *, int); + +ENGINE *ENGINE_new(void); +int ENGINE_free(ENGINE *); +int ENGINE_up_ref(ENGINE *); +int ENGINE_set_id(ENGINE *, const char *); +int ENGINE_set_name(ENGINE *, const char *); +int ENGINE_set_RSA(ENGINE *, const RSA_METHOD *); +int ENGINE_set_DSA(ENGINE *, const DSA_METHOD *); +int ENGINE_set_DH(ENGINE *, const DH_METHOD *); +int ENGINE_set_RAND(ENGINE *, const RAND_METHOD *); +int ENGINE_set_destroy_function(ENGINE *, ENGINE_GEN_INT_FUNC_PTR); +int ENGINE_set_init_function(ENGINE *, ENGINE_GEN_INT_FUNC_PTR); +int ENGINE_set_finish_function(ENGINE *, ENGINE_GEN_INT_FUNC_PTR); +int ENGINE_set_ctrl_function(ENGINE *, ENGINE_CTRL_FUNC_PTR); +int ENGINE_set_load_privkey_function(ENGINE *, ENGINE_LOAD_KEY_PTR); +int ENGINE_set_load_pubkey_function(ENGINE *, ENGINE_LOAD_KEY_PTR); +int ENGINE_set_ciphers(ENGINE *, ENGINE_CIPHERS_PTR); +int ENGINE_set_digests(ENGINE *, ENGINE_DIGESTS_PTR); +int ENGINE_set_flags(ENGINE *, int); +int ENGINE_set_cmd_defns(ENGINE *, const ENGINE_CMD_DEFN *); +const char *ENGINE_get_id(const ENGINE *); +const char *ENGINE_get_name(const ENGINE *); +const RSA_METHOD *ENGINE_get_RSA(const ENGINE *); +const DSA_METHOD *ENGINE_get_DSA(const ENGINE *); +const DH_METHOD *ENGINE_get_DH(const ENGINE *); +const RAND_METHOD *ENGINE_get_RAND(const ENGINE *); + +const EVP_CIPHER *ENGINE_get_cipher(ENGINE *, int); +const EVP_MD *ENGINE_get_digest(ENGINE *, int); +int ENGINE_get_flags(const ENGINE *); +const ENGINE_CMD_DEFN *ENGINE_get_cmd_defns(const ENGINE *); +EVP_PKEY *ENGINE_load_private_key(ENGINE *, const char *, UI_METHOD *, void *); +EVP_PKEY *ENGINE_load_public_key(ENGINE *, const char *, UI_METHOD *, void *); +void ENGINE_add_conf_module(void); +""" + +MACROS = """ +/* these became macros in 1.1.0 */ +void ENGINE_load_openssl(void); +void ENGINE_load_dynamic(void); +void ENGINE_cleanup(void); + +void ENGINE_load_cryptodev(void); +""" + +CUSTOMIZATIONS = """ +#if defined(LIBRESSL_VERSION_NUMBER) +static const long Cryptography_HAS_ENGINE_CRYPTODEV = 0; +void (*ENGINE_load_cryptodev)(void) = NULL; +#else +static const long Cryptography_HAS_ENGINE_CRYPTODEV = 1; +#endif +""" diff --git a/src/_cffi_src/openssl/err.py b/src/_cffi_src/openssl/err.py new file mode 100644 index 0000000..4784408 --- /dev/null +++ b/src/_cffi_src/openssl/err.py @@ -0,0 +1,286 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +static const int Cryptography_HAS_EC_CODES; +static const int Cryptography_HAS_RSA_R_PKCS_DECODING_ERROR; + +struct ERR_string_data_st { + unsigned long error; + const char *string; +}; +typedef struct ERR_string_data_st ERR_STRING_DATA; +typedef ... ERR_STATE; + +static const int ERR_LIB_DH; +static const int ERR_LIB_EVP; +static const int ERR_LIB_EC; +static const int ERR_LIB_PEM; +static const int ERR_LIB_ASN1; +static const int ERR_LIB_RSA; +static const int ERR_LIB_PKCS12; +static const int ERR_LIB_SSL; +static const int ERR_LIB_X509; + +static const int ASN1_F_ASN1_EX_C2I; +static const int ASN1_F_ASN1_FIND_END; +static const int ASN1_F_ASN1_GENERATE_V3; +static const int ASN1_F_ASN1_GET_OBJECT; +static const int ASN1_F_ASN1_ITEM_I2D_FP; +static const int ASN1_F_ASN1_ITEM_PACK; +static const int ASN1_F_ASN1_ITEM_SIGN; +static const int ASN1_F_ASN1_ITEM_UNPACK; +static const int ASN1_F_ASN1_ITEM_VERIFY; +static const int ASN1_F_ASN1_MBSTRING_NCOPY; +static const int ASN1_F_ASN1_TEMPLATE_EX_D2I; +static const int ASN1_F_ASN1_TEMPLATE_NEW; +static const int ASN1_F_ASN1_TEMPLATE_NOEXP_D2I; +static const int ASN1_F_ASN1_TYPE_GET_INT_OCTETSTRING; +static const int ASN1_F_ASN1_TYPE_GET_OCTETSTRING; +static const int ASN1_F_ASN1_VERIFY; +static const int ASN1_F_BITSTR_CB; +static const int ASN1_F_D2I_ASN1_UINTEGER; +static const int ASN1_F_D2I_PRIVATEKEY; +static const int ASN1_F_I2D_DSA_PUBKEY; +static const int ASN1_F_LONG_C2I; +static const int ASN1_F_OID_MODULE_INIT; +static const int ASN1_F_PARSE_TAGGING; +static const int ASN1_F_PKCS5_PBE_SET; +static const int ASN1_F_B64_READ_ASN1; +static const int ASN1_F_B64_WRITE_ASN1; +static const int ASN1_F_SMIME_READ_ASN1; +static const int ASN1_F_SMIME_TEXT; + +static const int ASN1_R_BOOLEAN_IS_WRONG_LENGTH; +static const int ASN1_R_BUFFER_TOO_SMALL; +static const int ASN1_R_CIPHER_HAS_NO_OBJECT_IDENTIFIER; +static const int ASN1_R_DATA_IS_WRONG; +static const int ASN1_R_DECODE_ERROR; +static const int ASN1_R_DEPTH_EXCEEDED; +static const int ASN1_R_ENCODE_ERROR; +static const int ASN1_R_ERROR_GETTING_TIME; +static const int ASN1_R_ERROR_LOADING_SECTION; +static const int ASN1_R_MSTRING_WRONG_TAG; +static const int ASN1_R_NESTED_ASN1_STRING; +static const int ASN1_R_NO_MATCHING_CHOICE_TYPE; +static const int ASN1_R_UNKNOWN_MESSAGE_DIGEST_ALGORITHM; +static const int ASN1_R_UNKNOWN_OBJECT_TYPE; +static const int ASN1_R_UNKNOWN_PUBLIC_KEY_TYPE; +static const int ASN1_R_UNKNOWN_TAG; +static const int ASN1_R_UNSUPPORTED_ANY_DEFINED_BY_TYPE; +static const int ASN1_R_UNSUPPORTED_PUBLIC_KEY_TYPE; +static const int ASN1_R_UNSUPPORTED_TYPE; +static const int ASN1_R_WRONG_TAG; +static const int ASN1_R_NO_CONTENT_TYPE; +static const int ASN1_R_NO_MULTIPART_BODY_FAILURE; +static const int ASN1_R_NO_MULTIPART_BOUNDARY; +static const int ASN1_R_HEADER_TOO_LONG; + +static const int DH_F_COMPUTE_KEY; + +static const int DH_R_INVALID_PUBKEY; + +static const int EVP_F_AES_INIT_KEY; +static const int EVP_F_EVP_CIPHER_CTX_CTRL; +static const int EVP_F_EVP_CIPHER_CTX_SET_KEY_LENGTH; +static const int EVP_F_EVP_CIPHERINIT_EX; +static const int EVP_F_EVP_DECRYPTFINAL_EX; +static const int EVP_F_EVP_DIGESTINIT_EX; +static const int EVP_F_EVP_ENCRYPTFINAL_EX; +static const int EVP_F_EVP_MD_CTX_COPY_EX; +static const int EVP_F_EVP_OPENINIT; +static const int EVP_F_EVP_PBE_ALG_ADD; +static const int EVP_F_EVP_PBE_CIPHERINIT; +static const int EVP_F_EVP_PKCS82PKEY; +static const int EVP_F_EVP_PKEY_COPY_PARAMETERS; +static const int EVP_F_EVP_PKEY_DECRYPT; +static const int EVP_F_EVP_PKEY_ENCRYPT; +static const int EVP_F_EVP_PKEY_NEW; +static const int EVP_F_EVP_SIGNFINAL; +static const int EVP_F_EVP_VERIFYFINAL; +static const int EVP_F_PKCS5_PBE_KEYIVGEN; +static const int EVP_F_PKCS5_V2_PBE_KEYIVGEN; +static const int EVP_F_RC2_MAGIC_TO_METH; +static const int EVP_F_RC5_CTRL; +static const int EVP_F_CAMELLIA_INIT_KEY; + +static const int EVP_R_AES_KEY_SETUP_FAILED; +static const int EVP_R_BAD_DECRYPT; +static const int EVP_R_CIPHER_PARAMETER_ERROR; +static const int EVP_R_CTRL_NOT_IMPLEMENTED; +static const int EVP_R_CTRL_OPERATION_NOT_IMPLEMENTED; +static const int EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH; +static const int EVP_R_DECODE_ERROR; +static const int EVP_R_DIFFERENT_KEY_TYPES; +static const int EVP_R_INITIALIZATION_ERROR; +static const int EVP_R_INPUT_NOT_INITIALIZED; +static const int EVP_R_INVALID_KEY_LENGTH; +static const int EVP_R_KEYGEN_FAILURE; +static const int EVP_R_MISSING_PARAMETERS; +static const int EVP_R_NO_CIPHER_SET; +static const int EVP_R_NO_DIGEST_SET; +static const int EVP_R_PUBLIC_KEY_NOT_RSA; +static const int EVP_R_UNKNOWN_PBE_ALGORITHM; +static const int EVP_R_UNSUPPORTED_CIPHER; +static const int EVP_R_UNSUPPORTED_KEY_DERIVATION_FUNCTION; +static const int EVP_R_UNSUPPORTED_KEYLENGTH; +static const int EVP_R_UNSUPPORTED_SALT_TYPE; +static const int EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM; +static const int EVP_R_WRONG_FINAL_BLOCK_LENGTH; +static const int EVP_R_CAMELLIA_KEY_SETUP_FAILED; + +static const int EC_F_EC_GROUP_NEW_BY_CURVE_NAME; + +static const int EC_R_UNKNOWN_GROUP; + +static const int PEM_F_D2I_PKCS8PRIVATEKEY_BIO; +static const int PEM_F_D2I_PKCS8PRIVATEKEY_FP; +static const int PEM_F_DO_PK8PKEY; +static const int PEM_F_DO_PK8PKEY_FP; +static const int PEM_F_LOAD_IV; +static const int PEM_F_PEM_ASN1_READ; +static const int PEM_F_PEM_ASN1_READ_BIO; +static const int PEM_F_PEM_ASN1_WRITE; +static const int PEM_F_PEM_ASN1_WRITE_BIO; +static const int PEM_F_PEM_DEF_CALLBACK; +static const int PEM_F_PEM_DO_HEADER; +static const int PEM_F_PEM_GET_EVP_CIPHER_INFO; +static const int PEM_F_PEM_READ; +static const int PEM_F_PEM_READ_BIO; +static const int PEM_F_PEM_READ_BIO_PRIVATEKEY; +static const int PEM_F_PEM_READ_PRIVATEKEY; +static const int PEM_F_PEM_SIGNFINAL; +static const int PEM_F_PEM_WRITE; +static const int PEM_F_PEM_WRITE_BIO; +static const int PEM_F_PEM_X509_INFO_READ; +static const int PEM_F_PEM_X509_INFO_READ_BIO; +static const int PEM_F_PEM_X509_INFO_WRITE_BIO; + +static const int PEM_R_BAD_BASE64_DECODE; +static const int PEM_R_BAD_DECRYPT; +static const int PEM_R_BAD_END_LINE; +static const int PEM_R_BAD_IV_CHARS; +static const int PEM_R_BAD_PASSWORD_READ; +static const int PEM_R_ERROR_CONVERTING_PRIVATE_KEY; +static const int PEM_R_NO_START_LINE; +static const int PEM_R_NOT_DEK_INFO; +static const int PEM_R_NOT_ENCRYPTED; +static const int PEM_R_NOT_PROC_TYPE; +static const int PEM_R_PROBLEMS_GETTING_PASSWORD; +static const int PEM_R_READ_KEY; +static const int PEM_R_SHORT_HEADER; +static const int PEM_R_UNSUPPORTED_CIPHER; +static const int PEM_R_UNSUPPORTED_ENCRYPTION; + +static const int PKCS12_F_PKCS12_PBE_CRYPT; + +static const int PKCS12_R_PKCS12_CIPHERFINAL_ERROR; + +static const int RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE; +static const int RSA_R_DATA_TOO_LARGE_FOR_MODULUS; +static const int RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY; +static const int RSA_R_BLOCK_TYPE_IS_NOT_01; +static const int RSA_R_BLOCK_TYPE_IS_NOT_02; +static const int RSA_R_PKCS_DECODING_ERROR; +static const int RSA_R_OAEP_DECODING_ERROR; +static const int RSA_F_RSA_SIGN; + +static const int SSL_TLSEXT_ERR_OK; +static const int SSL_TLSEXT_ERR_ALERT_WARNING; +static const int SSL_TLSEXT_ERR_ALERT_FATAL; +static const int SSL_TLSEXT_ERR_NOACK; + +static const int SSL_AD_CLOSE_NOTIFY; +static const int SSL_AD_UNEXPECTED_MESSAGE; +static const int SSL_AD_BAD_RECORD_MAC; +static const int SSL_AD_RECORD_OVERFLOW; +static const int SSL_AD_DECOMPRESSION_FAILURE; +static const int SSL_AD_HANDSHAKE_FAILURE; +static const int SSL_AD_BAD_CERTIFICATE; +static const int SSL_AD_UNSUPPORTED_CERTIFICATE; +static const int SSL_AD_CERTIFICATE_REVOKED; +static const int SSL_AD_CERTIFICATE_EXPIRED; +static const int SSL_AD_CERTIFICATE_UNKNOWN; +static const int SSL_AD_ILLEGAL_PARAMETER; +static const int SSL_AD_UNKNOWN_CA; +static const int SSL_AD_ACCESS_DENIED; +static const int SSL_AD_DECODE_ERROR; +static const int SSL_AD_DECRYPT_ERROR; +static const int SSL_AD_PROTOCOL_VERSION; +static const int SSL_AD_INSUFFICIENT_SECURITY; +static const int SSL_AD_INTERNAL_ERROR; +static const int SSL_AD_USER_CANCELLED; +static const int SSL_AD_NO_RENEGOTIATION; + +static const int SSL_AD_UNSUPPORTED_EXTENSION; +static const int SSL_AD_CERTIFICATE_UNOBTAINABLE; +static const int SSL_AD_UNRECOGNIZED_NAME; +static const int SSL_AD_BAD_CERTIFICATE_STATUS_RESPONSE; +static const int SSL_AD_BAD_CERTIFICATE_HASH_VALUE; +static const int SSL_AD_UNKNOWN_PSK_IDENTITY; + +static const int X509_R_CERT_ALREADY_IN_HASH_TABLE; +""" + +FUNCTIONS = """ +char *ERR_error_string(unsigned long, char *); +void ERR_error_string_n(unsigned long, char *, size_t); +const char *ERR_lib_error_string(unsigned long); +const char *ERR_func_error_string(unsigned long); +const char *ERR_reason_error_string(unsigned long); +void ERR_print_errors(BIO *); +void ERR_print_errors_fp(FILE *); +unsigned long ERR_get_error(void); +unsigned long ERR_peek_error(void); +unsigned long ERR_peek_last_error(void); +unsigned long ERR_get_error_line(const char **, int *); +unsigned long ERR_peek_error_line(const char **, int *); +unsigned long ERR_peek_last_error_line(const char **, int *); +unsigned long ERR_get_error_line_data(const char **, int *, + const char **, int *); +void ERR_clear_error(void); +unsigned long ERR_peek_error_line_data(const char **, + int *, const char **, int *); +unsigned long ERR_peek_last_error_line_data(const char **, + int *, const char **, int *); +void ERR_put_error(int, int, int, const char *, int); +void ERR_add_error_data(int, ...); +int ERR_get_next_error_library(void); +ERR_STATE *ERR_get_state(void); +""" + +MACROS = """ +/* ERR_free_strings became a macro in 1.1.0 */ +void ERR_free_strings(void); + +unsigned long ERR_PACK(int, int, int); +int ERR_GET_LIB(unsigned long); +int ERR_GET_FUNC(unsigned long); +int ERR_GET_REASON(unsigned long); + +""" + +CUSTOMIZATIONS = """ +// OpenSSL without EC. e.g. RHEL +#ifndef OPENSSL_NO_EC +static const long Cryptography_HAS_EC_CODES = 1; +#else +static const long Cryptography_HAS_EC_CODES = 0; +static const int EC_R_UNKNOWN_GROUP = 0; +static const int EC_F_EC_GROUP_NEW_BY_CURVE_NAME = 0; +#endif + +#ifdef RSA_R_PKCS_DECODING_ERROR +static const long Cryptography_HAS_RSA_R_PKCS_DECODING_ERROR = 1; +#else +static const long Cryptography_HAS_RSA_R_PKCS_DECODING_ERROR = 0; +static const long RSA_R_PKCS_DECODING_ERROR = 0; +#endif +""" diff --git a/src/_cffi_src/openssl/evp.py b/src/_cffi_src/openssl/evp.py new file mode 100644 index 0000000..a5032d1 --- /dev/null +++ b/src/_cffi_src/openssl/evp.py @@ -0,0 +1,219 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... EVP_CIPHER; +typedef ... EVP_CIPHER_CTX; +typedef ... EVP_MD; +typedef ... EVP_MD_CTX; + +typedef ... EVP_PKEY; +typedef ... EVP_PKEY_CTX; +static const int EVP_PKEY_RSA; +static const int EVP_PKEY_DSA; +static const int EVP_PKEY_DH; +static const int EVP_PKEY_EC; +static const int EVP_MAX_MD_SIZE; +static const int EVP_CTRL_GCM_SET_IVLEN; +static const int EVP_CTRL_GCM_GET_TAG; +static const int EVP_CTRL_GCM_SET_TAG; + +static const int Cryptography_HAS_GCM; +static const int Cryptography_HAS_PBKDF2_HMAC; +static const int Cryptography_HAS_PKEY_CTX; +static const int Cryptography_HAS_SCRYPT; +""" + +FUNCTIONS = """ +const EVP_CIPHER *EVP_get_cipherbyname(const char *); +int EVP_EncryptInit_ex(EVP_CIPHER_CTX *, const EVP_CIPHER *, ENGINE *, + const unsigned char *, const unsigned char *); +int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *, int); +int EVP_EncryptUpdate(EVP_CIPHER_CTX *, unsigned char *, int *, + const unsigned char *, int); +int EVP_EncryptFinal_ex(EVP_CIPHER_CTX *, unsigned char *, int *); +int EVP_DecryptInit_ex(EVP_CIPHER_CTX *, const EVP_CIPHER *, ENGINE *, + const unsigned char *, const unsigned char *); +int EVP_DecryptUpdate(EVP_CIPHER_CTX *, unsigned char *, int *, + const unsigned char *, int); +int EVP_DecryptFinal_ex(EVP_CIPHER_CTX *, unsigned char *, int *); +int EVP_CipherInit_ex(EVP_CIPHER_CTX *, const EVP_CIPHER *, ENGINE *, + const unsigned char *, const unsigned char *, int); +int EVP_CipherUpdate(EVP_CIPHER_CTX *, unsigned char *, int *, + const unsigned char *, int); +int EVP_CipherFinal_ex(EVP_CIPHER_CTX *, unsigned char *, int *); +int EVP_CIPHER_block_size(const EVP_CIPHER *); +int EVP_CIPHER_CTX_cleanup(EVP_CIPHER_CTX *); +EVP_CIPHER_CTX *EVP_CIPHER_CTX_new(void); +void EVP_CIPHER_CTX_free(EVP_CIPHER_CTX *); +int EVP_CIPHER_CTX_set_key_length(EVP_CIPHER_CTX *, int); +const EVP_CIPHER *EVP_CIPHER_CTX_cipher(const EVP_CIPHER_CTX *); + +int EVP_MD_CTX_copy_ex(EVP_MD_CTX *, const EVP_MD_CTX *); +int EVP_DigestInit_ex(EVP_MD_CTX *, const EVP_MD *, ENGINE *); +int EVP_DigestUpdate(EVP_MD_CTX *, const void *, size_t); +int EVP_DigestFinal_ex(EVP_MD_CTX *, unsigned char *, unsigned int *); +const EVP_MD *EVP_get_digestbyname(const char *); +const EVP_MD *EVP_MD_CTX_md(const EVP_MD_CTX *); +int EVP_MD_size(const EVP_MD *); + +EVP_PKEY *EVP_PKEY_new(void); +void EVP_PKEY_free(EVP_PKEY *); +int EVP_PKEY_type(int); +int EVP_PKEY_size(EVP_PKEY *); +RSA *EVP_PKEY_get1_RSA(EVP_PKEY *); +DSA *EVP_PKEY_get1_DSA(EVP_PKEY *); +DH *EVP_PKEY_get1_DH(EVP_PKEY *); + +int EVP_PKEY_encrypt(EVP_PKEY_CTX *, unsigned char *, size_t *, + const unsigned char *, size_t); +int EVP_PKEY_decrypt(EVP_PKEY_CTX *, unsigned char *, size_t *, + const unsigned char *, size_t); + +int EVP_SignInit(EVP_MD_CTX *, const EVP_MD *); +int EVP_SignUpdate(EVP_MD_CTX *, const void *, size_t); +int EVP_SignFinal(EVP_MD_CTX *, unsigned char *, unsigned int *, EVP_PKEY *); + +int EVP_VerifyInit(EVP_MD_CTX *, const EVP_MD *); +int EVP_VerifyUpdate(EVP_MD_CTX *, const void *, size_t); +int EVP_VerifyFinal(EVP_MD_CTX *, const unsigned char *, unsigned int, + EVP_PKEY *); + +const EVP_MD *EVP_md5(void); +const EVP_MD *EVP_sha1(void); +const EVP_MD *EVP_ripemd160(void); +const EVP_MD *EVP_sha224(void); +const EVP_MD *EVP_sha256(void); +const EVP_MD *EVP_sha384(void); +const EVP_MD *EVP_sha512(void); + +int PKCS5_PBKDF2_HMAC_SHA1(const char *, int, const unsigned char *, int, int, + int, unsigned char *); + +EVP_PKEY_CTX *EVP_PKEY_CTX_new(EVP_PKEY *, ENGINE *); +EVP_PKEY_CTX *EVP_PKEY_CTX_new_id(int, ENGINE *); +EVP_PKEY_CTX *EVP_PKEY_CTX_dup(EVP_PKEY_CTX *); +void EVP_PKEY_CTX_free(EVP_PKEY_CTX *); +int EVP_PKEY_sign_init(EVP_PKEY_CTX *); +int EVP_PKEY_sign(EVP_PKEY_CTX *, unsigned char *, size_t *, + const unsigned char *, size_t); +int EVP_PKEY_verify_init(EVP_PKEY_CTX *); +int EVP_PKEY_verify(EVP_PKEY_CTX *, const unsigned char *, size_t, + const unsigned char *, size_t); +int EVP_PKEY_encrypt_init(EVP_PKEY_CTX *); +int EVP_PKEY_decrypt_init(EVP_PKEY_CTX *); + +int EVP_PKEY_set1_RSA(EVP_PKEY *, RSA *); +int EVP_PKEY_set1_DSA(EVP_PKEY *, DSA *); +int EVP_PKEY_set1_DH(EVP_PKEY *, DH *); + +int EVP_PKEY_get_attr_count(const EVP_PKEY *); +int EVP_PKEY_get_attr_by_NID(const EVP_PKEY *, int, int); +X509_ATTRIBUTE *EVP_PKEY_get_attr(const EVP_PKEY *, int); +X509_ATTRIBUTE *EVP_PKEY_delete_attr(EVP_PKEY *, int); +int EVP_PKEY_add1_attr(EVP_PKEY *, X509_ATTRIBUTE *); +int EVP_PKEY_add1_attr_by_OBJ(EVP_PKEY *, const ASN1_OBJECT *, int, + const unsigned char *, int); +int EVP_PKEY_add1_attr_by_NID(EVP_PKEY *, int, int, + const unsigned char *, int); +int EVP_PKEY_add1_attr_by_txt(EVP_PKEY *, const char *, int, + const unsigned char *, int); + +int EVP_PKEY_cmp(const EVP_PKEY *, const EVP_PKEY *); + +int EVP_PKEY_id(const EVP_PKEY *); +int Cryptography_EVP_PKEY_id(const EVP_PKEY *); + +/* in 1.1.0 _create and _destroy were renamed to _new and _free. The following + two functions wrap both the old and new functions so we can call them + without worrying about what OpenSSL we're running against. */ +EVP_MD_CTX *Cryptography_EVP_MD_CTX_new(void); +void Cryptography_EVP_MD_CTX_free(EVP_MD_CTX *); +""" + +MACROS = """ +/* PKCS8_PRIV_KEY_INFO * became const in 1.1.0 */ +EVP_PKEY *EVP_PKCS82PKEY(PKCS8_PRIV_KEY_INFO *); + +/* EVP_PKEY * became const in 1.1.0 */ +int EVP_PKEY_bits(EVP_PKEY *); + +/* became a macro in 1.1.0 */ +void EVP_CIPHER_CTX_init(EVP_CIPHER_CTX *); + +void OpenSSL_add_all_algorithms(void); +int EVP_PKEY_assign_RSA(EVP_PKEY *, RSA *); +int EVP_PKEY_assign_DSA(EVP_PKEY *, DSA *); + +int EVP_PKEY_assign_EC_KEY(EVP_PKEY *, EC_KEY *); +EC_KEY *EVP_PKEY_get1_EC_KEY(EVP_PKEY *); +int EVP_PKEY_set1_EC_KEY(EVP_PKEY *, EC_KEY *); + +int EVP_MD_CTX_block_size(const EVP_MD_CTX *); +int EVP_CIPHER_CTX_block_size(const EVP_CIPHER_CTX *); +int EVP_CIPHER_CTX_ctrl(EVP_CIPHER_CTX *, int, int, void *); + +int PKCS5_PBKDF2_HMAC(const char *, int, const unsigned char *, int, int, + const EVP_MD *, int, unsigned char *); + +int EVP_PKEY_CTX_set_signature_md(EVP_PKEY_CTX *, const EVP_MD *); + +int EVP_PBE_scrypt(const char *, size_t, const unsigned char *, size_t, + uint64_t, uint64_t, uint64_t, uint64_t, unsigned char *, + size_t); +""" + +CUSTOMIZATIONS = """ +#ifdef EVP_CTRL_GCM_SET_TAG +const long Cryptography_HAS_GCM = 1; +#else +const long Cryptography_HAS_GCM = 0; +const long EVP_CTRL_GCM_GET_TAG = -1; +const long EVP_CTRL_GCM_SET_TAG = -1; +const long EVP_CTRL_GCM_SET_IVLEN = -1; +#endif + +const long Cryptography_HAS_PBKDF2_HMAC = 1; +const long Cryptography_HAS_PKEY_CTX = 1; + +#ifdef OPENSSL_NO_EC +int (*EVP_PKEY_assign_EC_KEY)(EVP_PKEY *, EC_KEY *) = NULL; +EC_KEY *(*EVP_PKEY_get1_EC_KEY)(EVP_PKEY *) = NULL; +int (*EVP_PKEY_set1_EC_KEY)(EVP_PKEY *, EC_KEY *) = NULL; +#endif + +int Cryptography_EVP_PKEY_id(const EVP_PKEY *key) { + return EVP_PKEY_id(key); +} + +EVP_MD_CTX *Cryptography_EVP_MD_CTX_new(void) { +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 || defined(LIBRESSL_VERSION_NUMBER) + return EVP_MD_CTX_create(); +#else + return EVP_MD_CTX_new(); +#endif +} +void Cryptography_EVP_MD_CTX_free(EVP_MD_CTX *ctx) { +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 || defined(LIBRESSL_VERSION_NUMBER) + EVP_MD_CTX_destroy(ctx); +#else + EVP_MD_CTX_free(ctx); +#endif +} +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 || defined(LIBRESSL_VERSION_NUMBER) \ + || defined(OPENSSL_NO_SCRYPT) +static const long Cryptography_HAS_SCRYPT = 0; +int (*EVP_PBE_scrypt)(const char *, size_t, const unsigned char *, size_t, + uint64_t, uint64_t, uint64_t, uint64_t, unsigned char *, + size_t) = NULL; +#else +static const long Cryptography_HAS_SCRYPT = 1; +#endif +""" diff --git a/src/_cffi_src/openssl/hmac.py b/src/_cffi_src/openssl/hmac.py new file mode 100644 index 0000000..daedd32 --- /dev/null +++ b/src/_cffi_src/openssl/hmac.py @@ -0,0 +1,51 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... HMAC_CTX; +""" + +FUNCTIONS = """ +int HMAC_Init_ex(HMAC_CTX *, const void *, int, const EVP_MD *, ENGINE *); +int HMAC_Update(HMAC_CTX *, const unsigned char *, size_t); +int HMAC_Final(HMAC_CTX *, unsigned char *, unsigned int *); +int HMAC_CTX_copy(HMAC_CTX *, HMAC_CTX *); + +HMAC_CTX *Cryptography_HMAC_CTX_new(void); +void Cryptography_HMAC_CTX_free(HMAC_CTX *ctx); +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +HMAC_CTX *Cryptography_HMAC_CTX_new(void) { +#if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER && !defined(LIBRESSL_VERSION_NUMBER) + return HMAC_CTX_new(); +#else + /* This uses OPENSSL_zalloc in 1.1.0, which is malloc + memset */ + HMAC_CTX *ctx = (HMAC_CTX *)OPENSSL_malloc(sizeof(HMAC_CTX)); + memset(ctx, 0, sizeof(HMAC_CTX)); + return ctx; +#endif +} + + +void Cryptography_HMAC_CTX_free(HMAC_CTX *ctx) { +#if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER && !defined(LIBRESSL_VERSION_NUMBER) + return HMAC_CTX_free(ctx); +#else + if (ctx != NULL) { + HMAC_CTX_cleanup(ctx); + OPENSSL_free(ctx); + } +#endif +} +""" diff --git a/src/_cffi_src/openssl/nid.py b/src/_cffi_src/openssl/nid.py new file mode 100644 index 0000000..5ee495a --- /dev/null +++ b/src/_cffi_src/openssl/nid.py @@ -0,0 +1,230 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +static const int NID_undef; +static const int NID_dsa; +static const int NID_dsaWithSHA; +static const int NID_dsaWithSHA1; +static const int NID_md2; +static const int NID_md4; +static const int NID_md5; +static const int NID_mdc2; +static const int NID_ripemd160; +static const int NID_sha; +static const int NID_sha1; +static const int NID_sha256; +static const int NID_sha384; +static const int NID_sha512; +static const int NID_sha224; +static const int NID_sha; +static const int NID_ecdsa_with_SHA1; +static const int NID_ecdsa_with_SHA224; +static const int NID_ecdsa_with_SHA256; +static const int NID_ecdsa_with_SHA384; +static const int NID_ecdsa_with_SHA512; +static const int NID_pbe_WithSHA1And3_Key_TripleDES_CBC; +static const int NID_X9_62_c2pnb163v1; +static const int NID_X9_62_c2pnb163v2; +static const int NID_X9_62_c2pnb163v3; +static const int NID_X9_62_c2pnb176v1; +static const int NID_X9_62_c2tnb191v1; +static const int NID_X9_62_c2tnb191v2; +static const int NID_X9_62_c2tnb191v3; +static const int NID_X9_62_c2onb191v4; +static const int NID_X9_62_c2onb191v5; +static const int NID_X9_62_c2pnb208w1; +static const int NID_X9_62_c2tnb239v1; +static const int NID_X9_62_c2tnb239v2; +static const int NID_X9_62_c2tnb239v3; +static const int NID_X9_62_c2onb239v4; +static const int NID_X9_62_c2onb239v5; +static const int NID_X9_62_c2pnb272w1; +static const int NID_X9_62_c2pnb304w1; +static const int NID_X9_62_c2tnb359v1; +static const int NID_X9_62_c2pnb368w1; +static const int NID_X9_62_c2tnb431r1; +static const int NID_X9_62_prime192v1; +static const int NID_X9_62_prime192v2; +static const int NID_X9_62_prime192v3; +static const int NID_X9_62_prime239v1; +static const int NID_X9_62_prime239v2; +static const int NID_X9_62_prime239v3; +static const int NID_X9_62_prime256v1; +static const int NID_secp112r1; +static const int NID_secp112r2; +static const int NID_secp128r1; +static const int NID_secp128r2; +static const int NID_secp160k1; +static const int NID_secp160r1; +static const int NID_secp160r2; +static const int NID_sect163k1; +static const int NID_sect163r1; +static const int NID_sect163r2; +static const int NID_secp192k1; +static const int NID_secp224k1; +static const int NID_secp224r1; +static const int NID_secp256k1; +static const int NID_secp384r1; +static const int NID_secp521r1; +static const int NID_sect113r1; +static const int NID_sect113r2; +static const int NID_sect131r1; +static const int NID_sect131r2; +static const int NID_sect193r1; +static const int NID_sect193r2; +static const int NID_sect233k1; +static const int NID_sect233r1; +static const int NID_sect239k1; +static const int NID_sect283k1; +static const int NID_sect283r1; +static const int NID_sect409k1; +static const int NID_sect409r1; +static const int NID_sect571k1; +static const int NID_sect571r1; +static const int NID_wap_wsg_idm_ecid_wtls1; +static const int NID_wap_wsg_idm_ecid_wtls3; +static const int NID_wap_wsg_idm_ecid_wtls4; +static const int NID_wap_wsg_idm_ecid_wtls5; +static const int NID_wap_wsg_idm_ecid_wtls6; +static const int NID_wap_wsg_idm_ecid_wtls7; +static const int NID_wap_wsg_idm_ecid_wtls8; +static const int NID_wap_wsg_idm_ecid_wtls9; +static const int NID_wap_wsg_idm_ecid_wtls10; +static const int NID_wap_wsg_idm_ecid_wtls11; +static const int NID_wap_wsg_idm_ecid_wtls12; +static const int NID_ipsec3; +static const int NID_ipsec4; +static const char *const SN_X9_62_c2pnb163v1; +static const char *const SN_X9_62_c2pnb163v2; +static const char *const SN_X9_62_c2pnb163v3; +static const char *const SN_X9_62_c2pnb176v1; +static const char *const SN_X9_62_c2tnb191v1; +static const char *const SN_X9_62_c2tnb191v2; +static const char *const SN_X9_62_c2tnb191v3; +static const char *const SN_X9_62_c2onb191v4; +static const char *const SN_X9_62_c2onb191v5; +static const char *const SN_X9_62_c2pnb208w1; +static const char *const SN_X9_62_c2tnb239v1; +static const char *const SN_X9_62_c2tnb239v2; +static const char *const SN_X9_62_c2tnb239v3; +static const char *const SN_X9_62_c2onb239v4; +static const char *const SN_X9_62_c2onb239v5; +static const char *const SN_X9_62_c2pnb272w1; +static const char *const SN_X9_62_c2pnb304w1; +static const char *const SN_X9_62_c2tnb359v1; +static const char *const SN_X9_62_c2pnb368w1; +static const char *const SN_X9_62_c2tnb431r1; +static const char *const SN_X9_62_prime192v1; +static const char *const SN_X9_62_prime192v2; +static const char *const SN_X9_62_prime192v3; +static const char *const SN_X9_62_prime239v1; +static const char *const SN_X9_62_prime239v2; +static const char *const SN_X9_62_prime239v3; +static const char *const SN_X9_62_prime256v1; +static const char *const SN_secp112r1; +static const char *const SN_secp112r2; +static const char *const SN_secp128r1; +static const char *const SN_secp128r2; +static const char *const SN_secp160k1; +static const char *const SN_secp160r1; +static const char *const SN_secp160r2; +static const char *const SN_sect163k1; +static const char *const SN_sect163r1; +static const char *const SN_sect163r2; +static const char *const SN_secp192k1; +static const char *const SN_secp224k1; +static const char *const SN_secp224r1; +static const char *const SN_secp256k1; +static const char *const SN_secp384r1; +static const char *const SN_secp521r1; +static const char *const SN_sect113r1; +static const char *const SN_sect113r2; +static const char *const SN_sect131r1; +static const char *const SN_sect131r2; +static const char *const SN_sect193r1; +static const char *const SN_sect193r2; +static const char *const SN_sect233k1; +static const char *const SN_sect233r1; +static const char *const SN_sect239k1; +static const char *const SN_sect283k1; +static const char *const SN_sect283r1; +static const char *const SN_sect409k1; +static const char *const SN_sect409r1; +static const char *const SN_sect571k1; +static const char *const SN_sect571r1; +static const char *const SN_wap_wsg_idm_ecid_wtls1; +static const char *const SN_wap_wsg_idm_ecid_wtls3; +static const char *const SN_wap_wsg_idm_ecid_wtls4; +static const char *const SN_wap_wsg_idm_ecid_wtls5; +static const char *const SN_wap_wsg_idm_ecid_wtls6; +static const char *const SN_wap_wsg_idm_ecid_wtls7; +static const char *const SN_wap_wsg_idm_ecid_wtls8; +static const char *const SN_wap_wsg_idm_ecid_wtls9; +static const char *const SN_wap_wsg_idm_ecid_wtls10; +static const char *const SN_wap_wsg_idm_ecid_wtls11; +static const char *const SN_wap_wsg_idm_ecid_wtls12; +static const char *const SN_ipsec3; +static const char *const SN_ipsec4; + +static const int NID_subject_key_identifier; +static const int NID_authority_key_identifier; +static const int NID_policy_constraints; +static const int NID_ext_key_usage; +static const int NID_info_access; +static const int NID_key_usage; +static const int NID_subject_alt_name; +static const int NID_issuer_alt_name; +static const int NID_basic_constraints; +static const int NID_issuing_distribution_point; +static const int NID_certificate_issuer; +static const int NID_name_constraints; +static const int NID_crl_distribution_points; +static const int NID_certificate_policies; +static const int NID_inhibit_any_policy; + +static const int NID_private_key_usage_period; +static const int NID_crl_number; +static const int NID_crl_reason; +static const int NID_invalidity_date; +static const int NID_delta_crl; +static const int NID_any_policy; +static const int NID_policy_mappings; +static const int NID_target_information; +static const int NID_no_rev_avail; + +static const int NID_commonName; +static const int NID_countryName; +static const int NID_localityName; +static const int NID_stateOrProvinceName; +static const int NID_organizationName; +static const int NID_organizationalUnitName; +static const int NID_serialNumber; +static const int NID_surname; +static const int NID_givenName; +static const int NID_title; +static const int NID_generationQualifier; +static const int NID_dnQualifier; +static const int NID_pseudonym; +static const int NID_domainComponent; +static const int NID_pkcs9_emailAddress; + +static const int NID_ad_OCSP; +static const int NID_ad_ca_issuers; +""" + +FUNCTIONS = """ +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/openssl/objects.py b/src/_cffi_src/openssl/objects.py new file mode 100644 index 0000000..19ff4c1 --- /dev/null +++ b/src/_cffi_src/openssl/objects.py @@ -0,0 +1,44 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef struct { + int type; + int alias; + const char *name; + const char *data; +} OBJ_NAME; + +static const long OBJ_NAME_TYPE_MD_METH; +""" + +FUNCTIONS = """ +ASN1_OBJECT *OBJ_nid2obj(int); +const char *OBJ_nid2ln(int); +const char *OBJ_nid2sn(int); +int OBJ_obj2nid(const ASN1_OBJECT *); +int OBJ_ln2nid(const char *); +int OBJ_sn2nid(const char *); +int OBJ_txt2nid(const char *); +ASN1_OBJECT *OBJ_txt2obj(const char *, int); +int OBJ_obj2txt(char *, int, const ASN1_OBJECT *, int); +int OBJ_cmp(const ASN1_OBJECT *, const ASN1_OBJECT *); +ASN1_OBJECT *OBJ_dup(const ASN1_OBJECT *); +int OBJ_create(const char *, const char *, const char *); +void OBJ_NAME_do_all(int, void (*) (const OBJ_NAME *, void *), void *); +""" + +MACROS = """ +/* OBJ_cleanup became a macro in 1.1.0 */ +void OBJ_cleanup(void); +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/openssl/ocsp.py b/src/_cffi_src/openssl/ocsp.py new file mode 100644 index 0000000..5865dba --- /dev/null +++ b/src/_cffi_src/openssl/ocsp.py @@ -0,0 +1,67 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... OCSP_REQUEST; +typedef ... OCSP_ONEREQ; +typedef ... OCSP_RESPONSE; +typedef ... OCSP_BASICRESP; +typedef ... OCSP_SINGLERESP; +typedef ... OCSP_CERTID; +""" + +FUNCTIONS = """ +int OCSP_response_status(OCSP_RESPONSE *); +OCSP_BASICRESP *OCSP_response_get1_basic(OCSP_RESPONSE *); +int OCSP_BASICRESP_get_ext_count(OCSP_BASICRESP *); +X509_EXTENSION *OCSP_BASICRESP_get_ext(OCSP_BASICRESP *, int); +int OCSP_resp_count(OCSP_BASICRESP *); +OCSP_SINGLERESP *OCSP_resp_get0(OCSP_BASICRESP *, int); +int OCSP_SINGLERESP_get_ext_count(OCSP_SINGLERESP *); +X509_EXTENSION *OCSP_SINGLERESP_get_ext(OCSP_SINGLERESP *, int); + +int OCSP_single_get0_status(OCSP_SINGLERESP *, int *, ASN1_GENERALIZEDTIME **, + ASN1_GENERALIZEDTIME **, ASN1_GENERALIZEDTIME **); + +int OCSP_request_onereq_count(OCSP_REQUEST *); +OCSP_ONEREQ *OCSP_request_onereq_get0(OCSP_REQUEST *, int); +int OCSP_ONEREQ_get_ext_count(OCSP_ONEREQ *); +X509_EXTENSION *OCSP_ONEREQ_get_ext(OCSP_ONEREQ *, int); +OCSP_CERTID *OCSP_onereq_get0_id(OCSP_ONEREQ *); + + +OCSP_BASICRESP *OCSP_BASICRESP_new(void); +void OCSP_BASICRESP_free(OCSP_BASICRESP *); +OCSP_SINGLERESP *OCSP_basic_add1_status(OCSP_BASICRESP *, OCSP_CERTID *, int, + int, ASN1_TIME *, ASN1_TIME *, + ASN1_TIME *); +int OCSP_basic_add1_nonce(OCSP_BASICRESP *, unsigned char *, int); +int OCSP_basic_add1_cert(OCSP_BASICRESP *, X509 *); +int OCSP_BASICRESP_add1_ext_i2d(OCSP_BASICRESP *, int, void *, int, + unsigned long); +int OCSP_basic_sign(OCSP_BASICRESP *, X509 *, EVP_PKEY *, const EVP_MD *, + Cryptography_STACK_OF_X509 *, unsigned long); +OCSP_RESPONSE *OCSP_response_create(int, OCSP_BASICRESP *); + +OCSP_REQUEST *OCSP_REQUEST_new(void); +void OCSP_REQUEST_free(OCSP_REQUEST *); +int OCSP_request_add1_nonce(OCSP_REQUEST *, unsigned char *, int); +int OCSP_REQUEST_add1_ext_i2d(OCSP_REQUEST *, int, void *, int, unsigned long); +""" + +MACROS = """ +OCSP_REQUEST *d2i_OCSP_REQUEST_bio(BIO *, OCSP_REQUEST **); +OCSP_RESPONSE *d2i_OCSP_RESPONSE_bio(BIO *, OCSP_RESPONSE **); +int i2d_OCSP_REQUEST_bio(BIO *, OCSP_REQUEST *); +int i2d_OCSP_RESPONSE_bio(BIO *, OCSP_RESPONSE *); +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/openssl/opensslv.py b/src/_cffi_src/openssl/opensslv.py new file mode 100644 index 0000000..a4e88f0 --- /dev/null +++ b/src/_cffi_src/openssl/opensslv.py @@ -0,0 +1,25 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +/* Note that these will be resolved when cryptography is compiled and are NOT + guaranteed to be the version that it actually loads. */ +static const int OPENSSL_VERSION_NUMBER; +static const char *const OPENSSL_VERSION_TEXT; +""" + +FUNCTIONS = """ +""" + +MACROS = """ +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/openssl/osrandom_engine.py b/src/_cffi_src/openssl/osrandom_engine.py new file mode 100644 index 0000000..10c5a60 --- /dev/null +++ b/src/_cffi_src/openssl/osrandom_engine.py @@ -0,0 +1,29 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import os + +HERE = os.path.dirname(os.path.abspath(__file__)) + +with open(os.path.join(HERE, "src/osrandom_engine.h")) as f: + INCLUDES = f.read() + +TYPES = """ +static const char *const Cryptography_osrandom_engine_name; +static const char *const Cryptography_osrandom_engine_id; +""" + +FUNCTIONS = """ +int Cryptography_add_osrandom_engine(void); +""" + +MACROS = """ +""" + +with open(os.path.join(HERE, "src/osrandom_engine.c")) as f: + CUSTOMIZATIONS = f.read() + +CONDITIONAL_NAMES = {} diff --git a/src/_cffi_src/openssl/pem.py b/src/_cffi_src/openssl/pem.py new file mode 100644 index 0000000..99881dd --- /dev/null +++ b/src/_cffi_src/openssl/pem.py @@ -0,0 +1,95 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef int pem_password_cb(char *buf, int size, int rwflag, void *userdata); +""" + +FUNCTIONS = """ +X509 *PEM_read_bio_X509(BIO *, X509 **, pem_password_cb *, void *); +int PEM_write_bio_X509(BIO *, X509 *); + +int PEM_write_bio_PrivateKey(BIO *, EVP_PKEY *, const EVP_CIPHER *, + unsigned char *, int, pem_password_cb *, void *); + +EVP_PKEY *PEM_read_bio_PrivateKey(BIO *, EVP_PKEY **, pem_password_cb *, + void *); + +int PEM_write_bio_PKCS8PrivateKey(BIO *, EVP_PKEY *, const EVP_CIPHER *, + char *, int, pem_password_cb *, void *); +int PEM_write_bio_PKCS8PrivateKey_nid(BIO *, EVP_PKEY *, int, char *, int, + pem_password_cb *, void *); + +int i2d_PKCS8PrivateKey_bio(BIO *, EVP_PKEY *, const EVP_CIPHER *, + char *, int, pem_password_cb *, void *); +int i2d_PKCS8PrivateKey_nid_bio(BIO *, EVP_PKEY *, int, + char *, int, pem_password_cb *, void *); + +int i2d_PKCS7_bio(BIO *, PKCS7 *); +PKCS7 *d2i_PKCS7_bio(BIO *, PKCS7 **); + +EVP_PKEY *d2i_PKCS8PrivateKey_bio(BIO *, EVP_PKEY **, pem_password_cb *, + void *); + +int PEM_write_bio_X509_REQ(BIO *, X509_REQ *); + +X509_REQ *PEM_read_bio_X509_REQ(BIO *, X509_REQ **, pem_password_cb *, void *); + +X509_CRL *PEM_read_bio_X509_CRL(BIO *, X509_CRL **, pem_password_cb *, void *); + +int PEM_write_bio_X509_CRL(BIO *, X509_CRL *); + +X509 *PEM_read_bio_X509_AUX(BIO *, X509 **, pem_password_cb *, void *); + +PKCS7 *PEM_read_bio_PKCS7(BIO *, PKCS7 **, pem_password_cb *, void *); +int PEM_write_bio_PKCS7(BIO *, PKCS7 *); + +DH *PEM_read_bio_DHparams(BIO *, DH **, pem_password_cb *, void *); + +DSA *PEM_read_bio_DSAPrivateKey(BIO *, DSA **, pem_password_cb *, void *); + +RSA *PEM_read_bio_RSAPrivateKey(BIO *, RSA **, pem_password_cb *, void *); + +int PEM_write_bio_DSAPrivateKey(BIO *, DSA *, const EVP_CIPHER *, + unsigned char *, int, + pem_password_cb *, void *); + +int PEM_write_bio_RSAPrivateKey(BIO *, RSA *, const EVP_CIPHER *, + unsigned char *, int, + pem_password_cb *, void *); + +DSA *PEM_read_bio_DSA_PUBKEY(BIO *, DSA **, pem_password_cb *, void *); + +RSA *PEM_read_bio_RSAPublicKey(BIO *, RSA **, pem_password_cb *, void *); + +int PEM_write_bio_DSA_PUBKEY(BIO *, DSA *); + +int PEM_write_bio_RSAPublicKey(BIO *, const RSA *); + +EVP_PKEY *PEM_read_bio_PUBKEY(BIO *, EVP_PKEY **, pem_password_cb *, void *); +int PEM_write_bio_PUBKEY(BIO *, EVP_PKEY *); +""" + +MACROS = """ +int PEM_write_bio_ECPrivateKey(BIO *, EC_KEY *, const EVP_CIPHER *, + unsigned char *, int, pem_password_cb *, + void *); +int PEM_write_bio_DHparams(BIO *, DH *); +""" + +CUSTOMIZATIONS = """ +// Cryptography_HAS_EC is provided by ec.py so we don't need to define it here +#ifdef OPENSSL_NO_EC +int (*PEM_write_bio_ECPrivateKey)(BIO *, EC_KEY *, const EVP_CIPHER *, + unsigned char *, int, pem_password_cb *, + void *) = NULL; +#endif + +""" diff --git a/src/_cffi_src/openssl/pkcs12.py b/src/_cffi_src/openssl/pkcs12.py new file mode 100644 index 0000000..0bbd1e5 --- /dev/null +++ b/src/_cffi_src/openssl/pkcs12.py @@ -0,0 +1,30 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... PKCS12; +""" + +FUNCTIONS = """ +void PKCS12_free(PKCS12 *); + +PKCS12 *d2i_PKCS12_bio(BIO *, PKCS12 **); +int i2d_PKCS12_bio(BIO *, PKCS12 *); +""" + +MACROS = """ +int PKCS12_parse(PKCS12 *, const char *, EVP_PKEY **, X509 **, + Cryptography_STACK_OF_X509 **); +PKCS12 *PKCS12_create(char *, char *, EVP_PKEY *, X509 *, + Cryptography_STACK_OF_X509 *, int, int, int, int, int); +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/openssl/pkcs7.py b/src/_cffi_src/openssl/pkcs7.py new file mode 100644 index 0000000..0dd8958 --- /dev/null +++ b/src/_cffi_src/openssl/pkcs7.py @@ -0,0 +1,86 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef struct { + Cryptography_STACK_OF_X509 *cert; + Cryptography_STACK_OF_X509_CRL *crl; + ...; +} PKCS7_SIGNED; + +typedef struct { + Cryptography_STACK_OF_X509 *cert; + Cryptography_STACK_OF_X509_CRL *crl; + ...; +} PKCS7_SIGN_ENVELOPE; + +typedef ... PKCS7_DIGEST; +typedef ... PKCS7_ENCRYPT; +typedef ... PKCS7_ENVELOPE; + +typedef struct { + ASN1_OBJECT *type; + union { + char *ptr; + ASN1_OCTET_STRING *data; + PKCS7_SIGNED *sign; + PKCS7_ENVELOPE *enveloped; + PKCS7_SIGN_ENVELOPE *signed_and_enveloped; + PKCS7_DIGEST *digest; + PKCS7_ENCRYPT *encrypted; + ASN1_TYPE *other; + } d; + ...; +} PKCS7; + +static const int PKCS7_BINARY; +static const int PKCS7_DETACHED; +static const int PKCS7_NOATTR; +static const int PKCS7_NOCERTS; +static const int PKCS7_NOCHAIN; +static const int PKCS7_NOINTERN; +static const int PKCS7_NOSIGS; +static const int PKCS7_NOSMIMECAP; +static const int PKCS7_NOVERIFY; +static const int PKCS7_STREAM; +static const int PKCS7_TEXT; +""" + +FUNCTIONS = """ +PKCS7 *SMIME_read_PKCS7(BIO *, BIO **); +int SMIME_write_PKCS7(BIO *, PKCS7 *, BIO *, int); + +void PKCS7_free(PKCS7 *); + +PKCS7 *PKCS7_sign(X509 *, EVP_PKEY *, Cryptography_STACK_OF_X509 *, + BIO *, int); +int PKCS7_verify(PKCS7 *, Cryptography_STACK_OF_X509 *, X509_STORE *, BIO *, + BIO *, int); +Cryptography_STACK_OF_X509 *PKCS7_get0_signers(PKCS7 *, + Cryptography_STACK_OF_X509 *, + int); + +PKCS7 *PKCS7_encrypt(Cryptography_STACK_OF_X509 *, BIO *, + const EVP_CIPHER *, int); +int PKCS7_decrypt(PKCS7 *, EVP_PKEY *, X509 *, BIO *, int); + +BIO *PKCS7_dataInit(PKCS7 *, BIO *); +""" + +MACROS = """ +int PKCS7_type_is_encrypted(PKCS7 *); +int PKCS7_type_is_signed(PKCS7 *); +int PKCS7_type_is_enveloped(PKCS7 *); +int PKCS7_type_is_signedAndEnveloped(PKCS7 *); +int PKCS7_type_is_data(PKCS7 *); +int PKCS7_type_is_digest(PKCS7 *); +""" + +CUSTOMIZATIONS = "" diff --git a/src/_cffi_src/openssl/rand.py b/src/_cffi_src/openssl/rand.py new file mode 100644 index 0000000..5f24a8d --- /dev/null +++ b/src/_cffi_src/openssl/rand.py @@ -0,0 +1,49 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +static const long Cryptography_HAS_EGD; +""" + +FUNCTIONS = """ +void RAND_seed(const void *, int); +void RAND_add(const void *, int, double); +int RAND_status(void); +const char *RAND_file_name(char *, size_t); +int RAND_load_file(const char *, long); +int RAND_write_file(const char *); +int RAND_bytes(unsigned char *, int); +""" + +MACROS = """ +/* ERR_load_RAND_strings started returning an int in 1.1.0. Unfortunately we + can't declare a conditional signature like that. Since it always returns + 1 we'll just lie about the signature to preserve compatibility for + pyOpenSSL (which calls this in its rand.py as of mid-2016) */ +void ERR_load_RAND_strings(void); + +/* RAND_cleanup became a macro in 1.1.0 */ +void RAND_cleanup(void); + +int RAND_egd(const char *); +int RAND_egd_bytes(const char *, int); +int RAND_query_egd_bytes(const char *, unsigned char *, int); +""" + +CUSTOMIZATIONS = """ +#if defined(LIBRESSL_VERSION_NUMBER) || CRYPTOGRAPHY_OPENSSL_110_OR_GREATER +static const long Cryptography_HAS_EGD = 0; +int (*RAND_egd)(const char *) = NULL; +int (*RAND_egd_bytes)(const char *, int) = NULL; +int (*RAND_query_egd_bytes)(const char *, unsigned char *, int) = NULL; +#else +static const long Cryptography_HAS_EGD = 1; +#endif +""" diff --git a/src/_cffi_src/openssl/rsa.py b/src/_cffi_src/openssl/rsa.py new file mode 100644 index 0000000..e920cf2 --- /dev/null +++ b/src/_cffi_src/openssl/rsa.py @@ -0,0 +1,195 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include +""" + +TYPES = """ +typedef ... RSA; +typedef ... BN_GENCB; +static const int RSA_PKCS1_PADDING; +static const int RSA_SSLV23_PADDING; +static const int RSA_NO_PADDING; +static const int RSA_PKCS1_OAEP_PADDING; +static const int RSA_X931_PADDING; +static const int RSA_PKCS1_PSS_PADDING; +static const int RSA_F4; + +static const int Cryptography_HAS_PSS_PADDING; +static const int Cryptography_HAS_MGF1_MD; +static const int Cryptography_HAS_RSA_OAEP_MD; +""" + +FUNCTIONS = """ +RSA *RSA_new(void); +void RSA_free(RSA *); +int RSA_size(const RSA *); +int RSA_generate_key_ex(RSA *, int, BIGNUM *, BN_GENCB *); +int RSA_check_key(const RSA *); +RSA *RSAPublicKey_dup(RSA *); +int RSA_blinding_on(RSA *, BN_CTX *); +void RSA_blinding_off(RSA *); +int RSA_public_encrypt(int, const unsigned char *, unsigned char *, + RSA *, int); +int RSA_private_encrypt(int, const unsigned char *, unsigned char *, + RSA *, int); +int RSA_public_decrypt(int, const unsigned char *, unsigned char *, + RSA *, int); +int RSA_private_decrypt(int, const unsigned char *, unsigned char *, + RSA *, int); +int RSA_print(BIO *, const RSA *, int); +int RSA_verify_PKCS1_PSS(RSA *, const unsigned char *, const EVP_MD *, + const unsigned char *, int); +int RSA_padding_add_PKCS1_PSS(RSA *, unsigned char *, const unsigned char *, + const EVP_MD *, int); +int RSA_padding_add_PKCS1_OAEP(unsigned char *, int, const unsigned char *, + int, const unsigned char *, int); +int RSA_padding_check_PKCS1_OAEP(unsigned char *, int, const unsigned char *, + int, int, const unsigned char *, int); + +/* added in 1.1.0 when the RSA struct was opaqued */ +int RSA_set0_key(RSA *, BIGNUM *, BIGNUM *, BIGNUM *); +int RSA_set0_factors(RSA *, BIGNUM *, BIGNUM *); +int RSA_set0_crt_params(RSA *, BIGNUM *, BIGNUM *, BIGNUM *); +void RSA_get0_key(const RSA *, const BIGNUM **, const BIGNUM **, + const BIGNUM **); +void RSA_get0_factors(const RSA *, const BIGNUM **, const BIGNUM **); +void RSA_get0_crt_params(const RSA *, const BIGNUM **, const BIGNUM **, + const BIGNUM **); +""" + +MACROS = """ +int EVP_PKEY_CTX_set_rsa_padding(EVP_PKEY_CTX *, int); +int EVP_PKEY_CTX_set_rsa_pss_saltlen(EVP_PKEY_CTX *, int); +int EVP_PKEY_CTX_set_rsa_mgf1_md(EVP_PKEY_CTX *, EVP_MD *); + +int EVP_PKEY_CTX_set_rsa_oaep_md(EVP_PKEY_CTX *, EVP_MD *); +""" + +CUSTOMIZATIONS = """ +static const long Cryptography_HAS_PSS_PADDING = 1; + +#if CRYPTOGRAPHY_OPENSSL_101_OR_GREATER +static const long Cryptography_HAS_MGF1_MD = 1; +#else +static const long Cryptography_HAS_MGF1_MD = 0; +int (*EVP_PKEY_CTX_set_rsa_mgf1_md)(EVP_PKEY_CTX *, EVP_MD *) = NULL; +#endif +#if defined(EVP_PKEY_CTX_set_rsa_oaep_md) +static const long Cryptography_HAS_RSA_OAEP_MD = 1; +#else +static const long Cryptography_HAS_RSA_OAEP_MD = 0; +int (*EVP_PKEY_CTX_set_rsa_oaep_md)(EVP_PKEY_CTX *, EVP_MD *) = NULL; +#endif + +/* These functions were added in OpenSSL 1.1.0-pre5 (beta2) */ +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110PRE5 || defined(LIBRESSL_VERSION_NUMBER) +int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) +{ + /* If the fields n and e in r are NULL, the corresponding input + * parameters MUST be non-NULL for n and e. d may be + * left NULL (in case only the public key is used). + */ + if ((r->n == NULL && n == NULL) + || (r->e == NULL && e == NULL)) + return 0; + + if (n != NULL) { + BN_free(r->n); + r->n = n; + } + if (e != NULL) { + BN_free(r->e); + r->e = e; + } + if (d != NULL) { + BN_free(r->d); + r->d = d; + } + + return 1; +} + +int RSA_set0_factors(RSA *r, BIGNUM *p, BIGNUM *q) +{ + /* If the fields p and q in r are NULL, the corresponding input + * parameters MUST be non-NULL. + */ + if ((r->p == NULL && p == NULL) + || (r->q == NULL && q == NULL)) + return 0; + + if (p != NULL) { + BN_free(r->p); + r->p = p; + } + if (q != NULL) { + BN_free(r->q); + r->q = q; + } + + return 1; +} + +int RSA_set0_crt_params(RSA *r, BIGNUM *dmp1, BIGNUM *dmq1, BIGNUM *iqmp) +{ + /* If the fields dmp1, dmq1 and iqmp in r are NULL, the corresponding input + * parameters MUST be non-NULL. + */ + if ((r->dmp1 == NULL && dmp1 == NULL) + || (r->dmq1 == NULL && dmq1 == NULL) + || (r->iqmp == NULL && iqmp == NULL)) + return 0; + + if (dmp1 != NULL) { + BN_free(r->dmp1); + r->dmp1 = dmp1; + } + if (dmq1 != NULL) { + BN_free(r->dmq1); + r->dmq1 = dmq1; + } + if (iqmp != NULL) { + BN_free(r->iqmp); + r->iqmp = iqmp; + } + + return 1; +} + +void RSA_get0_key(const RSA *r, + const BIGNUM **n, const BIGNUM **e, const BIGNUM **d) +{ + if (n != NULL) + *n = r->n; + if (e != NULL) + *e = r->e; + if (d != NULL) + *d = r->d; +} + +void RSA_get0_factors(const RSA *r, const BIGNUM **p, const BIGNUM **q) +{ + if (p != NULL) + *p = r->p; + if (q != NULL) + *q = r->q; +} + +void RSA_get0_crt_params(const RSA *r, + const BIGNUM **dmp1, const BIGNUM **dmq1, + const BIGNUM **iqmp) +{ + if (dmp1 != NULL) + *dmp1 = r->dmp1; + if (dmq1 != NULL) + *dmq1 = r->dmq1; + if (iqmp != NULL) + *iqmp = r->iqmp; +} +#endif +""" diff --git a/src/_cffi_src/openssl/src/osrandom_engine.c b/src/_cffi_src/openssl/src/osrandom_engine.c new file mode 100644 index 0000000..52f55af --- /dev/null +++ b/src/_cffi_src/openssl/src/osrandom_engine.c @@ -0,0 +1,576 @@ +/* osurandom engine + * + * Windows CryptGenRandom() + * macOS >= 10.12 getentropy() + * OpenBSD 5.6+ getentropy() + * other BSD getentropy() if SYS_getentropy is defined + * Linux 3.4.17+ getrandom() with fallback to /dev/urandom + * other /dev/urandom with cached fd + * + * The /dev/urandom, getrandom and getentropy code is derived from Python's + * Python/random.c, written by Antoine Pitrou and Victor Stinner. + * + * Copyright 2001-2016 Python Software Foundation; All Rights Reserved. + */ + +static const char *Cryptography_osrandom_engine_id = "osrandom"; + +/**************************************************************************** + * Windows + */ +#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM +static const char *Cryptography_osrandom_engine_name = "osrandom_engine CryptGenRandom()"; +static HCRYPTPROV hCryptProv = 0; + +static int osrandom_init(ENGINE *e) { + if (hCryptProv != 0) { + return 1; + } + if (CryptAcquireContext(&hCryptProv, NULL, NULL, + PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) { + return 1; + } else { + ERR_Cryptography_OSRandom_error( + CRYPTOGRAPHY_OSRANDOM_F_INIT, + CRYPTOGRAPHY_OSRANDOM_R_CRYPTACQUIRECONTEXT, + __FILE__, __LINE__ + ); + return 0; + } +} + +static int osrandom_rand_bytes(unsigned char *buffer, int size) { + if (hCryptProv == 0) { + return 0; + } + + if (!CryptGenRandom(hCryptProv, (DWORD)size, buffer)) { + ERR_Cryptography_OSRandom_error( + CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, + CRYPTOGRAPHY_OSRANDOM_R_CRYPTGENRANDOM, + __FILE__, __LINE__ + ); + return 0; + } + return 1; +} + +static int osrandom_finish(ENGINE *e) { + if (CryptReleaseContext(hCryptProv, 0)) { + hCryptProv = 0; + return 1; + } else { + ERR_Cryptography_OSRandom_error( + CRYPTOGRAPHY_OSRANDOM_F_FINISH, + CRYPTOGRAPHY_OSRANDOM_R_CRYPTRELEASECONTEXT, + __FILE__, __LINE__ + ); + return 0; + } +} + +static int osrandom_rand_status(void) { + return hCryptProv != 0; +} + +static const char *osurandom_get_implementation(void) { + return "CryptGenRandom"; +} + +#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM */ + +/**************************************************************************** + * BSD getentropy + */ +#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY +static const char *Cryptography_osrandom_engine_name = "osrandom_engine getentropy()"; + +static int osrandom_init(ENGINE *e) { + return 1; +} + +static int osrandom_rand_bytes(unsigned char *buffer, int size) { + int len, res; + while (size > 0) { + /* OpenBSD and macOS restrict maximum buffer size to 256. */ + len = size > 256 ? 256 : size; + res = getentropy(buffer, len); + if (res < 0) { + ERR_Cryptography_OSRandom_error( + CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, + CRYPTOGRAPHY_OSRANDOM_R_GETENTROPY_FAILED, + __FILE__, __LINE__ + ); + return 0; + } + buffer += len; + size -= len; + } + return 1; +} + +static int osrandom_finish(ENGINE *e) { + return 1; +} + +static int osrandom_rand_status(void) { + return 1; +} + +static const char *osurandom_get_implementation(void) { + return "getentropy"; +} +#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY */ + +/**************************************************************************** + * /dev/urandom helpers for all non-BSD Unix platforms + */ +#ifdef CRYPTOGRAPHY_OSRANDOM_NEEDS_DEV_URANDOM + +static struct { + int fd; + dev_t st_dev; + ino_t st_ino; +} urandom_cache = { -1 }; + +/* return -1 on error */ +static int dev_urandom_fd(void) { + int fd, n, flags; + struct stat st; + + /* Check that fd still points to the correct device */ + if (urandom_cache.fd >= 0) { + if (fstat(urandom_cache.fd, &st) + || st.st_dev != urandom_cache.st_dev + || st.st_ino != urandom_cache.st_ino) { + /* Somebody replaced our FD. Invalidate our cache but don't + * close the fd. */ + urandom_cache.fd = -1; + } + } + if (urandom_cache.fd < 0) { + fd = open("/dev/urandom", O_RDONLY); + if (fd < 0) { + goto error; + } + if (fstat(fd, &st)) { + goto error; + } + /* set CLOEXEC flag */ + flags = fcntl(fd, F_GETFD); + if (flags == -1) { + goto error; + } else if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { + goto error; + } + /* Another thread initialized the fd */ + if (urandom_cache.fd >= 0) { + do { + n = close(fd); + } while (n < 0 && errno == EINTR); + return urandom_cache.fd; + } + urandom_cache.st_dev = st.st_dev; + urandom_cache.st_ino = st.st_ino; + urandom_cache.fd = fd; + } + return urandom_cache.fd; + + error: + if (fd != -1) { + do { + n = close(fd); + } while (n < 0 && errno == EINTR); + } + ERR_Cryptography_OSRandom_error( + CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_FD, + CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_OPEN_FAILED, + __FILE__, __LINE__ + ); + return -1; +} + +static int dev_urandom_read(unsigned char *buffer, int size) { + int fd; + ssize_t n; + + fd = dev_urandom_fd(); + if (fd < 0) { + return 0; + } + + while (size > 0) { + do { + n = read(fd, buffer, (size_t)size); + } while (n < 0 && errno == EINTR); + + if (n <= 0) { + ERR_Cryptography_OSRandom_error( + CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_READ, + CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_READ_FAILED, + __FILE__, __LINE__ + ); + return 0; + } + buffer += n; + size -= n; + } + return 1; +} + +static void dev_urandom_close(void) { + if (urandom_cache.fd >= 0) { + int fd, n; + struct stat st; + + if (fstat(urandom_cache.fd, &st) + && st.st_dev == urandom_cache.st_dev + && st.st_ino == urandom_cache.st_ino) { + fd = urandom_cache.fd; + urandom_cache.fd = -1; + do { + n = close(fd); + } while (n < 0 && errno == EINTR); + } + } +} +#endif /* CRYPTOGRAPHY_OSRANDOM_NEEDS_DEV_URANDOM */ + +/**************************************************************************** + * Linux getrandom engine with fallback to dev_urandom + */ + +#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM +static const char *Cryptography_osrandom_engine_name = "osrandom_engine getrandom()"; + +static int getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT; + +static int osrandom_init(ENGINE *e) { + /* We try to detect working getrandom until we succeed. */ + if (getrandom_works != CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS) { + long n; + char dest[1]; + n = syscall(SYS_getrandom, dest, sizeof(dest), GRND_NONBLOCK); + if (n == sizeof(dest)) { + getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS; + } else { + int e = errno; + switch(e) { + case ENOSYS: + /* Fallback: Kernel does not support the syscall. */ + getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK; + break; + case EPERM: + /* Fallback: seccomp prevents syscall */ + getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK; + break; + case EAGAIN: + /* Failure: Kernel CRPNG has not been seeded yet */ + ERR_Cryptography_OSRandom_error( + CRYPTOGRAPHY_OSRANDOM_F_INIT, + CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_EAGAIN, + __FILE__, __LINE__ + ); + getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED; + break; + default: + /* EINTR cannot occur for buflen < 256. */ + ERR_Cryptography_OSRandom_error( + CRYPTOGRAPHY_OSRANDOM_F_INIT, + CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_UNEXPECTED, + "errno", e + ); + getrandom_works = CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED; + break; + } + } + } + + /* fallback to dev urandom */ + if (getrandom_works == CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK) { + int fd = dev_urandom_fd(); + if (fd < 0) { + return 0; + } + } + return 1; +} + +static int osrandom_rand_bytes(unsigned char *buffer, int size) { + long n; + + switch(getrandom_works) { + case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED: + ERR_Cryptography_OSRandom_error( + CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, + CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED, + __FILE__, __LINE__ + ); + return 0; + case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT: + ERR_Cryptography_OSRandom_error( + CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, + CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_NOT_INIT, + __FILE__, __LINE__ + ); + return 0; + case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK: + return dev_urandom_read(buffer, size); + case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS: + while (size > 0) { + do { + n = syscall(SYS_getrandom, buffer, size, GRND_NONBLOCK); + } while (n < 0 && errno == EINTR); + + if (n <= 0) { + ERR_Cryptography_OSRandom_error( + CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES, + CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_FAILED, + __FILE__, __LINE__ + ); + return 0; + } + buffer += n; + size -= n; + } + return 1; + } + return 0; /* unreachable */ +} + +static int osrandom_finish(ENGINE *e) { + dev_urandom_close(); + return 1; +} + +static int osrandom_rand_status(void) { + switch(getrandom_works) { + case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED: + return 0; + case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT: + return 0; + case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK: + return urandom_cache.fd >= 0; + case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS: + return 1; + } + return 0; /* unreachable */ +} + +static const char *osurandom_get_implementation(void) { + switch(getrandom_works) { + case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED: + return ""; + case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT: + return ""; + case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK: + return "/dev/urandom"; + case CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS: + return "getrandom"; + } + return ""; /* unreachable */ +} +#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM */ + +/**************************************************************************** + * dev_urandom engine for all remaining platforms + */ + +#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM +static const char *Cryptography_osrandom_engine_name = "osrandom_engine /dev/urandom"; + +static int osrandom_init(ENGINE *e) { + int fd = dev_urandom_fd(); + if (fd < 0) { + return 0; + } + return 1; +} + +static int osrandom_rand_bytes(unsigned char *buffer, int size) { + return dev_urandom_read(buffer, size); +} + +static int osrandom_finish(ENGINE *e) { + dev_urandom_close(); + return 1; +} + +static int osrandom_rand_status(void) { + return urandom_cache.fd >= 0; +} + +static const char *osurandom_get_implementation(void) { + return "/dev/urandom"; +} +#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM */ + +/**************************************************************************** + * ENGINE boiler plate + */ + +/* This replicates the behavior of the OpenSSL FIPS RNG, which returns a + -1 in the event that there is an error when calling RAND_pseudo_bytes. */ +static int osrandom_pseudo_rand_bytes(unsigned char *buffer, int size) { + int res = osrandom_rand_bytes(buffer, size); + if (res == 0) { + return -1; + } else { + return res; + } +} + +static RAND_METHOD osrandom_rand = { + NULL, + osrandom_rand_bytes, + NULL, + NULL, + osrandom_pseudo_rand_bytes, + osrandom_rand_status, +}; + +static const ENGINE_CMD_DEFN osrandom_cmd_defns[] = { + {CRYPTOGRAPHY_OSRANDOM_GET_IMPLEMENTATION, + "get_implementation", + "Get CPRNG implementation.", + ENGINE_CMD_FLAG_NO_INPUT}, + {0, NULL, NULL, 0} +}; + +static int osrandom_ctrl(ENGINE *e, int cmd, long i, void *p, void (*f) (void)) { + const char *name; + size_t len; + + switch (cmd) { + case CRYPTOGRAPHY_OSRANDOM_GET_IMPLEMENTATION: + /* i: buffer size, p: char* buffer */ + name = osurandom_get_implementation(); + len = strlen(name); + if ((p == NULL) && (i == 0)) { + /* return required buffer len */ + return len; + } + if ((p == NULL) || i < 0 || ((size_t)i <= len)) { + /* no buffer or buffer too small */ + ENGINEerr(ENGINE_F_ENGINE_CTRL, ENGINE_R_INVALID_ARGUMENT); + return 0; + } + strncpy((char *)p, name, len); + return len; + default: + ENGINEerr(ENGINE_F_ENGINE_CTRL, ENGINE_R_CTRL_COMMAND_NOT_IMPLEMENTED); + return 0; + } +} + +/* error reporting */ +#define ERR_FUNC(func) ERR_PACK(0, func, 0) +#define ERR_REASON(reason) ERR_PACK(0, 0, reason) + +static ERR_STRING_DATA CRYPTOGRAPHY_OSRANDOM_lib_name[] = { + {0, "osrandom_engine"}, + {0, NULL} +}; + +static ERR_STRING_DATA CRYPTOGRAPHY_OSRANDOM_str_funcs[] = { + {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_INIT), + "osrandom_init"}, + {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES), + "osrandom_rand_bytes"}, + {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_FINISH), + "osrandom_finish"}, + {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_FD), + "dev_urandom_fd"}, + {ERR_FUNC(CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_READ), + "dev_urandom_read"}, + {0, NULL} +}; + +static ERR_STRING_DATA CRYPTOGRAPHY_OSRANDOM_str_reasons[] = { + {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_CRYPTACQUIRECONTEXT), + "CryptAcquireContext() failed."}, + {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_CRYPTGENRANDOM), + "CryptGenRandom() failed."}, + {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_CRYPTRELEASECONTEXT), + "CryptReleaseContext() failed."}, + {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETENTROPY_FAILED), + "getentropy() failed"}, + {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_OPEN_FAILED), + "open('/dev/urandom') failed."}, + {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_READ_FAILED), + "Reading from /dev/urandom fd failed."}, + {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED), + "getrandom() initialization failed."}, + {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_EAGAIN), + "getrandom() initialization failed with EAGAIN. Most likely Kernel " + "CPRNG is not seeded yet."}, + {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_UNEXPECTED), + "getrandom() initialization failed with unexpected errno."}, + {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_FAILED), + "getrandom() syscall failed."}, + {ERR_REASON(CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_NOT_INIT), + "getrandom() engine was not properly initialized."}, + {0, NULL} +}; + +static int Cryptography_OSRandom_lib_error_code = 0; + +static void ERR_load_Cryptography_OSRandom_strings(void) +{ + if (Cryptography_OSRandom_lib_error_code == 0) { + Cryptography_OSRandom_lib_error_code = ERR_get_next_error_library(); + ERR_load_strings(Cryptography_OSRandom_lib_error_code, + CRYPTOGRAPHY_OSRANDOM_lib_name); + ERR_load_strings(Cryptography_OSRandom_lib_error_code, + CRYPTOGRAPHY_OSRANDOM_str_funcs); + ERR_load_strings(Cryptography_OSRandom_lib_error_code, + CRYPTOGRAPHY_OSRANDOM_str_reasons); + } +} + +static void ERR_Cryptography_OSRandom_error(int function, int reason, + char *file, int line) +{ + ERR_PUT_error(Cryptography_OSRandom_lib_error_code, function, reason, + file, line); +} + +/* Returns 1 if successfully added, 2 if engine has previously been added, + and 0 for error. */ +int Cryptography_add_osrandom_engine(void) { + ENGINE *e; + + ERR_load_Cryptography_OSRandom_strings(); + + e = ENGINE_by_id(Cryptography_osrandom_engine_id); + if (e != NULL) { + ENGINE_free(e); + return 2; + } else { + ERR_clear_error(); + } + + e = ENGINE_new(); + if (e == NULL) { + return 0; + } + if(!ENGINE_set_id(e, Cryptography_osrandom_engine_id) || + !ENGINE_set_name(e, Cryptography_osrandom_engine_name) || + !ENGINE_set_RAND(e, &osrandom_rand) || + !ENGINE_set_init_function(e, osrandom_init) || + !ENGINE_set_finish_function(e, osrandom_finish) || + !ENGINE_set_cmd_defns(e, osrandom_cmd_defns) || + !ENGINE_set_ctrl_function(e, osrandom_ctrl)) { + ENGINE_free(e); + return 0; + } + if (!ENGINE_add(e)) { + ENGINE_free(e); + return 0; + } + if (!ENGINE_free(e)) { + return 0; + } + + return 1; +} diff --git a/src/_cffi_src/openssl/src/osrandom_engine.h b/src/_cffi_src/openssl/src/osrandom_engine.h new file mode 100644 index 0000000..d28ebf3 --- /dev/null +++ b/src/_cffi_src/openssl/src/osrandom_engine.h @@ -0,0 +1,88 @@ +#ifdef _WIN32 + #include +#else + #include + #include + /* for defined(BSD) */ + #include + + #ifdef BSD + /* for SYS_getentropy */ + #include + #endif + + #ifdef __APPLE__ + #include + #endif + + #ifdef __linux__ + /* for SYS_getrandom */ + #include + #ifndef GRND_NONBLOCK + #define GRND_NONBLOCK 0x0001 + #endif /* GRND_NONBLOCK */ + #endif /* __linux__ */ +#endif /* _WIN32 */ + +#define CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM 1 +#define CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY 2 +#define CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM 3 +#define CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM 4 + +#ifndef CRYPTOGRAPHY_OSRANDOM_ENGINE + #if defined(_WIN32) + /* Windows */ + #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_CRYPTGENRANDOM + #elif defined(BSD) && defined(SYS_getentropy) + /* OpenBSD 5.6+ or macOS 10.12+ */ + #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_GETENTROPY + #elif defined(__linux__) && defined(SYS_getrandom) + /* Linux 3.4.17+ */ + #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM + #else + /* Keep this as last entry, fall back to /dev/urandom */ + #define CRYPTOGRAPHY_OSRANDOM_ENGINE CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM + #endif +#endif /* CRYPTOGRAPHY_OSRANDOM_ENGINE */ + +/* Fallbacks need /dev/urandom helper functions. */ +#if CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_GETRANDOM || \ + CRYPTOGRAPHY_OSRANDOM_ENGINE == CRYPTOGRAPHY_OSRANDOM_ENGINE_DEV_URANDOM + #define CRYPTOGRAPHY_OSRANDOM_NEEDS_DEV_URANDOM 1 +#endif + +enum { + CRYPTOGRAPHY_OSRANDOM_GETRANDOM_INIT_FAILED = -2, + CRYPTOGRAPHY_OSRANDOM_GETRANDOM_NOT_INIT, + CRYPTOGRAPHY_OSRANDOM_GETRANDOM_FALLBACK, + CRYPTOGRAPHY_OSRANDOM_GETRANDOM_WORKS +}; + +/* engine ctrl */ +#define CRYPTOGRAPHY_OSRANDOM_GET_IMPLEMENTATION ENGINE_CMD_BASE + +/* error reporting */ +static void ERR_load_Cryptography_OSRandom_strings(void); +static void ERR_Cryptography_OSRandom_error(int function, int reason, + char *file, int line); + +#define CRYPTOGRAPHY_OSRANDOM_F_INIT 100 +#define CRYPTOGRAPHY_OSRANDOM_F_RAND_BYTES 101 +#define CRYPTOGRAPHY_OSRANDOM_F_FINISH 102 +#define CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_FD 300 +#define CRYPTOGRAPHY_OSRANDOM_F_DEV_URANDOM_READ 301 + +#define CRYPTOGRAPHY_OSRANDOM_R_CRYPTACQUIRECONTEXT 100 +#define CRYPTOGRAPHY_OSRANDOM_R_CRYPTGENRANDOM 101 +#define CRYPTOGRAPHY_OSRANDOM_R_CRYPTRELEASECONTEXT 102 + +#define CRYPTOGRAPHY_OSRANDOM_R_GETENTROPY_FAILED 200 + +#define CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_OPEN_FAILED 300 +#define CRYPTOGRAPHY_OSRANDOM_R_DEV_URANDOM_READ_FAILED 301 + +#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED 400 +#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_EAGAIN 401 +#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_INIT_FAILED_UNEXPECTED 402 +#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_FAILED 403 +#define CRYPTOGRAPHY_OSRANDOM_R_GETRANDOM_NOT_INIT 404 diff --git a/src/_cffi_src/openssl/ssl.py b/src/_cffi_src/openssl/ssl.py new file mode 100644 index 0000000..28c14ca --- /dev/null +++ b/src/_cffi_src/openssl/ssl.py @@ -0,0 +1,719 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include + +typedef STACK_OF(SSL_CIPHER) Cryptography_STACK_OF_SSL_CIPHER; +""" + +TYPES = """ +static const long Cryptography_HAS_SSL_ST; +static const long Cryptography_HAS_TLS_ST; +static const long Cryptography_HAS_SSL2; +static const long Cryptography_HAS_SSL3_METHOD; +static const long Cryptography_HAS_TLSv1_1; +static const long Cryptography_HAS_TLSv1_2; +static const long Cryptography_HAS_SECURE_RENEGOTIATION; +static const long Cryptography_HAS_COMPRESSION; +static const long Cryptography_HAS_TLSEXT_STATUS_REQ_CB; +static const long Cryptography_HAS_STATUS_REQ_OCSP_RESP; +static const long Cryptography_HAS_TLSEXT_STATUS_REQ_TYPE; +static const long Cryptography_HAS_GET_SERVER_TMP_KEY; +static const long Cryptography_HAS_SSL_CTX_SET_CLIENT_CERT_ENGINE; +static const long Cryptography_HAS_SSL_CTX_CLEAR_OPTIONS; +static const long Cryptography_HAS_NPN_NEGOTIATED; + +/* Internally invented symbol to tell us if SNI is supported */ +static const long Cryptography_HAS_TLSEXT_HOSTNAME; + +/* Internally invented symbol to tell us if SSL_MODE_RELEASE_BUFFERS is + * supported + */ +static const long Cryptography_HAS_RELEASE_BUFFERS; + +/* Internally invented symbol to tell us if SSL_OP_NO_COMPRESSION is + * supported + */ +static const long Cryptography_HAS_OP_NO_COMPRESSION; + +static const long Cryptography_HAS_SSL_OP_MSIE_SSLV2_RSA_PADDING; +static const long Cryptography_HAS_SSL_SET_SSL_CTX; +static const long Cryptography_HAS_SSL_OP_NO_TICKET; +static const long Cryptography_HAS_NETBSD_D1_METH; +static const long Cryptography_HAS_NEXTPROTONEG; +static const long Cryptography_HAS_ALPN; +static const long Cryptography_HAS_SET_CERT_CB; + +static const long SSL_FILETYPE_PEM; +static const long SSL_FILETYPE_ASN1; +static const long SSL_ERROR_NONE; +static const long SSL_ERROR_ZERO_RETURN; +static const long SSL_ERROR_WANT_READ; +static const long SSL_ERROR_WANT_WRITE; +static const long SSL_ERROR_WANT_X509_LOOKUP; +static const long SSL_ERROR_WANT_CONNECT; +static const long SSL_ERROR_SYSCALL; +static const long SSL_ERROR_SSL; +static const long SSL_SENT_SHUTDOWN; +static const long SSL_RECEIVED_SHUTDOWN; +static const long SSL_OP_NO_SSLv2; +static const long SSL_OP_NO_SSLv3; +static const long SSL_OP_NO_TLSv1; +static const long SSL_OP_NO_TLSv1_1; +static const long SSL_OP_NO_TLSv1_2; +static const long SSL_OP_NO_COMPRESSION; +static const long SSL_OP_SINGLE_DH_USE; +static const long SSL_OP_EPHEMERAL_RSA; +static const long SSL_OP_MICROSOFT_SESS_ID_BUG; +static const long SSL_OP_NETSCAPE_CHALLENGE_BUG; +static const long SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG; +static const long SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG; +static const long SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER; +static const long SSL_OP_MSIE_SSLV2_RSA_PADDING; +static const long SSL_OP_SSLEAY_080_CLIENT_DH_BUG; +static const long SSL_OP_TLS_D5_BUG; +static const long SSL_OP_TLS_BLOCK_PADDING_BUG; +static const long SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS; +static const long SSL_OP_CIPHER_SERVER_PREFERENCE; +static const long SSL_OP_TLS_ROLLBACK_BUG; +static const long SSL_OP_PKCS1_CHECK_1; +static const long SSL_OP_PKCS1_CHECK_2; +static const long SSL_OP_NETSCAPE_CA_DN_BUG; +static const long SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG; +static const long SSL_OP_NO_QUERY_MTU; +static const long SSL_OP_COOKIE_EXCHANGE; +static const long SSL_OP_NO_TICKET; +static const long SSL_OP_ALL; +static const long SSL_OP_SINGLE_ECDH_USE; +static const long SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION; +static const long SSL_OP_LEGACY_SERVER_CONNECT; +static const long SSL_VERIFY_PEER; +static const long SSL_VERIFY_FAIL_IF_NO_PEER_CERT; +static const long SSL_VERIFY_CLIENT_ONCE; +static const long SSL_VERIFY_NONE; +static const long SSL_SESS_CACHE_OFF; +static const long SSL_SESS_CACHE_CLIENT; +static const long SSL_SESS_CACHE_SERVER; +static const long SSL_SESS_CACHE_BOTH; +static const long SSL_SESS_CACHE_NO_AUTO_CLEAR; +static const long SSL_SESS_CACHE_NO_INTERNAL_LOOKUP; +static const long SSL_SESS_CACHE_NO_INTERNAL_STORE; +static const long SSL_SESS_CACHE_NO_INTERNAL; +static const long SSL_ST_CONNECT; +static const long SSL_ST_ACCEPT; +static const long SSL_ST_MASK; +static const long SSL_ST_INIT; +static const long SSL_ST_BEFORE; +static const long SSL_ST_OK; +static const long SSL_ST_RENEGOTIATE; +static const long SSL_CB_LOOP; +static const long SSL_CB_EXIT; +static const long SSL_CB_READ; +static const long SSL_CB_WRITE; +static const long SSL_CB_ALERT; +static const long SSL_CB_READ_ALERT; +static const long SSL_CB_WRITE_ALERT; +static const long SSL_CB_ACCEPT_LOOP; +static const long SSL_CB_ACCEPT_EXIT; +static const long SSL_CB_CONNECT_LOOP; +static const long SSL_CB_CONNECT_EXIT; +static const long SSL_CB_HANDSHAKE_START; +static const long SSL_CB_HANDSHAKE_DONE; +static const long SSL_MODE_RELEASE_BUFFERS; +static const long SSL_MODE_ENABLE_PARTIAL_WRITE; +static const long SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER; +static const long SSL_MODE_AUTO_RETRY; +static const long SSL3_RANDOM_SIZE; +static const long TLS_ST_BEFORE; +static const long TLS_ST_OK; + +static const long OPENSSL_NPN_NEGOTIATED; + +typedef ... SSL_METHOD; +typedef ... SSL_CTX; + +typedef ... SSL_SESSION; + +typedef ... SSL; + +static const long TLSEXT_NAMETYPE_host_name; +static const long TLSEXT_STATUSTYPE_ocsp; + +typedef ... SSL_CIPHER; +typedef ... Cryptography_STACK_OF_SSL_CIPHER; +typedef ... COMP_METHOD; +""" + +FUNCTIONS = """ +/* SSL */ +const char *SSL_state_string_long(const SSL *); +SSL_SESSION *SSL_get1_session(SSL *); +int SSL_set_session(SSL *, SSL_SESSION *); +int SSL_get_verify_mode(const SSL *); +void SSL_set_verify(SSL *, int, int (*)(int, X509_STORE_CTX *)); +void SSL_set_verify_depth(SSL *, int); +int SSL_get_verify_depth(const SSL *); +int (*SSL_get_verify_callback(const SSL *))(int, X509_STORE_CTX *); +void SSL_set_info_callback(SSL *ssl, void (*)(const SSL *, int, int)); +void (*SSL_get_info_callback(const SSL *))(const SSL *, int, int); +SSL *SSL_new(SSL_CTX *); +void SSL_free(SSL *); +int SSL_set_fd(SSL *, int); +SSL_CTX *SSL_get_SSL_CTX(const SSL *); +SSL_CTX *SSL_set_SSL_CTX(SSL *, SSL_CTX *); +BIO *SSL_get_rbio(const SSL *); +BIO *SSL_get_wbio(const SSL *); +void SSL_set_bio(SSL *, BIO *, BIO *); +void SSL_set_connect_state(SSL *); +void SSL_set_accept_state(SSL *); +void SSL_set_shutdown(SSL *, int); +int SSL_get_shutdown(const SSL *); +int SSL_pending(const SSL *); +int SSL_write(SSL *, const void *, int); +int SSL_read(SSL *, void *, int); +int SSL_peek(SSL *, void *, int); +X509 *SSL_get_peer_certificate(const SSL *); +int SSL_get_ex_data_X509_STORE_CTX_idx(void); + +int SSL_use_certificate(SSL *, X509 *); +int SSL_use_certificate_ASN1(SSL *, const unsigned char *, int); +int SSL_use_certificate_file(SSL *, const char *, int); +int SSL_use_PrivateKey(SSL *, EVP_PKEY *); +int SSL_use_PrivateKey_ASN1(int, SSL *, const unsigned char *, long); +int SSL_use_PrivateKey_file(SSL *, const char *, int); +int SSL_check_private_key(const SSL *); + +Cryptography_STACK_OF_X509 *SSL_get_peer_cert_chain(const SSL *); +Cryptography_STACK_OF_X509_NAME *SSL_get_client_CA_list(const SSL *); + +int SSL_get_error(const SSL *, int); +int SSL_do_handshake(SSL *); +int SSL_shutdown(SSL *); +int SSL_renegotiate(SSL *); +int SSL_renegotiate_pending(SSL *); +const char *SSL_get_cipher_list(const SSL *, int); +Cryptography_STACK_OF_SSL_CIPHER *SSL_get_ciphers(const SSL *); + +/* context */ +void SSL_CTX_free(SSL_CTX *); +long SSL_CTX_set_timeout(SSL_CTX *, long); +int SSL_CTX_set_default_verify_paths(SSL_CTX *); +void SSL_CTX_set_verify(SSL_CTX *, int, int (*)(int, X509_STORE_CTX *)); +void SSL_CTX_set_verify_depth(SSL_CTX *, int); +int (*SSL_CTX_get_verify_callback(const SSL_CTX *))(int, X509_STORE_CTX *); +int SSL_CTX_get_verify_mode(const SSL_CTX *); +int SSL_CTX_get_verify_depth(const SSL_CTX *); +int SSL_CTX_set_cipher_list(SSL_CTX *, const char *); +int SSL_CTX_load_verify_locations(SSL_CTX *, const char *, const char *); +void SSL_CTX_set_default_passwd_cb(SSL_CTX *, pem_password_cb *); +void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *, void *); +int SSL_CTX_use_certificate(SSL_CTX *, X509 *); +int SSL_CTX_use_certificate_ASN1(SSL_CTX *, int, const unsigned char *); +int SSL_CTX_use_certificate_file(SSL_CTX *, const char *, int); +int SSL_CTX_use_certificate_chain_file(SSL_CTX *, const char *); +int SSL_CTX_use_PrivateKey(SSL_CTX *, EVP_PKEY *); +int SSL_CTX_use_PrivateKey_ASN1(int, SSL_CTX *, const unsigned char *, long); +int SSL_CTX_use_PrivateKey_file(SSL_CTX *, const char *, int); +int SSL_CTX_check_private_key(const SSL_CTX *); +void SSL_CTX_set_cert_verify_callback(SSL_CTX *, + int (*)(X509_STORE_CTX *,void *), + void *); +int SSL_CTX_set_session_id_context(SSL_CTX *, const unsigned char *, + unsigned int); + +void SSL_CTX_set_cert_store(SSL_CTX *, X509_STORE *); +X509_STORE *SSL_CTX_get_cert_store(const SSL_CTX *); +int SSL_CTX_add_client_CA(SSL_CTX *, X509 *); + +void SSL_CTX_set_client_CA_list(SSL_CTX *, Cryptography_STACK_OF_X509_NAME *); + +void SSL_CTX_set_info_callback(SSL_CTX *, void (*)(const SSL *, int, int)); +void (*SSL_CTX_get_info_callback(SSL_CTX *))(const SSL *, int, int); + +/* SSL_SESSION */ +void SSL_SESSION_free(SSL_SESSION *); + +/* Information about actually used cipher */ +const char *SSL_CIPHER_get_name(const SSL_CIPHER *); +int SSL_CIPHER_get_bits(const SSL_CIPHER *, int *); + +size_t SSL_get_finished(const SSL *, void *, size_t); +size_t SSL_get_peer_finished(const SSL *, void *, size_t); +Cryptography_STACK_OF_X509_NAME *SSL_load_client_CA_file(const char *); + +const char *SSL_get_servername(const SSL *, const int); +""" + +MACROS = """ +/* Function signature changed to const char * in 1.1.0 */ +const char *SSL_CIPHER_get_version(const SSL_CIPHER *); +/* These became macros in 1.1.0 */ +int SSL_library_init(void); +void SSL_load_error_strings(void); + +/* these CRYPTO_EX_DATA functions became macros in 1.1.0 */ +int SSL_get_ex_new_index(long, void *, CRYPTO_EX_new *, CRYPTO_EX_dup *, + CRYPTO_EX_free *); +int SSL_set_ex_data(SSL *, int, void *); +int SSL_CTX_get_ex_new_index(long, void *, CRYPTO_EX_new *, CRYPTO_EX_dup *, + CRYPTO_EX_free *); +int SSL_CTX_set_ex_data(SSL_CTX *, int, void *); + +SSL_SESSION *SSL_get_session(const SSL *); +const unsigned char *SSL_SESSION_get_id(const SSL_SESSION *, unsigned int *); + +/* not a macro, but older OpenSSLs don't pass the args as const */ +char *SSL_CIPHER_description(const SSL_CIPHER *, char *, int); +int SSL_SESSION_print(BIO *, const SSL_SESSION *); + +/* not macros, but will be conditionally bound so can't live in functions */ +const COMP_METHOD *SSL_get_current_compression(SSL *); +const COMP_METHOD *SSL_get_current_expansion(SSL *); +const char *SSL_COMP_get_name(const COMP_METHOD *); +int SSL_CTX_set_client_cert_engine(SSL_CTX *, ENGINE *); + +unsigned long SSL_set_mode(SSL *, unsigned long); +unsigned long SSL_get_mode(SSL *); + +unsigned long SSL_set_options(SSL *, unsigned long); +unsigned long SSL_get_options(SSL *); + +void SSL_set_app_data(SSL *, char *); +char * SSL_get_app_data(SSL *); +void SSL_set_read_ahead(SSL *, int); + +int SSL_want_read(const SSL *); +int SSL_want_write(const SSL *); + +long SSL_total_renegotiations(SSL *); +long SSL_get_secure_renegotiation_support(SSL *); + +/* Defined as unsigned long because SSL_OP_ALL is greater than signed 32-bit + and Windows defines long as 32-bit. */ +unsigned long SSL_CTX_set_options(SSL_CTX *, unsigned long); +unsigned long SSL_CTX_clear_options(SSL_CTX *, unsigned long); +unsigned long SSL_CTX_get_options(SSL_CTX *); +unsigned long SSL_CTX_set_mode(SSL_CTX *, unsigned long); +unsigned long SSL_CTX_get_mode(SSL_CTX *); +unsigned long SSL_CTX_set_session_cache_mode(SSL_CTX *, unsigned long); +unsigned long SSL_CTX_get_session_cache_mode(SSL_CTX *); +unsigned long SSL_CTX_set_tmp_dh(SSL_CTX *, DH *); +unsigned long SSL_CTX_set_tmp_ecdh(SSL_CTX *, EC_KEY *); +unsigned long SSL_CTX_add_extra_chain_cert(SSL_CTX *, X509 *); + +/*- These aren't macros these functions are all const X on openssl > 1.0.x -*/ + +/* methods */ + +/* + * TLSv1_1 and TLSv1_2 are recent additions. Only sufficiently new versions of + * OpenSSL support them. + */ +const SSL_METHOD *TLSv1_1_method(void); +const SSL_METHOD *TLSv1_1_server_method(void); +const SSL_METHOD *TLSv1_1_client_method(void); + +const SSL_METHOD *TLSv1_2_method(void); +const SSL_METHOD *TLSv1_2_server_method(void); +const SSL_METHOD *TLSv1_2_client_method(void); + +const SSL_METHOD *SSLv3_method(void); +const SSL_METHOD *SSLv3_server_method(void); +const SSL_METHOD *SSLv3_client_method(void); + +const SSL_METHOD *TLSv1_method(void); +const SSL_METHOD *TLSv1_server_method(void); +const SSL_METHOD *TLSv1_client_method(void); + +const SSL_METHOD *DTLSv1_method(void); +const SSL_METHOD *DTLSv1_server_method(void); +const SSL_METHOD *DTLSv1_client_method(void); + +const SSL_METHOD *SSLv23_method(void); +const SSL_METHOD *SSLv23_server_method(void); +const SSL_METHOD *SSLv23_client_method(void); + +/*- These aren't macros these arguments are all const X on openssl > 1.0.x -*/ +SSL_CTX *SSL_CTX_new(SSL_METHOD *); +long SSL_CTX_get_timeout(const SSL_CTX *); + +const SSL_CIPHER *SSL_get_current_cipher(const SSL *); +const char *SSL_get_version(const SSL *); +int SSL_version(const SSL *); + +void *SSL_CTX_get_ex_data(const SSL_CTX *, int); +void *SSL_get_ex_data(const SSL *, int); + +void SSL_set_tlsext_host_name(SSL *, char *); +void SSL_CTX_set_tlsext_servername_callback( + SSL_CTX *, + int (*)(const SSL *, int *, void *)); +void SSL_CTX_set_tlsext_servername_arg( + SSL_CTX *, void *); + +long SSL_set_tlsext_status_ocsp_resp(SSL *, unsigned char *, int); +long SSL_get_tlsext_status_ocsp_resp(SSL *, const unsigned char **); +long SSL_set_tlsext_status_type(SSL *, long); +long SSL_CTX_set_tlsext_status_cb(SSL_CTX *, int(*)(SSL *, void *)); +long SSL_CTX_set_tlsext_status_arg(SSL_CTX *, void *); + +long SSL_session_reused(SSL *); + +/* NPN APIs were introduced in OpenSSL 1.0.1. To continue to support earlier + * versions some special handling of these is necessary. + */ +void SSL_CTX_set_next_protos_advertised_cb(SSL_CTX *, + int (*)(SSL *, + const unsigned char **, + unsigned int *, + void *), + void *); +void SSL_CTX_set_next_proto_select_cb(SSL_CTX *, + int (*)(SSL *, + unsigned char **, + unsigned char *, + const unsigned char *, + unsigned int, + void *), + void *); +int SSL_select_next_proto(unsigned char **, unsigned char *, + const unsigned char *, unsigned int, + const unsigned char *, unsigned int); +void SSL_get0_next_proto_negotiated(const SSL *, + const unsigned char **, unsigned *); + +int sk_SSL_CIPHER_num(Cryptography_STACK_OF_SSL_CIPHER *); +const SSL_CIPHER *sk_SSL_CIPHER_value(Cryptography_STACK_OF_SSL_CIPHER *, int); + +/* ALPN APIs were introduced in OpenSSL 1.0.2. To continue to support earlier + * versions some special handling of these is necessary. + */ +int SSL_CTX_set_alpn_protos(SSL_CTX *, const unsigned char *, unsigned); +int SSL_set_alpn_protos(SSL *, const unsigned char *, unsigned); +void SSL_CTX_set_alpn_select_cb(SSL_CTX *, + int (*) (SSL *, + const unsigned char **, + unsigned char *, + const unsigned char *, + unsigned int, + void *), + void *); +void SSL_get0_alpn_selected(const SSL *, const unsigned char **, unsigned *); + +long SSL_get_server_tmp_key(SSL *, EVP_PKEY **); + +/* SSL_CTX_set_cert_cb is introduced in OpenSSL 1.0.2. To continue to support + * earlier versions some special handling of these is necessary. + */ +void SSL_CTX_set_cert_cb(SSL_CTX *, int (*)(SSL *, void *), void *); +void SSL_set_cert_cb(SSL *, int (*)(SSL *, void *), void *); + +/* Added in 1.0.2 */ +const SSL_METHOD *SSL_CTX_get_ssl_method(SSL_CTX *); +/* Added in 1.0.1 */ +int SSL_SESSION_set1_id_context(SSL_SESSION *, const unsigned char *, + unsigned int); +/* Added in 1.1.0 for the great opaquing of structs */ +size_t SSL_SESSION_get_master_key(const SSL_SESSION *, unsigned char *, + size_t); +size_t SSL_get_client_random(const SSL *, unsigned char *, size_t); +size_t SSL_get_server_random(const SSL *, unsigned char *, size_t); + +long SSL_CTX_sess_number(SSL_CTX *); +long SSL_CTX_sess_connect(SSL_CTX *); +long SSL_CTX_sess_connect_good(SSL_CTX *); +long SSL_CTX_sess_connect_renegotiate(SSL_CTX *); +long SSL_CTX_sess_accept(SSL_CTX *); +long SSL_CTX_sess_accept_good(SSL_CTX *); +long SSL_CTX_sess_accept_renegotiate(SSL_CTX *); +long SSL_CTX_sess_hits(SSL_CTX *); +long SSL_CTX_sess_cb_hits(SSL_CTX *); +long SSL_CTX_sess_misses(SSL_CTX *); +long SSL_CTX_sess_timeouts(SSL_CTX *); +long SSL_CTX_sess_cache_full(SSL_CTX *); +""" + +CUSTOMIZATIONS = """ +/* Added in 1.0.1 but we need it in all versions now due to the great + opaquing. */ +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_101 +/* from ssl.h */ +#define SSL_F_SSL_SESSION_SET1_ID_CONTEXT 312 +#define SSL_R_SSL_SESSION_ID_CONTEXT_TOO_LONG 273 +/* from ssl/ssl_sess.c */ +int SSL_SESSION_set1_id_context(SSL_SESSION *s, const unsigned char *sid_ctx, + unsigned int sid_ctx_len) +{ + if (sid_ctx_len > SSL_MAX_SID_CTX_LENGTH) { + SSLerr(SSL_F_SSL_SESSION_SET1_ID_CONTEXT, + SSL_R_SSL_SESSION_ID_CONTEXT_TOO_LONG); + return 0; + } + s->sid_ctx_length = sid_ctx_len; + memcpy(s->sid_ctx, sid_ctx, sid_ctx_len); + + return 1; +} +#endif + +/* Added in 1.0.2 but we need it in all versions now due to the great + opaquing. */ +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_102 || defined(LIBRESSL_VERSION_NUMBER) +/* from ssl/ssl_lib.c */ +const SSL_METHOD *SSL_CTX_get_ssl_method(SSL_CTX *ctx) { + return ctx->method; +} +#endif + +/* Added in 1.1.0 in the great opaquing, but we need to define it for older + OpenSSLs. Such is our burden. */ +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 || defined(LIBRESSL_VERSION_NUMBER) +/* from ssl/ssl_lib.c */ +size_t SSL_get_client_random(const SSL *ssl, unsigned char *out, size_t outlen) +{ + if (outlen == 0) + return sizeof(ssl->s3->client_random); + if (outlen > sizeof(ssl->s3->client_random)) + outlen = sizeof(ssl->s3->client_random); + memcpy(out, ssl->s3->client_random, outlen); + return outlen; +} +/* Added in 1.1.0 as well */ +/* from ssl/ssl_lib.c */ +size_t SSL_get_server_random(const SSL *ssl, unsigned char *out, size_t outlen) +{ + if (outlen == 0) + return sizeof(ssl->s3->server_random); + if (outlen > sizeof(ssl->s3->server_random)) + outlen = sizeof(ssl->s3->server_random); + memcpy(out, ssl->s3->server_random, outlen); + return outlen; +} +/* Added in 1.1.0 as well */ +/* from ssl/ssl_lib.c */ +size_t SSL_SESSION_get_master_key(const SSL_SESSION *session, + unsigned char *out, size_t outlen) +{ + if (session->master_key_length < 0) { + /* Should never happen */ + return 0; + } + if (outlen == 0) + return session->master_key_length; + if (outlen > (size_t)session->master_key_length) + outlen = session->master_key_length; + memcpy(out, session->master_key, outlen); + return outlen; +} +#endif + +static const long Cryptography_HAS_SECURE_RENEGOTIATION = 1; + +/* Cryptography now compiles out all SSLv2 bindings. This exists to allow + * clients that use it to check for SSLv2 support to keep functioning as + * expected. + */ +static const long Cryptography_HAS_SSL2 = 0; + +#ifdef OPENSSL_NO_SSL3_METHOD +static const long Cryptography_HAS_SSL3_METHOD = 0; +SSL_METHOD* (*SSLv3_method)(void) = NULL; +SSL_METHOD* (*SSLv3_client_method)(void) = NULL; +SSL_METHOD* (*SSLv3_server_method)(void) = NULL; +#else +static const long Cryptography_HAS_SSL3_METHOD = 1; +#endif + +static const long Cryptography_HAS_TLSEXT_HOSTNAME = 1; +static const long Cryptography_HAS_TLSEXT_STATUS_REQ_CB = 1; +static const long Cryptography_HAS_STATUS_REQ_OCSP_RESP = 1; +static const long Cryptography_HAS_TLSEXT_STATUS_REQ_TYPE = 1; + +#ifdef SSL_MODE_RELEASE_BUFFERS +static const long Cryptography_HAS_RELEASE_BUFFERS = 1; +#else +static const long Cryptography_HAS_RELEASE_BUFFERS = 0; +const long SSL_MODE_RELEASE_BUFFERS = 0; +#endif + +#ifdef SSL_OP_NO_COMPRESSION +static const long Cryptography_HAS_OP_NO_COMPRESSION = 1; +#else +static const long Cryptography_HAS_OP_NO_COMPRESSION = 0; +const long SSL_OP_NO_COMPRESSION = 0; +#endif + +#ifdef SSL_OP_NO_TLSv1_1 +static const long Cryptography_HAS_TLSv1_1 = 1; +#else +static const long Cryptography_HAS_TLSv1_1 = 0; +static const long SSL_OP_NO_TLSv1_1 = 0; +SSL_METHOD* (*TLSv1_1_method)(void) = NULL; +SSL_METHOD* (*TLSv1_1_client_method)(void) = NULL; +SSL_METHOD* (*TLSv1_1_server_method)(void) = NULL; +#endif + +#ifdef SSL_OP_NO_TLSv1_2 +static const long Cryptography_HAS_TLSv1_2 = 1; +#else +static const long Cryptography_HAS_TLSv1_2 = 0; +static const long SSL_OP_NO_TLSv1_2 = 0; +SSL_METHOD* (*TLSv1_2_method)(void) = NULL; +SSL_METHOD* (*TLSv1_2_client_method)(void) = NULL; +SSL_METHOD* (*TLSv1_2_server_method)(void) = NULL; +#endif + +#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING +static const long Cryptography_HAS_SSL_OP_MSIE_SSLV2_RSA_PADDING = 1; +#else +static const long Cryptography_HAS_SSL_OP_MSIE_SSLV2_RSA_PADDING = 0; +const long SSL_OP_MSIE_SSLV2_RSA_PADDING = 0; +#endif + +#ifdef OPENSSL_NO_EC +long (*SSL_CTX_set_tmp_ecdh)(SSL_CTX *, EC_KEY *) = NULL; +#endif + +#ifdef SSL_OP_NO_TICKET +static const long Cryptography_HAS_SSL_OP_NO_TICKET = 1; +#else +static const long Cryptography_HAS_SSL_OP_NO_TICKET = 0; +const long SSL_OP_NO_TICKET = 0; +#endif + +static const long Cryptography_HAS_SSL_SET_SSL_CTX = 1; + +/* NetBSD shipped without including d1_meth.c. This workaround checks to see + if the version of NetBSD we're currently running on is old enough to + have the bug and provides an empty implementation so we can link and + then remove the function from the ffi object. */ +#ifdef __NetBSD__ +# include +# if (__NetBSD_Version__ < 699003800) +static const long Cryptography_HAS_NETBSD_D1_METH = 0; +const SSL_METHOD *DTLSv1_method(void) { + return NULL; +} +# else +static const long Cryptography_HAS_NETBSD_D1_METH = 1; +# endif +#else +static const long Cryptography_HAS_NETBSD_D1_METH = 1; +#endif + +/* Because OPENSSL defines macros that claim lack of support for things, rather + * than macros that claim support for things, we need to do a version check in + * addition to a definition check. NPN was added in 1.0.1: for any version + * before that, there is no compatibility. + */ +#if defined(OPENSSL_NO_NEXTPROTONEG) || CRYPTOGRAPHY_OPENSSL_LESS_THAN_101 +static const long Cryptography_HAS_NEXTPROTONEG = 0; +void (*SSL_CTX_set_next_protos_advertised_cb)(SSL_CTX *, + int (*)(SSL *, + const unsigned char **, + unsigned int *, + void *), + void *) = NULL; +void (*SSL_CTX_set_next_proto_select_cb)(SSL_CTX *, + int (*)(SSL *, + unsigned char **, + unsigned char *, + const unsigned char *, + unsigned int, + void *), + void *) = NULL; +int (*SSL_select_next_proto)(unsigned char **, unsigned char *, + const unsigned char *, unsigned int, + const unsigned char *, unsigned int) = NULL; +void (*SSL_get0_next_proto_negotiated)(const SSL *, + const unsigned char **, + unsigned *) = NULL; +#else +static const long Cryptography_HAS_NEXTPROTONEG = 1; +#endif + +/* ALPN was added in OpenSSL 1.0.2. */ +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_102 && !defined(LIBRESSL_VERSION_NUMBER) +int (*SSL_CTX_set_alpn_protos)(SSL_CTX *, + const unsigned char *, + unsigned) = NULL; +int (*SSL_set_alpn_protos)(SSL *, const unsigned char *, unsigned) = NULL; +void (*SSL_CTX_set_alpn_select_cb)(SSL_CTX *, + int (*) (SSL *, + const unsigned char **, + unsigned char *, + const unsigned char *, + unsigned int, + void *), + void *) = NULL; +void (*SSL_get0_alpn_selected)(const SSL *, + const unsigned char **, + unsigned *) = NULL; +static const long Cryptography_HAS_ALPN = 0; +#else +static const long Cryptography_HAS_ALPN = 1; +#endif + +/* SSL_CTX_set_cert_cb was added in OpenSSL 1.0.2. */ +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_102 || defined(LIBRESSL_VERSION_NUMBER) +void (*SSL_CTX_set_cert_cb)(SSL_CTX *, int (*)(SSL *, void *), void *) = NULL; +void (*SSL_set_cert_cb)(SSL *, int (*)(SSL *, void *), void *) = NULL; +static const long Cryptography_HAS_SET_CERT_CB = 0; +#else +static const long Cryptography_HAS_SET_CERT_CB = 1; +#endif + + +/* In OpenSSL 1.0.2i+ the handling of COMP_METHOD when OPENSSL_NO_COMP was + changed and we no longer need to typedef void */ +#if (defined(OPENSSL_NO_COMP) && CRYPTOGRAPHY_OPENSSL_LESS_THAN_102I) || \ + defined(LIBRESSL_VERSION_NUMBER) +static const long Cryptography_HAS_COMPRESSION = 0; +typedef void COMP_METHOD; +#else +static const long Cryptography_HAS_COMPRESSION = 1; +#endif + +#if defined(SSL_CTRL_GET_SERVER_TMP_KEY) +static const long Cryptography_HAS_GET_SERVER_TMP_KEY = 1; +#else +static const long Cryptography_HAS_GET_SERVER_TMP_KEY = 0; +long (*SSL_get_server_tmp_key)(SSL *, EVP_PKEY **) = NULL; +#endif + +static const long Cryptography_HAS_SSL_CTX_SET_CLIENT_CERT_ENGINE = 1; + +static const long Cryptography_HAS_SSL_CTX_CLEAR_OPTIONS = 1; + +/* in OpenSSL 1.1.0 the SSL_ST values were renamed to TLS_ST and several were + removed */ +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 || defined(LIBRESSL_VERSION_NUMBER) +static const long Cryptography_HAS_SSL_ST = 1; +#else +static const long Cryptography_HAS_SSL_ST = 0; +static const long SSL_ST_BEFORE = 0; +static const long SSL_ST_OK = 0; +static const long SSL_ST_INIT = 0; +static const long SSL_ST_RENEGOTIATE = 0; +#endif +#if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER && !defined(LIBRESSL_VERSION_NUMBER) +static const long Cryptography_HAS_TLS_ST = 1; +#else +static const long Cryptography_HAS_TLS_ST = 0; +static const long TLS_ST_BEFORE = 0; +static const long TLS_ST_OK = 0; +#endif + +/* This define is available in 1.0.1+ so we can remove this when we drop + support for 1.0.0 */ +#ifdef OPENSSL_NPN_NEGOTIATED +static const long Cryptography_HAS_NPN_NEGOTIATED = 1; +#else +static const long OPENSSL_NPN_NEGOTIATED = -1; +static const long Cryptography_HAS_NPN_NEGOTIATED = 0; +#endif +""" diff --git a/src/_cffi_src/openssl/x509.py b/src/_cffi_src/openssl/x509.py new file mode 100644 index 0000000..d3a97a2 --- /dev/null +++ b/src/_cffi_src/openssl/x509.py @@ -0,0 +1,443 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include + +/* + * This is part of a work-around for the difficulty cffi has in dealing with + * `STACK_OF(foo)` as the name of a type. We invent a new, simpler name that + * will be an alias for this type and use the alias throughout. This works + * together with another opaque typedef for the same name in the TYPES section. + * Note that the result is an opaque type. + */ +typedef STACK_OF(X509) Cryptography_STACK_OF_X509; +typedef STACK_OF(X509_CRL) Cryptography_STACK_OF_X509_CRL; +typedef STACK_OF(X509_REVOKED) Cryptography_STACK_OF_X509_REVOKED; +""" + +TYPES = """ +typedef ... Cryptography_STACK_OF_X509; +typedef ... Cryptography_STACK_OF_X509_CRL; +typedef ... Cryptography_STACK_OF_X509_REVOKED; + +typedef struct { + ASN1_OBJECT *algorithm; + ...; +} X509_ALGOR; + +typedef ... X509_ATTRIBUTE; +typedef ... X509_CINF; +typedef ... X509_EXTENSION; +typedef ... X509_EXTENSIONS; +typedef ... X509_REQ; +typedef ... X509_REQ_INFO; +typedef ... X509_REVOKED; +typedef ... X509_CRL_INFO; +typedef ... X509_CRL; +typedef ... X509; + +typedef ... NETSCAPE_SPKI; + +typedef ... PKCS8_PRIV_KEY_INFO; + +static const int X509_FLAG_COMPAT; +static const int X509_FLAG_NO_HEADER; +static const int X509_FLAG_NO_VERSION; +static const int X509_FLAG_NO_SERIAL; +static const int X509_FLAG_NO_SIGNAME; +static const int X509_FLAG_NO_ISSUER; +static const int X509_FLAG_NO_VALIDITY; +static const int X509_FLAG_NO_SUBJECT; +static const int X509_FLAG_NO_PUBKEY; +static const int X509_FLAG_NO_EXTENSIONS; +static const int X509_FLAG_NO_SIGDUMP; +static const int X509_FLAG_NO_AUX; +static const int X509_FLAG_NO_ATTRIBUTES; + +static const int XN_FLAG_SEP_MASK; +static const int XN_FLAG_COMPAT; +static const int XN_FLAG_SEP_COMMA_PLUS; +static const int XN_FLAG_SEP_CPLUS_SPC; +static const int XN_FLAG_SEP_SPLUS_SPC; +static const int XN_FLAG_SEP_MULTILINE; +static const int XN_FLAG_DN_REV; +static const int XN_FLAG_FN_MASK; +static const int XN_FLAG_FN_SN; +static const int XN_FLAG_FN_LN; +static const int XN_FLAG_FN_OID; +static const int XN_FLAG_FN_NONE; +static const int XN_FLAG_SPC_EQ; +static const int XN_FLAG_DUMP_UNKNOWN_FIELDS; +static const int XN_FLAG_FN_ALIGN; +static const int XN_FLAG_RFC2253; +static const int XN_FLAG_ONELINE; +static const int XN_FLAG_MULTILINE; +""" + +FUNCTIONS = """ +X509 *X509_new(void); +void X509_free(X509 *); +X509 *X509_dup(X509 *); +int X509_cmp(const X509 *, const X509 *); + +int X509_print_ex(BIO *, X509 *, unsigned long, unsigned long); + +int X509_set_version(X509 *, long); + +EVP_PKEY *X509_get_pubkey(X509 *); +int X509_set_pubkey(X509 *, EVP_PKEY *); + +unsigned char *X509_alias_get0(X509 *, int *); +int X509_sign(X509 *, EVP_PKEY *, const EVP_MD *); + +int X509_digest(const X509 *, const EVP_MD *, unsigned char *, unsigned int *); + +ASN1_TIME *X509_gmtime_adj(ASN1_TIME *, long); + +unsigned long X509_subject_name_hash(X509 *); + +int X509_set_subject_name(X509 *, X509_NAME *); + +int X509_set_issuer_name(X509 *, X509_NAME *); + +int X509_add_ext(X509 *, X509_EXTENSION *, int); +X509_EXTENSION *X509_delete_ext(X509 *, int); +X509_EXTENSION *X509_EXTENSION_dup(X509_EXTENSION *); + +ASN1_OBJECT *X509_EXTENSION_get_object(X509_EXTENSION *); +void X509_EXTENSION_free(X509_EXTENSION *); + +int i2d_X509(X509 *, unsigned char **); + +int X509_REQ_set_version(X509_REQ *, long); +X509_REQ *X509_REQ_new(void); +void X509_REQ_free(X509_REQ *); +int X509_REQ_set_pubkey(X509_REQ *, EVP_PKEY *); +int X509_REQ_set_subject_name(X509_REQ *, X509_NAME *); +int X509_REQ_sign(X509_REQ *, EVP_PKEY *, const EVP_MD *); +int X509_REQ_verify(X509_REQ *, EVP_PKEY *); +int X509_REQ_digest(const X509_REQ *, const EVP_MD *, + unsigned char *, unsigned int *); +EVP_PKEY *X509_REQ_get_pubkey(X509_REQ *); +int X509_REQ_print(BIO *, X509_REQ *); +int X509_REQ_print_ex(BIO *, X509_REQ *, unsigned long, unsigned long); +int X509_REQ_add_extensions(X509_REQ *, X509_EXTENSIONS *); +X509_EXTENSIONS *X509_REQ_get_extensions(X509_REQ *); + +int X509V3_EXT_print(BIO *, X509_EXTENSION *, unsigned long, int); +ASN1_OCTET_STRING *X509_EXTENSION_get_data(X509_EXTENSION *); + +X509_REVOKED *X509_REVOKED_new(void); +void X509_REVOKED_free(X509_REVOKED *); + +int X509_REVOKED_set_serialNumber(X509_REVOKED *, ASN1_INTEGER *); + +int X509_REVOKED_add_ext(X509_REVOKED *, X509_EXTENSION*, int); +int X509_REVOKED_add1_ext_i2d(X509_REVOKED *, int, void *, int, unsigned long); +X509_EXTENSION *X509_REVOKED_delete_ext(X509_REVOKED *, int); + +int X509_REVOKED_set_revocationDate(X509_REVOKED *, ASN1_TIME *); + +X509_CRL *X509_CRL_new(void); +X509_CRL *d2i_X509_CRL_bio(BIO *, X509_CRL **); +int X509_CRL_add0_revoked(X509_CRL *, X509_REVOKED *); +int X509_CRL_add_ext(X509_CRL *, X509_EXTENSION *, int); +int X509_CRL_cmp(const X509_CRL *, const X509_CRL *); +int X509_CRL_print(BIO *, X509_CRL *); +int X509_CRL_set_issuer_name(X509_CRL *, X509_NAME *); +int X509_CRL_set_version(X509_CRL *, long); +int X509_CRL_sign(X509_CRL *, EVP_PKEY *, const EVP_MD *); +int X509_CRL_sort(X509_CRL *); +int X509_CRL_verify(X509_CRL *, EVP_PKEY *); +int i2d_X509_CRL_bio(BIO *, X509_CRL *); +void X509_CRL_free(X509_CRL *); + +int NETSCAPE_SPKI_verify(NETSCAPE_SPKI *, EVP_PKEY *); +int NETSCAPE_SPKI_sign(NETSCAPE_SPKI *, EVP_PKEY *, const EVP_MD *); +char *NETSCAPE_SPKI_b64_encode(NETSCAPE_SPKI *); +NETSCAPE_SPKI *NETSCAPE_SPKI_b64_decode(const char *, int); +EVP_PKEY *NETSCAPE_SPKI_get_pubkey(NETSCAPE_SPKI *); +int NETSCAPE_SPKI_set_pubkey(NETSCAPE_SPKI *, EVP_PKEY *); +NETSCAPE_SPKI *NETSCAPE_SPKI_new(void); +void NETSCAPE_SPKI_free(NETSCAPE_SPKI *); + +/* ASN1 serialization */ +int i2d_X509_bio(BIO *, X509 *); +X509 *d2i_X509_bio(BIO *, X509 **); + +int i2d_X509_REQ_bio(BIO *, X509_REQ *); +X509_REQ *d2i_X509_REQ_bio(BIO *, X509_REQ **); + +int i2d_PrivateKey_bio(BIO *, EVP_PKEY *); +EVP_PKEY *d2i_PrivateKey_bio(BIO *, EVP_PKEY **); +int i2d_PUBKEY_bio(BIO *, EVP_PKEY *); +EVP_PKEY *d2i_PUBKEY_bio(BIO *, EVP_PKEY **); + +ASN1_INTEGER *X509_get_serialNumber(X509 *); +int X509_set_serialNumber(X509 *, ASN1_INTEGER *); + +const char *X509_verify_cert_error_string(long); + +const char *X509_get_default_cert_area(void); +const char *X509_get_default_cert_dir(void); +const char *X509_get_default_cert_file(void); +const char *X509_get_default_cert_dir_env(void); +const char *X509_get_default_cert_file_env(void); +const char *X509_get_default_private_dir(void); + +int i2d_RSA_PUBKEY(RSA *, unsigned char **); +RSA *d2i_RSA_PUBKEY(RSA **, const unsigned char **, long); +RSA *d2i_RSAPublicKey(RSA **, const unsigned char **, long); +RSA *d2i_RSAPrivateKey(RSA **, const unsigned char **, long); +int i2d_DSA_PUBKEY(DSA *, unsigned char **); +DSA *d2i_DSA_PUBKEY(DSA **, const unsigned char **, long); +DSA *d2i_DSAPublicKey(DSA **, const unsigned char **, long); +DSA *d2i_DSAPrivateKey(DSA **, const unsigned char **, long); + +RSA *d2i_RSAPrivateKey_bio(BIO *, RSA **); +int i2d_RSAPrivateKey_bio(BIO *, RSA *); +RSA *d2i_RSAPublicKey_bio(BIO *, RSA **); +int i2d_RSAPublicKey_bio(BIO *, RSA *); +RSA *d2i_RSA_PUBKEY_bio(BIO *, RSA **); +int i2d_RSA_PUBKEY_bio(BIO *, RSA *); +DSA *d2i_DSA_PUBKEY_bio(BIO *, DSA **); +int i2d_DSA_PUBKEY_bio(BIO *, DSA *); +DSA *d2i_DSAPrivateKey_bio(BIO *, DSA **); +int i2d_DSAPrivateKey_bio(BIO *, DSA *); + +PKCS8_PRIV_KEY_INFO *d2i_PKCS8_PRIV_KEY_INFO_bio(BIO *, + PKCS8_PRIV_KEY_INFO **); +void PKCS8_PRIV_KEY_INFO_free(PKCS8_PRIV_KEY_INFO *); +""" + +MACROS = """ +/* These became const X509 in 1.1.0 */ +int X509_get_ext_count(X509 *); +X509_EXTENSION *X509_get_ext(X509 *, int); +int X509_get_ext_by_NID(X509 *, int, int); +X509_NAME *X509_get_subject_name(X509 *); +X509_NAME *X509_get_issuer_name(X509 *); + +/* This became const ASN1_OBJECT * in 1.1.0 */ +X509_EXTENSION *X509_EXTENSION_create_by_OBJ(X509_EXTENSION **, + ASN1_OBJECT *, int, + ASN1_OCTET_STRING *); + + +/* This became const X509_EXTENSION * in 1.1.0 */ +int X509_EXTENSION_get_critical(X509_EXTENSION *); + +/* This became const X509_REVOKED * in 1.1.0 */ +int X509_REVOKED_get_ext_count(X509_REVOKED *); +X509_EXTENSION *X509_REVOKED_get_ext(X509_REVOKED *, int); + +/* This became const X509_CRL * in 1.1.0 */ +X509_EXTENSION *X509_CRL_get_ext(X509_CRL *, int); +int X509_CRL_get_ext_count(X509_CRL *); + +/* these CRYPTO_EX_DATA functions became macros in 1.1.0 */ +int X509_get_ex_new_index(long, void *, CRYPTO_EX_new *, CRYPTO_EX_dup *, + CRYPTO_EX_free *); +int X509_set_ex_data(X509 *, int, void *); +void *X509_get_ex_data(X509 *, int); + +X509_REVOKED *Cryptography_X509_REVOKED_dup(X509_REVOKED *); + +int i2d_X509_CINF(X509_CINF *, unsigned char **); +int i2d_X509_CRL_INFO(X509_CRL_INFO *, unsigned char **); +int i2d_X509_REQ_INFO(X509_REQ_INFO *, unsigned char **); + +/* new in 1.0.2 */ +int i2d_re_X509_tbs(X509 *, unsigned char **); +int X509_get_signature_nid(const X509 *); + +const X509_ALGOR *X509_get0_tbs_sigalg(const X509 *); + +/* in 1.1.0 becomes const ASN1_BIT_STRING, const X509_ALGOR */ +void X509_get0_signature(ASN1_BIT_STRING **, X509_ALGOR **, X509 *); + +long X509_get_version(X509 *); + +ASN1_TIME *X509_get_notBefore(X509 *); +ASN1_TIME *X509_get_notAfter(X509 *); + +long X509_REQ_get_version(X509_REQ *); +X509_NAME *X509_REQ_get_subject_name(X509_REQ *); + +Cryptography_STACK_OF_X509 *sk_X509_new_null(void); +void sk_X509_free(Cryptography_STACK_OF_X509 *); +int sk_X509_num(Cryptography_STACK_OF_X509 *); +int sk_X509_push(Cryptography_STACK_OF_X509 *, X509 *); +X509 *sk_X509_value(Cryptography_STACK_OF_X509 *, int); + +X509_EXTENSIONS *sk_X509_EXTENSION_new_null(void); +int sk_X509_EXTENSION_num(X509_EXTENSIONS *); +X509_EXTENSION *sk_X509_EXTENSION_value(X509_EXTENSIONS *, int); +int sk_X509_EXTENSION_push(X509_EXTENSIONS *, X509_EXTENSION *); +int sk_X509_EXTENSION_insert(X509_EXTENSIONS *, X509_EXTENSION *, int); +X509_EXTENSION *sk_X509_EXTENSION_delete(X509_EXTENSIONS *, int); +void sk_X509_EXTENSION_free(X509_EXTENSIONS *); + +int sk_X509_REVOKED_num(Cryptography_STACK_OF_X509_REVOKED *); +X509_REVOKED *sk_X509_REVOKED_value(Cryptography_STACK_OF_X509_REVOKED *, int); + +Cryptography_STACK_OF_X509_CRL *sk_X509_CRL_new_null(void); +void sk_X509_CRL_free(Cryptography_STACK_OF_X509_CRL *); +int sk_X509_CRL_num(Cryptography_STACK_OF_X509_CRL *); +int sk_X509_CRL_push(Cryptography_STACK_OF_X509_CRL *, X509_CRL *); +X509_CRL *sk_X509_CRL_value(Cryptography_STACK_OF_X509_CRL *, int); + +int i2d_RSAPublicKey(RSA *, unsigned char **); +int i2d_RSAPrivateKey(RSA *, unsigned char **); +int i2d_DSAPublicKey(DSA *, unsigned char **); +int i2d_DSAPrivateKey(DSA *, unsigned char **); + +int X509_CRL_get_version(X509_CRL *); +ASN1_TIME *X509_CRL_get_lastUpdate(X509_CRL *); +ASN1_TIME *X509_CRL_get_nextUpdate(X509_CRL *); +X509_NAME *X509_CRL_get_issuer(X509_CRL *); +Cryptography_STACK_OF_X509_REVOKED *X509_CRL_get_REVOKED(X509_CRL *); + +/* These aren't macros these arguments are all const X on openssl > 1.0.x */ +int X509_CRL_set_lastUpdate(X509_CRL *, ASN1_TIME *); +int X509_CRL_set_nextUpdate(X509_CRL *, ASN1_TIME *); +int X509_set_notBefore(X509 *, ASN1_TIME *); +int X509_set_notAfter(X509 *, ASN1_TIME *); + +int i2d_EC_PUBKEY(EC_KEY *, unsigned char **); +EC_KEY *d2i_EC_PUBKEY(EC_KEY **, const unsigned char **, long); +EC_KEY *d2i_EC_PUBKEY_bio(BIO *, EC_KEY **); +int i2d_EC_PUBKEY_bio(BIO *, EC_KEY *); +EC_KEY *d2i_ECPrivateKey(EC_KEY **, const unsigned char **, long); +EC_KEY *d2i_ECPrivateKey_bio(BIO *, EC_KEY **); +int i2d_ECPrivateKey(EC_KEY *, unsigned char **); +int i2d_ECPrivateKey_bio(BIO *, EC_KEY *); + +EC_KEY *o2i_ECPublicKey(EC_KEY **, const unsigned char **, long); +int i2o_ECPublicKey(EC_KEY *, unsigned char **); + +// declared in safestack +int sk_ASN1_OBJECT_num(Cryptography_STACK_OF_ASN1_OBJECT *); +ASN1_OBJECT *sk_ASN1_OBJECT_value(Cryptography_STACK_OF_ASN1_OBJECT *, int); +void sk_ASN1_OBJECT_free(Cryptography_STACK_OF_ASN1_OBJECT *); +Cryptography_STACK_OF_ASN1_OBJECT *sk_ASN1_OBJECT_new_null(void); +int sk_ASN1_OBJECT_push(Cryptography_STACK_OF_ASN1_OBJECT *, ASN1_OBJECT *); + +/* these functions were added in 1.1.0 */ +const ASN1_INTEGER *X509_REVOKED_get0_serialNumber(const X509_REVOKED *); +const ASN1_TIME *X509_REVOKED_get0_revocationDate(const X509_REVOKED *); +void X509_CRL_get0_signature(const X509_CRL *, const ASN1_BIT_STRING **, + const X509_ALGOR **); +int i2d_re_X509_REQ_tbs(X509_REQ *, unsigned char **); +int i2d_re_X509_CRL_tbs(X509_CRL *, unsigned char **); +void X509_REQ_get0_signature(const X509_REQ *, const ASN1_BIT_STRING **, + const X509_ALGOR **); +""" + +CUSTOMIZATIONS = """ +/* Added in 1.0.2 beta but we need it in all versions now due to the great + opaquing. */ +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_102 || defined(LIBRESSL_VERSION_NUMBER) +/* from x509/x_x509.c version 1.0.2 */ +void X509_get0_signature(ASN1_BIT_STRING **psig, X509_ALGOR **palg, + const X509 *x) +{ + if (psig) + *psig = x->signature; + if (palg) + *palg = x->sig_alg; +} + +int X509_get_signature_nid(const X509 *x) +{ + return OBJ_obj2nid(x->sig_alg->algorithm); +} + +#endif + +/* Added in 1.0.2beta3 but we need it in all versions now due to the great + opaquing. */ +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_102BETA3 || defined(LIBRESSL_VERSION_NUMBER) +/* from x509/x_x509.c */ +int i2d_re_X509_tbs(X509 *x, unsigned char **pp) +{ + /* in 1.0.2+ this function also sets x->cert_info->enc.modified = 1 + but older OpenSSLs don't have the enc ASN1_ENCODING member in the + X509 struct. Setting modified to 1 marks the encoding + (x->cert_info->enc.enc) as invalid, but since the entire struct isn't + present we don't care. */ + return i2d_X509_CINF(x->cert_info, pp); +} +#endif + +#ifdef OPENSSL_NO_EC +int (*i2d_EC_PUBKEY)(EC_KEY *, unsigned char **) = NULL; +EC_KEY *(*d2i_EC_PUBKEY)(EC_KEY **, const unsigned char **, long) = NULL; +EC_KEY *(*d2i_EC_PUBKEY_bio)(BIO *, EC_KEY **) = NULL; +int (*i2d_EC_PUBKEY_bio)(BIO *, EC_KEY *) = NULL; +EC_KEY *(*d2i_ECPrivateKey)(EC_KEY **, const unsigned char **, long) = NULL; +EC_KEY *(*d2i_ECPrivateKey_bio)(BIO *, EC_KEY **) = NULL; +int (*i2d_ECPrivateKey)(EC_KEY *, unsigned char **) = NULL; +int (*i2d_ECPrivateKey_bio)(BIO *, EC_KEY *) = NULL; + +EC_KEY *(*o2i_ECPublicKey)(EC_KEY **, const unsigned char **, long) = NULL; +int (*i2o_ECPublicKey)(EC_KEY *, unsigned char **) = NULL; +#endif + +/* X509_REVOKED_dup only exists on 1.0.2+. It is implemented using + IMPLEMENT_ASN1_DUP_FUNCTION. The below is the equivalent so we have + it available on all OpenSSLs. */ +X509_REVOKED *Cryptography_X509_REVOKED_dup(X509_REVOKED *rev) { + return ASN1_item_dup(ASN1_ITEM_rptr(X509_REVOKED), rev); +} + +/* Added in 1.1.0 but we need it in all versions now due to the great + opaquing. */ +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110 || defined(LIBRESSL_VERSION_NUMBER) + +const X509_ALGOR *X509_get0_tbs_sigalg(const X509 *x) +{ + return x->cert_info->signature; +} + +/* from x509/x509_req.c */ +void X509_REQ_get0_signature(const X509_REQ *req, const ASN1_BIT_STRING **psig, + const X509_ALGOR **palg) +{ + if (psig != NULL) + *psig = req->signature; + if (palg != NULL) + *palg = req->sig_alg; +} +int i2d_re_X509_REQ_tbs(X509_REQ *req, unsigned char **pp) +{ + req->req_info->enc.modified = 1; + return i2d_X509_REQ_INFO(req->req_info, pp); +} +int i2d_re_X509_CRL_tbs(X509_CRL *crl, unsigned char **pp) { + crl->crl->enc.modified = 1; + return i2d_X509_CRL_INFO(crl->crl, pp); +} + +void X509_CRL_get0_signature(const X509_CRL *crl, const ASN1_BIT_STRING **psig, + const X509_ALGOR **palg) +{ + if (psig != NULL) + *psig = crl->signature; + if (palg != NULL) + *palg = crl->sig_alg; +} +const ASN1_TIME *X509_REVOKED_get0_revocationDate(const X509_REVOKED *x) +{ + return x->revocationDate; +} +const ASN1_INTEGER *X509_REVOKED_get0_serialNumber(const X509_REVOKED *x) +{ + return x->serialNumber; +} +#endif +""" diff --git a/src/_cffi_src/openssl/x509_vfy.py b/src/_cffi_src/openssl/x509_vfy.py new file mode 100644 index 0000000..8bd6e41 --- /dev/null +++ b/src/_cffi_src/openssl/x509_vfy.py @@ -0,0 +1,285 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include + +/* + * This is part of a work-around for the difficulty cffi has in dealing with + * `STACK_OF(foo)` as the name of a type. We invent a new, simpler name that + * will be an alias for this type and use the alias throughout. This works + * together with another opaque typedef for the same name in the TYPES section. + * Note that the result is an opaque type. + */ +typedef STACK_OF(ASN1_OBJECT) Cryptography_STACK_OF_ASN1_OBJECT; +typedef STACK_OF(X509_OBJECT) Cryptography_STACK_OF_X509_OBJECT; +""" + +TYPES = """ +static const long Cryptography_HAS_102_VERIFICATION_ERROR_CODES; +static const long Cryptography_HAS_102_VERIFICATION_PARAMS; +static const long Cryptography_HAS_X509_V_FLAG_TRUSTED_FIRST; +static const long Cryptography_HAS_X509_V_FLAG_PARTIAL_CHAIN; + +typedef ... Cryptography_STACK_OF_ASN1_OBJECT; +typedef ... Cryptography_STACK_OF_X509_OBJECT; + +typedef ... X509_OBJECT; +typedef ... X509_STORE; +typedef ... X509_VERIFY_PARAM; +typedef ... X509_STORE_CTX; + +/* While these are defined in the source as ints, they're tagged here + as longs, just in case they ever grow to large, such as what we saw + with OP_ALL. */ + +/* Verification error codes */ +static const int X509_V_OK; +static const int X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT; +static const int X509_V_ERR_UNABLE_TO_GET_CRL; +static const int X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE; +static const int X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE; +static const int X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY; +static const int X509_V_ERR_CERT_SIGNATURE_FAILURE; +static const int X509_V_ERR_CRL_SIGNATURE_FAILURE; +static const int X509_V_ERR_CERT_NOT_YET_VALID; +static const int X509_V_ERR_CERT_HAS_EXPIRED; +static const int X509_V_ERR_CRL_NOT_YET_VALID; +static const int X509_V_ERR_CRL_HAS_EXPIRED; +static const int X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD; +static const int X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD; +static const int X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD; +static const int X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD; +static const int X509_V_ERR_OUT_OF_MEM; +static const int X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT; +static const int X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN; +static const int X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY; +static const int X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE; +static const int X509_V_ERR_CERT_CHAIN_TOO_LONG; +static const int X509_V_ERR_CERT_REVOKED; +static const int X509_V_ERR_INVALID_CA; +static const int X509_V_ERR_PATH_LENGTH_EXCEEDED; +static const int X509_V_ERR_INVALID_PURPOSE; +static const int X509_V_ERR_CERT_UNTRUSTED; +static const int X509_V_ERR_CERT_REJECTED; +static const int X509_V_ERR_SUBJECT_ISSUER_MISMATCH; +static const int X509_V_ERR_AKID_SKID_MISMATCH; +static const int X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH; +static const int X509_V_ERR_KEYUSAGE_NO_CERTSIGN; +static const int X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER; +static const int X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION; +static const int X509_V_ERR_KEYUSAGE_NO_CRL_SIGN; +static const int X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION; +static const int X509_V_ERR_INVALID_NON_CA; +static const int X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED; +static const int X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE; +static const int X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED; +static const int X509_V_ERR_INVALID_EXTENSION; +static const int X509_V_ERR_INVALID_POLICY_EXTENSION; +static const int X509_V_ERR_NO_EXPLICIT_POLICY; +static const int X509_V_ERR_DIFFERENT_CRL_SCOPE; +static const int X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE; +static const int X509_V_ERR_UNNESTED_RESOURCE; +static const int X509_V_ERR_PERMITTED_VIOLATION; +static const int X509_V_ERR_EXCLUDED_VIOLATION; +static const int X509_V_ERR_SUBTREE_MINMAX; +static const int X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE; +static const int X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX; +static const int X509_V_ERR_UNSUPPORTED_NAME_SYNTAX; +static const int X509_V_ERR_CRL_PATH_VALIDATION_ERROR; +static const int X509_V_ERR_SUITE_B_INVALID_VERSION; +static const int X509_V_ERR_SUITE_B_INVALID_ALGORITHM; +static const int X509_V_ERR_SUITE_B_INVALID_CURVE; +static const int X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM; +static const int X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED; +static const int X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256; +static const int X509_V_ERR_HOSTNAME_MISMATCH; +static const int X509_V_ERR_EMAIL_MISMATCH; +static const int X509_V_ERR_IP_ADDRESS_MISMATCH; +static const int X509_V_ERR_APPLICATION_VERIFICATION; + +/* Verification parameters */ +static const long X509_V_FLAG_CB_ISSUER_CHECK; +static const long X509_V_FLAG_USE_CHECK_TIME; +static const long X509_V_FLAG_CRL_CHECK; +static const long X509_V_FLAG_CRL_CHECK_ALL; +static const long X509_V_FLAG_IGNORE_CRITICAL; +static const long X509_V_FLAG_X509_STRICT; +static const long X509_V_FLAG_ALLOW_PROXY_CERTS; +static const long X509_V_FLAG_POLICY_CHECK; +static const long X509_V_FLAG_EXPLICIT_POLICY; +static const long X509_V_FLAG_INHIBIT_ANY; +static const long X509_V_FLAG_INHIBIT_MAP; +static const long X509_V_FLAG_NOTIFY_POLICY; +static const long X509_V_FLAG_EXTENDED_CRL_SUPPORT; +static const long X509_V_FLAG_USE_DELTAS; +static const long X509_V_FLAG_CHECK_SS_SIGNATURE; +static const long X509_V_FLAG_TRUSTED_FIRST; +static const long X509_V_FLAG_SUITEB_128_LOS_ONLY; +static const long X509_V_FLAG_SUITEB_192_LOS; +static const long X509_V_FLAG_SUITEB_128_LOS; +static const long X509_V_FLAG_PARTIAL_CHAIN; + +static const long X509_LU_X509; +static const long X509_LU_CRL; +""" + +FUNCTIONS = """ +int X509_verify_cert(X509_STORE_CTX *); + +/* X509_STORE */ +X509_STORE *X509_STORE_new(void); +int X509_STORE_add_cert(X509_STORE *, X509 *); +int X509_STORE_add_crl(X509_STORE *, X509_CRL *); +int X509_STORE_load_locations(X509_STORE *, const char *, const char *); +int X509_STORE_set1_param(X509_STORE *, X509_VERIFY_PARAM *); +int X509_STORE_set_default_paths(X509_STORE *); +int X509_STORE_set_flags(X509_STORE *, unsigned long); +void X509_STORE_free(X509_STORE *); + + +/* X509_STORE_CTX */ +X509_STORE_CTX *X509_STORE_CTX_new(void); +void X509_STORE_CTX_cleanup(X509_STORE_CTX *); +void X509_STORE_CTX_free(X509_STORE_CTX *); +int X509_STORE_CTX_init(X509_STORE_CTX *, X509_STORE *, X509 *, + Cryptography_STACK_OF_X509 *); +void X509_STORE_CTX_trusted_stack(X509_STORE_CTX *, + Cryptography_STACK_OF_X509 *); +void X509_STORE_CTX_set_cert(X509_STORE_CTX *, X509 *); +void X509_STORE_CTX_set_chain(X509_STORE_CTX *,Cryptography_STACK_OF_X509 *); +X509_VERIFY_PARAM *X509_STORE_CTX_get0_param(X509_STORE_CTX *); +void X509_STORE_CTX_set0_param(X509_STORE_CTX *, X509_VERIFY_PARAM *); +int X509_STORE_CTX_set_default(X509_STORE_CTX *, const char *); +void X509_STORE_CTX_set_verify_cb(X509_STORE_CTX *, + int (*)(int, X509_STORE_CTX *)); +Cryptography_STACK_OF_X509 *X509_STORE_CTX_get_chain(X509_STORE_CTX *); +Cryptography_STACK_OF_X509 *X509_STORE_CTX_get1_chain(X509_STORE_CTX *); +int X509_STORE_CTX_get_error(X509_STORE_CTX *); +void X509_STORE_CTX_set_error(X509_STORE_CTX *, int); +int X509_STORE_CTX_get_error_depth(X509_STORE_CTX *); +X509 *X509_STORE_CTX_get_current_cert(X509_STORE_CTX *); +int X509_STORE_CTX_set_ex_data(X509_STORE_CTX *, int, void *); +void *X509_STORE_CTX_get_ex_data(X509_STORE_CTX *, int); + +/* X509_VERIFY_PARAM */ +X509_VERIFY_PARAM *X509_VERIFY_PARAM_new(void); +int X509_VERIFY_PARAM_set_flags(X509_VERIFY_PARAM *, unsigned long); +int X509_VERIFY_PARAM_clear_flags(X509_VERIFY_PARAM *, unsigned long); +unsigned long X509_VERIFY_PARAM_get_flags(X509_VERIFY_PARAM *); +int X509_VERIFY_PARAM_set_purpose(X509_VERIFY_PARAM *, int); +int X509_VERIFY_PARAM_set_trust(X509_VERIFY_PARAM *, int); +void X509_VERIFY_PARAM_set_time(X509_VERIFY_PARAM *, time_t); +int X509_VERIFY_PARAM_add0_policy(X509_VERIFY_PARAM *, ASN1_OBJECT *); +int X509_VERIFY_PARAM_set1_policies(X509_VERIFY_PARAM *, + Cryptography_STACK_OF_ASN1_OBJECT *); +void X509_VERIFY_PARAM_set_depth(X509_VERIFY_PARAM *, int); +int X509_VERIFY_PARAM_get_depth(const X509_VERIFY_PARAM *); +void X509_VERIFY_PARAM_free(X509_VERIFY_PARAM *); +""" + +MACROS = """ +/* this CRYPTO_EX_DATA function became a macro in 1.1.0 */ +int X509_STORE_CTX_get_ex_new_index(long, void *, CRYPTO_EX_new *, + CRYPTO_EX_dup *, CRYPTO_EX_free *); + +/* X509_STORE_CTX */ +void X509_STORE_CTX_set0_crls(X509_STORE_CTX *, + Cryptography_STACK_OF_X509_CRL *); + +/* X509_VERIFY_PARAM */ +int X509_VERIFY_PARAM_set1_host(X509_VERIFY_PARAM *, const char *, + size_t); +void X509_VERIFY_PARAM_set_hostflags(X509_VERIFY_PARAM *, unsigned int); +int X509_VERIFY_PARAM_set1_email(X509_VERIFY_PARAM *, const char *, + size_t); +int X509_VERIFY_PARAM_set1_ip(X509_VERIFY_PARAM *, const unsigned char *, + size_t); +int X509_VERIFY_PARAM_set1_ip_asc(X509_VERIFY_PARAM *, const char *); + +int sk_X509_OBJECT_num(Cryptography_STACK_OF_X509_OBJECT *); +X509_OBJECT *sk_X509_OBJECT_value(Cryptography_STACK_OF_X509_OBJECT *, int); +X509_VERIFY_PARAM *X509_STORE_get0_param(X509_STORE *); +Cryptography_STACK_OF_X509_OBJECT *X509_STORE_get0_objects(X509_STORE *); +X509 *X509_OBJECT_get0_X509(X509_OBJECT *); +int X509_OBJECT_get_type(const X509_OBJECT *); +""" + +CUSTOMIZATIONS = """ +/* OpenSSL 1.0.2beta2+ verification error codes */ +#if CRYPTOGRAPHY_OPENSSL_102BETA2_OR_GREATER && \ + !defined(LIBRESSL_VERSION_NUMBER) +static const long Cryptography_HAS_102_VERIFICATION_ERROR_CODES = 1; +#else +static const long Cryptography_HAS_102_VERIFICATION_ERROR_CODES = 0; +static const long X509_V_ERR_SUITE_B_INVALID_VERSION = 0; +static const long X509_V_ERR_SUITE_B_INVALID_ALGORITHM = 0; +static const long X509_V_ERR_SUITE_B_INVALID_CURVE = 0; +static const long X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM = 0; +static const long X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED = 0; +static const long X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256 = 0; +static const long X509_V_ERR_HOSTNAME_MISMATCH = 0; +static const long X509_V_ERR_EMAIL_MISMATCH = 0; +static const long X509_V_ERR_IP_ADDRESS_MISMATCH = 0; +#endif + +/* OpenSSL 1.0.2beta2+ verification parameters */ +#if CRYPTOGRAPHY_OPENSSL_102BETA2_OR_GREATER && \ + !defined(LIBRESSL_VERSION_NUMBER) +static const long Cryptography_HAS_102_VERIFICATION_PARAMS = 1; +#else +static const long Cryptography_HAS_102_VERIFICATION_PARAMS = 0; +/* X509_V_FLAG_TRUSTED_FIRST is also new in 1.0.2+, but it is added separately + below because it shows up in some earlier 3rd party OpenSSL packages. */ +static const long X509_V_FLAG_SUITEB_128_LOS_ONLY = 0; +static const long X509_V_FLAG_SUITEB_192_LOS = 0; +static const long X509_V_FLAG_SUITEB_128_LOS = 0; + +int (*X509_VERIFY_PARAM_set1_host)(X509_VERIFY_PARAM *, const char *, + size_t) = NULL; +int (*X509_VERIFY_PARAM_set1_email)(X509_VERIFY_PARAM *, const char *, + size_t) = NULL; +int (*X509_VERIFY_PARAM_set1_ip)(X509_VERIFY_PARAM *, const unsigned char *, + size_t) = NULL; +int (*X509_VERIFY_PARAM_set1_ip_asc)(X509_VERIFY_PARAM *, const char *) = NULL; +void (*X509_VERIFY_PARAM_set_hostflags)(X509_VERIFY_PARAM *, + unsigned int) = NULL; +#endif + +/* OpenSSL 1.0.2+ or Solaris's backport */ +#ifdef X509_V_FLAG_PARTIAL_CHAIN +static const long Cryptography_HAS_X509_V_FLAG_PARTIAL_CHAIN = 1; +#else +static const long Cryptography_HAS_X509_V_FLAG_PARTIAL_CHAIN = 0; +static const long X509_V_FLAG_PARTIAL_CHAIN = 0; +#endif + +/* OpenSSL 1.0.2+, *or* Fedora 20's flavor of OpenSSL 1.0.1e... */ +#ifdef X509_V_FLAG_TRUSTED_FIRST +static const long Cryptography_HAS_X509_V_FLAG_TRUSTED_FIRST = 1; +#else +static const long Cryptography_HAS_X509_V_FLAG_TRUSTED_FIRST = 0; +static const long X509_V_FLAG_TRUSTED_FIRST = 0; +#endif + +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110PRE6 || defined(LIBRESSL_VERSION_NUMBER) +Cryptography_STACK_OF_X509_OBJECT *X509_STORE_get0_objects(X509_STORE *ctx) { + return ctx->objs; +} +X509_VERIFY_PARAM *X509_STORE_get0_param(X509_STORE *store) { + return store->param; +} +int X509_OBJECT_get_type(const X509_OBJECT *x) { + return x->type; +} +#endif + +#if CRYPTOGRAPHY_OPENSSL_LESS_THAN_110PRE5 || defined(LIBRESSL_VERSION_NUMBER) +X509 *X509_OBJECT_get0_X509(X509_OBJECT *x) { + return x->data.x509; +} +#endif +""" diff --git a/src/_cffi_src/openssl/x509name.py b/src/_cffi_src/openssl/x509name.py new file mode 100644 index 0000000..0554a02 --- /dev/null +++ b/src/_cffi_src/openssl/x509name.py @@ -0,0 +1,89 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include + +/* + * See the comment above Cryptography_STACK_OF_X509 in x509.py + */ +typedef STACK_OF(X509_NAME) Cryptography_STACK_OF_X509_NAME; +typedef STACK_OF(X509_NAME_ENTRY) Cryptography_STACK_OF_X509_NAME_ENTRY; +""" + +TYPES = """ +typedef ... Cryptography_STACK_OF_X509_NAME_ENTRY; +typedef ... X509_NAME; +typedef ... X509_NAME_ENTRY; +typedef ... Cryptography_STACK_OF_X509_NAME; +""" + +FUNCTIONS = """ +X509_NAME *X509_NAME_new(void); +void X509_NAME_free(X509_NAME *); + +unsigned long X509_NAME_hash(X509_NAME *); + +int i2d_X509_NAME(X509_NAME *, unsigned char **); +int X509_NAME_add_entry_by_txt(X509_NAME *, const char *, int, + const unsigned char *, int, int, int); +X509_NAME_ENTRY *X509_NAME_delete_entry(X509_NAME *, int); +void X509_NAME_ENTRY_free(X509_NAME_ENTRY *); +int X509_NAME_get_index_by_NID(X509_NAME *, int, int); +int X509_NAME_cmp(const X509_NAME *, const X509_NAME *); +X509_NAME *X509_NAME_dup(X509_NAME *); +int Cryptography_X509_NAME_ENTRY_set(X509_NAME_ENTRY *); +""" + +MACROS = """ +/* These became const X509_NAME * in 1.1.0 */ +int X509_NAME_entry_count(X509_NAME *); +X509_NAME_ENTRY *X509_NAME_get_entry(X509_NAME *, int); +char *X509_NAME_oneline(X509_NAME *, char *, int); + +/* These became const X509_NAME_ENTRY * in 1.1.0 */ +ASN1_OBJECT *X509_NAME_ENTRY_get_object(X509_NAME_ENTRY *); +ASN1_STRING *X509_NAME_ENTRY_get_data(X509_NAME_ENTRY *); +int X509_NAME_add_entry(X509_NAME *, X509_NAME_ENTRY *, int, int); + +/* this became const unsigned char * in 1.1.0 */ +int X509_NAME_add_entry_by_NID(X509_NAME *, int, int, unsigned char *, + int, int, int); + +/* These became const ASN1_OBJECT * in 1.1.0 */ +X509_NAME_ENTRY *X509_NAME_ENTRY_create_by_OBJ(X509_NAME_ENTRY **, + ASN1_OBJECT *, int, + const unsigned char *, int); +int X509_NAME_add_entry_by_OBJ(X509_NAME *, ASN1_OBJECT *, int, + unsigned char *, int, int, int); + +Cryptography_STACK_OF_X509_NAME *sk_X509_NAME_new_null(void); +int sk_X509_NAME_num(Cryptography_STACK_OF_X509_NAME *); +int sk_X509_NAME_push(Cryptography_STACK_OF_X509_NAME *, X509_NAME *); +X509_NAME *sk_X509_NAME_value(Cryptography_STACK_OF_X509_NAME *, int); +void sk_X509_NAME_free(Cryptography_STACK_OF_X509_NAME *); +int sk_X509_NAME_ENTRY_num(Cryptography_STACK_OF_X509_NAME_ENTRY *); +Cryptography_STACK_OF_X509_NAME_ENTRY *sk_X509_NAME_ENTRY_new_null(void); +int sk_X509_NAME_ENTRY_push(Cryptography_STACK_OF_X509_NAME_ENTRY *, + X509_NAME_ENTRY *); +X509_NAME_ENTRY *sk_X509_NAME_ENTRY_value( + Cryptography_STACK_OF_X509_NAME_ENTRY *, int); +Cryptography_STACK_OF_X509_NAME_ENTRY *sk_X509_NAME_ENTRY_dup( + Cryptography_STACK_OF_X509_NAME_ENTRY * +); +""" + +CUSTOMIZATIONS = """ +#if CRYPTOGRAPHY_OPENSSL_110_OR_GREATER && !defined(LIBRESSL_VERSION_NUMBER) +int Cryptography_X509_NAME_ENTRY_set(X509_NAME_ENTRY *ne) { + return X509_NAME_ENTRY_set(ne); +} +#else +int Cryptography_X509_NAME_ENTRY_set(X509_NAME_ENTRY *ne) { + return ne->set; +} +#endif +""" diff --git a/src/_cffi_src/openssl/x509v3.py b/src/_cffi_src/openssl/x509v3.py new file mode 100644 index 0000000..38099a9 --- /dev/null +++ b/src/_cffi_src/openssl/x509v3.py @@ -0,0 +1,304 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +INCLUDES = """ +#include + +/* + * This is part of a work-around for the difficulty cffi has in dealing with + * `LHASH_OF(foo)` as the name of a type. We invent a new, simpler name that + * will be an alias for this type and use the alias throughout. This works + * together with another opaque typedef for the same name in the TYPES section. + * Note that the result is an opaque type. + */ +typedef LHASH_OF(CONF_VALUE) Cryptography_LHASH_OF_CONF_VALUE; + +typedef STACK_OF(ACCESS_DESCRIPTION) Cryptography_STACK_OF_ACCESS_DESCRIPTION; +typedef STACK_OF(DIST_POINT) Cryptography_STACK_OF_DIST_POINT; +typedef STACK_OF(POLICYQUALINFO) Cryptography_STACK_OF_POLICYQUALINFO; +typedef STACK_OF(POLICYINFO) Cryptography_STACK_OF_POLICYINFO; +typedef STACK_OF(ASN1_INTEGER) Cryptography_STACK_OF_ASN1_INTEGER; +typedef STACK_OF(GENERAL_SUBTREE) Cryptography_STACK_OF_GENERAL_SUBTREE; +""" + +TYPES = """ +typedef ... Cryptography_STACK_OF_ACCESS_DESCRIPTION; +typedef ... Cryptography_STACK_OF_POLICYQUALINFO; +typedef ... Cryptography_STACK_OF_POLICYINFO; +typedef ... Cryptography_STACK_OF_ASN1_INTEGER; +typedef ... Cryptography_STACK_OF_GENERAL_SUBTREE; +typedef ... EXTENDED_KEY_USAGE; +typedef ... CONF; + +typedef struct { + X509 *issuer_cert; + X509 *subject_cert; + ...; +} X509V3_CTX; + +typedef void * (*X509V3_EXT_D2I)(void *, const unsigned char **, long); + +typedef struct { + ASN1_ITEM_EXP *it; + X509V3_EXT_D2I d2i; + ...; +} X509V3_EXT_METHOD; + +static const int GEN_OTHERNAME; +static const int GEN_EMAIL; +static const int GEN_X400; +static const int GEN_DNS; +static const int GEN_URI; +static const int GEN_DIRNAME; +static const int GEN_EDIPARTY; +static const int GEN_IPADD; +static const int GEN_RID; + +typedef struct { + ASN1_OBJECT *type_id; + ASN1_TYPE *value; +} OTHERNAME; + +typedef struct { + ...; +} EDIPARTYNAME; + +typedef struct { + int ca; + ASN1_INTEGER *pathlen; +} BASIC_CONSTRAINTS; + +typedef struct { + Cryptography_STACK_OF_GENERAL_SUBTREE *permittedSubtrees; + Cryptography_STACK_OF_GENERAL_SUBTREE *excludedSubtrees; +} NAME_CONSTRAINTS; + +typedef struct { + ASN1_INTEGER *requireExplicitPolicy; + ASN1_INTEGER *inhibitPolicyMapping; +} POLICY_CONSTRAINTS; + + +typedef struct { + int type; + union { + char *ptr; + OTHERNAME *otherName; /* otherName */ + ASN1_IA5STRING *rfc822Name; + ASN1_IA5STRING *dNSName; + ASN1_TYPE *x400Address; + X509_NAME *directoryName; + EDIPARTYNAME *ediPartyName; + ASN1_IA5STRING *uniformResourceIdentifier; + ASN1_OCTET_STRING *iPAddress; + ASN1_OBJECT *registeredID; + + /* Old names */ + ASN1_OCTET_STRING *ip; /* iPAddress */ + X509_NAME *dirn; /* dirn */ + ASN1_IA5STRING *ia5; /* rfc822Name, dNSName, */ + /* uniformResourceIdentifier */ + ASN1_OBJECT *rid; /* registeredID */ + ASN1_TYPE *other; /* x400Address */ + } d; + ...; +} GENERAL_NAME; + +typedef struct { + GENERAL_NAME *base; + ASN1_INTEGER *minimum; + ASN1_INTEGER *maximum; +} GENERAL_SUBTREE; + +typedef struct stack_st_GENERAL_NAME GENERAL_NAMES; + +typedef struct { + ASN1_OCTET_STRING *keyid; + GENERAL_NAMES *issuer; + ASN1_INTEGER *serial; +} AUTHORITY_KEYID; + +typedef struct { + ASN1_OBJECT *method; + GENERAL_NAME *location; +} ACCESS_DESCRIPTION; + +typedef ... Cryptography_LHASH_OF_CONF_VALUE; + + +typedef ... Cryptography_STACK_OF_DIST_POINT; + +typedef struct { + int type; + union { + GENERAL_NAMES *fullname; + Cryptography_STACK_OF_X509_NAME_ENTRY *relativename; + } name; + ...; +} DIST_POINT_NAME; + +typedef struct { + DIST_POINT_NAME *distpoint; + ASN1_BIT_STRING *reasons; + GENERAL_NAMES *CRLissuer; + ...; +} DIST_POINT; + +typedef struct { + ASN1_STRING *organization; + Cryptography_STACK_OF_ASN1_INTEGER *noticenos; +} NOTICEREF; + +typedef struct { + NOTICEREF *noticeref; + ASN1_STRING *exptext; +} USERNOTICE; + +typedef struct { + ASN1_OBJECT *pqualid; + union { + ASN1_IA5STRING *cpsuri; + USERNOTICE *usernotice; + ASN1_TYPE *other; + } d; +} POLICYQUALINFO; + +typedef struct { + ASN1_OBJECT *policyid; + Cryptography_STACK_OF_POLICYQUALINFO *qualifiers; +} POLICYINFO; + +typedef void (*sk_GENERAL_NAME_freefunc)(GENERAL_NAME *); +""" + + +FUNCTIONS = """ +int X509V3_EXT_add_alias(int, int); +void X509V3_set_ctx(X509V3_CTX *, X509 *, X509 *, X509_REQ *, X509_CRL *, int); +int GENERAL_NAME_print(BIO *, GENERAL_NAME *); +GENERAL_NAMES *GENERAL_NAMES_new(void); +void GENERAL_NAMES_free(GENERAL_NAMES *); +void *X509V3_EXT_d2i(X509_EXTENSION *); +int X509_check_ca(X509 *); +""" + +MACROS = """ +/* X509 became a const arg in 1.1.0 */ +void *X509_get_ext_d2i(X509 *, int, int *, int *); +/* The last two char * args became const char * in 1.1.0 */ +X509_EXTENSION *X509V3_EXT_nconf(CONF *, X509V3_CTX *, char *, char *); +/* This is a macro defined by a call to DECLARE_ASN1_FUNCTIONS in the + x509v3.h header. */ +BASIC_CONSTRAINTS *BASIC_CONSTRAINTS_new(void); +void BASIC_CONSTRAINTS_free(BASIC_CONSTRAINTS *); +/* This is a macro defined by a call to DECLARE_ASN1_FUNCTIONS in the + x509v3.h header. */ +AUTHORITY_KEYID *AUTHORITY_KEYID_new(void); +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 *); + +POLICY_CONSTRAINTS *POLICY_CONSTRAINTS_new(void); +void POLICY_CONSTRAINTS_free(POLICY_CONSTRAINTS *); + +void *X509V3_set_ctx_nodb(X509V3_CTX *); + +int i2d_GENERAL_NAMES(GENERAL_NAMES *, unsigned char **); +GENERAL_NAMES *d2i_GENERAL_NAMES(GENERAL_NAMES **, const unsigned char **, + long); + +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); +void sk_GENERAL_NAME_pop_free(struct stack_st_GENERAL_NAME *, + sk_GENERAL_NAME_freefunc); + +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 *); + +ACCESS_DESCRIPTION *ACCESS_DESCRIPTION_new(void); +void ACCESS_DESCRIPTION_free(ACCESS_DESCRIPTION *); + +X509_EXTENSION *X509V3_EXT_conf_nid(Cryptography_LHASH_OF_CONF_VALUE *, + X509V3_CTX *, int, char *); + +/* These aren't macros these functions are all const X on openssl > 1.0.x */ +const X509V3_EXT_METHOD *X509V3_EXT_get(X509_EXTENSION *); +const X509V3_EXT_METHOD *X509V3_EXT_get_nid(int); + +Cryptography_STACK_OF_DIST_POINT *sk_DIST_POINT_new_null(void); +void sk_DIST_POINT_free(Cryptography_STACK_OF_DIST_POINT *); +int sk_DIST_POINT_num(Cryptography_STACK_OF_DIST_POINT *); +DIST_POINT *sk_DIST_POINT_value(Cryptography_STACK_OF_DIST_POINT *, int); +int sk_DIST_POINT_push(Cryptography_STACK_OF_DIST_POINT *, DIST_POINT *); + +void sk_POLICYINFO_free(Cryptography_STACK_OF_POLICYINFO *); +int sk_POLICYINFO_num(Cryptography_STACK_OF_POLICYINFO *); +POLICYINFO *sk_POLICYINFO_value(Cryptography_STACK_OF_POLICYINFO *, int); +int sk_POLICYINFO_push(Cryptography_STACK_OF_POLICYINFO *, POLICYINFO *); +Cryptography_STACK_OF_POLICYINFO *sk_POLICYINFO_new_null(void); + +POLICYINFO *POLICYINFO_new(void); +void POLICYINFO_free(POLICYINFO *); + +POLICYQUALINFO *POLICYQUALINFO_new(void); +void POLICYQUALINFO_free(POLICYQUALINFO *); + +NOTICEREF *NOTICEREF_new(void); +void NOTICEREF_free(NOTICEREF *); + +USERNOTICE *USERNOTICE_new(void); +void USERNOTICE_free(USERNOTICE *); + +void sk_POLICYQUALINFO_free(Cryptography_STACK_OF_POLICYQUALINFO *); +int sk_POLICYQUALINFO_num(Cryptography_STACK_OF_POLICYQUALINFO *); +POLICYQUALINFO *sk_POLICYQUALINFO_value(Cryptography_STACK_OF_POLICYQUALINFO *, + int); +int sk_POLICYQUALINFO_push(Cryptography_STACK_OF_POLICYQUALINFO *, + POLICYQUALINFO *); +Cryptography_STACK_OF_POLICYQUALINFO *sk_POLICYQUALINFO_new_null(void); + +Cryptography_STACK_OF_GENERAL_SUBTREE *sk_GENERAL_SUBTREE_new_null(void); +void sk_GENERAL_SUBTREE_free(Cryptography_STACK_OF_GENERAL_SUBTREE *); +int sk_GENERAL_SUBTREE_num(Cryptography_STACK_OF_GENERAL_SUBTREE *); +GENERAL_SUBTREE *sk_GENERAL_SUBTREE_value( + Cryptography_STACK_OF_GENERAL_SUBTREE *, int +); +int sk_GENERAL_SUBTREE_push(Cryptography_STACK_OF_GENERAL_SUBTREE *, + GENERAL_SUBTREE *); + +GENERAL_SUBTREE *GENERAL_SUBTREE_new(void); + +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); +int sk_ASN1_INTEGER_push(Cryptography_STACK_OF_ASN1_INTEGER *, ASN1_INTEGER *); +Cryptography_STACK_OF_ASN1_INTEGER *sk_ASN1_INTEGER_new_null(void); + +X509_EXTENSION *X509V3_EXT_i2d(int, int, void *); + +DIST_POINT *DIST_POINT_new(void); +void DIST_POINT_free(DIST_POINT *); + +DIST_POINT_NAME *DIST_POINT_NAME_new(void); +void DIST_POINT_NAME_free(DIST_POINT_NAME *); + +GENERAL_NAME *GENERAL_NAME_new(void); +void GENERAL_NAME_free(GENERAL_NAME *); +""" + +CUSTOMIZATIONS = """ +""" diff --git a/src/_cffi_src/utils.py b/src/_cffi_src/utils.py new file mode 100644 index 0000000..00c8bad --- /dev/null +++ b/src/_cffi_src/utils.py @@ -0,0 +1,100 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import sys +from distutils.ccompiler import new_compiler +from distutils.dist import Distribution + +from cffi import FFI + + +def build_ffi_for_binding(module_name, module_prefix, modules, libraries=[], + extra_compile_args=[], extra_link_args=[]): + """ + Modules listed in ``modules`` should have the following attributes: + + * ``INCLUDES``: A string containing C includes. + * ``TYPES``: A string containing C declarations for types. + * ``FUNCTIONS``: A string containing C declarations for functions. + * ``MACROS``: A string containing C declarations for any macros. + * ``CUSTOMIZATIONS``: A string containing arbitrary top-level C code, this + can be used to do things like test for a define and provide an + alternate implementation based on that. + """ + types = [] + includes = [] + functions = [] + macros = [] + customizations = [] + for name in modules: + __import__(module_prefix + name) + module = sys.modules[module_prefix + name] + + types.append(module.TYPES) + macros.append(module.MACROS) + functions.append(module.FUNCTIONS) + includes.append(module.INCLUDES) + customizations.append(module.CUSTOMIZATIONS) + + # We include functions here so that if we got any of their definitions + # wrong, the underlying C compiler will explode. In C you are allowed + # to re-declare a function if it has the same signature. That is: + # int foo(int); + # int foo(int); + # is legal, but the following will fail to compile: + # int foo(int); + # int foo(short); + verify_source = "\n".join( + includes + + functions + + customizations + ) + ffi = build_ffi( + module_name, + cdef_source="\n".join(types + functions + macros), + verify_source=verify_source, + libraries=libraries, + extra_compile_args=extra_compile_args, + extra_link_args=extra_link_args, + ) + + return ffi + + +def build_ffi(module_name, cdef_source, verify_source, libraries=[], + extra_compile_args=[], extra_link_args=[]): + ffi = FFI() + ffi.cdef(cdef_source) + ffi.set_source( + module_name, + verify_source, + libraries=libraries, + extra_compile_args=extra_compile_args, + extra_link_args=extra_link_args, + ) + return ffi + + +def extra_link_args(compiler_type): + if compiler_type == 'msvc': + # Enable NX and ASLR for Windows builds on MSVC. These are enabled by + # default on Python 3.3+ but not on 2.x. + return ['/NXCOMPAT', '/DYNAMICBASE'] + else: + return [] + + +def compiler_type(): + """ + Gets the compiler type from distutils. On Windows with MSVC it will be + "msvc". On OS X and linux it is "unix". + """ + dist = Distribution() + dist.parse_config_files() + cmd = dist.get_command_obj('build') + cmd.ensure_finalized() + compiler = new_compiler(compiler=cmd.compiler) + return compiler.compiler_type diff --git a/src/cryptography.egg-info/PKG-INFO b/src/cryptography.egg-info/PKG-INFO new file mode 100644 index 0000000..4409380 --- /dev/null +++ b/src/cryptography.egg-info/PKG-INFO @@ -0,0 +1,94 @@ +Metadata-Version: 1.1 +Name: cryptography +Version: 1.7.1 +Summary: cryptography is a package which provides cryptographic recipes and primitives to Python developers. +Home-page: https://github.com/pyca/cryptography +Author: The cryptography developers +Author-email: cryptography-dev@python.org +License: BSD or Apache License, Version 2.0 +Description: Cryptography + ============ + + .. image:: https://img.shields.io/pypi/v/cryptography.svg + :target: https://pypi.python.org/pypi/cryptography/ + :alt: Latest Version + + .. image:: https://readthedocs.org/projects/cryptography/badge/?version=latest + :target: https://cryptography.io + :alt: Latest Docs + + .. image:: https://travis-ci.org/pyca/cryptography.svg?branch=master + :target: https://travis-ci.org/pyca/cryptography + + .. image:: https://codecov.io/github/pyca/cryptography/coverage.svg?branch=master + :target: https://codecov.io/github/pyca/cryptography?branch=master + + + ``cryptography`` is a package which provides cryptographic recipes and + primitives to Python developers. Our goal is for it to be your "cryptographic + standard library". It supports Python 2.6-2.7, Python 3.3+, and PyPy 2.6+. + + ``cryptography`` includes both high level recipes, and low level interfaces to + common cryptographic algorithms such as symmetric ciphers, message digests and + key derivation functions. For example, to encrypt something with + ``cryptography``'s high level symmetric encryption recipe: + + .. code-block:: pycon + + >>> from cryptography.fernet import Fernet + >>> # Put this somewhere safe! + >>> key = Fernet.generate_key() + >>> f = Fernet(key) + >>> token = f.encrypt(b"A really secret message. Not for prying eyes.") + >>> token + '...' + >>> f.decrypt(token) + 'A really secret message. Not for prying eyes.' + + You can find more information in the `documentation`_. + + You can install ``cryptography`` with: + + .. code-block:: console + + $ pip install cryptography + + For full details see `the installation documentation`_. + + Discussion + ~~~~~~~~~~ + + If you run into bugs, you can file them in our `issue tracker`_. + + We maintain a `cryptography-dev`_ mailing list for development discussion. + + You can also join ``#cryptography-dev`` on Freenode to ask questions or get + involved. + + + .. _`documentation`: https://cryptography.io/ + .. _`the installation documentation`: https://cryptography.io/en/latest/installation/ + .. _`issue tracker`: https://github.com/pyca/cryptography/issues + .. _`cryptography-dev`: https://mail.python.org/mailman/listinfo/cryptography-dev + +Platform: UNKNOWN +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License +Classifier: License :: OSI Approved :: BSD License +Classifier: Natural Language :: English +Classifier: Operating System :: MacOS :: MacOS X +Classifier: Operating System :: POSIX +Classifier: Operating System :: POSIX :: BSD +Classifier: Operating System :: POSIX :: Linux +Classifier: Operating System :: Microsoft :: Windows +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Security :: Cryptography diff --git a/src/cryptography.egg-info/SOURCES.txt b/src/cryptography.egg-info/SOURCES.txt new file mode 100644 index 0000000..73f3738 --- /dev/null +++ b/src/cryptography.egg-info/SOURCES.txt @@ -0,0 +1,290 @@ +AUTHORS.rst +CHANGELOG.rst +CONTRIBUTING.rst +LICENSE +LICENSE.APACHE +LICENSE.BSD +MANIFEST.in +README.rst +setup.py +docs/Makefile +docs/api-stability.rst +docs/changelog.rst +docs/community.rst +docs/conf.py +docs/cryptography-docs.py +docs/doing-a-release.rst +docs/exceptions.rst +docs/faq.rst +docs/fernet.rst +docs/glossary.rst +docs/index.rst +docs/installation.rst +docs/limitations.rst +docs/make.bat +docs/random-numbers.rst +docs/security.rst +docs/spelling_wordlist.txt +docs/_static/.keep +docs/development/c-bindings.rst +docs/development/getting-started.rst +docs/development/index.rst +docs/development/reviewing-patches.rst +docs/development/submitting-patches.rst +docs/development/test-vectors.rst +docs/development/custom-vectors/arc4.rst +docs/development/custom-vectors/cast5.rst +docs/development/custom-vectors/idea.rst +docs/development/custom-vectors/rsa-oaep-sha2.rst +docs/development/custom-vectors/secp256k1.rst +docs/development/custom-vectors/seed.rst +docs/development/custom-vectors/arc4/generate_arc4.py +docs/development/custom-vectors/arc4/verify_arc4.go +docs/development/custom-vectors/cast5/generate_cast5.py +docs/development/custom-vectors/cast5/verify_cast5.go +docs/development/custom-vectors/idea/generate_idea.py +docs/development/custom-vectors/idea/verify_idea.py +docs/development/custom-vectors/rsa-oaep-sha2/VerifyRSAOAEPSHA2.java +docs/development/custom-vectors/rsa-oaep-sha2/generate_rsa_oaep_sha2.py +docs/development/custom-vectors/secp256k1/generate_secp256k1.py +docs/development/custom-vectors/secp256k1/verify_secp256k1.py +docs/development/custom-vectors/seed/generate_seed.py +docs/development/custom-vectors/seed/verify_seed.py +docs/hazmat/backends/commoncrypto.rst +docs/hazmat/backends/index.rst +docs/hazmat/backends/interfaces.rst +docs/hazmat/backends/multibackend.rst +docs/hazmat/backends/openssl.rst +docs/hazmat/bindings/commoncrypto.rst +docs/hazmat/bindings/index.rst +docs/hazmat/bindings/openssl.rst +docs/hazmat/primitives/constant-time.rst +docs/hazmat/primitives/cryptographic-hashes.rst +docs/hazmat/primitives/index.rst +docs/hazmat/primitives/interfaces.rst +docs/hazmat/primitives/key-derivation-functions.rst +docs/hazmat/primitives/keywrap.rst +docs/hazmat/primitives/padding.rst +docs/hazmat/primitives/symmetric-encryption.rst +docs/hazmat/primitives/twofactor.rst +docs/hazmat/primitives/asymmetric/dh.rst +docs/hazmat/primitives/asymmetric/dsa.rst +docs/hazmat/primitives/asymmetric/ec.rst +docs/hazmat/primitives/asymmetric/index.rst +docs/hazmat/primitives/asymmetric/interfaces.rst +docs/hazmat/primitives/asymmetric/rsa.rst +docs/hazmat/primitives/asymmetric/serialization.rst +docs/hazmat/primitives/asymmetric/utils.rst +docs/hazmat/primitives/mac/cmac.rst +docs/hazmat/primitives/mac/hmac.rst +docs/hazmat/primitives/mac/index.rst +docs/x509/index.rst +docs/x509/reference.rst +docs/x509/tutorial.rst +src/_cffi_src/__init__.py +src/_cffi_src/build_commoncrypto.py +src/_cffi_src/build_constant_time.py +src/_cffi_src/build_openssl.py +src/_cffi_src/build_padding.py +src/_cffi_src/utils.py +src/_cffi_src/commoncrypto/__init__.py +src/_cffi_src/commoncrypto/cf.py +src/_cffi_src/commoncrypto/common_cryptor.py +src/_cffi_src/commoncrypto/common_digest.py +src/_cffi_src/commoncrypto/common_hmac.py +src/_cffi_src/commoncrypto/common_key_derivation.py +src/_cffi_src/commoncrypto/common_symmetric_key_wrap.py +src/_cffi_src/commoncrypto/seccertificate.py +src/_cffi_src/commoncrypto/secimport.py +src/_cffi_src/commoncrypto/secitem.py +src/_cffi_src/commoncrypto/seckey.py +src/_cffi_src/commoncrypto/seckeychain.py +src/_cffi_src/commoncrypto/secpolicy.py +src/_cffi_src/commoncrypto/sectransform.py +src/_cffi_src/commoncrypto/sectrust.py +src/_cffi_src/commoncrypto/secure_transport.py +src/_cffi_src/hazmat_src/constant_time.c +src/_cffi_src/hazmat_src/constant_time.h +src/_cffi_src/hazmat_src/padding.c +src/_cffi_src/hazmat_src/padding.h +src/_cffi_src/openssl/__init__.py +src/_cffi_src/openssl/aes.py +src/_cffi_src/openssl/asn1.py +src/_cffi_src/openssl/bignum.py +src/_cffi_src/openssl/bio.py +src/_cffi_src/openssl/callbacks.py +src/_cffi_src/openssl/cmac.py +src/_cffi_src/openssl/cms.py +src/_cffi_src/openssl/conf.py +src/_cffi_src/openssl/crypto.py +src/_cffi_src/openssl/cryptography.py +src/_cffi_src/openssl/dh.py +src/_cffi_src/openssl/dsa.py +src/_cffi_src/openssl/ec.py +src/_cffi_src/openssl/ecdh.py +src/_cffi_src/openssl/ecdsa.py +src/_cffi_src/openssl/engine.py +src/_cffi_src/openssl/err.py +src/_cffi_src/openssl/evp.py +src/_cffi_src/openssl/hmac.py +src/_cffi_src/openssl/nid.py +src/_cffi_src/openssl/objects.py +src/_cffi_src/openssl/ocsp.py +src/_cffi_src/openssl/opensslv.py +src/_cffi_src/openssl/osrandom_engine.py +src/_cffi_src/openssl/pem.py +src/_cffi_src/openssl/pkcs12.py +src/_cffi_src/openssl/pkcs7.py +src/_cffi_src/openssl/rand.py +src/_cffi_src/openssl/rsa.py +src/_cffi_src/openssl/ssl.py +src/_cffi_src/openssl/x509.py +src/_cffi_src/openssl/x509_vfy.py +src/_cffi_src/openssl/x509name.py +src/_cffi_src/openssl/x509v3.py +src/_cffi_src/openssl/src/osrandom_engine.c +src/_cffi_src/openssl/src/osrandom_engine.h +src/cryptography/__about__.py +src/cryptography/__init__.py +src/cryptography/exceptions.py +src/cryptography/fernet.py +src/cryptography/utils.py +src/cryptography.egg-info/PKG-INFO +src/cryptography.egg-info/SOURCES.txt +src/cryptography.egg-info/dependency_links.txt +src/cryptography.egg-info/entry_points.txt +src/cryptography.egg-info/not-zip-safe +src/cryptography.egg-info/requires.txt +src/cryptography.egg-info/top_level.txt +src/cryptography/hazmat/__init__.py +src/cryptography/hazmat/backends/__init__.py +src/cryptography/hazmat/backends/interfaces.py +src/cryptography/hazmat/backends/multibackend.py +src/cryptography/hazmat/backends/commoncrypto/__init__.py +src/cryptography/hazmat/backends/commoncrypto/backend.py +src/cryptography/hazmat/backends/commoncrypto/ciphers.py +src/cryptography/hazmat/backends/commoncrypto/hashes.py +src/cryptography/hazmat/backends/commoncrypto/hmac.py +src/cryptography/hazmat/backends/openssl/__init__.py +src/cryptography/hazmat/backends/openssl/backend.py +src/cryptography/hazmat/backends/openssl/ciphers.py +src/cryptography/hazmat/backends/openssl/cmac.py +src/cryptography/hazmat/backends/openssl/decode_asn1.py +src/cryptography/hazmat/backends/openssl/dh.py +src/cryptography/hazmat/backends/openssl/dsa.py +src/cryptography/hazmat/backends/openssl/ec.py +src/cryptography/hazmat/backends/openssl/encode_asn1.py +src/cryptography/hazmat/backends/openssl/hashes.py +src/cryptography/hazmat/backends/openssl/hmac.py +src/cryptography/hazmat/backends/openssl/rsa.py +src/cryptography/hazmat/backends/openssl/utils.py +src/cryptography/hazmat/backends/openssl/x509.py +src/cryptography/hazmat/bindings/__init__.py +src/cryptography/hazmat/bindings/commoncrypto/__init__.py +src/cryptography/hazmat/bindings/commoncrypto/binding.py +src/cryptography/hazmat/bindings/openssl/__init__.py +src/cryptography/hazmat/bindings/openssl/_conditional.py +src/cryptography/hazmat/bindings/openssl/binding.py +src/cryptography/hazmat/primitives/__init__.py +src/cryptography/hazmat/primitives/cmac.py +src/cryptography/hazmat/primitives/constant_time.py +src/cryptography/hazmat/primitives/hashes.py +src/cryptography/hazmat/primitives/hmac.py +src/cryptography/hazmat/primitives/keywrap.py +src/cryptography/hazmat/primitives/padding.py +src/cryptography/hazmat/primitives/serialization.py +src/cryptography/hazmat/primitives/asymmetric/__init__.py +src/cryptography/hazmat/primitives/asymmetric/dh.py +src/cryptography/hazmat/primitives/asymmetric/dsa.py +src/cryptography/hazmat/primitives/asymmetric/ec.py +src/cryptography/hazmat/primitives/asymmetric/padding.py +src/cryptography/hazmat/primitives/asymmetric/rsa.py +src/cryptography/hazmat/primitives/asymmetric/utils.py +src/cryptography/hazmat/primitives/ciphers/__init__.py +src/cryptography/hazmat/primitives/ciphers/algorithms.py +src/cryptography/hazmat/primitives/ciphers/base.py +src/cryptography/hazmat/primitives/ciphers/modes.py +src/cryptography/hazmat/primitives/interfaces/__init__.py +src/cryptography/hazmat/primitives/kdf/__init__.py +src/cryptography/hazmat/primitives/kdf/concatkdf.py +src/cryptography/hazmat/primitives/kdf/hkdf.py +src/cryptography/hazmat/primitives/kdf/kbkdf.py +src/cryptography/hazmat/primitives/kdf/pbkdf2.py +src/cryptography/hazmat/primitives/kdf/scrypt.py +src/cryptography/hazmat/primitives/kdf/x963kdf.py +src/cryptography/hazmat/primitives/twofactor/__init__.py +src/cryptography/hazmat/primitives/twofactor/hotp.py +src/cryptography/hazmat/primitives/twofactor/totp.py +src/cryptography/hazmat/primitives/twofactor/utils.py +src/cryptography/x509/__init__.py +src/cryptography/x509/base.py +src/cryptography/x509/extensions.py +src/cryptography/x509/general_name.py +src/cryptography/x509/name.py +src/cryptography/x509/oid.py +tests/__init__.py +tests/conftest.py +tests/doubles.py +tests/test_cryptography_utils.py +tests/test_fernet.py +tests/test_interfaces.py +tests/test_utils.py +tests/test_warnings.py +tests/test_x509.py +tests/test_x509_crlbuilder.py +tests/test_x509_ext.py +tests/test_x509_revokedcertbuilder.py +tests/utils.py +tests/hazmat/__init__.py +tests/hazmat/backends/__init__.py +tests/hazmat/backends/test_backendinit.py +tests/hazmat/backends/test_commoncrypto.py +tests/hazmat/backends/test_multibackend.py +tests/hazmat/backends/test_openssl.py +tests/hazmat/bindings/test_commoncrypto.py +tests/hazmat/bindings/test_openssl.py +tests/hazmat/primitives/__init__.py +tests/hazmat/primitives/fixtures_dsa.py +tests/hazmat/primitives/fixtures_ec.py +tests/hazmat/primitives/fixtures_rsa.py +tests/hazmat/primitives/test_3des.py +tests/hazmat/primitives/test_aes.py +tests/hazmat/primitives/test_arc4.py +tests/hazmat/primitives/test_asym_utils.py +tests/hazmat/primitives/test_block.py +tests/hazmat/primitives/test_blowfish.py +tests/hazmat/primitives/test_camellia.py +tests/hazmat/primitives/test_cast5.py +tests/hazmat/primitives/test_ciphers.py +tests/hazmat/primitives/test_cmac.py +tests/hazmat/primitives/test_concatkdf.py +tests/hazmat/primitives/test_constant_time.py +tests/hazmat/primitives/test_dh.py +tests/hazmat/primitives/test_dsa.py +tests/hazmat/primitives/test_ec.py +tests/hazmat/primitives/test_hash_vectors.py +tests/hazmat/primitives/test_hashes.py +tests/hazmat/primitives/test_hkdf.py +tests/hazmat/primitives/test_hkdf_vectors.py +tests/hazmat/primitives/test_hmac.py +tests/hazmat/primitives/test_hmac_vectors.py +tests/hazmat/primitives/test_idea.py +tests/hazmat/primitives/test_kbkdf.py +tests/hazmat/primitives/test_kbkdf_vectors.py +tests/hazmat/primitives/test_keywrap.py +tests/hazmat/primitives/test_padding.py +tests/hazmat/primitives/test_pbkdf2hmac.py +tests/hazmat/primitives/test_pbkdf2hmac_vectors.py +tests/hazmat/primitives/test_rsa.py +tests/hazmat/primitives/test_scrypt.py +tests/hazmat/primitives/test_seed.py +tests/hazmat/primitives/test_serialization.py +tests/hazmat/primitives/test_x963_vectors.py +tests/hazmat/primitives/test_x963kdf.py +tests/hazmat/primitives/utils.py +tests/hazmat/primitives/twofactor/__init__.py +tests/hazmat/primitives/twofactor/test_hotp.py +tests/hazmat/primitives/twofactor/test_totp.py +tests/hypothesis/__init__.py +tests/hypothesis/test_fernet.py +tests/hypothesis/test_padding.py \ No newline at end of file diff --git a/src/cryptography.egg-info/dependency_links.txt b/src/cryptography.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/cryptography.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/cryptography.egg-info/entry_points.txt b/src/cryptography.egg-info/entry_points.txt new file mode 100644 index 0000000..337f1c3 --- /dev/null +++ b/src/cryptography.egg-info/entry_points.txt @@ -0,0 +1,4 @@ +[cryptography.backends] +commoncrypto = cryptography.hazmat.backends.commoncrypto:backend +openssl = cryptography.hazmat.backends.openssl:backend + diff --git a/src/cryptography.egg-info/not-zip-safe b/src/cryptography.egg-info/not-zip-safe new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/cryptography.egg-info/not-zip-safe @@ -0,0 +1 @@ + diff --git a/src/cryptography.egg-info/requires.txt b/src/cryptography.egg-info/requires.txt new file mode 100644 index 0000000..51ce32f --- /dev/null +++ b/src/cryptography.egg-info/requires.txt @@ -0,0 +1,28 @@ +idna>=2.0 +pyasn1>=0.1.8 +six>=1.4.1 +setuptools>=11.3 +enum34 +ipaddress +cffi>=1.4.1 + +[docstest] +doc8 +pyenchant +readme_renderer >= 16.0 +sphinx +sphinx_rtd_theme +sphinxcontrib-spelling + +[pep8test] +flake8 +flake8-import-order +pep8-naming + +[test] +pytest>=2.9.0 +pretend +iso8601 +pyasn1_modules +pytz +hypothesis>=1.11.4 diff --git a/src/cryptography.egg-info/top_level.txt b/src/cryptography.egg-info/top_level.txt new file mode 100644 index 0000000..0d38bc5 --- /dev/null +++ b/src/cryptography.egg-info/top_level.txt @@ -0,0 +1 @@ +cryptography diff --git a/src/cryptography/__about__.py b/src/cryptography/__about__.py new file mode 100644 index 0000000..dd4e5e6 --- /dev/null +++ b/src/cryptography/__about__.py @@ -0,0 +1,23 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +__all__ = [ + "__title__", "__summary__", "__uri__", "__version__", "__author__", + "__email__", "__license__", "__copyright__", +] + +__title__ = "cryptography" +__summary__ = ("cryptography is a package which provides cryptographic recipes" + " and primitives to Python developers.") +__uri__ = "https://github.com/pyca/cryptography" + +__version__ = "1.7.1" + +__author__ = "The cryptography developers" +__email__ = "cryptography-dev@python.org" + +__license__ = "BSD or Apache License, Version 2.0" +__copyright__ = "Copyright 2013-2016 {0}".format(__author__) diff --git a/src/cryptography/__init__.py b/src/cryptography/__init__.py new file mode 100644 index 0000000..940c66b --- /dev/null +++ b/src/cryptography/__init__.py @@ -0,0 +1,27 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import sys +import warnings + +from cryptography.__about__ import ( + __author__, __copyright__, __email__, __license__, __summary__, __title__, + __uri__, __version__ +) + + +__all__ = [ + "__title__", "__summary__", "__uri__", "__version__", "__author__", + "__email__", "__license__", "__copyright__", +] + +if sys.version_info[:2] == (2, 6): + warnings.warn( + "Python 2.6 is no longer supported by the Python core team, please " + "upgrade your Python. A future version of cryptography will drop " + "support for Python 2.6", + DeprecationWarning + ) diff --git a/src/cryptography/exceptions.py b/src/cryptography/exceptions.py new file mode 100644 index 0000000..648cf9d --- /dev/null +++ b/src/cryptography/exceptions.py @@ -0,0 +1,57 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from enum import Enum + + +class _Reasons(Enum): + BACKEND_MISSING_INTERFACE = 0 + UNSUPPORTED_HASH = 1 + UNSUPPORTED_CIPHER = 2 + UNSUPPORTED_PADDING = 3 + UNSUPPORTED_MGF = 4 + UNSUPPORTED_PUBLIC_KEY_ALGORITHM = 5 + UNSUPPORTED_ELLIPTIC_CURVE = 6 + UNSUPPORTED_SERIALIZATION = 7 + UNSUPPORTED_X509 = 8 + UNSUPPORTED_EXCHANGE_ALGORITHM = 9 + UNSUPPORTED_DIFFIE_HELLMAN = 10 + + +class UnsupportedAlgorithm(Exception): + def __init__(self, message, reason=None): + super(UnsupportedAlgorithm, self).__init__(message) + self._reason = reason + + +class AlreadyFinalized(Exception): + pass + + +class AlreadyUpdated(Exception): + pass + + +class NotYetFinalized(Exception): + pass + + +class InvalidTag(Exception): + pass + + +class InvalidSignature(Exception): + pass + + +class InternalError(Exception): + def __init__(self, msg, err_code): + super(InternalError, self).__init__(msg) + self.err_code = err_code + + +class InvalidKey(Exception): + pass diff --git a/src/cryptography/fernet.py b/src/cryptography/fernet.py new file mode 100644 index 0000000..99eb10e --- /dev/null +++ b/src/cryptography/fernet.py @@ -0,0 +1,143 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import base64 +import binascii +import os +import struct +import time + +import six + +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes, padding +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes +from cryptography.hazmat.primitives.hmac import HMAC + + +class InvalidToken(Exception): + pass + + +_MAX_CLOCK_SKEW = 60 + + +class Fernet(object): + def __init__(self, key, backend=None): + if backend is None: + backend = default_backend() + + key = base64.urlsafe_b64decode(key) + if len(key) != 32: + raise ValueError( + "Fernet key must be 32 url-safe base64-encoded bytes." + ) + + self._signing_key = key[:16] + self._encryption_key = key[16:] + self._backend = backend + + @classmethod + def generate_key(cls): + return base64.urlsafe_b64encode(os.urandom(32)) + + def encrypt(self, data): + current_time = int(time.time()) + iv = os.urandom(16) + return self._encrypt_from_parts(data, current_time, iv) + + def _encrypt_from_parts(self, data, current_time, iv): + if not isinstance(data, bytes): + raise TypeError("data must be bytes.") + + padder = padding.PKCS7(algorithms.AES.block_size).padder() + padded_data = padder.update(data) + padder.finalize() + encryptor = Cipher( + algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend + ).encryptor() + ciphertext = encryptor.update(padded_data) + encryptor.finalize() + + basic_parts = ( + b"\x80" + struct.pack(">Q", current_time) + iv + ciphertext + ) + + h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend) + h.update(basic_parts) + hmac = h.finalize() + return base64.urlsafe_b64encode(basic_parts + hmac) + + def decrypt(self, token, ttl=None): + if not isinstance(token, bytes): + raise TypeError("token must be bytes.") + + current_time = int(time.time()) + + try: + data = base64.urlsafe_b64decode(token) + except (TypeError, binascii.Error): + raise InvalidToken + + if not data or six.indexbytes(data, 0) != 0x80: + raise InvalidToken + + try: + timestamp, = struct.unpack(">Q", data[1:9]) + except struct.error: + raise InvalidToken + if ttl is not None: + if timestamp + ttl < current_time: + raise InvalidToken + + if current_time + _MAX_CLOCK_SKEW < timestamp: + raise InvalidToken + + h = HMAC(self._signing_key, hashes.SHA256(), backend=self._backend) + h.update(data[:-32]) + try: + h.verify(data[-32:]) + except InvalidSignature: + raise InvalidToken + + iv = data[9:25] + ciphertext = data[25:-32] + decryptor = Cipher( + algorithms.AES(self._encryption_key), modes.CBC(iv), self._backend + ).decryptor() + plaintext_padded = decryptor.update(ciphertext) + try: + plaintext_padded += decryptor.finalize() + except ValueError: + raise InvalidToken + unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder() + + unpadded = unpadder.update(plaintext_padded) + try: + unpadded += unpadder.finalize() + except ValueError: + raise InvalidToken + return unpadded + + +class MultiFernet(object): + def __init__(self, fernets): + fernets = list(fernets) + if not fernets: + raise ValueError( + "MultiFernet requires at least one Fernet instance" + ) + self._fernets = fernets + + def encrypt(self, msg): + return self._fernets[0].encrypt(msg) + + def decrypt(self, msg, ttl=None): + for f in self._fernets: + try: + return f.decrypt(msg, ttl) + except InvalidToken: + pass + raise InvalidToken diff --git a/src/cryptography/hazmat/__init__.py b/src/cryptography/hazmat/__init__.py new file mode 100644 index 0000000..9f06a99 --- /dev/null +++ b/src/cryptography/hazmat/__init__.py @@ -0,0 +1,11 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. +""" +Hazardous Materials + +This is a "Hazardous Materials" module. You should ONLY use it if you're +100% absolutely sure that you know what you're doing because this module +is full of land mines, dragons, and dinosaurs with laser guns. +""" +from __future__ import absolute_import, division, print_function diff --git a/src/cryptography/hazmat/backends/__init__.py b/src/cryptography/hazmat/backends/__init__.py new file mode 100644 index 0000000..161a187 --- /dev/null +++ b/src/cryptography/hazmat/backends/__init__.py @@ -0,0 +1,73 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pkg_resources + +from cryptography.hazmat.backends.multibackend import MultiBackend + + +_available_backends_list = None + + +def _available_backends(): + global _available_backends_list + + if _available_backends_list is None: + entry_point_backends = [ + # DeprecatedIn16 + # setuptools 11.3 deprecated support for the require parameter to + # load(), and introduced the new resolve() method instead. + # We previously removed this fallback, but users are having issues + # where Python loads an older setuptools due to various syspath + # weirdness. + ep.resolve() if hasattr(ep, "resolve") else ep.load(require=False) + for ep in pkg_resources.iter_entry_points( + "cryptography.backends" + ) + ] + + _available_backends_list = _backend_import_fallback( + entry_point_backends + ) + + return _available_backends_list + + +def _backend_import_fallback(backends): + # If backends already exist just return them. This branch is here + # to get full line coverage from our tests. + if backends: + return backends + + # if iter_entry_points fails to find any backends then manually try to + # import our current backends as a workaround for issues with application + # bundlers like pyinstaller, cx_freeze, etc + + # OpenSSL is guaranteed to be present until we unbundle the backends. + from cryptography.hazmat.backends.openssl.backend import backend as be_ossl + backends = [be_ossl] + try: + from cryptography.hazmat.backends.commoncrypto.backend import ( + backend as be_cc + ) + except ImportError: + pass + else: + backends.append(be_cc) + + return backends + + +_default_backend = None + + +def default_backend(): + global _default_backend + + if _default_backend is None: + _default_backend = MultiBackend(_available_backends()) + + return _default_backend diff --git a/src/cryptography/hazmat/backends/commoncrypto/__init__.py b/src/cryptography/hazmat/backends/commoncrypto/__init__.py new file mode 100644 index 0000000..1d52a25 --- /dev/null +++ b/src/cryptography/hazmat/backends/commoncrypto/__init__.py @@ -0,0 +1,10 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography.hazmat.backends.commoncrypto.backend import backend + + +__all__ = ["backend"] diff --git a/src/cryptography/hazmat/backends/commoncrypto/backend.py b/src/cryptography/hazmat/backends/commoncrypto/backend.py new file mode 100644 index 0000000..838e572 --- /dev/null +++ b/src/cryptography/hazmat/backends/commoncrypto/backend.py @@ -0,0 +1,250 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from collections import namedtuple + +from cryptography import utils +from cryptography.exceptions import InternalError +from cryptography.hazmat.backends.commoncrypto.ciphers import ( + _CipherContext, _GCMCipherContext +) +from cryptography.hazmat.backends.commoncrypto.hashes import _HashContext +from cryptography.hazmat.backends.commoncrypto.hmac import _HMACContext +from cryptography.hazmat.backends.interfaces import ( + CipherBackend, HMACBackend, HashBackend, PBKDF2HMACBackend +) +from cryptography.hazmat.bindings.commoncrypto.binding import Binding +from cryptography.hazmat.primitives.ciphers.algorithms import ( + AES, ARC4, Blowfish, CAST5, TripleDES +) +from cryptography.hazmat.primitives.ciphers.modes import ( + CBC, CFB, CFB8, CTR, ECB, GCM, OFB +) + + +HashMethods = namedtuple( + "HashMethods", ["ctx", "hash_init", "hash_update", "hash_final"] +) + + +@utils.register_interface(CipherBackend) +@utils.register_interface(HashBackend) +@utils.register_interface(HMACBackend) +@utils.register_interface(PBKDF2HMACBackend) +class Backend(object): + """ + CommonCrypto API wrapper. + """ + name = "commoncrypto" + + def __init__(self): + self._binding = Binding() + self._ffi = self._binding.ffi + self._lib = self._binding.lib + + self._cipher_registry = {} + self._register_default_ciphers() + self._hash_mapping = { + "md5": HashMethods( + "CC_MD5_CTX *", self._lib.CC_MD5_Init, + self._lib.CC_MD5_Update, self._lib.CC_MD5_Final + ), + "sha1": HashMethods( + "CC_SHA1_CTX *", self._lib.CC_SHA1_Init, + self._lib.CC_SHA1_Update, self._lib.CC_SHA1_Final + ), + "sha224": HashMethods( + "CC_SHA256_CTX *", self._lib.CC_SHA224_Init, + self._lib.CC_SHA224_Update, self._lib.CC_SHA224_Final + ), + "sha256": HashMethods( + "CC_SHA256_CTX *", self._lib.CC_SHA256_Init, + self._lib.CC_SHA256_Update, self._lib.CC_SHA256_Final + ), + "sha384": HashMethods( + "CC_SHA512_CTX *", self._lib.CC_SHA384_Init, + self._lib.CC_SHA384_Update, self._lib.CC_SHA384_Final + ), + "sha512": HashMethods( + "CC_SHA512_CTX *", self._lib.CC_SHA512_Init, + self._lib.CC_SHA512_Update, self._lib.CC_SHA512_Final + ), + } + + self._supported_hmac_algorithms = { + "md5": self._lib.kCCHmacAlgMD5, + "sha1": self._lib.kCCHmacAlgSHA1, + "sha224": self._lib.kCCHmacAlgSHA224, + "sha256": self._lib.kCCHmacAlgSHA256, + "sha384": self._lib.kCCHmacAlgSHA384, + "sha512": self._lib.kCCHmacAlgSHA512, + } + + self._supported_pbkdf2_hmac_algorithms = { + "sha1": self._lib.kCCPRFHmacAlgSHA1, + "sha224": self._lib.kCCPRFHmacAlgSHA224, + "sha256": self._lib.kCCPRFHmacAlgSHA256, + "sha384": self._lib.kCCPRFHmacAlgSHA384, + "sha512": self._lib.kCCPRFHmacAlgSHA512, + } + + def hash_supported(self, algorithm): + return algorithm.name in self._hash_mapping + + def hmac_supported(self, algorithm): + return algorithm.name in self._supported_hmac_algorithms + + def create_hash_ctx(self, algorithm): + return _HashContext(self, algorithm) + + def create_hmac_ctx(self, key, algorithm): + return _HMACContext(self, key, algorithm) + + def cipher_supported(self, cipher, mode): + # In OS X 10.11.2-5 (as of this writing) CommonCrypto has a bug with + # Blowfish key lengths less than 64-bit. Filed as radar://26636600 + if isinstance(cipher, Blowfish) and len(cipher.key) < 8: + return False + else: + return (type(cipher), type(mode)) in self._cipher_registry + + def create_symmetric_encryption_ctx(self, cipher, mode): + if isinstance(mode, GCM): + return _GCMCipherContext( + self, cipher, mode, self._lib.kCCEncrypt + ) + else: + return _CipherContext(self, cipher, mode, self._lib.kCCEncrypt) + + def create_symmetric_decryption_ctx(self, cipher, mode): + if isinstance(mode, GCM): + return _GCMCipherContext( + self, cipher, mode, self._lib.kCCDecrypt + ) + else: + return _CipherContext(self, cipher, mode, self._lib.kCCDecrypt) + + def pbkdf2_hmac_supported(self, algorithm): + return algorithm.name in self._supported_pbkdf2_hmac_algorithms + + def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material): + alg_enum = self._supported_pbkdf2_hmac_algorithms[algorithm.name] + buf = self._ffi.new("uint8_t[]", length) + res = self._lib.CCKeyDerivationPBKDF( + self._lib.kCCPBKDF2, + key_material, + len(key_material), + salt, + len(salt), + alg_enum, + iterations, + buf, + length + ) + self._check_cipher_response(res) + + return self._ffi.buffer(buf)[:] + + def _register_cipher_adapter(self, cipher_cls, cipher_const, mode_cls, + mode_const): + if (cipher_cls, mode_cls) in self._cipher_registry: + raise ValueError("Duplicate registration for: {0} {1}.".format( + cipher_cls, mode_cls) + ) + self._cipher_registry[cipher_cls, mode_cls] = (cipher_const, + mode_const) + + def _register_default_ciphers(self): + for mode_cls, mode_const in [ + (CBC, self._lib.kCCModeCBC), + (ECB, self._lib.kCCModeECB), + (CFB, self._lib.kCCModeCFB), + (CFB8, self._lib.kCCModeCFB8), + (OFB, self._lib.kCCModeOFB), + (CTR, self._lib.kCCModeCTR), + (GCM, self._lib.kCCModeGCM), + ]: + self._register_cipher_adapter( + AES, + self._lib.kCCAlgorithmAES128, + mode_cls, + mode_const + ) + for mode_cls, mode_const in [ + (CBC, self._lib.kCCModeCBC), + (ECB, self._lib.kCCModeECB), + (CFB, self._lib.kCCModeCFB), + (CFB8, self._lib.kCCModeCFB8), + (OFB, self._lib.kCCModeOFB), + ]: + self._register_cipher_adapter( + TripleDES, + self._lib.kCCAlgorithm3DES, + mode_cls, + mode_const + ) + for mode_cls, mode_const in [ + (CBC, self._lib.kCCModeCBC), + (ECB, self._lib.kCCModeECB), + (CFB, self._lib.kCCModeCFB), + (OFB, self._lib.kCCModeOFB) + ]: + self._register_cipher_adapter( + Blowfish, + self._lib.kCCAlgorithmBlowfish, + mode_cls, + mode_const + ) + for mode_cls, mode_const in [ + (CBC, self._lib.kCCModeCBC), + (ECB, self._lib.kCCModeECB), + (CFB, self._lib.kCCModeCFB), + (OFB, self._lib.kCCModeOFB), + (CTR, self._lib.kCCModeCTR) + ]: + self._register_cipher_adapter( + CAST5, + self._lib.kCCAlgorithmCAST, + mode_cls, + mode_const + ) + self._register_cipher_adapter( + ARC4, + self._lib.kCCAlgorithmRC4, + type(None), + self._lib.kCCModeRC4 + ) + + def _check_cipher_response(self, response): + if response == self._lib.kCCSuccess: + return + elif response == self._lib.kCCAlignmentError: + # This error is not currently triggered due to a bug filed as + # rdar://15589470 + raise ValueError( + "The length of the provided data is not a multiple of " + "the block length." + ) + else: + raise InternalError( + "The backend returned an unknown error, consider filing a bug." + " Code: {0}.".format(response), + response + ) + + def _release_cipher_ctx(self, ctx): + """ + Called by the garbage collector and used to safely dereference and + release the context. + """ + if ctx[0] != self._ffi.NULL: + res = self._lib.CCCryptorRelease(ctx[0]) + self._check_cipher_response(res) + ctx[0] = self._ffi.NULL + + +backend = Backend() diff --git a/src/cryptography/hazmat/backends/commoncrypto/ciphers.py b/src/cryptography/hazmat/backends/commoncrypto/ciphers.py new file mode 100644 index 0000000..1ce8aec --- /dev/null +++ b/src/cryptography/hazmat/backends/commoncrypto/ciphers.py @@ -0,0 +1,193 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.exceptions import ( + InvalidTag, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.primitives import ciphers, constant_time +from cryptography.hazmat.primitives.ciphers import modes +from cryptography.hazmat.primitives.ciphers.modes import ( + CFB, CFB8, CTR, OFB +) + + +@utils.register_interface(ciphers.CipherContext) +class _CipherContext(object): + def __init__(self, backend, cipher, mode, operation): + self._backend = backend + self._cipher = cipher + self._mode = mode + self._operation = operation + # There is a bug in CommonCrypto where block ciphers do not raise + # kCCAlignmentError when finalizing if you supply non-block aligned + # data. To work around this we need to keep track of the block + # alignment ourselves, but only for alg+mode combos that require + # block alignment. OFB, CFB, and CTR make a block cipher algorithm + # into a stream cipher so we don't need to track them (and thus their + # block size is effectively 1 byte just like OpenSSL/CommonCrypto + # treat RC4 and other stream cipher block sizes). + # This bug has been filed as rdar://15589470 + self._bytes_processed = 0 + if (isinstance(cipher, ciphers.BlockCipherAlgorithm) and not + isinstance(mode, (OFB, CFB, CFB8, CTR))): + self._byte_block_size = cipher.block_size // 8 + else: + self._byte_block_size = 1 + + registry = self._backend._cipher_registry + try: + cipher_enum, mode_enum = registry[type(cipher), type(mode)] + except KeyError: + raise UnsupportedAlgorithm( + "cipher {0} in {1} mode is not supported " + "by this backend.".format( + cipher.name, mode.name if mode else mode), + _Reasons.UNSUPPORTED_CIPHER + ) + + ctx = self._backend._ffi.new("CCCryptorRef *") + ctx = self._backend._ffi.gc(ctx, self._backend._release_cipher_ctx) + + if isinstance(mode, modes.ModeWithInitializationVector): + iv_nonce = mode.initialization_vector + elif isinstance(mode, modes.ModeWithNonce): + iv_nonce = mode.nonce + else: + iv_nonce = self._backend._ffi.NULL + + if isinstance(mode, CTR): + mode_option = self._backend._lib.kCCModeOptionCTR_BE + else: + mode_option = 0 + + res = self._backend._lib.CCCryptorCreateWithMode( + operation, + mode_enum, cipher_enum, + self._backend._lib.ccNoPadding, iv_nonce, + cipher.key, len(cipher.key), + self._backend._ffi.NULL, 0, 0, mode_option, ctx) + self._backend._check_cipher_response(res) + + self._ctx = ctx + + def update(self, data): + # Count bytes processed to handle block alignment. + self._bytes_processed += len(data) + buf = self._backend._ffi.new( + "unsigned char[]", len(data) + self._byte_block_size - 1) + outlen = self._backend._ffi.new("size_t *") + res = self._backend._lib.CCCryptorUpdate( + self._ctx[0], data, len(data), buf, + len(data) + self._byte_block_size - 1, outlen) + self._backend._check_cipher_response(res) + return self._backend._ffi.buffer(buf)[:outlen[0]] + + def finalize(self): + # Raise error if block alignment is wrong. + if self._bytes_processed % self._byte_block_size: + raise ValueError( + "The length of the provided data is not a multiple of " + "the block length." + ) + buf = self._backend._ffi.new("unsigned char[]", self._byte_block_size) + outlen = self._backend._ffi.new("size_t *") + res = self._backend._lib.CCCryptorFinal( + self._ctx[0], buf, len(buf), outlen) + self._backend._check_cipher_response(res) + self._backend._release_cipher_ctx(self._ctx) + return self._backend._ffi.buffer(buf)[:outlen[0]] + + +@utils.register_interface(ciphers.AEADCipherContext) +@utils.register_interface(ciphers.AEADEncryptionContext) +class _GCMCipherContext(object): + def __init__(self, backend, cipher, mode, operation): + self._backend = backend + self._cipher = cipher + self._mode = mode + self._operation = operation + self._tag = None + + registry = self._backend._cipher_registry + try: + cipher_enum, mode_enum = registry[type(cipher), type(mode)] + except KeyError: + raise UnsupportedAlgorithm( + "cipher {0} in {1} mode is not supported " + "by this backend.".format( + cipher.name, mode.name if mode else mode), + _Reasons.UNSUPPORTED_CIPHER + ) + + ctx = self._backend._ffi.new("CCCryptorRef *") + ctx = self._backend._ffi.gc(ctx, self._backend._release_cipher_ctx) + + self._ctx = ctx + + res = self._backend._lib.CCCryptorCreateWithMode( + operation, + mode_enum, cipher_enum, + self._backend._lib.ccNoPadding, + self._backend._ffi.NULL, + cipher.key, len(cipher.key), + self._backend._ffi.NULL, 0, 0, 0, self._ctx) + self._backend._check_cipher_response(res) + + res = self._backend._lib.CCCryptorGCMAddIV( + self._ctx[0], + mode.initialization_vector, + len(mode.initialization_vector) + ) + self._backend._check_cipher_response(res) + # CommonCrypto has a bug where calling update without at least one + # call to authenticate_additional_data will result in null byte output + # for ciphertext. The following empty byte string call prevents the + # issue, which is present in at least 10.8 and 10.9. + # Filed as rdar://18314544 + self.authenticate_additional_data(b"") + + def update(self, data): + buf = self._backend._ffi.new("unsigned char[]", len(data)) + args = (self._ctx[0], data, len(data), buf) + if self._operation == self._backend._lib.kCCEncrypt: + res = self._backend._lib.CCCryptorGCMEncrypt(*args) + else: + res = self._backend._lib.CCCryptorGCMDecrypt(*args) + + self._backend._check_cipher_response(res) + return self._backend._ffi.buffer(buf)[:] + + def finalize(self): + # CommonCrypto has a yet another bug where you must make at least one + # call to update. If you pass just AAD and call finalize without a call + # to update you'll get null bytes for tag. The following update call + # prevents this issue, which is present in at least 10.8 and 10.9. + # Filed as rdar://18314580 + self.update(b"") + tag_size = self._cipher.block_size // 8 + tag_buf = self._backend._ffi.new("unsigned char[]", tag_size) + tag_len = self._backend._ffi.new("size_t *", tag_size) + res = self._backend._lib.CCCryptorGCMFinal( + self._ctx[0], tag_buf, tag_len + ) + self._backend._check_cipher_response(res) + self._backend._release_cipher_ctx(self._ctx) + self._tag = self._backend._ffi.buffer(tag_buf)[:] + if (self._operation == self._backend._lib.kCCDecrypt and + not constant_time.bytes_eq( + self._tag[:len(self._mode.tag)], self._mode.tag + )): + raise InvalidTag + return b"" + + def authenticate_additional_data(self, data): + res = self._backend._lib.CCCryptorGCMAddAAD( + self._ctx[0], data, len(data) + ) + self._backend._check_cipher_response(res) + + tag = utils.read_only_property("_tag") diff --git a/src/cryptography/hazmat/backends/commoncrypto/hashes.py b/src/cryptography/hazmat/backends/commoncrypto/hashes.py new file mode 100644 index 0000000..a54e983 --- /dev/null +++ b/src/cryptography/hazmat/backends/commoncrypto/hashes.py @@ -0,0 +1,55 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.primitives import hashes + + +@utils.register_interface(hashes.HashContext) +class _HashContext(object): + def __init__(self, backend, algorithm, ctx=None): + self._algorithm = algorithm + self._backend = backend + + if ctx is None: + try: + methods = self._backend._hash_mapping[self.algorithm.name] + except KeyError: + raise UnsupportedAlgorithm( + "{0} is not a supported hash on this backend.".format( + algorithm.name), + _Reasons.UNSUPPORTED_HASH + ) + ctx = self._backend._ffi.new(methods.ctx) + res = methods.hash_init(ctx) + assert res == 1 + + self._ctx = ctx + + algorithm = utils.read_only_property("_algorithm") + + def copy(self): + methods = self._backend._hash_mapping[self.algorithm.name] + new_ctx = self._backend._ffi.new(methods.ctx) + # CommonCrypto has no APIs for copying hashes, so we have to copy the + # underlying struct. + new_ctx[0] = self._ctx[0] + + return _HashContext(self._backend, self.algorithm, ctx=new_ctx) + + def update(self, data): + methods = self._backend._hash_mapping[self.algorithm.name] + res = methods.hash_update(self._ctx, data, len(data)) + assert res == 1 + + def finalize(self): + methods = self._backend._hash_mapping[self.algorithm.name] + buf = self._backend._ffi.new("unsigned char[]", + self.algorithm.digest_size) + res = methods.hash_final(buf, self._ctx) + assert res == 1 + return self._backend._ffi.buffer(buf)[:] diff --git a/src/cryptography/hazmat/backends/commoncrypto/hmac.py b/src/cryptography/hazmat/backends/commoncrypto/hmac.py new file mode 100644 index 0000000..ae623d8 --- /dev/null +++ b/src/cryptography/hazmat/backends/commoncrypto/hmac.py @@ -0,0 +1,59 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.exceptions import ( + InvalidSignature, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.primitives import constant_time, hashes, interfaces + + +@utils.register_interface(interfaces.MACContext) +@utils.register_interface(hashes.HashContext) +class _HMACContext(object): + def __init__(self, backend, key, algorithm, ctx=None): + self._algorithm = algorithm + self._backend = backend + if ctx is None: + ctx = self._backend._ffi.new("CCHmacContext *") + try: + alg = self._backend._supported_hmac_algorithms[algorithm.name] + except KeyError: + raise UnsupportedAlgorithm( + "{0} is not a supported HMAC hash on this backend.".format( + algorithm.name), + _Reasons.UNSUPPORTED_HASH + ) + + self._backend._lib.CCHmacInit(ctx, alg, key, len(key)) + + self._ctx = ctx + self._key = key + + algorithm = utils.read_only_property("_algorithm") + + def copy(self): + copied_ctx = self._backend._ffi.new("CCHmacContext *") + # CommonCrypto has no APIs for copying HMACs, so we have to copy the + # underlying struct. + copied_ctx[0] = self._ctx[0] + return _HMACContext( + self._backend, self._key, self.algorithm, ctx=copied_ctx + ) + + def update(self, data): + self._backend._lib.CCHmacUpdate(self._ctx, data, len(data)) + + def finalize(self): + buf = self._backend._ffi.new("unsigned char[]", + self.algorithm.digest_size) + self._backend._lib.CCHmacFinal(self._ctx, buf) + return self._backend._ffi.buffer(buf)[:] + + def verify(self, signature): + digest = self.finalize() + if not constant_time.bytes_eq(digest, signature): + raise InvalidSignature("Signature did not match digest.") diff --git a/src/cryptography/hazmat/backends/interfaces.py b/src/cryptography/hazmat/backends/interfaces.py new file mode 100644 index 0000000..c5f2951 --- /dev/null +++ b/src/cryptography/hazmat/backends/interfaces.py @@ -0,0 +1,376 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc + +import six + + +@six.add_metaclass(abc.ABCMeta) +class CipherBackend(object): + @abc.abstractmethod + def cipher_supported(self, cipher, mode): + """ + Return True if the given cipher and mode are supported. + """ + + @abc.abstractmethod + def create_symmetric_encryption_ctx(self, cipher, mode): + """ + Get a CipherContext that can be used for encryption. + """ + + @abc.abstractmethod + def create_symmetric_decryption_ctx(self, cipher, mode): + """ + Get a CipherContext that can be used for decryption. + """ + + +@six.add_metaclass(abc.ABCMeta) +class HashBackend(object): + @abc.abstractmethod + def hash_supported(self, algorithm): + """ + Return True if the hash algorithm is supported by this backend. + """ + + @abc.abstractmethod + def create_hash_ctx(self, algorithm): + """ + Create a HashContext for calculating a message digest. + """ + + +@six.add_metaclass(abc.ABCMeta) +class HMACBackend(object): + @abc.abstractmethod + def hmac_supported(self, algorithm): + """ + Return True if the hash algorithm is supported for HMAC by this + backend. + """ + + @abc.abstractmethod + def create_hmac_ctx(self, key, algorithm): + """ + Create a MACContext for calculating a message authentication code. + """ + + +@six.add_metaclass(abc.ABCMeta) +class CMACBackend(object): + @abc.abstractmethod + def cmac_algorithm_supported(self, algorithm): + """ + Returns True if the block cipher is supported for CMAC by this backend + """ + + @abc.abstractmethod + def create_cmac_ctx(self, algorithm): + """ + Create a MACContext for calculating a message authentication code. + """ + + +@six.add_metaclass(abc.ABCMeta) +class PBKDF2HMACBackend(object): + @abc.abstractmethod + def pbkdf2_hmac_supported(self, algorithm): + """ + Return True if the hash algorithm is supported for PBKDF2 by this + backend. + """ + + @abc.abstractmethod + def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material): + """ + Return length bytes derived from provided PBKDF2 parameters. + """ + + +@six.add_metaclass(abc.ABCMeta) +class RSABackend(object): + @abc.abstractmethod + def generate_rsa_private_key(self, public_exponent, key_size): + """ + Generate an RSAPrivateKey instance with public_exponent and a modulus + of key_size bits. + """ + + @abc.abstractmethod + def rsa_padding_supported(self, padding): + """ + Returns True if the backend supports the given padding options. + """ + + @abc.abstractmethod + def generate_rsa_parameters_supported(self, public_exponent, key_size): + """ + Returns True if the backend supports the given parameters for key + generation. + """ + + @abc.abstractmethod + def load_rsa_private_numbers(self, numbers): + """ + Returns an RSAPrivateKey provider. + """ + + @abc.abstractmethod + def load_rsa_public_numbers(self, numbers): + """ + Returns an RSAPublicKey provider. + """ + + +@six.add_metaclass(abc.ABCMeta) +class DSABackend(object): + @abc.abstractmethod + def generate_dsa_parameters(self, key_size): + """ + Generate a DSAParameters instance with a modulus of key_size bits. + """ + + @abc.abstractmethod + def generate_dsa_private_key(self, parameters): + """ + Generate a DSAPrivateKey instance with parameters as a DSAParameters + object. + """ + + @abc.abstractmethod + def generate_dsa_private_key_and_parameters(self, key_size): + """ + Generate a DSAPrivateKey instance using key size only. + """ + + @abc.abstractmethod + def dsa_hash_supported(self, algorithm): + """ + Return True if the hash algorithm is supported by the backend for DSA. + """ + + @abc.abstractmethod + def dsa_parameters_supported(self, p, q, g): + """ + Return True if the parameters are supported by the backend for DSA. + """ + + @abc.abstractmethod + def load_dsa_private_numbers(self, numbers): + """ + Returns a DSAPrivateKey provider. + """ + + @abc.abstractmethod + def load_dsa_public_numbers(self, numbers): + """ + Returns a DSAPublicKey provider. + """ + + @abc.abstractmethod + def load_dsa_parameter_numbers(self, numbers): + """ + Returns a DSAParameters provider. + """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurveBackend(object): + @abc.abstractmethod + def elliptic_curve_signature_algorithm_supported( + self, signature_algorithm, curve + ): + """ + Returns True if the backend supports the named elliptic curve with the + specified signature algorithm. + """ + + @abc.abstractmethod + def elliptic_curve_supported(self, curve): + """ + Returns True if the backend supports the named elliptic curve. + """ + + @abc.abstractmethod + def generate_elliptic_curve_private_key(self, curve): + """ + Return an object conforming to the EllipticCurvePrivateKey interface. + """ + + @abc.abstractmethod + def load_elliptic_curve_public_numbers(self, numbers): + """ + Return an EllipticCurvePublicKey provider using the given numbers. + """ + + @abc.abstractmethod + def load_elliptic_curve_private_numbers(self, numbers): + """ + Return an EllipticCurvePrivateKey provider using the given numbers. + """ + + @abc.abstractmethod + def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve): + """ + Returns whether the exchange algorithm is supported by this backend. + """ + + @abc.abstractmethod + def derive_elliptic_curve_private_key(self, private_value, curve): + """ + Compute the private key given the private value and curve. + """ + + +@six.add_metaclass(abc.ABCMeta) +class PEMSerializationBackend(object): + @abc.abstractmethod + def load_pem_private_key(self, data, password): + """ + Loads a private key from PEM encoded data, using the provided password + if the data is encrypted. + """ + + @abc.abstractmethod + def load_pem_public_key(self, data): + """ + Loads a public key from PEM encoded data. + """ + + +@six.add_metaclass(abc.ABCMeta) +class DERSerializationBackend(object): + @abc.abstractmethod + def load_der_private_key(self, data, password): + """ + Loads a private key from DER encoded data. Uses the provided password + if the data is encrypted. + """ + + @abc.abstractmethod + def load_der_public_key(self, data): + """ + Loads a public key from DER encoded data. + """ + + +@six.add_metaclass(abc.ABCMeta) +class X509Backend(object): + @abc.abstractmethod + def load_pem_x509_certificate(self, data): + """ + Load an X.509 certificate from PEM encoded data. + """ + + @abc.abstractmethod + def load_der_x509_certificate(self, data): + """ + Load an X.509 certificate from DER encoded data. + """ + + @abc.abstractmethod + def load_der_x509_csr(self, data): + """ + Load an X.509 CSR from DER encoded data. + """ + + @abc.abstractmethod + def load_pem_x509_csr(self, data): + """ + Load an X.509 CSR from PEM encoded data. + """ + + @abc.abstractmethod + def create_x509_csr(self, builder, private_key, algorithm): + """ + Create and sign an X.509 CSR from a CSR builder object. + """ + + @abc.abstractmethod + def create_x509_certificate(self, builder, private_key, algorithm): + """ + Create and sign an X.509 certificate from a CertificateBuilder object. + """ + + @abc.abstractmethod + def create_x509_crl(self, builder, private_key, algorithm): + """ + Create and sign an X.509 CertificateRevocationList from a + CertificateRevocationListBuilder object. + """ + + @abc.abstractmethod + def create_x509_revoked_certificate(self, builder): + """ + Create a RevokedCertificate object from a RevokedCertificateBuilder + object. + """ + + @abc.abstractmethod + def x509_name_bytes(self, name): + """ + Compute the DER encoded bytes of an X509 Name object. + """ + + +@six.add_metaclass(abc.ABCMeta) +class DHBackend(object): + @abc.abstractmethod + def generate_dh_parameters(self, generator, key_size): + """ + Generate a DHParameters instance with a modulus of key_size bits. + Using the given generator. Often 2 or 5. + """ + + @abc.abstractmethod + def generate_dh_private_key(self, parameters): + """ + Generate a DHPrivateKey instance with parameters as a DHParameters + object. + """ + + @abc.abstractmethod + def generate_dh_private_key_and_parameters(self, generator, key_size): + """ + Generate a DHPrivateKey instance using key size only. + Using the given generator. Often 2 or 5. + """ + + @abc.abstractmethod + def load_dh_private_numbers(self, numbers): + """ + Load a DHPrivateKey from DHPrivateNumbers + """ + + @abc.abstractmethod + def load_dh_public_numbers(self, numbers): + """ + Load a DHPublicKey from DHPublicNumbers. + """ + + @abc.abstractmethod + def load_dh_parameter_numbers(self, numbers): + """ + Load DHParameters from DHParameterNumbers. + """ + + @abc.abstractmethod + def dh_parameters_supported(self, p, g): + """ + Returns whether the backend supports DH with these parameter values. + """ + + +@six.add_metaclass(abc.ABCMeta) +class ScryptBackend(object): + @abc.abstractmethod + def derive_scrypt(self, key_material, salt, length, n, r, p): + """ + Return bytes derived from provided Scrypt parameters. + """ diff --git a/src/cryptography/hazmat/backends/multibackend.py b/src/cryptography/hazmat/backends/multibackend.py new file mode 100644 index 0000000..097b490 --- /dev/null +++ b/src/cryptography/hazmat/backends/multibackend.py @@ -0,0 +1,505 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.backends.interfaces import ( + CMACBackend, CipherBackend, DERSerializationBackend, DHBackend, + DSABackend, EllipticCurveBackend, HMACBackend, HashBackend, + PBKDF2HMACBackend, PEMSerializationBackend, RSABackend, ScryptBackend, + X509Backend +) + + +@utils.register_interface(CMACBackend) +@utils.register_interface(CipherBackend) +@utils.register_interface(DERSerializationBackend) +@utils.register_interface(HashBackend) +@utils.register_interface(HMACBackend) +@utils.register_interface(PBKDF2HMACBackend) +@utils.register_interface(RSABackend) +@utils.register_interface(DSABackend) +@utils.register_interface(EllipticCurveBackend) +@utils.register_interface(PEMSerializationBackend) +@utils.register_interface(X509Backend) +@utils.register_interface(DHBackend) +@utils.register_interface(ScryptBackend) +class MultiBackend(object): + name = "multibackend" + + def __init__(self, backends): + if len(backends) == 0: + raise ValueError( + "Multibackend cannot be initialized with no backends. If you " + "are seeing this error when trying to use default_backend() " + "please try uninstalling and reinstalling cryptography." + ) + + self._backends = backends + + def _filtered_backends(self, interface): + for b in self._backends: + if isinstance(b, interface): + yield b + + def cipher_supported(self, cipher, mode): + return any( + b.cipher_supported(cipher, mode) + for b in self._filtered_backends(CipherBackend) + ) + + def create_symmetric_encryption_ctx(self, cipher, mode): + for b in self._filtered_backends(CipherBackend): + try: + return b.create_symmetric_encryption_ctx(cipher, mode) + except UnsupportedAlgorithm: + pass + raise UnsupportedAlgorithm( + "cipher {0} in {1} mode is not supported by this backend.".format( + cipher.name, mode.name if mode else mode), + _Reasons.UNSUPPORTED_CIPHER + ) + + def create_symmetric_decryption_ctx(self, cipher, mode): + for b in self._filtered_backends(CipherBackend): + try: + return b.create_symmetric_decryption_ctx(cipher, mode) + except UnsupportedAlgorithm: + pass + raise UnsupportedAlgorithm( + "cipher {0} in {1} mode is not supported by this backend.".format( + cipher.name, mode.name if mode else mode), + _Reasons.UNSUPPORTED_CIPHER + ) + + def hash_supported(self, algorithm): + return any( + b.hash_supported(algorithm) + for b in self._filtered_backends(HashBackend) + ) + + def create_hash_ctx(self, algorithm): + for b in self._filtered_backends(HashBackend): + try: + return b.create_hash_ctx(algorithm) + except UnsupportedAlgorithm: + pass + raise UnsupportedAlgorithm( + "{0} is not a supported hash on this backend.".format( + algorithm.name), + _Reasons.UNSUPPORTED_HASH + ) + + def hmac_supported(self, algorithm): + return any( + b.hmac_supported(algorithm) + for b in self._filtered_backends(HMACBackend) + ) + + def create_hmac_ctx(self, key, algorithm): + for b in self._filtered_backends(HMACBackend): + try: + return b.create_hmac_ctx(key, algorithm) + except UnsupportedAlgorithm: + pass + raise UnsupportedAlgorithm( + "{0} is not a supported hash on this backend.".format( + algorithm.name), + _Reasons.UNSUPPORTED_HASH + ) + + def pbkdf2_hmac_supported(self, algorithm): + return any( + b.pbkdf2_hmac_supported(algorithm) + for b in self._filtered_backends(PBKDF2HMACBackend) + ) + + def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material): + for b in self._filtered_backends(PBKDF2HMACBackend): + try: + return b.derive_pbkdf2_hmac( + algorithm, length, salt, iterations, key_material + ) + except UnsupportedAlgorithm: + pass + raise UnsupportedAlgorithm( + "{0} is not a supported hash on this backend.".format( + algorithm.name), + _Reasons.UNSUPPORTED_HASH + ) + + def generate_rsa_private_key(self, public_exponent, key_size): + for b in self._filtered_backends(RSABackend): + return b.generate_rsa_private_key(public_exponent, key_size) + raise UnsupportedAlgorithm("RSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def generate_rsa_parameters_supported(self, public_exponent, key_size): + for b in self._filtered_backends(RSABackend): + return b.generate_rsa_parameters_supported( + public_exponent, key_size + ) + raise UnsupportedAlgorithm("RSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def rsa_padding_supported(self, padding): + for b in self._filtered_backends(RSABackend): + return b.rsa_padding_supported(padding) + raise UnsupportedAlgorithm("RSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def load_rsa_private_numbers(self, numbers): + for b in self._filtered_backends(RSABackend): + return b.load_rsa_private_numbers(numbers) + + raise UnsupportedAlgorithm("RSA is not supported by the backend", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def load_rsa_public_numbers(self, numbers): + for b in self._filtered_backends(RSABackend): + return b.load_rsa_public_numbers(numbers) + + raise UnsupportedAlgorithm("RSA is not supported by the backend", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def generate_dsa_parameters(self, key_size): + for b in self._filtered_backends(DSABackend): + return b.generate_dsa_parameters(key_size) + raise UnsupportedAlgorithm("DSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def generate_dsa_private_key(self, parameters): + for b in self._filtered_backends(DSABackend): + return b.generate_dsa_private_key(parameters) + raise UnsupportedAlgorithm("DSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def generate_dsa_private_key_and_parameters(self, key_size): + for b in self._filtered_backends(DSABackend): + return b.generate_dsa_private_key_and_parameters(key_size) + raise UnsupportedAlgorithm("DSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def dsa_hash_supported(self, algorithm): + for b in self._filtered_backends(DSABackend): + return b.dsa_hash_supported(algorithm) + raise UnsupportedAlgorithm("DSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def dsa_parameters_supported(self, p, q, g): + for b in self._filtered_backends(DSABackend): + return b.dsa_parameters_supported(p, q, g) + raise UnsupportedAlgorithm("DSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def load_dsa_public_numbers(self, numbers): + for b in self._filtered_backends(DSABackend): + return b.load_dsa_public_numbers(numbers) + raise UnsupportedAlgorithm("DSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def load_dsa_private_numbers(self, numbers): + for b in self._filtered_backends(DSABackend): + return b.load_dsa_private_numbers(numbers) + raise UnsupportedAlgorithm("DSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def load_dsa_parameter_numbers(self, numbers): + for b in self._filtered_backends(DSABackend): + return b.load_dsa_parameter_numbers(numbers) + raise UnsupportedAlgorithm("DSA is not supported by the backend.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + def cmac_algorithm_supported(self, algorithm): + return any( + b.cmac_algorithm_supported(algorithm) + for b in self._filtered_backends(CMACBackend) + ) + + def create_cmac_ctx(self, algorithm): + for b in self._filtered_backends(CMACBackend): + try: + return b.create_cmac_ctx(algorithm) + except UnsupportedAlgorithm: + pass + raise UnsupportedAlgorithm("This backend does not support CMAC.", + _Reasons.UNSUPPORTED_CIPHER) + + def elliptic_curve_supported(self, curve): + return any( + b.elliptic_curve_supported(curve) + for b in self._filtered_backends(EllipticCurveBackend) + ) + + def elliptic_curve_signature_algorithm_supported( + self, signature_algorithm, curve + ): + return any( + b.elliptic_curve_signature_algorithm_supported( + signature_algorithm, curve + ) + for b in self._filtered_backends(EllipticCurveBackend) + ) + + def generate_elliptic_curve_private_key(self, curve): + for b in self._filtered_backends(EllipticCurveBackend): + try: + return b.generate_elliptic_curve_private_key(curve) + except UnsupportedAlgorithm: + continue + + raise UnsupportedAlgorithm( + "This backend does not support this elliptic curve.", + _Reasons.UNSUPPORTED_ELLIPTIC_CURVE + ) + + def load_elliptic_curve_private_numbers(self, numbers): + for b in self._filtered_backends(EllipticCurveBackend): + try: + return b.load_elliptic_curve_private_numbers(numbers) + except UnsupportedAlgorithm: + continue + + raise UnsupportedAlgorithm( + "This backend does not support this elliptic curve.", + _Reasons.UNSUPPORTED_ELLIPTIC_CURVE + ) + + def load_elliptic_curve_public_numbers(self, numbers): + for b in self._filtered_backends(EllipticCurveBackend): + try: + return b.load_elliptic_curve_public_numbers(numbers) + except UnsupportedAlgorithm: + continue + + raise UnsupportedAlgorithm( + "This backend does not support this elliptic curve.", + _Reasons.UNSUPPORTED_ELLIPTIC_CURVE + ) + + def derive_elliptic_curve_private_key(self, private_value, curve): + for b in self._filtered_backends(EllipticCurveBackend): + try: + return b.derive_elliptic_curve_private_key(private_value, + curve) + except UnsupportedAlgorithm: + continue + + raise UnsupportedAlgorithm( + "This backend does not support this elliptic curve.", + _Reasons.UNSUPPORTED_ELLIPTIC_CURVE + ) + + def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve): + return any( + b.elliptic_curve_exchange_algorithm_supported(algorithm, curve) + for b in self._filtered_backends(EllipticCurveBackend) + ) + + def load_pem_private_key(self, data, password): + for b in self._filtered_backends(PEMSerializationBackend): + return b.load_pem_private_key(data, password) + + raise UnsupportedAlgorithm( + "This backend does not support this key serialization.", + _Reasons.UNSUPPORTED_SERIALIZATION + ) + + def load_pem_public_key(self, data): + for b in self._filtered_backends(PEMSerializationBackend): + return b.load_pem_public_key(data) + + raise UnsupportedAlgorithm( + "This backend does not support this key serialization.", + _Reasons.UNSUPPORTED_SERIALIZATION + ) + + def load_der_private_key(self, data, password): + for b in self._filtered_backends(DERSerializationBackend): + return b.load_der_private_key(data, password) + + raise UnsupportedAlgorithm( + "This backend does not support this key serialization.", + _Reasons.UNSUPPORTED_SERIALIZATION + ) + + def load_der_public_key(self, data): + for b in self._filtered_backends(DERSerializationBackend): + return b.load_der_public_key(data) + + raise UnsupportedAlgorithm( + "This backend does not support this key serialization.", + _Reasons.UNSUPPORTED_SERIALIZATION + ) + + def load_pem_x509_certificate(self, data): + for b in self._filtered_backends(X509Backend): + return b.load_pem_x509_certificate(data) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) + + def load_der_x509_certificate(self, data): + for b in self._filtered_backends(X509Backend): + return b.load_der_x509_certificate(data) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) + + def load_pem_x509_crl(self, data): + for b in self._filtered_backends(X509Backend): + return b.load_pem_x509_crl(data) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) + + def load_der_x509_crl(self, data): + for b in self._filtered_backends(X509Backend): + return b.load_der_x509_crl(data) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) + + def load_der_x509_csr(self, data): + for b in self._filtered_backends(X509Backend): + return b.load_der_x509_csr(data) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) + + def load_pem_x509_csr(self, data): + for b in self._filtered_backends(X509Backend): + return b.load_pem_x509_csr(data) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) + + def create_x509_csr(self, builder, private_key, algorithm): + for b in self._filtered_backends(X509Backend): + return b.create_x509_csr(builder, private_key, algorithm) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) + + def create_x509_certificate(self, builder, private_key, algorithm): + for b in self._filtered_backends(X509Backend): + return b.create_x509_certificate(builder, private_key, algorithm) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) + + def create_x509_crl(self, builder, private_key, algorithm): + for b in self._filtered_backends(X509Backend): + return b.create_x509_crl(builder, private_key, algorithm) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) + + def create_x509_revoked_certificate(self, builder): + for b in self._filtered_backends(X509Backend): + return b.create_x509_revoked_certificate(builder) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) + + def generate_dh_parameters(self, generator, key_size): + for b in self._filtered_backends(DHBackend): + return b.generate_dh_parameters(generator, key_size) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def load_dh_parameter_numbers(self, numbers): + for b in self._filtered_backends(DHBackend): + return b.load_dh_parameter_numbers(numbers) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def generate_dh_private_key(self, parameters): + for b in self._filtered_backends(DHBackend): + return b.generate_dh_private_key(parameters) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def load_dh_private_numbers(self, numbers): + for b in self._filtered_backends(DHBackend): + return b.load_dh_private_numbers(numbers) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def load_dh_public_numbers(self, numbers): + for b in self._filtered_backends(DHBackend): + return b.load_dh_public_numbers(numbers) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def generate_dh_private_key_and_parameters(self, generator, key_size): + for b in self._filtered_backends(DHBackend): + return b.generate_dh_private_key_and_parameters(generator, + key_size) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def dh_parameters_supported(self, p, g): + for b in self._filtered_backends(DHBackend): + return b.dh_parameters_supported(p, g) + + raise UnsupportedAlgorithm( + "This backend does not support Diffie-Hellman", + _Reasons.UNSUPPORTED_DIFFIE_HELLMAN + ) + + def x509_name_bytes(self, name): + for b in self._filtered_backends(X509Backend): + return b.x509_name_bytes(name) + + raise UnsupportedAlgorithm( + "This backend does not support X.509.", + _Reasons.UNSUPPORTED_X509 + ) + + def derive_scrypt(self, key_material, salt, length, n, r, p): + for b in self._filtered_backends(ScryptBackend): + return b.derive_scrypt(key_material, salt, length, n, r, p) + raise UnsupportedAlgorithm("This backend does not support scrypt.") diff --git a/src/cryptography/hazmat/backends/openssl/__init__.py b/src/cryptography/hazmat/backends/openssl/__init__.py new file mode 100644 index 0000000..8eadeb6 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/__init__.py @@ -0,0 +1,10 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography.hazmat.backends.openssl.backend import backend + + +__all__ = ["backend"] diff --git a/src/cryptography/hazmat/backends/openssl/backend.py b/src/cryptography/hazmat/backends/openssl/backend.py new file mode 100644 index 0000000..71063c1 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/backend.py @@ -0,0 +1,1913 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import base64 +import calendar +import collections +import contextlib +import itertools +import sys +from contextlib import contextmanager + +import six + +from cryptography import utils, x509 +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.backends.interfaces import ( + CMACBackend, CipherBackend, DERSerializationBackend, DHBackend, DSABackend, + EllipticCurveBackend, HMACBackend, HashBackend, PBKDF2HMACBackend, + PEMSerializationBackend, RSABackend, ScryptBackend, X509Backend +) +from cryptography.hazmat.backends.openssl.ciphers import ( + _AESCTRCipherContext, _CipherContext +) +from cryptography.hazmat.backends.openssl.cmac import _CMACContext +from cryptography.hazmat.backends.openssl.dh import ( + _DHParameters, _DHPrivateKey, _DHPublicKey +) +from cryptography.hazmat.backends.openssl.dsa import ( + _DSAParameters, _DSAPrivateKey, _DSAPublicKey +) +from cryptography.hazmat.backends.openssl.ec import ( + _EllipticCurvePrivateKey, _EllipticCurvePublicKey +) +from cryptography.hazmat.backends.openssl.encode_asn1 import ( + _CRL_ENTRY_EXTENSION_ENCODE_HANDLERS, + _CRL_EXTENSION_ENCODE_HANDLERS, _EXTENSION_ENCODE_HANDLERS, + _encode_asn1_int_gc, _encode_asn1_str_gc, _encode_name_gc, _txt2obj_gc, +) +from cryptography.hazmat.backends.openssl.hashes import _HashContext +from cryptography.hazmat.backends.openssl.hmac import _HMACContext +from cryptography.hazmat.backends.openssl.rsa import ( + _RSAPrivateKey, _RSAPublicKey +) +from cryptography.hazmat.backends.openssl.x509 import ( + _Certificate, _CertificateRevocationList, + _CertificateSigningRequest, _RevokedCertificate +) +from cryptography.hazmat.bindings._openssl import ffi as _ffi +from cryptography.hazmat.bindings.openssl import binding +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa +from cryptography.hazmat.primitives.asymmetric.padding import ( + MGF1, OAEP, PKCS1v15, PSS +) +from cryptography.hazmat.primitives.ciphers.algorithms import ( + AES, ARC4, Blowfish, CAST5, Camellia, IDEA, SEED, TripleDES +) +from cryptography.hazmat.primitives.ciphers.modes import ( + CBC, CFB, CFB8, CTR, ECB, GCM, OFB +) + + +_MemoryBIO = collections.namedtuple("_MemoryBIO", ["bio", "char_ptr"]) + + +class _PasswordUserdata(object): + def __init__(self, password): + self.password = password + self.called = 0 + self.exception = None + + +@binding.ffi_callback("int (char *, int, int, void *)", + name="Cryptography_pem_password_cb") +def _pem_password_cb(buf, size, writing, userdata_handle): + """ + A pem_password_cb function pointer that copied the password to + OpenSSL as required and returns the number of bytes copied. + + typedef int pem_password_cb(char *buf, int size, + int rwflag, void *userdata); + + Useful for decrypting PKCS8 files and so on. + + The userdata pointer must point to a cffi handle of a + _PasswordUserdata instance. + """ + ud = _ffi.from_handle(userdata_handle) + ud.called += 1 + + if not ud.password: + ud.exception = TypeError( + "Password was not given but private key is encrypted." + ) + return -1 + elif len(ud.password) < size: + pw_buf = _ffi.buffer(buf, size) + pw_buf[:len(ud.password)] = ud.password + return len(ud.password) + else: + ud.exception = ValueError( + "Passwords longer than {0} bytes are not supported " + "by this backend.".format(size - 1) + ) + return 0 + + +@utils.register_interface(CipherBackend) +@utils.register_interface(CMACBackend) +@utils.register_interface(DERSerializationBackend) +@utils.register_interface(DHBackend) +@utils.register_interface(DSABackend) +@utils.register_interface(EllipticCurveBackend) +@utils.register_interface(HashBackend) +@utils.register_interface(HMACBackend) +@utils.register_interface(PBKDF2HMACBackend) +@utils.register_interface(RSABackend) +@utils.register_interface(PEMSerializationBackend) +@utils.register_interface(X509Backend) +@utils.register_interface_if( + binding.Binding().lib.Cryptography_HAS_SCRYPT, ScryptBackend +) +class Backend(object): + """ + OpenSSL API binding interfaces. + """ + name = "openssl" + + def __init__(self): + self._binding = binding.Binding() + self._ffi = self._binding.ffi + self._lib = self._binding.lib + + # Set the default string mask for encoding ASN1 strings to UTF8. This + # is the default for newer OpenSSLs for several years and is + # recommended in RFC 2459. + res = self._lib.ASN1_STRING_set_default_mask_asc(b"utf8only") + self.openssl_assert(res == 1) + + self._cipher_registry = {} + self._register_default_ciphers() + self.activate_osrandom_engine() + + def openssl_assert(self, ok): + return binding._openssl_assert(self._lib, ok) + + def activate_builtin_random(self): + # Obtain a new structural reference. + e = self._lib.ENGINE_get_default_RAND() + if e != self._ffi.NULL: + self._lib.ENGINE_unregister_RAND(e) + # Reset the RNG to use the new engine. + self._lib.RAND_cleanup() + # decrement the structural reference from get_default_RAND + res = self._lib.ENGINE_finish(e) + self.openssl_assert(res == 1) + + @contextlib.contextmanager + def _get_osurandom_engine(self): + # Fetches an engine by id and returns it. This creates a structural + # reference. + e = self._lib.ENGINE_by_id(self._binding._osrandom_engine_id) + self.openssl_assert(e != self._ffi.NULL) + # Initialize the engine for use. This adds a functional reference. + res = self._lib.ENGINE_init(e) + self.openssl_assert(res == 1) + + try: + yield e + finally: + # Decrement the structural ref incremented by ENGINE_by_id. + res = self._lib.ENGINE_free(e) + self.openssl_assert(res == 1) + # Decrement the functional ref incremented by ENGINE_init. + res = self._lib.ENGINE_finish(e) + self.openssl_assert(res == 1) + + def activate_osrandom_engine(self): + # Unregister and free the current engine. + self.activate_builtin_random() + with self._get_osurandom_engine() as e: + # Set the engine as the default RAND provider. + res = self._lib.ENGINE_set_default_RAND(e) + self.openssl_assert(res == 1) + # Reset the RNG to use the new engine. + self._lib.RAND_cleanup() + + def osrandom_engine_implementation(self): + buf = self._ffi.new("char[]", 64) + with self._get_osurandom_engine() as e: + res = self._lib.ENGINE_ctrl_cmd(e, b"get_implementation", + len(buf), buf, + self._ffi.NULL, 0) + self.openssl_assert(res > 0) + return self._ffi.string(buf).decode('ascii') + + def openssl_version_text(self): + """ + Friendly string name of the loaded OpenSSL library. This is not + necessarily the same version as it was compiled against. + + Example: OpenSSL 1.0.1e 11 Feb 2013 + """ + return self._ffi.string( + self._lib.OpenSSL_version(self._lib.OPENSSL_VERSION) + ).decode("ascii") + + def create_hmac_ctx(self, key, algorithm): + return _HMACContext(self, key, algorithm) + + def _build_openssl_digest_name(self, algorithm): + if algorithm.name == "blake2b" or algorithm.name == "blake2s": + alg = "{0}{1}".format( + algorithm.name, algorithm.digest_size * 8 + ).encode("ascii") + else: + alg = algorithm.name.encode("ascii") + + return alg + + def hash_supported(self, algorithm): + name = self._build_openssl_digest_name(algorithm) + digest = self._lib.EVP_get_digestbyname(name) + return digest != self._ffi.NULL + + def hmac_supported(self, algorithm): + return self.hash_supported(algorithm) + + def create_hash_ctx(self, algorithm): + return _HashContext(self, algorithm) + + def cipher_supported(self, cipher, mode): + if self._evp_cipher_supported(cipher, mode): + return True + elif isinstance(mode, CTR) and isinstance(cipher, AES): + return True + else: + return False + + def _evp_cipher_supported(self, cipher, mode): + try: + adapter = self._cipher_registry[type(cipher), type(mode)] + except KeyError: + return False + evp_cipher = adapter(self, cipher, mode) + return self._ffi.NULL != evp_cipher + + def register_cipher_adapter(self, cipher_cls, mode_cls, adapter): + if (cipher_cls, mode_cls) in self._cipher_registry: + raise ValueError("Duplicate registration for: {0} {1}.".format( + cipher_cls, mode_cls) + ) + self._cipher_registry[cipher_cls, mode_cls] = adapter + + def _register_default_ciphers(self): + for mode_cls in [CBC, CTR, ECB, OFB, CFB, CFB8, GCM]: + self.register_cipher_adapter( + AES, + mode_cls, + GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}") + ) + for mode_cls in [CBC, CTR, ECB, OFB, CFB]: + self.register_cipher_adapter( + Camellia, + mode_cls, + GetCipherByName("{cipher.name}-{cipher.key_size}-{mode.name}") + ) + for mode_cls in [CBC, CFB, CFB8, OFB]: + self.register_cipher_adapter( + TripleDES, + mode_cls, + GetCipherByName("des-ede3-{mode.name}") + ) + self.register_cipher_adapter( + TripleDES, + ECB, + GetCipherByName("des-ede3") + ) + for mode_cls in [CBC, CFB, OFB, ECB]: + self.register_cipher_adapter( + Blowfish, + mode_cls, + GetCipherByName("bf-{mode.name}") + ) + for mode_cls in [CBC, CFB, OFB, ECB]: + self.register_cipher_adapter( + SEED, + mode_cls, + GetCipherByName("seed-{mode.name}") + ) + for cipher_cls, mode_cls in itertools.product( + [CAST5, IDEA], + [CBC, OFB, CFB, ECB], + ): + self.register_cipher_adapter( + cipher_cls, + mode_cls, + GetCipherByName("{cipher.name}-{mode.name}") + ) + self.register_cipher_adapter( + ARC4, + type(None), + GetCipherByName("rc4") + ) + + def create_symmetric_encryption_ctx(self, cipher, mode): + if (isinstance(mode, CTR) and isinstance(cipher, AES) and + not self._evp_cipher_supported(cipher, mode)): + # This is needed to provide support for AES CTR mode in OpenSSL + # 1.0.0. It can be removed when we drop 1.0.0 support (RHEL 6.4). + return _AESCTRCipherContext(self, cipher, mode) + else: + return _CipherContext(self, cipher, mode, _CipherContext._ENCRYPT) + + def create_symmetric_decryption_ctx(self, cipher, mode): + if (isinstance(mode, CTR) and isinstance(cipher, AES) and + not self._evp_cipher_supported(cipher, mode)): + # This is needed to provide support for AES CTR mode in OpenSSL + # 1.0.0. It can be removed when we drop 1.0.0 support (RHEL 6.4). + return _AESCTRCipherContext(self, cipher, mode) + else: + return _CipherContext(self, cipher, mode, _CipherContext._DECRYPT) + + def pbkdf2_hmac_supported(self, algorithm): + return self.hmac_supported(algorithm) + + def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material): + buf = self._ffi.new("unsigned char[]", length) + evp_md = self._lib.EVP_get_digestbyname( + algorithm.name.encode("ascii")) + self.openssl_assert(evp_md != self._ffi.NULL) + res = self._lib.PKCS5_PBKDF2_HMAC( + key_material, + len(key_material), + salt, + len(salt), + iterations, + evp_md, + length, + buf + ) + self.openssl_assert(res == 1) + return self._ffi.buffer(buf)[:] + + def _consume_errors(self): + return binding._consume_errors(self._lib) + + def _bn_to_int(self, bn): + assert bn != self._ffi.NULL + + if six.PY3: + # Python 3 has constant time from_bytes, so use that. + bn_num_bytes = self._lib.BN_num_bytes(bn) + bin_ptr = self._ffi.new("unsigned char[]", bn_num_bytes) + bin_len = self._lib.BN_bn2bin(bn, bin_ptr) + # A zero length means the BN has value 0 + self.openssl_assert(bin_len >= 0) + return int.from_bytes(self._ffi.buffer(bin_ptr)[:bin_len], "big") + else: + # Under Python 2 the best we can do is hex() + hex_cdata = self._lib.BN_bn2hex(bn) + self.openssl_assert(hex_cdata != self._ffi.NULL) + hex_str = self._ffi.string(hex_cdata) + self._lib.OPENSSL_free(hex_cdata) + return int(hex_str, 16) + + def _int_to_bn(self, num, bn=None): + """ + Converts a python integer to a BIGNUM. The returned BIGNUM will not + be garbage collected (to support adding them to structs that take + ownership of the object). Be sure to register it for GC if it will + be discarded after use. + """ + assert bn is None or bn != self._ffi.NULL + + if bn is None: + bn = self._ffi.NULL + + if six.PY3: + # Python 3 has constant time to_bytes, so use that. + + binary = num.to_bytes(int(num.bit_length() / 8.0 + 1), "big") + bn_ptr = self._lib.BN_bin2bn(binary, len(binary), bn) + self.openssl_assert(bn_ptr != self._ffi.NULL) + return bn_ptr + + else: + # Under Python 2 the best we can do is hex() + + hex_num = hex(num).rstrip("L").lstrip("0x").encode("ascii") or b"0" + bn_ptr = self._ffi.new("BIGNUM **") + bn_ptr[0] = bn + res = self._lib.BN_hex2bn(bn_ptr, hex_num) + self.openssl_assert(res != 0) + self.openssl_assert(bn_ptr[0] != self._ffi.NULL) + return bn_ptr[0] + + def generate_rsa_private_key(self, public_exponent, key_size): + rsa._verify_rsa_parameters(public_exponent, key_size) + + rsa_cdata = self._lib.RSA_new() + self.openssl_assert(rsa_cdata != self._ffi.NULL) + rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + + bn = self._int_to_bn(public_exponent) + bn = self._ffi.gc(bn, self._lib.BN_free) + + res = self._lib.RSA_generate_key_ex( + rsa_cdata, key_size, bn, self._ffi.NULL + ) + self.openssl_assert(res == 1) + evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) + + return _RSAPrivateKey(self, rsa_cdata, evp_pkey) + + def generate_rsa_parameters_supported(self, public_exponent, key_size): + return (public_exponent >= 3 and public_exponent & 1 != 0 and + key_size >= 512) + + def load_rsa_private_numbers(self, numbers): + rsa._check_private_key_components( + numbers.p, + numbers.q, + numbers.d, + numbers.dmp1, + numbers.dmq1, + numbers.iqmp, + numbers.public_numbers.e, + numbers.public_numbers.n + ) + rsa_cdata = self._lib.RSA_new() + self.openssl_assert(rsa_cdata != self._ffi.NULL) + rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + p = self._int_to_bn(numbers.p) + q = self._int_to_bn(numbers.q) + d = self._int_to_bn(numbers.d) + dmp1 = self._int_to_bn(numbers.dmp1) + dmq1 = self._int_to_bn(numbers.dmq1) + iqmp = self._int_to_bn(numbers.iqmp) + e = self._int_to_bn(numbers.public_numbers.e) + n = self._int_to_bn(numbers.public_numbers.n) + res = self._lib.RSA_set0_factors(rsa_cdata, p, q) + self.openssl_assert(res == 1) + res = self._lib.RSA_set0_key(rsa_cdata, n, e, d) + self.openssl_assert(res == 1) + res = self._lib.RSA_set0_crt_params(rsa_cdata, dmp1, dmq1, iqmp) + self.openssl_assert(res == 1) + res = self._lib.RSA_blinding_on(rsa_cdata, self._ffi.NULL) + self.openssl_assert(res == 1) + evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) + + return _RSAPrivateKey(self, rsa_cdata, evp_pkey) + + def load_rsa_public_numbers(self, numbers): + rsa._check_public_key_components(numbers.e, numbers.n) + rsa_cdata = self._lib.RSA_new() + self.openssl_assert(rsa_cdata != self._ffi.NULL) + rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + e = self._int_to_bn(numbers.e) + n = self._int_to_bn(numbers.n) + res = self._lib.RSA_set0_key(rsa_cdata, n, e, self._ffi.NULL) + self.openssl_assert(res == 1) + evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) + + return _RSAPublicKey(self, rsa_cdata, evp_pkey) + + def _create_evp_pkey_gc(self): + evp_pkey = self._lib.EVP_PKEY_new() + self.openssl_assert(evp_pkey != self._ffi.NULL) + evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + return evp_pkey + + def _rsa_cdata_to_evp_pkey(self, rsa_cdata): + evp_pkey = self._create_evp_pkey_gc() + res = self._lib.EVP_PKEY_set1_RSA(evp_pkey, rsa_cdata) + self.openssl_assert(res == 1) + return evp_pkey + + def _bytes_to_bio(self, data): + """ + Return a _MemoryBIO namedtuple of (BIO, char*). + + The char* is the storage for the BIO and it must stay alive until the + BIO is finished with. + """ + data_char_p = self._ffi.new("char[]", data) + bio = self._lib.BIO_new_mem_buf( + data_char_p, len(data) + ) + self.openssl_assert(bio != self._ffi.NULL) + + return _MemoryBIO(self._ffi.gc(bio, self._lib.BIO_free), data_char_p) + + def _create_mem_bio_gc(self): + """ + Creates an empty memory BIO. + """ + bio_method = self._lib.BIO_s_mem() + self.openssl_assert(bio_method != self._ffi.NULL) + bio = self._lib.BIO_new(bio_method) + self.openssl_assert(bio != self._ffi.NULL) + bio = self._ffi.gc(bio, self._lib.BIO_free) + return bio + + def _read_mem_bio(self, bio): + """ + Reads a memory BIO. This only works on memory BIOs. + """ + buf = self._ffi.new("char **") + buf_len = self._lib.BIO_get_mem_data(bio, buf) + self.openssl_assert(buf_len > 0) + self.openssl_assert(buf[0] != self._ffi.NULL) + bio_data = self._ffi.buffer(buf[0], buf_len)[:] + return bio_data + + def _evp_pkey_to_private_key(self, evp_pkey): + """ + Return the appropriate type of PrivateKey given an evp_pkey cdata + pointer. + """ + + key_type = self._lib.EVP_PKEY_id(evp_pkey) + + if key_type == self._lib.EVP_PKEY_RSA: + rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey) + self.openssl_assert(rsa_cdata != self._ffi.NULL) + rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + return _RSAPrivateKey(self, rsa_cdata, evp_pkey) + elif key_type == self._lib.EVP_PKEY_DSA: + dsa_cdata = self._lib.EVP_PKEY_get1_DSA(evp_pkey) + self.openssl_assert(dsa_cdata != self._ffi.NULL) + dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + return _DSAPrivateKey(self, dsa_cdata, evp_pkey) + elif (self._lib.Cryptography_HAS_EC == 1 and + key_type == self._lib.EVP_PKEY_EC): + ec_cdata = self._lib.EVP_PKEY_get1_EC_KEY(evp_pkey) + self.openssl_assert(ec_cdata != self._ffi.NULL) + ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) + return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) + else: + raise UnsupportedAlgorithm("Unsupported key type.") + + def _evp_pkey_to_public_key(self, evp_pkey): + """ + Return the appropriate type of PublicKey given an evp_pkey cdata + pointer. + """ + + key_type = self._lib.EVP_PKEY_id(evp_pkey) + + if key_type == self._lib.EVP_PKEY_RSA: + rsa_cdata = self._lib.EVP_PKEY_get1_RSA(evp_pkey) + self.openssl_assert(rsa_cdata != self._ffi.NULL) + rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + return _RSAPublicKey(self, rsa_cdata, evp_pkey) + elif key_type == self._lib.EVP_PKEY_DSA: + dsa_cdata = self._lib.EVP_PKEY_get1_DSA(evp_pkey) + self.openssl_assert(dsa_cdata != self._ffi.NULL) + dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + return _DSAPublicKey(self, dsa_cdata, evp_pkey) + elif (self._lib.Cryptography_HAS_EC == 1 and + key_type == self._lib.EVP_PKEY_EC): + ec_cdata = self._lib.EVP_PKEY_get1_EC_KEY(evp_pkey) + self.openssl_assert(ec_cdata != self._ffi.NULL) + ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) + return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) + else: + raise UnsupportedAlgorithm("Unsupported key type.") + + def _pem_password_cb(self, password): + """ + Generate a pem_password_cb function pointer that copied the password to + OpenSSL as required and returns the number of bytes copied. + + typedef int pem_password_cb(char *buf, int size, + int rwflag, void *userdata); + + Useful for decrypting PKCS8 files and so on. + + Returns a tuple of (cdata function pointer, userdata). + """ + # Forward compatibility for new static callbacks: + # _pem_password_cb is not a nested function because closures don't + # work well with static callbacks. Static callbacks are registered + # globally. The backend is passed in as userdata argument. + + userdata = _PasswordUserdata(password=password) + return _pem_password_cb, userdata + + def _oaep_hash_supported(self, algorithm): + if self._lib.Cryptography_HAS_RSA_OAEP_MD: + return isinstance( + algorithm, ( + hashes.SHA1, + hashes.SHA224, + hashes.SHA256, + hashes.SHA384, + hashes.SHA512, + ) + ) + else: + return isinstance(algorithm, hashes.SHA1) + + def _pss_mgf1_hash_supported(self, algorithm): + if self._lib.Cryptography_HAS_MGF1_MD: + return self.hash_supported(algorithm) + else: + return isinstance(algorithm, hashes.SHA1) + + def rsa_padding_supported(self, padding): + if isinstance(padding, PKCS1v15): + return True + elif isinstance(padding, PSS) and isinstance(padding._mgf, MGF1): + return self._pss_mgf1_hash_supported(padding._mgf._algorithm) + elif isinstance(padding, OAEP) and isinstance(padding._mgf, MGF1): + return ( + self._oaep_hash_supported(padding._mgf._algorithm) and + self._oaep_hash_supported(padding._algorithm) + ) + else: + return False + + def generate_dsa_parameters(self, key_size): + if key_size not in (1024, 2048, 3072): + raise ValueError("Key size must be 1024 or 2048 or 3072 bits.") + + ctx = self._lib.DSA_new() + self.openssl_assert(ctx != self._ffi.NULL) + ctx = self._ffi.gc(ctx, self._lib.DSA_free) + + res = self._lib.DSA_generate_parameters_ex( + ctx, key_size, self._ffi.NULL, 0, + self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + ) + + self.openssl_assert(res == 1) + + return _DSAParameters(self, ctx) + + def generate_dsa_private_key(self, parameters): + ctx = self._lib.DSAparams_dup(parameters._dsa_cdata) + self.openssl_assert(ctx != self._ffi.NULL) + ctx = self._ffi.gc(ctx, self._lib.DSA_free) + self._lib.DSA_generate_key(ctx) + evp_pkey = self._dsa_cdata_to_evp_pkey(ctx) + + return _DSAPrivateKey(self, ctx, evp_pkey) + + def generate_dsa_private_key_and_parameters(self, key_size): + parameters = self.generate_dsa_parameters(key_size) + return self.generate_dsa_private_key(parameters) + + def _dsa_cdata_set_values(self, dsa_cdata, p, q, g, pub_key, priv_key): + res = self._lib.DSA_set0_pqg(dsa_cdata, p, q, g) + self.openssl_assert(res == 1) + res = self._lib.DSA_set0_key(dsa_cdata, pub_key, priv_key) + self.openssl_assert(res == 1) + + def load_dsa_private_numbers(self, numbers): + dsa._check_dsa_private_numbers(numbers) + parameter_numbers = numbers.public_numbers.parameter_numbers + + dsa_cdata = self._lib.DSA_new() + self.openssl_assert(dsa_cdata != self._ffi.NULL) + dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + + p = self._int_to_bn(parameter_numbers.p) + q = self._int_to_bn(parameter_numbers.q) + g = self._int_to_bn(parameter_numbers.g) + pub_key = self._int_to_bn(numbers.public_numbers.y) + priv_key = self._int_to_bn(numbers.x) + self._dsa_cdata_set_values(dsa_cdata, p, q, g, pub_key, priv_key) + + evp_pkey = self._dsa_cdata_to_evp_pkey(dsa_cdata) + + return _DSAPrivateKey(self, dsa_cdata, evp_pkey) + + def load_dsa_public_numbers(self, numbers): + dsa._check_dsa_parameters(numbers.parameter_numbers) + dsa_cdata = self._lib.DSA_new() + self.openssl_assert(dsa_cdata != self._ffi.NULL) + dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + + p = self._int_to_bn(numbers.parameter_numbers.p) + q = self._int_to_bn(numbers.parameter_numbers.q) + g = self._int_to_bn(numbers.parameter_numbers.g) + pub_key = self._int_to_bn(numbers.y) + priv_key = self._ffi.NULL + self._dsa_cdata_set_values(dsa_cdata, p, q, g, pub_key, priv_key) + + evp_pkey = self._dsa_cdata_to_evp_pkey(dsa_cdata) + + return _DSAPublicKey(self, dsa_cdata, evp_pkey) + + def load_dsa_parameter_numbers(self, numbers): + dsa._check_dsa_parameters(numbers) + dsa_cdata = self._lib.DSA_new() + self.openssl_assert(dsa_cdata != self._ffi.NULL) + dsa_cdata = self._ffi.gc(dsa_cdata, self._lib.DSA_free) + + p = self._int_to_bn(numbers.p) + q = self._int_to_bn(numbers.q) + g = self._int_to_bn(numbers.g) + res = self._lib.DSA_set0_pqg(dsa_cdata, p, q, g) + self.openssl_assert(res == 1) + + return _DSAParameters(self, dsa_cdata) + + def _dsa_cdata_to_evp_pkey(self, dsa_cdata): + evp_pkey = self._create_evp_pkey_gc() + res = self._lib.EVP_PKEY_set1_DSA(evp_pkey, dsa_cdata) + self.openssl_assert(res == 1) + return evp_pkey + + def dsa_hash_supported(self, algorithm): + return self.hash_supported(algorithm) + + def dsa_parameters_supported(self, p, q, g): + return True + + def cmac_algorithm_supported(self, algorithm): + return ( + self._lib.Cryptography_HAS_CMAC == 1 and + self.cipher_supported( + algorithm, CBC(b"\x00" * algorithm.block_size) + ) + ) + + def create_cmac_ctx(self, algorithm): + return _CMACContext(self, algorithm) + + def create_x509_csr(self, builder, private_key, algorithm): + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError('Algorithm must be a registered hash algorithm.') + + if self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_101: + if isinstance(private_key, _DSAPrivateKey): + raise NotImplementedError( + "Certificate signing requests aren't implemented for DSA" + " keys on OpenSSL versions less than 1.0.1." + ) + if isinstance(private_key, _EllipticCurvePrivateKey): + raise NotImplementedError( + "Certificate signing requests aren't implemented for EC" + " keys on OpenSSL versions less than 1.0.1." + ) + + # Resolve the signature algorithm. + evp_md = self._lib.EVP_get_digestbyname( + algorithm.name.encode('ascii') + ) + self.openssl_assert(evp_md != self._ffi.NULL) + + # Create an empty request. + x509_req = self._lib.X509_REQ_new() + self.openssl_assert(x509_req != self._ffi.NULL) + x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free) + + # Set x509 version. + res = self._lib.X509_REQ_set_version(x509_req, x509.Version.v1.value) + self.openssl_assert(res == 1) + + # Set subject name. + res = self._lib.X509_REQ_set_subject_name( + x509_req, _encode_name_gc(self, builder._subject_name) + ) + self.openssl_assert(res == 1) + + # Set subject public key. + public_key = private_key.public_key() + res = self._lib.X509_REQ_set_pubkey( + x509_req, public_key._evp_pkey + ) + self.openssl_assert(res == 1) + + # Add extensions. + sk_extension = self._lib.sk_X509_EXTENSION_new_null() + self.openssl_assert(sk_extension != self._ffi.NULL) + sk_extension = self._ffi.gc( + sk_extension, self._lib.sk_X509_EXTENSION_free + ) + # gc is not necessary for CSRs, as sk_X509_EXTENSION_free + # will release all the X509_EXTENSIONs. + self._create_x509_extensions( + extensions=builder._extensions, + handlers=_EXTENSION_ENCODE_HANDLERS, + x509_obj=sk_extension, + add_func=self._lib.sk_X509_EXTENSION_insert, + gc=False + ) + res = self._lib.X509_REQ_add_extensions(x509_req, sk_extension) + self.openssl_assert(res == 1) + + # Sign the request using the requester's private key. + res = self._lib.X509_REQ_sign( + x509_req, private_key._evp_pkey, evp_md + ) + if res == 0: + errors = self._consume_errors() + self.openssl_assert(errors[0][1] == self._lib.ERR_LIB_RSA) + self.openssl_assert( + errors[0][3] == self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY + ) + raise ValueError("Digest too big for RSA key") + + return _CertificateSigningRequest(self, x509_req) + + def create_x509_certificate(self, builder, private_key, algorithm): + if not isinstance(builder, x509.CertificateBuilder): + raise TypeError('Builder type mismatch.') + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError('Algorithm must be a registered hash algorithm.') + + if self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_101: + if isinstance(private_key, _DSAPrivateKey): + raise NotImplementedError( + "Certificate signatures aren't implemented for DSA" + " keys on OpenSSL versions less than 1.0.1." + ) + if isinstance(private_key, _EllipticCurvePrivateKey): + raise NotImplementedError( + "Certificate signatures aren't implemented for EC" + " keys on OpenSSL versions less than 1.0.1." + ) + + # Resolve the signature algorithm. + evp_md = self._lib.EVP_get_digestbyname( + algorithm.name.encode('ascii') + ) + self.openssl_assert(evp_md != self._ffi.NULL) + + # Create an empty certificate. + x509_cert = self._lib.X509_new() + x509_cert = self._ffi.gc(x509_cert, backend._lib.X509_free) + + # Set the x509 version. + res = self._lib.X509_set_version(x509_cert, builder._version.value) + self.openssl_assert(res == 1) + + # Set the subject's name. + res = self._lib.X509_set_subject_name( + x509_cert, _encode_name_gc(self, builder._subject_name) + ) + self.openssl_assert(res == 1) + + # Set the subject's public key. + res = self._lib.X509_set_pubkey( + x509_cert, builder._public_key._evp_pkey + ) + self.openssl_assert(res == 1) + + # Set the certificate serial number. + serial_number = _encode_asn1_int_gc(self, builder._serial_number) + res = self._lib.X509_set_serialNumber(x509_cert, serial_number) + self.openssl_assert(res == 1) + + # Set the "not before" time. + res = self._lib.ASN1_TIME_set( + self._lib.X509_get_notBefore(x509_cert), + calendar.timegm(builder._not_valid_before.timetuple()) + ) + if res == self._ffi.NULL: + self._raise_time_set_error() + + # Set the "not after" time. + res = self._lib.ASN1_TIME_set( + self._lib.X509_get_notAfter(x509_cert), + calendar.timegm(builder._not_valid_after.timetuple()) + ) + if res == self._ffi.NULL: + self._raise_time_set_error() + + # Add extensions. + self._create_x509_extensions( + extensions=builder._extensions, + handlers=_EXTENSION_ENCODE_HANDLERS, + x509_obj=x509_cert, + add_func=self._lib.X509_add_ext, + gc=True + ) + + # Set the issuer name. + res = self._lib.X509_set_issuer_name( + x509_cert, _encode_name_gc(self, builder._issuer_name) + ) + self.openssl_assert(res == 1) + + # Sign the certificate with the issuer's private key. + res = self._lib.X509_sign( + x509_cert, private_key._evp_pkey, evp_md + ) + if res == 0: + errors = self._consume_errors() + self.openssl_assert(errors[0][1] == self._lib.ERR_LIB_RSA) + self.openssl_assert( + errors[0][3] == self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY + ) + raise ValueError("Digest too big for RSA key") + + return _Certificate(self, x509_cert) + + def _raise_time_set_error(self): + errors = self._consume_errors() + self.openssl_assert(errors[0][1] == self._lib.ERR_LIB_ASN1) + self.openssl_assert( + errors[0][3] == self._lib.ASN1_R_ERROR_GETTING_TIME + ) + raise ValueError( + "Invalid time. This error can occur if you set a time too far in " + "the future on Windows." + ) + + def create_x509_crl(self, builder, private_key, algorithm): + if not isinstance(builder, x509.CertificateRevocationListBuilder): + raise TypeError('Builder type mismatch.') + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError('Algorithm must be a registered hash algorithm.') + + if self._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_101: + if isinstance(private_key, _DSAPrivateKey): + raise NotImplementedError( + "CRL signatures aren't implemented for DSA" + " keys on OpenSSL versions less than 1.0.1." + ) + if isinstance(private_key, _EllipticCurvePrivateKey): + raise NotImplementedError( + "CRL signatures aren't implemented for EC" + " keys on OpenSSL versions less than 1.0.1." + ) + + evp_md = self._lib.EVP_get_digestbyname( + algorithm.name.encode('ascii') + ) + self.openssl_assert(evp_md != self._ffi.NULL) + + # Create an empty CRL. + x509_crl = self._lib.X509_CRL_new() + x509_crl = self._ffi.gc(x509_crl, backend._lib.X509_CRL_free) + + # Set the x509 CRL version. We only support v2 (integer value 1). + res = self._lib.X509_CRL_set_version(x509_crl, 1) + self.openssl_assert(res == 1) + + # Set the issuer name. + res = self._lib.X509_CRL_set_issuer_name( + x509_crl, _encode_name_gc(self, builder._issuer_name) + ) + self.openssl_assert(res == 1) + + # Set the last update time. + last_update = self._lib.ASN1_TIME_set( + self._ffi.NULL, calendar.timegm(builder._last_update.timetuple()) + ) + self.openssl_assert(last_update != self._ffi.NULL) + last_update = self._ffi.gc(last_update, self._lib.ASN1_TIME_free) + res = self._lib.X509_CRL_set_lastUpdate(x509_crl, last_update) + self.openssl_assert(res == 1) + + # Set the next update time. + next_update = self._lib.ASN1_TIME_set( + self._ffi.NULL, calendar.timegm(builder._next_update.timetuple()) + ) + self.openssl_assert(next_update != self._ffi.NULL) + next_update = self._ffi.gc(next_update, self._lib.ASN1_TIME_free) + res = self._lib.X509_CRL_set_nextUpdate(x509_crl, next_update) + self.openssl_assert(res == 1) + + # Add extensions. + self._create_x509_extensions( + extensions=builder._extensions, + handlers=_CRL_EXTENSION_ENCODE_HANDLERS, + x509_obj=x509_crl, + add_func=self._lib.X509_CRL_add_ext, + gc=True + ) + + # add revoked certificates + for revoked_cert in builder._revoked_certificates: + # Duplicating because the X509_CRL takes ownership and will free + # this memory when X509_CRL_free is called. + revoked = self._lib.Cryptography_X509_REVOKED_dup( + revoked_cert._x509_revoked + ) + self.openssl_assert(revoked != self._ffi.NULL) + res = self._lib.X509_CRL_add0_revoked(x509_crl, revoked) + self.openssl_assert(res == 1) + + res = self._lib.X509_CRL_sign( + x509_crl, private_key._evp_pkey, evp_md + ) + if res == 0: + errors = self._consume_errors() + self.openssl_assert(errors[0][1] == self._lib.ERR_LIB_RSA) + self.openssl_assert( + errors[0][3] == self._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY + ) + raise ValueError("Digest too big for RSA key") + + return _CertificateRevocationList(self, x509_crl) + + def _create_x509_extensions(self, extensions, handlers, x509_obj, + add_func, gc): + for i, extension in enumerate(extensions): + x509_extension = self._create_x509_extension( + handlers, extension + ) + self.openssl_assert(x509_extension != self._ffi.NULL) + + if gc: + x509_extension = self._ffi.gc( + x509_extension, self._lib.X509_EXTENSION_free + ) + res = add_func(x509_obj, x509_extension, i) + self.openssl_assert(res >= 1) + + def _create_x509_extension(self, handlers, extension): + if isinstance(extension.value, x509.UnrecognizedExtension): + obj = _txt2obj_gc(self, extension.oid.dotted_string) + value = _encode_asn1_str_gc( + self, extension.value.value, len(extension.value.value) + ) + return self._lib.X509_EXTENSION_create_by_OBJ( + self._ffi.NULL, + obj, + 1 if extension.critical else 0, + value + ) + else: + try: + encode = handlers[extension.oid] + except KeyError: + raise NotImplementedError( + 'Extension not supported: {0}'.format(extension.oid) + ) + + ext_struct = encode(self, extension.value) + nid = self._lib.OBJ_txt2nid( + extension.oid.dotted_string.encode("ascii") + ) + backend.openssl_assert(nid != self._lib.NID_undef) + return self._lib.X509V3_EXT_i2d( + nid, 1 if extension.critical else 0, ext_struct + ) + + def create_x509_revoked_certificate(self, builder): + if not isinstance(builder, x509.RevokedCertificateBuilder): + raise TypeError('Builder type mismatch.') + + x509_revoked = self._lib.X509_REVOKED_new() + self.openssl_assert(x509_revoked != self._ffi.NULL) + x509_revoked = self._ffi.gc(x509_revoked, self._lib.X509_REVOKED_free) + serial_number = _encode_asn1_int_gc(self, builder._serial_number) + res = self._lib.X509_REVOKED_set_serialNumber( + x509_revoked, serial_number + ) + self.openssl_assert(res == 1) + rev_date = self._lib.ASN1_TIME_set( + self._ffi.NULL, + calendar.timegm(builder._revocation_date.timetuple()) + ) + self.openssl_assert(rev_date != self._ffi.NULL) + rev_date = self._ffi.gc(rev_date, self._lib.ASN1_TIME_free) + res = self._lib.X509_REVOKED_set_revocationDate(x509_revoked, rev_date) + self.openssl_assert(res == 1) + # add CRL entry extensions + self._create_x509_extensions( + extensions=builder._extensions, + handlers=_CRL_ENTRY_EXTENSION_ENCODE_HANDLERS, + x509_obj=x509_revoked, + add_func=self._lib.X509_REVOKED_add_ext, + gc=True + ) + return _RevokedCertificate(self, None, x509_revoked) + + def load_pem_private_key(self, data, password): + return self._load_key( + self._lib.PEM_read_bio_PrivateKey, + self._evp_pkey_to_private_key, + data, + password, + ) + + def load_pem_public_key(self, data): + mem_bio = self._bytes_to_bio(data) + evp_pkey = self._lib.PEM_read_bio_PUBKEY( + mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + ) + if evp_pkey != self._ffi.NULL: + evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + return self._evp_pkey_to_public_key(evp_pkey) + else: + # It's not a (RSA/DSA/ECDSA) subjectPublicKeyInfo, but we still + # need to check to see if it is a pure PKCS1 RSA public key (not + # embedded in a subjectPublicKeyInfo) + self._consume_errors() + res = self._lib.BIO_reset(mem_bio.bio) + self.openssl_assert(res == 1) + rsa_cdata = self._lib.PEM_read_bio_RSAPublicKey( + mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + ) + if rsa_cdata != self._ffi.NULL: + rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) + return _RSAPublicKey(self, rsa_cdata, evp_pkey) + else: + self._handle_key_loading_error() + + def load_der_private_key(self, data, password): + # OpenSSL has a function called d2i_AutoPrivateKey that in theory + # handles this automatically, however it doesn't handle encrypted + # private keys. Instead we try to load the key two different ways. + # First we'll try to load it as a traditional key. + bio_data = self._bytes_to_bio(data) + key = self._evp_pkey_from_der_traditional_key(bio_data, password) + if key: + return self._evp_pkey_to_private_key(key) + else: + # Finally we try to load it with the method that handles encrypted + # PKCS8 properly. + return self._load_key( + self._lib.d2i_PKCS8PrivateKey_bio, + self._evp_pkey_to_private_key, + data, + password, + ) + + def _evp_pkey_from_der_traditional_key(self, bio_data, password): + key = self._lib.d2i_PrivateKey_bio(bio_data.bio, self._ffi.NULL) + if key != self._ffi.NULL: + key = self._ffi.gc(key, self._lib.EVP_PKEY_free) + if password is not None: + raise TypeError( + "Password was given but private key is not encrypted." + ) + + return key + else: + self._consume_errors() + return None + + def load_der_public_key(self, data): + mem_bio = self._bytes_to_bio(data) + evp_pkey = self._lib.d2i_PUBKEY_bio(mem_bio.bio, self._ffi.NULL) + if evp_pkey != self._ffi.NULL: + evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + return self._evp_pkey_to_public_key(evp_pkey) + else: + # It's not a (RSA/DSA/ECDSA) subjectPublicKeyInfo, but we still + # need to check to see if it is a pure PKCS1 RSA public key (not + # embedded in a subjectPublicKeyInfo) + self._consume_errors() + res = self._lib.BIO_reset(mem_bio.bio) + self.openssl_assert(res == 1) + rsa_cdata = self._lib.d2i_RSAPublicKey_bio( + mem_bio.bio, self._ffi.NULL + ) + if rsa_cdata != self._ffi.NULL: + rsa_cdata = self._ffi.gc(rsa_cdata, self._lib.RSA_free) + evp_pkey = self._rsa_cdata_to_evp_pkey(rsa_cdata) + return _RSAPublicKey(self, rsa_cdata, evp_pkey) + else: + self._handle_key_loading_error() + + def load_pem_x509_certificate(self, data): + mem_bio = self._bytes_to_bio(data) + x509 = self._lib.PEM_read_bio_X509( + mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + ) + if x509 == self._ffi.NULL: + self._consume_errors() + raise ValueError("Unable to load certificate") + + x509 = self._ffi.gc(x509, self._lib.X509_free) + return _Certificate(self, x509) + + def load_der_x509_certificate(self, data): + mem_bio = self._bytes_to_bio(data) + x509 = self._lib.d2i_X509_bio(mem_bio.bio, self._ffi.NULL) + if x509 == self._ffi.NULL: + self._consume_errors() + raise ValueError("Unable to load certificate") + + x509 = self._ffi.gc(x509, self._lib.X509_free) + return _Certificate(self, x509) + + def load_pem_x509_crl(self, data): + mem_bio = self._bytes_to_bio(data) + x509_crl = self._lib.PEM_read_bio_X509_CRL( + mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + ) + if x509_crl == self._ffi.NULL: + self._consume_errors() + raise ValueError("Unable to load CRL") + + x509_crl = self._ffi.gc(x509_crl, self._lib.X509_CRL_free) + return _CertificateRevocationList(self, x509_crl) + + def load_der_x509_crl(self, data): + mem_bio = self._bytes_to_bio(data) + x509_crl = self._lib.d2i_X509_CRL_bio(mem_bio.bio, self._ffi.NULL) + if x509_crl == self._ffi.NULL: + self._consume_errors() + raise ValueError("Unable to load CRL") + + x509_crl = self._ffi.gc(x509_crl, self._lib.X509_CRL_free) + return _CertificateRevocationList(self, x509_crl) + + def load_pem_x509_csr(self, data): + mem_bio = self._bytes_to_bio(data) + x509_req = self._lib.PEM_read_bio_X509_REQ( + mem_bio.bio, self._ffi.NULL, self._ffi.NULL, self._ffi.NULL + ) + if x509_req == self._ffi.NULL: + self._consume_errors() + raise ValueError("Unable to load request") + + x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free) + return _CertificateSigningRequest(self, x509_req) + + def load_der_x509_csr(self, data): + mem_bio = self._bytes_to_bio(data) + x509_req = self._lib.d2i_X509_REQ_bio(mem_bio.bio, self._ffi.NULL) + if x509_req == self._ffi.NULL: + self._consume_errors() + raise ValueError("Unable to load request") + + x509_req = self._ffi.gc(x509_req, self._lib.X509_REQ_free) + return _CertificateSigningRequest(self, x509_req) + + def _load_key(self, openssl_read_func, convert_func, data, password): + mem_bio = self._bytes_to_bio(data) + + password_cb, userdata = self._pem_password_cb(password) + userdata_handle = self._ffi.new_handle(userdata) + + evp_pkey = openssl_read_func( + mem_bio.bio, + self._ffi.NULL, + password_cb, + userdata_handle, + ) + + if evp_pkey == self._ffi.NULL: + if userdata.exception is not None: + errors = self._consume_errors() + self.openssl_assert(errors) + raise userdata.exception + else: + self._handle_key_loading_error() + + evp_pkey = self._ffi.gc(evp_pkey, self._lib.EVP_PKEY_free) + + if password is not None and userdata.called == 0: + raise TypeError( + "Password was given but private key is not encrypted.") + + assert ( + (password is not None and userdata.called == 1) or + password is None + ) + + return convert_func(evp_pkey) + + def _handle_key_loading_error(self): + errors = self._consume_errors() + + if not errors: + raise ValueError("Could not unserialize key data.") + + elif errors[0][1:] in ( + ( + self._lib.ERR_LIB_EVP, + self._lib.EVP_F_EVP_DECRYPTFINAL_EX, + self._lib.EVP_R_BAD_DECRYPT + ), + ( + self._lib.ERR_LIB_PKCS12, + self._lib.PKCS12_F_PKCS12_PBE_CRYPT, + self._lib.PKCS12_R_PKCS12_CIPHERFINAL_ERROR, + ) + ): + raise ValueError("Bad decrypt. Incorrect password?") + + elif errors[0][1:] in ( + ( + self._lib.ERR_LIB_PEM, + self._lib.PEM_F_PEM_GET_EVP_CIPHER_INFO, + self._lib.PEM_R_UNSUPPORTED_ENCRYPTION + ), + + ( + self._lib.ERR_LIB_EVP, + self._lib.EVP_F_EVP_PBE_CIPHERINIT, + self._lib.EVP_R_UNKNOWN_PBE_ALGORITHM + ) + ): + raise UnsupportedAlgorithm( + "PEM data is encrypted with an unsupported cipher", + _Reasons.UNSUPPORTED_CIPHER + ) + + elif any( + error[1:] == ( + self._lib.ERR_LIB_EVP, + self._lib.EVP_F_EVP_PKCS82PKEY, + self._lib.EVP_R_UNSUPPORTED_PRIVATE_KEY_ALGORITHM + ) + for error in errors + ): + raise UnsupportedAlgorithm( + "Unsupported public key algorithm.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ) + + else: + assert errors[0][1] in ( + self._lib.ERR_LIB_EVP, + self._lib.ERR_LIB_PEM, + self._lib.ERR_LIB_ASN1, + ) + raise ValueError("Could not unserialize key data.") + + def elliptic_curve_supported(self, curve): + if self._lib.Cryptography_HAS_EC != 1: + return False + + try: + curve_nid = self._elliptic_curve_to_nid(curve) + except UnsupportedAlgorithm: + curve_nid = self._lib.NID_undef + + ctx = self._lib.EC_GROUP_new_by_curve_name(curve_nid) + + if ctx == self._ffi.NULL: + errors = self._consume_errors() + self.openssl_assert( + curve_nid == self._lib.NID_undef or + errors[0][1:] == ( + self._lib.ERR_LIB_EC, + self._lib.EC_F_EC_GROUP_NEW_BY_CURVE_NAME, + self._lib.EC_R_UNKNOWN_GROUP + ) + ) + return False + else: + self.openssl_assert(curve_nid != self._lib.NID_undef) + self._lib.EC_GROUP_free(ctx) + return True + + def elliptic_curve_signature_algorithm_supported( + self, signature_algorithm, curve + ): + if self._lib.Cryptography_HAS_EC != 1: + return False + + # We only support ECDSA right now. + if not isinstance(signature_algorithm, ec.ECDSA): + return False + + return self.elliptic_curve_supported(curve) + + def generate_elliptic_curve_private_key(self, curve): + """ + Generate a new private key on the named curve. + """ + + if self.elliptic_curve_supported(curve): + curve_nid = self._elliptic_curve_to_nid(curve) + + ec_cdata = self._lib.EC_KEY_new_by_curve_name(curve_nid) + self.openssl_assert(ec_cdata != self._ffi.NULL) + ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) + + res = self._lib.EC_KEY_generate_key(ec_cdata) + self.openssl_assert(res == 1) + + res = self._lib.EC_KEY_check_key(ec_cdata) + self.openssl_assert(res == 1) + + evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) + + return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) + else: + raise UnsupportedAlgorithm( + "Backend object does not support {0}.".format(curve.name), + _Reasons.UNSUPPORTED_ELLIPTIC_CURVE + ) + + def load_elliptic_curve_private_numbers(self, numbers): + public = numbers.public_numbers + + curve_nid = self._elliptic_curve_to_nid(public.curve) + + ec_cdata = self._lib.EC_KEY_new_by_curve_name(curve_nid) + self.openssl_assert(ec_cdata != self._ffi.NULL) + ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) + + ec_cdata = self._ec_key_set_public_key_affine_coordinates( + ec_cdata, public.x, public.y) + + res = self._lib.EC_KEY_set_private_key( + ec_cdata, self._int_to_bn(numbers.private_value)) + self.openssl_assert(res == 1) + evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) + + return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) + + def load_elliptic_curve_public_numbers(self, numbers): + curve_nid = self._elliptic_curve_to_nid(numbers.curve) + + ec_cdata = self._lib.EC_KEY_new_by_curve_name(curve_nid) + self.openssl_assert(ec_cdata != self._ffi.NULL) + ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) + + ec_cdata = self._ec_key_set_public_key_affine_coordinates( + ec_cdata, numbers.x, numbers.y) + evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) + + return _EllipticCurvePublicKey(self, ec_cdata, evp_pkey) + + def derive_elliptic_curve_private_key(self, private_value, curve): + curve_nid = self._elliptic_curve_to_nid(curve) + + ec_cdata = self._lib.EC_KEY_new_by_curve_name(curve_nid) + self.openssl_assert(ec_cdata != self._ffi.NULL) + ec_cdata = self._ffi.gc(ec_cdata, self._lib.EC_KEY_free) + + set_func, get_func, group = ( + self._ec_key_determine_group_get_set_funcs(ec_cdata) + ) + + point = self._lib.EC_POINT_new(group) + self.openssl_assert(point != self._ffi.NULL) + point = self._ffi.gc(point, self._lib.EC_POINT_free) + + value = self._int_to_bn(private_value) + value = self._ffi.gc(value, self._lib.BN_free) + + with self._tmp_bn_ctx() as bn_ctx: + res = self._lib.EC_POINT_mul(group, point, value, self._ffi.NULL, + self._ffi.NULL, bn_ctx) + self.openssl_assert(res == 1) + + bn_x = self._lib.BN_CTX_get(bn_ctx) + bn_y = self._lib.BN_CTX_get(bn_ctx) + + res = get_func(group, point, bn_x, bn_y, bn_ctx) + self.openssl_assert(res == 1) + + res = self._lib.EC_KEY_set_public_key(ec_cdata, point) + self.openssl_assert(res == 1) + res = self._lib.EC_KEY_set_private_key( + ec_cdata, self._int_to_bn(private_value)) + self.openssl_assert(res == 1) + + evp_pkey = self._ec_cdata_to_evp_pkey(ec_cdata) + + return _EllipticCurvePrivateKey(self, ec_cdata, evp_pkey) + + def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve): + return ( + self.elliptic_curve_supported(curve) and + self._lib.Cryptography_HAS_ECDH == 1 and + isinstance(algorithm, ec.ECDH) + ) + + def _ec_cdata_to_evp_pkey(self, ec_cdata): + evp_pkey = self._create_evp_pkey_gc() + res = self._lib.EVP_PKEY_set1_EC_KEY(evp_pkey, ec_cdata) + self.openssl_assert(res == 1) + return evp_pkey + + def _elliptic_curve_to_nid(self, curve): + """ + Get the NID for a curve name. + """ + + curve_aliases = { + "secp192r1": "prime192v1", + "secp256r1": "prime256v1" + } + + curve_name = curve_aliases.get(curve.name, curve.name) + + curve_nid = self._lib.OBJ_sn2nid(curve_name.encode()) + if curve_nid == self._lib.NID_undef: + raise UnsupportedAlgorithm( + "{0} is not a supported elliptic curve".format(curve.name), + _Reasons.UNSUPPORTED_ELLIPTIC_CURVE + ) + return curve_nid + + @contextmanager + def _tmp_bn_ctx(self): + bn_ctx = self._lib.BN_CTX_new() + self.openssl_assert(bn_ctx != self._ffi.NULL) + bn_ctx = self._ffi.gc(bn_ctx, self._lib.BN_CTX_free) + self._lib.BN_CTX_start(bn_ctx) + try: + yield bn_ctx + finally: + self._lib.BN_CTX_end(bn_ctx) + + def _ec_key_determine_group_get_set_funcs(self, ctx): + """ + Given an EC_KEY determine the group and what methods are required to + get/set point coordinates. + """ + self.openssl_assert(ctx != self._ffi.NULL) + + nid_two_field = self._lib.OBJ_sn2nid(b"characteristic-two-field") + self.openssl_assert(nid_two_field != self._lib.NID_undef) + + group = self._lib.EC_KEY_get0_group(ctx) + self.openssl_assert(group != self._ffi.NULL) + + method = self._lib.EC_GROUP_method_of(group) + self.openssl_assert(method != self._ffi.NULL) + + nid = self._lib.EC_METHOD_get_field_type(method) + self.openssl_assert(nid != self._lib.NID_undef) + + if nid == nid_two_field and self._lib.Cryptography_HAS_EC2M: + set_func = self._lib.EC_POINT_set_affine_coordinates_GF2m + get_func = self._lib.EC_POINT_get_affine_coordinates_GF2m + else: + set_func = self._lib.EC_POINT_set_affine_coordinates_GFp + get_func = self._lib.EC_POINT_get_affine_coordinates_GFp + + assert set_func and get_func + + return set_func, get_func, group + + def _ec_key_set_public_key_affine_coordinates(self, ctx, x, y): + """ + This is a port of EC_KEY_set_public_key_affine_coordinates that was + added in 1.0.1. + + Sets the public key point in the EC_KEY context to the affine x and y + values. + """ + + if x < 0 or y < 0: + raise ValueError( + "Invalid EC key. Both x and y must be non-negative." + ) + + set_func, get_func, group = ( + self._ec_key_determine_group_get_set_funcs(ctx) + ) + + point = self._lib.EC_POINT_new(group) + self.openssl_assert(point != self._ffi.NULL) + point = self._ffi.gc(point, self._lib.EC_POINT_free) + + bn_x = self._int_to_bn(x) + bn_y = self._int_to_bn(y) + + with self._tmp_bn_ctx() as bn_ctx: + check_x = self._lib.BN_CTX_get(bn_ctx) + check_y = self._lib.BN_CTX_get(bn_ctx) + + res = set_func(group, point, bn_x, bn_y, bn_ctx) + if res != 1: + self._consume_errors() + raise ValueError("EC point not on curve") + + res = get_func(group, point, check_x, check_y, bn_ctx) + self.openssl_assert(res == 1) + + res = self._lib.BN_cmp(bn_x, check_x) + if res != 0: + self._consume_errors() + raise ValueError("Invalid EC Key X point.") + res = self._lib.BN_cmp(bn_y, check_y) + if res != 0: + self._consume_errors() + raise ValueError("Invalid EC Key Y point.") + + res = self._lib.EC_KEY_set_public_key(ctx, point) + self.openssl_assert(res == 1) + + res = self._lib.EC_KEY_check_key(ctx) + if res != 1: + self._consume_errors() + raise ValueError("Invalid EC key.") + + return ctx + + def _private_key_bytes(self, encoding, format, encryption_algorithm, + evp_pkey, cdata): + if not isinstance(format, serialization.PrivateFormat): + raise TypeError( + "format must be an item from the PrivateFormat enum" + ) + + if not isinstance(encryption_algorithm, + serialization.KeySerializationEncryption): + raise TypeError( + "Encryption algorithm must be a KeySerializationEncryption " + "instance" + ) + + if isinstance(encryption_algorithm, serialization.NoEncryption): + password = b"" + passlen = 0 + evp_cipher = self._ffi.NULL + elif isinstance(encryption_algorithm, + serialization.BestAvailableEncryption): + # This is a curated value that we will update over time. + evp_cipher = self._lib.EVP_get_cipherbyname( + b"aes-256-cbc" + ) + password = encryption_algorithm.password + passlen = len(password) + if passlen > 1023: + raise ValueError( + "Passwords longer than 1023 bytes are not supported by " + "this backend" + ) + else: + raise ValueError("Unsupported encryption type") + + key_type = self._lib.EVP_PKEY_id(evp_pkey) + if encoding is serialization.Encoding.PEM: + if format is serialization.PrivateFormat.PKCS8: + write_bio = self._lib.PEM_write_bio_PKCS8PrivateKey + key = evp_pkey + else: + assert format is serialization.PrivateFormat.TraditionalOpenSSL + if key_type == self._lib.EVP_PKEY_RSA: + write_bio = self._lib.PEM_write_bio_RSAPrivateKey + elif key_type == self._lib.EVP_PKEY_DSA: + write_bio = self._lib.PEM_write_bio_DSAPrivateKey + else: + assert self._lib.Cryptography_HAS_EC == 1 + assert key_type == self._lib.EVP_PKEY_EC + write_bio = self._lib.PEM_write_bio_ECPrivateKey + + key = cdata + elif encoding is serialization.Encoding.DER: + if format is serialization.PrivateFormat.TraditionalOpenSSL: + if not isinstance( + encryption_algorithm, serialization.NoEncryption + ): + raise ValueError( + "Encryption is not supported for DER encoded " + "traditional OpenSSL keys" + ) + + return self._private_key_bytes_traditional_der(key_type, cdata) + else: + assert format is serialization.PrivateFormat.PKCS8 + write_bio = self._lib.i2d_PKCS8PrivateKey_bio + key = evp_pkey + else: + raise TypeError("encoding must be an item from the Encoding enum") + + bio = self._create_mem_bio_gc() + res = write_bio( + bio, + key, + evp_cipher, + password, + passlen, + self._ffi.NULL, + self._ffi.NULL + ) + self.openssl_assert(res == 1) + return self._read_mem_bio(bio) + + def _private_key_bytes_traditional_der(self, key_type, cdata): + if key_type == self._lib.EVP_PKEY_RSA: + write_bio = self._lib.i2d_RSAPrivateKey_bio + elif (self._lib.Cryptography_HAS_EC == 1 and + key_type == self._lib.EVP_PKEY_EC): + write_bio = self._lib.i2d_ECPrivateKey_bio + else: + self.openssl_assert(key_type == self._lib.EVP_PKEY_DSA) + write_bio = self._lib.i2d_DSAPrivateKey_bio + + bio = self._create_mem_bio_gc() + res = write_bio(bio, cdata) + self.openssl_assert(res == 1) + return self._read_mem_bio(bio) + + def _public_key_bytes(self, encoding, format, key, evp_pkey, cdata): + if not isinstance(encoding, serialization.Encoding): + raise TypeError("encoding must be an item from the Encoding enum") + + if ( + format is serialization.PublicFormat.OpenSSH or + encoding is serialization.Encoding.OpenSSH + ): + if ( + format is not serialization.PublicFormat.OpenSSH or + encoding is not serialization.Encoding.OpenSSH + ): + raise ValueError( + "OpenSSH format must be used with OpenSSH encoding" + ) + return self._openssh_public_key_bytes(key) + elif format is serialization.PublicFormat.SubjectPublicKeyInfo: + if encoding is serialization.Encoding.PEM: + write_bio = self._lib.PEM_write_bio_PUBKEY + else: + assert encoding is serialization.Encoding.DER + write_bio = self._lib.i2d_PUBKEY_bio + + key = evp_pkey + elif format is serialization.PublicFormat.PKCS1: + # Only RSA is supported here. + assert self._lib.EVP_PKEY_id(evp_pkey) == self._lib.EVP_PKEY_RSA + if encoding is serialization.Encoding.PEM: + write_bio = self._lib.PEM_write_bio_RSAPublicKey + else: + assert encoding is serialization.Encoding.DER + write_bio = self._lib.i2d_RSAPublicKey_bio + + key = cdata + else: + raise TypeError( + "format must be an item from the PublicFormat enum" + ) + + bio = self._create_mem_bio_gc() + res = write_bio(bio, key) + self.openssl_assert(res == 1) + return self._read_mem_bio(bio) + + def _openssh_public_key_bytes(self, key): + if isinstance(key, rsa.RSAPublicKey): + public_numbers = key.public_numbers() + return b"ssh-rsa " + base64.b64encode( + serialization._ssh_write_string(b"ssh-rsa") + + serialization._ssh_write_mpint(public_numbers.e) + + serialization._ssh_write_mpint(public_numbers.n) + ) + elif isinstance(key, dsa.DSAPublicKey): + public_numbers = key.public_numbers() + parameter_numbers = public_numbers.parameter_numbers + return b"ssh-dss " + base64.b64encode( + serialization._ssh_write_string(b"ssh-dss") + + serialization._ssh_write_mpint(parameter_numbers.p) + + serialization._ssh_write_mpint(parameter_numbers.q) + + serialization._ssh_write_mpint(parameter_numbers.g) + + serialization._ssh_write_mpint(public_numbers.y) + ) + else: + assert isinstance(key, ec.EllipticCurvePublicKey) + public_numbers = key.public_numbers() + try: + curve_name = { + ec.SECP256R1: b"nistp256", + ec.SECP384R1: b"nistp384", + ec.SECP521R1: b"nistp521", + }[type(public_numbers.curve)] + except KeyError: + raise ValueError( + "Only SECP256R1, SECP384R1, and SECP521R1 curves are " + "supported by the SSH public key format" + ) + return b"ecdsa-sha2-" + curve_name + b" " + base64.b64encode( + serialization._ssh_write_string(b"ecdsa-sha2-" + curve_name) + + serialization._ssh_write_string(curve_name) + + serialization._ssh_write_string(public_numbers.encode_point()) + ) + + def generate_dh_parameters(self, generator, key_size): + if key_size < 512: + raise ValueError("DH key_size must be at least 512 bits") + + if generator not in (2, 5): + raise ValueError("DH generator must be 2 or 5") + + dh_param_cdata = self._lib.DH_new() + self.openssl_assert(dh_param_cdata != self._ffi.NULL) + dh_param_cdata = self._ffi.gc(dh_param_cdata, self._lib.DH_free) + + res = self._lib.DH_generate_parameters_ex( + dh_param_cdata, + key_size, + generator, + self._ffi.NULL + ) + self.openssl_assert(res == 1) + + return _DHParameters(self, dh_param_cdata) + + def generate_dh_private_key(self, parameters): + dh_key_cdata = self._lib.DHparams_dup(parameters._dh_cdata) + self.openssl_assert(dh_key_cdata != self._ffi.NULL) + dh_key_cdata = self._ffi.gc(dh_key_cdata, self._lib.DH_free) + + res = self._lib.DH_generate_key(dh_key_cdata) + self.openssl_assert(res == 1) + + return _DHPrivateKey(self, dh_key_cdata) + + def generate_dh_private_key_and_parameters(self, generator, key_size): + return self.generate_dh_private_key( + self.generate_dh_parameters(generator, key_size)) + + def load_dh_private_numbers(self, numbers): + parameter_numbers = numbers.public_numbers.parameter_numbers + + dh_cdata = self._lib.DH_new() + self.openssl_assert(dh_cdata != self._ffi.NULL) + dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) + + p = self._int_to_bn(parameter_numbers.p) + g = self._int_to_bn(parameter_numbers.g) + pub_key = self._int_to_bn(numbers.public_numbers.y) + priv_key = self._int_to_bn(numbers.x) + + res = self._lib.DH_set0_pqg(dh_cdata, p, self._ffi.NULL, g) + self.openssl_assert(res == 1) + + res = self._lib.DH_set0_key(dh_cdata, pub_key, priv_key) + self.openssl_assert(res == 1) + + codes = self._ffi.new("int[]", 1) + res = self._lib.DH_check(dh_cdata, codes) + self.openssl_assert(res == 1) + + if codes[0] != 0: + raise ValueError("DH private numbers did not pass safety checks.") + + return _DHPrivateKey(self, dh_cdata) + + def load_dh_public_numbers(self, numbers): + dh_cdata = self._lib.DH_new() + self.openssl_assert(dh_cdata != self._ffi.NULL) + dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) + + parameter_numbers = numbers.parameter_numbers + + p = self._int_to_bn(parameter_numbers.p) + g = self._int_to_bn(parameter_numbers.g) + pub_key = self._int_to_bn(numbers.y) + + res = self._lib.DH_set0_pqg(dh_cdata, p, self._ffi.NULL, g) + self.openssl_assert(res == 1) + + res = self._lib.DH_set0_key(dh_cdata, pub_key, self._ffi.NULL) + self.openssl_assert(res == 1) + + return _DHPublicKey(self, dh_cdata) + + def load_dh_parameter_numbers(self, numbers): + dh_cdata = self._lib.DH_new() + self.openssl_assert(dh_cdata != self._ffi.NULL) + dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) + + p = self._int_to_bn(numbers.p) + g = self._int_to_bn(numbers.g) + + res = self._lib.DH_set0_pqg(dh_cdata, p, self._ffi.NULL, g) + self.openssl_assert(res == 1) + + return _DHParameters(self, dh_cdata) + + def dh_parameters_supported(self, p, g): + dh_cdata = self._lib.DH_new() + self.openssl_assert(dh_cdata != self._ffi.NULL) + dh_cdata = self._ffi.gc(dh_cdata, self._lib.DH_free) + + p = self._int_to_bn(p) + g = self._int_to_bn(g) + + res = self._lib.DH_set0_pqg(dh_cdata, p, self._ffi.NULL, g) + self.openssl_assert(res == 1) + + codes = self._ffi.new("int[]", 1) + res = self._lib.DH_check(dh_cdata, codes) + self.openssl_assert(res == 1) + + return codes[0] == 0 + + def x509_name_bytes(self, name): + x509_name = _encode_name_gc(self, name) + pp = self._ffi.new("unsigned char **") + res = self._lib.i2d_X509_NAME(x509_name, pp) + self.openssl_assert(pp[0] != self._ffi.NULL) + pp = self._ffi.gc( + pp, lambda pointer: self._lib.OPENSSL_free(pointer[0]) + ) + self.openssl_assert(res > 0) + return self._ffi.buffer(pp[0], res)[:] + + def derive_scrypt(self, key_material, salt, length, n, r, p): + buf = self._ffi.new("unsigned char[]", length) + res = self._lib.EVP_PBE_scrypt(key_material, len(key_material), salt, + len(salt), n, r, p, sys.maxsize // 2, + buf, length) + self.openssl_assert(res == 1) + return self._ffi.buffer(buf)[:] + + +class GetCipherByName(object): + def __init__(self, fmt): + self._fmt = fmt + + def __call__(self, backend, cipher, mode): + cipher_name = self._fmt.format(cipher=cipher, mode=mode).lower() + return backend._lib.EVP_get_cipherbyname(cipher_name.encode("ascii")) + + +backend = Backend() diff --git a/src/cryptography/hazmat/backends/openssl/ciphers.py b/src/cryptography/hazmat/backends/openssl/ciphers.py new file mode 100644 index 0000000..bd5dfb3 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/ciphers.py @@ -0,0 +1,204 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.exceptions import InvalidTag, UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.primitives import ciphers +from cryptography.hazmat.primitives.ciphers import modes + + +@utils.register_interface(ciphers.CipherContext) +@utils.register_interface(ciphers.AEADCipherContext) +@utils.register_interface(ciphers.AEADEncryptionContext) +class _CipherContext(object): + _ENCRYPT = 1 + _DECRYPT = 0 + + def __init__(self, backend, cipher, mode, operation): + self._backend = backend + self._cipher = cipher + self._mode = mode + self._operation = operation + self._tag = None + + if isinstance(self._cipher, ciphers.BlockCipherAlgorithm): + self._block_size_bytes = self._cipher.block_size // 8 + else: + self._block_size_bytes = 1 + + ctx = self._backend._lib.EVP_CIPHER_CTX_new() + ctx = self._backend._ffi.gc( + ctx, self._backend._lib.EVP_CIPHER_CTX_free + ) + + registry = self._backend._cipher_registry + try: + adapter = registry[type(cipher), type(mode)] + except KeyError: + raise UnsupportedAlgorithm( + "cipher {0} in {1} mode is not supported " + "by this backend.".format( + cipher.name, mode.name if mode else mode), + _Reasons.UNSUPPORTED_CIPHER + ) + + evp_cipher = adapter(self._backend, cipher, mode) + if evp_cipher == self._backend._ffi.NULL: + raise UnsupportedAlgorithm( + "cipher {0} in {1} mode is not supported " + "by this backend.".format( + cipher.name, mode.name if mode else mode), + _Reasons.UNSUPPORTED_CIPHER + ) + + if isinstance(mode, modes.ModeWithInitializationVector): + iv_nonce = mode.initialization_vector + elif isinstance(mode, modes.ModeWithNonce): + iv_nonce = mode.nonce + else: + iv_nonce = self._backend._ffi.NULL + # begin init with cipher and operation type + res = self._backend._lib.EVP_CipherInit_ex(ctx, evp_cipher, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + operation) + self._backend.openssl_assert(res != 0) + # set the key length to handle variable key ciphers + res = self._backend._lib.EVP_CIPHER_CTX_set_key_length( + ctx, len(cipher.key) + ) + self._backend.openssl_assert(res != 0) + if isinstance(mode, modes.GCM): + res = self._backend._lib.EVP_CIPHER_CTX_ctrl( + ctx, self._backend._lib.EVP_CTRL_GCM_SET_IVLEN, + len(iv_nonce), self._backend._ffi.NULL + ) + self._backend.openssl_assert(res != 0) + if operation == self._DECRYPT: + res = self._backend._lib.EVP_CIPHER_CTX_ctrl( + ctx, self._backend._lib.EVP_CTRL_GCM_SET_TAG, + len(mode.tag), mode.tag + ) + self._backend.openssl_assert(res != 0) + + # pass key/iv + res = self._backend._lib.EVP_CipherInit_ex( + ctx, + self._backend._ffi.NULL, + self._backend._ffi.NULL, + cipher.key, + iv_nonce, + operation + ) + self._backend.openssl_assert(res != 0) + # We purposely disable padding here as it's handled higher up in the + # API. + self._backend._lib.EVP_CIPHER_CTX_set_padding(ctx, 0) + self._ctx = ctx + + def update(self, data): + buf = self._backend._ffi.new("unsigned char[]", + len(data) + self._block_size_bytes - 1) + outlen = self._backend._ffi.new("int *") + res = self._backend._lib.EVP_CipherUpdate(self._ctx, buf, outlen, data, + len(data)) + self._backend.openssl_assert(res != 0) + return self._backend._ffi.buffer(buf)[:outlen[0]] + + def finalize(self): + # OpenSSL 1.0.1 on Ubuntu 12.04 (and possibly other distributions) + # appears to have a bug where you must make at least one call to update + # even if you are only using authenticate_additional_data or the + # GCM tag will be wrong. An (empty) call to update resolves this + # and is harmless for all other versions of OpenSSL. + if isinstance(self._mode, modes.GCM): + self.update(b"") + + buf = self._backend._ffi.new("unsigned char[]", self._block_size_bytes) + outlen = self._backend._ffi.new("int *") + res = self._backend._lib.EVP_CipherFinal_ex(self._ctx, buf, outlen) + if res == 0: + errors = self._backend._consume_errors() + + if not errors and isinstance(self._mode, modes.GCM): + raise InvalidTag + + self._backend.openssl_assert( + errors[0][1:] == ( + self._backend._lib.ERR_LIB_EVP, + self._backend._lib.EVP_F_EVP_ENCRYPTFINAL_EX, + self._backend._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH + ) or errors[0][1:] == ( + self._backend._lib.ERR_LIB_EVP, + self._backend._lib.EVP_F_EVP_DECRYPTFINAL_EX, + self._backend._lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH + ) + ) + raise ValueError( + "The length of the provided data is not a multiple of " + "the block length." + ) + + if (isinstance(self._mode, modes.GCM) and + self._operation == self._ENCRYPT): + tag_buf = self._backend._ffi.new( + "unsigned char[]", self._block_size_bytes + ) + res = self._backend._lib.EVP_CIPHER_CTX_ctrl( + self._ctx, self._backend._lib.EVP_CTRL_GCM_GET_TAG, + self._block_size_bytes, tag_buf + ) + self._backend.openssl_assert(res != 0) + self._tag = self._backend._ffi.buffer(tag_buf)[:] + + res = self._backend._lib.EVP_CIPHER_CTX_cleanup(self._ctx) + self._backend.openssl_assert(res == 1) + return self._backend._ffi.buffer(buf)[:outlen[0]] + + def authenticate_additional_data(self, data): + outlen = self._backend._ffi.new("int *") + res = self._backend._lib.EVP_CipherUpdate( + self._ctx, self._backend._ffi.NULL, outlen, data, len(data) + ) + self._backend.openssl_assert(res != 0) + + tag = utils.read_only_property("_tag") + + +@utils.register_interface(ciphers.CipherContext) +class _AESCTRCipherContext(object): + """ + This is needed to provide support for AES CTR mode in OpenSSL 1.0.0. It can + be removed when we drop 1.0.0 support (RHEL 6.4 is the only thing that + ships it). + """ + def __init__(self, backend, cipher, mode): + self._backend = backend + + self._key = self._backend._ffi.new("AES_KEY *") + res = self._backend._lib.AES_set_encrypt_key( + cipher.key, len(cipher.key) * 8, self._key + ) + self._backend.openssl_assert(res == 0) + self._ecount = self._backend._ffi.new("unsigned char[]", 16) + self._nonce = self._backend._ffi.new("unsigned char[16]", mode.nonce) + self._num = self._backend._ffi.new("unsigned int *", 0) + + def update(self, data): + buf = self._backend._ffi.new("unsigned char[]", len(data)) + self._backend._lib.AES_ctr128_encrypt( + data, buf, len(data), self._key, self._nonce, + self._ecount, self._num + ) + return self._backend._ffi.buffer(buf)[:] + + def finalize(self): + self._key = None + self._ecount = None + self._nonce = None + self._num = None + return b"" diff --git a/src/cryptography/hazmat/backends/openssl/cmac.py b/src/cryptography/hazmat/backends/openssl/cmac.py new file mode 100644 index 0000000..eaefc27 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/cmac.py @@ -0,0 +1,80 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + + +from cryptography import utils +from cryptography.exceptions import ( + InvalidSignature, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.primitives import constant_time, interfaces +from cryptography.hazmat.primitives.ciphers.modes import CBC + + +@utils.register_interface(interfaces.MACContext) +class _CMACContext(object): + def __init__(self, backend, algorithm, ctx=None): + if not backend.cmac_algorithm_supported(algorithm): + raise UnsupportedAlgorithm("This backend does not support CMAC.", + _Reasons.UNSUPPORTED_CIPHER) + + self._backend = backend + self._key = algorithm.key + self._algorithm = algorithm + self._output_length = algorithm.block_size // 8 + + if ctx is None: + registry = self._backend._cipher_registry + adapter = registry[type(algorithm), CBC] + + evp_cipher = adapter(self._backend, algorithm, CBC) + + ctx = self._backend._lib.CMAC_CTX_new() + + self._backend.openssl_assert(ctx != self._backend._ffi.NULL) + ctx = self._backend._ffi.gc(ctx, self._backend._lib.CMAC_CTX_free) + + self._backend._lib.CMAC_Init( + ctx, self._key, len(self._key), + evp_cipher, self._backend._ffi.NULL + ) + + self._ctx = ctx + + algorithm = utils.read_only_property("_algorithm") + + def update(self, data): + res = self._backend._lib.CMAC_Update(self._ctx, data, len(data)) + self._backend.openssl_assert(res == 1) + + def finalize(self): + buf = self._backend._ffi.new("unsigned char[]", self._output_length) + length = self._backend._ffi.new("size_t *", self._output_length) + res = self._backend._lib.CMAC_Final( + self._ctx, buf, length + ) + self._backend.openssl_assert(res == 1) + + self._ctx = None + + return self._backend._ffi.buffer(buf)[:] + + def copy(self): + copied_ctx = self._backend._lib.CMAC_CTX_new() + copied_ctx = self._backend._ffi.gc( + copied_ctx, self._backend._lib.CMAC_CTX_free + ) + res = self._backend._lib.CMAC_CTX_copy( + copied_ctx, self._ctx + ) + self._backend.openssl_assert(res == 1) + return _CMACContext( + self._backend, self._algorithm, ctx=copied_ctx + ) + + def verify(self, signature): + digest = self.finalize() + if not constant_time.bytes_eq(digest, signature): + raise InvalidSignature("Signature did not match digest.") diff --git a/src/cryptography/hazmat/backends/openssl/decode_asn1.py b/src/cryptography/hazmat/backends/openssl/decode_asn1.py new file mode 100644 index 0000000..2cbc349 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/decode_asn1.py @@ -0,0 +1,784 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import datetime +import ipaddress + +from email.utils import parseaddr + +import idna + +import six + +from six.moves import urllib_parse + +from cryptography import x509 +from cryptography.x509.oid import ( + CRLEntryExtensionOID, CertificatePoliciesOID, ExtensionOID +) + + +def _obj2txt(backend, obj): + # Set to 80 on the recommendation of + # https://www.openssl.org/docs/crypto/OBJ_nid2ln.html#return_values + buf_len = 80 + buf = backend._ffi.new("char[]", buf_len) + res = backend._lib.OBJ_obj2txt(buf, buf_len, obj, 1) + backend.openssl_assert(res > 0) + return backend._ffi.buffer(buf, res)[:].decode() + + +def _decode_x509_name_entry(backend, x509_name_entry): + obj = backend._lib.X509_NAME_ENTRY_get_object(x509_name_entry) + backend.openssl_assert(obj != backend._ffi.NULL) + data = backend._lib.X509_NAME_ENTRY_get_data(x509_name_entry) + backend.openssl_assert(data != backend._ffi.NULL) + value = _asn1_string_to_utf8(backend, data) + oid = _obj2txt(backend, obj) + + return x509.NameAttribute(x509.ObjectIdentifier(oid), value) + + +def _decode_x509_name(backend, x509_name): + count = backend._lib.X509_NAME_entry_count(x509_name) + attributes = [] + prev_set_id = -1 + for x in range(count): + entry = backend._lib.X509_NAME_get_entry(x509_name, x) + attribute = _decode_x509_name_entry(backend, entry) + set_id = backend._lib.Cryptography_X509_NAME_ENTRY_set(entry) + if set_id != prev_set_id: + attributes.append(set([attribute])) + else: + # is in the same RDN a previous entry + attributes[-1].add(attribute) + prev_set_id = set_id + + return x509.Name(x509.RelativeDistinguishedName(rdn) for rdn in attributes) + + +def _decode_general_names(backend, gns): + num = backend._lib.sk_GENERAL_NAME_num(gns) + names = [] + for i in range(num): + gn = backend._lib.sk_GENERAL_NAME_value(gns, i) + backend.openssl_assert(gn != backend._ffi.NULL) + names.append(_decode_general_name(backend, gn)) + + return names + + +def _decode_general_name(backend, gn): + if gn.type == backend._lib.GEN_DNS: + data = _asn1_string_to_bytes(backend, gn.d.dNSName) + if not data: + decoded = u"" + elif data.startswith(b"*."): + # This is a wildcard name. We need to remove the leading wildcard, + # IDNA decode, then re-add the wildcard. Wildcard characters should + # always be left-most (RFC 2595 section 2.4). + decoded = u"*." + idna.decode(data[2:]) + else: + # Not a wildcard, decode away. If the string has a * in it anywhere + # invalid this will raise an InvalidCodePoint + decoded = idna.decode(data) + if data.startswith(b"."): + # idna strips leading periods. Name constraints can have that + # so we need to re-add it. Sigh. + decoded = u"." + decoded + + return x509.DNSName(decoded) + elif gn.type == backend._lib.GEN_URI: + data = _asn1_string_to_ascii(backend, gn.d.uniformResourceIdentifier) + parsed = urllib_parse.urlparse(data) + if parsed.hostname: + hostname = idna.decode(parsed.hostname) + else: + hostname = "" + if parsed.port: + netloc = hostname + u":" + six.text_type(parsed.port) + else: + netloc = hostname + + # Note that building a URL in this fashion means it should be + # semantically indistinguishable from the original but is not + # guaranteed to be exactly the same. + uri = urllib_parse.urlunparse(( + parsed.scheme, + netloc, + parsed.path, + parsed.params, + parsed.query, + parsed.fragment + )) + return x509.UniformResourceIdentifier(uri) + elif gn.type == backend._lib.GEN_RID: + oid = _obj2txt(backend, gn.d.registeredID) + return x509.RegisteredID(x509.ObjectIdentifier(oid)) + elif gn.type == backend._lib.GEN_IPADD: + 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) + ) + elif gn.type == backend._lib.GEN_EMAIL: + data = _asn1_string_to_ascii(backend, gn.d.rfc822Name) + name, address = parseaddr(data) + parts = address.split(u"@") + if name or not address: + # parseaddr has found a name (e.g. Name ) 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 decoding can be done since there is no domain component. + return x509.RFC822Name(address) + else: + # A normal email of the form user@domain.com. Let's attempt to + # decode the domain component and return the entire address. + return x509.RFC822Name( + parts[0] + u"@" + idna.decode(parts[1]) + ) + elif gn.type == backend._lib.GEN_OTHERNAME: + type_id = _obj2txt(backend, gn.d.otherName.type_id) + value = _asn1_to_der(backend, gn.d.otherName.value) + return x509.OtherName(x509.ObjectIdentifier(type_id), value) + else: + # x400Address or ediPartyName + raise x509.UnsupportedGeneralNameType( + "{0} is not a supported type".format( + x509._GENERAL_NAMES.get(gn.type, gn.type) + ), + gn.type + ) + + +def _decode_ocsp_no_check(backend, ext): + return x509.OCSPNoCheck() + + +def _decode_crl_number(backend, ext): + asn1_int = backend._ffi.cast("ASN1_INTEGER *", ext) + asn1_int = backend._ffi.gc(asn1_int, backend._lib.ASN1_INTEGER_free) + return x509.CRLNumber(_asn1_integer_to_int(backend, asn1_int)) + + +class _X509ExtensionParser(object): + def __init__(self, ext_count, get_ext, handlers): + self.ext_count = ext_count + self.get_ext = get_ext + self.handlers = handlers + + def parse(self, backend, x509_obj): + extensions = [] + seen_oids = set() + for i in range(self.ext_count(backend, x509_obj)): + ext = self.get_ext(backend, x509_obj, i) + backend.openssl_assert(ext != backend._ffi.NULL) + crit = backend._lib.X509_EXTENSION_get_critical(ext) + critical = crit == 1 + oid = x509.ObjectIdentifier( + _obj2txt(backend, backend._lib.X509_EXTENSION_get_object(ext)) + ) + if oid in seen_oids: + raise x509.DuplicateExtension( + "Duplicate {0} extension found".format(oid), oid + ) + try: + handler = self.handlers[oid] + except KeyError: + if critical: + raise x509.UnsupportedExtension( + "Critical extension {0} is not currently supported" + .format(oid), oid + ) + else: + # Dump the DER payload into an UnrecognizedExtension object + data = backend._lib.X509_EXTENSION_get_data(ext) + backend.openssl_assert(data != backend._ffi.NULL) + der = backend._ffi.buffer(data.data, data.length)[:] + unrecognized = x509.UnrecognizedExtension(oid, der) + extensions.append( + x509.Extension(oid, critical, unrecognized) + ) + else: + ext_data = backend._lib.X509V3_EXT_d2i(ext) + if ext_data == backend._ffi.NULL: + backend._consume_errors() + raise ValueError( + "The {0} extension is invalid and can't be " + "parsed".format(oid) + ) + + value = handler(backend, ext_data) + extensions.append(x509.Extension(oid, critical, value)) + + seen_oids.add(oid) + + return x509.Extensions(extensions) + + +def _decode_certificate_policies(backend, cp): + cp = backend._ffi.cast("Cryptography_STACK_OF_POLICYINFO *", cp) + cp = backend._ffi.gc(cp, backend._lib.sk_POLICYINFO_free) + num = backend._lib.sk_POLICYINFO_num(cp) + certificate_policies = [] + for i in range(num): + qualifiers = None + pi = backend._lib.sk_POLICYINFO_value(cp, i) + oid = x509.ObjectIdentifier(_obj2txt(backend, pi.policyid)) + if pi.qualifiers != backend._ffi.NULL: + qnum = backend._lib.sk_POLICYQUALINFO_num(pi.qualifiers) + qualifiers = [] + for j in range(qnum): + pqi = backend._lib.sk_POLICYQUALINFO_value( + pi.qualifiers, j + ) + pqualid = x509.ObjectIdentifier( + _obj2txt(backend, pqi.pqualid) + ) + if pqualid == CertificatePoliciesOID.CPS_QUALIFIER: + cpsuri = backend._ffi.buffer( + pqi.d.cpsuri.data, pqi.d.cpsuri.length + )[:].decode('ascii') + qualifiers.append(cpsuri) + else: + assert pqualid == CertificatePoliciesOID.CPS_USER_NOTICE + user_notice = _decode_user_notice( + backend, pqi.d.usernotice + ) + qualifiers.append(user_notice) + + certificate_policies.append( + x509.PolicyInformation(oid, qualifiers) + ) + + return x509.CertificatePolicies(certificate_policies) + + +def _decode_user_notice(backend, un): + explicit_text = None + notice_reference = None + + if un.exptext != backend._ffi.NULL: + explicit_text = _asn1_string_to_utf8(backend, un.exptext) + + if un.noticeref != backend._ffi.NULL: + organization = _asn1_string_to_utf8( + backend, un.noticeref.organization + ) + + num = backend._lib.sk_ASN1_INTEGER_num( + un.noticeref.noticenos + ) + notice_numbers = [] + for i in range(num): + asn1_int = backend._lib.sk_ASN1_INTEGER_value( + un.noticeref.noticenos, i + ) + notice_num = _asn1_integer_to_int(backend, asn1_int) + notice_numbers.append(notice_num) + + notice_reference = x509.NoticeReference( + organization, notice_numbers + ) + + return x509.UserNotice(notice_reference, explicit_text) + + +def _decode_basic_constraints(backend, bc_st): + basic_constraints = backend._ffi.cast("BASIC_CONSTRAINTS *", bc_st) + basic_constraints = backend._ffi.gc( + basic_constraints, backend._lib.BASIC_CONSTRAINTS_free + ) + # The byte representation of an ASN.1 boolean true is \xff. OpenSSL + # chooses to just map this to its ordinal value, so true is 255 and + # false is 0. + ca = basic_constraints.ca == 255 + path_length = _asn1_integer_to_int_or_none( + backend, basic_constraints.pathlen + ) + + return x509.BasicConstraints(ca, path_length) + + +def _decode_subject_key_identifier(backend, asn1_string): + asn1_string = backend._ffi.cast("ASN1_OCTET_STRING *", asn1_string) + asn1_string = backend._ffi.gc( + asn1_string, backend._lib.ASN1_OCTET_STRING_free + ) + return x509.SubjectKeyIdentifier( + backend._ffi.buffer(asn1_string.data, asn1_string.length)[:] + ) + + +def _decode_authority_key_identifier(backend, akid): + akid = backend._ffi.cast("AUTHORITY_KEYID *", akid) + akid = backend._ffi.gc(akid, backend._lib.AUTHORITY_KEYID_free) + key_identifier = None + authority_cert_issuer = None + + if akid.keyid != backend._ffi.NULL: + key_identifier = backend._ffi.buffer( + akid.keyid.data, akid.keyid.length + )[:] + + if akid.issuer != backend._ffi.NULL: + authority_cert_issuer = _decode_general_names( + backend, akid.issuer + ) + + authority_cert_serial_number = _asn1_integer_to_int_or_none( + backend, akid.serial + ) + + return x509.AuthorityKeyIdentifier( + key_identifier, authority_cert_issuer, authority_cert_serial_number + ) + + +def _decode_authority_information_access(backend, aia): + aia = backend._ffi.cast("Cryptography_STACK_OF_ACCESS_DESCRIPTION *", aia) + aia = backend._ffi.gc(aia, backend._lib.sk_ACCESS_DESCRIPTION_free) + num = backend._lib.sk_ACCESS_DESCRIPTION_num(aia) + access_descriptions = [] + for i in range(num): + ad = backend._lib.sk_ACCESS_DESCRIPTION_value(aia, i) + backend.openssl_assert(ad.method != backend._ffi.NULL) + oid = x509.ObjectIdentifier(_obj2txt(backend, ad.method)) + backend.openssl_assert(ad.location != backend._ffi.NULL) + gn = _decode_general_name(backend, ad.location) + access_descriptions.append(x509.AccessDescription(oid, gn)) + + return x509.AuthorityInformationAccess(access_descriptions) + + +def _decode_key_usage(backend, bit_string): + bit_string = backend._ffi.cast("ASN1_BIT_STRING *", bit_string) + bit_string = backend._ffi.gc(bit_string, backend._lib.ASN1_BIT_STRING_free) + get_bit = backend._lib.ASN1_BIT_STRING_get_bit + digital_signature = get_bit(bit_string, 0) == 1 + content_commitment = get_bit(bit_string, 1) == 1 + key_encipherment = get_bit(bit_string, 2) == 1 + data_encipherment = get_bit(bit_string, 3) == 1 + key_agreement = get_bit(bit_string, 4) == 1 + key_cert_sign = get_bit(bit_string, 5) == 1 + crl_sign = get_bit(bit_string, 6) == 1 + encipher_only = get_bit(bit_string, 7) == 1 + decipher_only = get_bit(bit_string, 8) == 1 + return x509.KeyUsage( + digital_signature, + content_commitment, + key_encipherment, + data_encipherment, + key_agreement, + key_cert_sign, + crl_sign, + encipher_only, + decipher_only + ) + + +def _decode_general_names_extension(backend, gns): + gns = backend._ffi.cast("GENERAL_NAMES *", gns) + gns = backend._ffi.gc(gns, backend._lib.GENERAL_NAMES_free) + general_names = _decode_general_names(backend, gns) + return general_names + + +def _decode_subject_alt_name(backend, ext): + return x509.SubjectAlternativeName( + _decode_general_names_extension(backend, ext) + ) + + +def _decode_issuer_alt_name(backend, ext): + return x509.IssuerAlternativeName( + _decode_general_names_extension(backend, ext) + ) + + +def _decode_name_constraints(backend, nc): + nc = backend._ffi.cast("NAME_CONSTRAINTS *", nc) + nc = backend._ffi.gc(nc, backend._lib.NAME_CONSTRAINTS_free) + permitted = _decode_general_subtrees(backend, nc.permittedSubtrees) + excluded = _decode_general_subtrees(backend, nc.excludedSubtrees) + return x509.NameConstraints( + permitted_subtrees=permitted, excluded_subtrees=excluded + ) + + +def _decode_general_subtrees(backend, stack_subtrees): + if stack_subtrees == backend._ffi.NULL: + return None + + num = backend._lib.sk_GENERAL_SUBTREE_num(stack_subtrees) + subtrees = [] + + for i in range(num): + obj = backend._lib.sk_GENERAL_SUBTREE_value(stack_subtrees, i) + backend.openssl_assert(obj != backend._ffi.NULL) + name = _decode_general_name(backend, obj.base) + subtrees.append(name) + + return subtrees + + +def _decode_policy_constraints(backend, pc): + pc = backend._ffi.cast("POLICY_CONSTRAINTS *", pc) + pc = backend._ffi.gc(pc, backend._lib.POLICY_CONSTRAINTS_free) + + require_explicit_policy = _asn1_integer_to_int_or_none( + backend, pc.requireExplicitPolicy + ) + inhibit_policy_mapping = _asn1_integer_to_int_or_none( + backend, pc.inhibitPolicyMapping + ) + + return x509.PolicyConstraints( + require_explicit_policy, inhibit_policy_mapping + ) + + +def _decode_extended_key_usage(backend, sk): + sk = backend._ffi.cast("Cryptography_STACK_OF_ASN1_OBJECT *", sk) + sk = backend._ffi.gc(sk, backend._lib.sk_ASN1_OBJECT_free) + num = backend._lib.sk_ASN1_OBJECT_num(sk) + ekus = [] + + for i in range(num): + obj = backend._lib.sk_ASN1_OBJECT_value(sk, i) + backend.openssl_assert(obj != backend._ffi.NULL) + oid = x509.ObjectIdentifier(_obj2txt(backend, obj)) + ekus.append(oid) + + return x509.ExtendedKeyUsage(ekus) + + +_DISTPOINT_TYPE_FULLNAME = 0 +_DISTPOINT_TYPE_RELATIVENAME = 1 + + +def _decode_crl_distribution_points(backend, cdps): + cdps = backend._ffi.cast("Cryptography_STACK_OF_DIST_POINT *", cdps) + cdps = backend._ffi.gc(cdps, backend._lib.sk_DIST_POINT_free) + num = backend._lib.sk_DIST_POINT_num(cdps) + + dist_points = [] + for i in range(num): + full_name = None + relative_name = None + crl_issuer = None + reasons = None + cdp = backend._lib.sk_DIST_POINT_value(cdps, i) + if cdp.reasons != backend._ffi.NULL: + # We will check each bit from RFC 5280 + # ReasonFlags ::= BIT STRING { + # unused (0), + # keyCompromise (1), + # cACompromise (2), + # affiliationChanged (3), + # superseded (4), + # cessationOfOperation (5), + # certificateHold (6), + # privilegeWithdrawn (7), + # aACompromise (8) } + reasons = [] + get_bit = backend._lib.ASN1_BIT_STRING_get_bit + if get_bit(cdp.reasons, 1): + reasons.append(x509.ReasonFlags.key_compromise) + + if get_bit(cdp.reasons, 2): + reasons.append(x509.ReasonFlags.ca_compromise) + + if get_bit(cdp.reasons, 3): + reasons.append(x509.ReasonFlags.affiliation_changed) + + if get_bit(cdp.reasons, 4): + reasons.append(x509.ReasonFlags.superseded) + + if get_bit(cdp.reasons, 5): + reasons.append(x509.ReasonFlags.cessation_of_operation) + + if get_bit(cdp.reasons, 6): + reasons.append(x509.ReasonFlags.certificate_hold) + + if get_bit(cdp.reasons, 7): + reasons.append(x509.ReasonFlags.privilege_withdrawn) + + if get_bit(cdp.reasons, 8): + reasons.append(x509.ReasonFlags.aa_compromise) + + reasons = frozenset(reasons) + + if cdp.CRLissuer != backend._ffi.NULL: + crl_issuer = _decode_general_names(backend, cdp.CRLissuer) + + # Certificates may have a crl_issuer/reasons and no distribution + # point so make sure it's not null. + if cdp.distpoint != backend._ffi.NULL: + # Type 0 is fullName, there is no #define for it in the code. + if cdp.distpoint.type == _DISTPOINT_TYPE_FULLNAME: + full_name = _decode_general_names( + backend, cdp.distpoint.name.fullname + ) + # OpenSSL code doesn't test for a specific type for + # relativename, everything that isn't fullname is considered + # relativename. Per RFC 5280: + # + # DistributionPointName ::= CHOICE { + # fullName [0] GeneralNames, + # nameRelativeToCRLIssuer [1] RelativeDistinguishedName } + else: + rns = cdp.distpoint.name.relativename + rnum = backend._lib.sk_X509_NAME_ENTRY_num(rns) + attributes = set() + for i in range(rnum): + rn = backend._lib.sk_X509_NAME_ENTRY_value( + rns, i + ) + backend.openssl_assert(rn != backend._ffi.NULL) + attributes.add( + _decode_x509_name_entry(backend, rn) + ) + + relative_name = x509.RelativeDistinguishedName(attributes) + + dist_points.append( + x509.DistributionPoint( + full_name, relative_name, reasons, crl_issuer + ) + ) + + return x509.CRLDistributionPoints(dist_points) + + +def _decode_inhibit_any_policy(backend, asn1_int): + asn1_int = backend._ffi.cast("ASN1_INTEGER *", asn1_int) + asn1_int = backend._ffi.gc(asn1_int, backend._lib.ASN1_INTEGER_free) + skip_certs = _asn1_integer_to_int(backend, asn1_int) + return x509.InhibitAnyPolicy(skip_certs) + + +# CRLReason ::= ENUMERATED { +# unspecified (0), +# keyCompromise (1), +# cACompromise (2), +# affiliationChanged (3), +# superseded (4), +# cessationOfOperation (5), +# certificateHold (6), +# -- value 7 is not used +# removeFromCRL (8), +# privilegeWithdrawn (9), +# aACompromise (10) } +_CRL_ENTRY_REASON_CODE_TO_ENUM = { + 0: x509.ReasonFlags.unspecified, + 1: x509.ReasonFlags.key_compromise, + 2: x509.ReasonFlags.ca_compromise, + 3: x509.ReasonFlags.affiliation_changed, + 4: x509.ReasonFlags.superseded, + 5: x509.ReasonFlags.cessation_of_operation, + 6: x509.ReasonFlags.certificate_hold, + 8: x509.ReasonFlags.remove_from_crl, + 9: x509.ReasonFlags.privilege_withdrawn, + 10: x509.ReasonFlags.aa_compromise, +} + + +_CRL_ENTRY_REASON_ENUM_TO_CODE = { + x509.ReasonFlags.unspecified: 0, + x509.ReasonFlags.key_compromise: 1, + x509.ReasonFlags.ca_compromise: 2, + x509.ReasonFlags.affiliation_changed: 3, + x509.ReasonFlags.superseded: 4, + x509.ReasonFlags.cessation_of_operation: 5, + x509.ReasonFlags.certificate_hold: 6, + x509.ReasonFlags.remove_from_crl: 8, + x509.ReasonFlags.privilege_withdrawn: 9, + x509.ReasonFlags.aa_compromise: 10 +} + + +def _decode_crl_reason(backend, enum): + enum = backend._ffi.cast("ASN1_ENUMERATED *", enum) + enum = backend._ffi.gc(enum, backend._lib.ASN1_ENUMERATED_free) + code = backend._lib.ASN1_ENUMERATED_get(enum) + + try: + return x509.CRLReason(_CRL_ENTRY_REASON_CODE_TO_ENUM[code]) + except KeyError: + raise ValueError("Unsupported reason code: {0}".format(code)) + + +def _decode_invalidity_date(backend, inv_date): + generalized_time = backend._ffi.cast( + "ASN1_GENERALIZEDTIME *", inv_date + ) + generalized_time = backend._ffi.gc( + generalized_time, backend._lib.ASN1_GENERALIZEDTIME_free + ) + return x509.InvalidityDate( + _parse_asn1_generalized_time(backend, generalized_time) + ) + + +def _decode_cert_issuer(backend, gns): + gns = backend._ffi.cast("GENERAL_NAMES *", gns) + gns = backend._ffi.gc(gns, backend._lib.GENERAL_NAMES_free) + general_names = _decode_general_names(backend, gns) + return x509.CertificateIssuer(general_names) + + +def _asn1_to_der(backend, asn1_type): + buf = backend._ffi.new("unsigned char **") + res = backend._lib.i2d_ASN1_TYPE(asn1_type, buf) + backend.openssl_assert(res >= 0) + backend.openssl_assert(buf[0] != backend._ffi.NULL) + buf = backend._ffi.gc( + buf, lambda buffer: backend._lib.OPENSSL_free(buffer[0]) + ) + return backend._ffi.buffer(buf[0], res)[:] + + +def _asn1_integer_to_int(backend, asn1_int): + bn = backend._lib.ASN1_INTEGER_to_BN(asn1_int, backend._ffi.NULL) + backend.openssl_assert(bn != backend._ffi.NULL) + bn = backend._ffi.gc(bn, backend._lib.BN_free) + return backend._bn_to_int(bn) + + +def _asn1_integer_to_int_or_none(backend, asn1_int): + if asn1_int == backend._ffi.NULL: + return None + else: + return _asn1_integer_to_int(backend, asn1_int) + + +def _asn1_string_to_bytes(backend, asn1_string): + return backend._ffi.buffer(asn1_string.data, asn1_string.length)[:] + + +def _asn1_string_to_ascii(backend, asn1_string): + return _asn1_string_to_bytes(backend, asn1_string).decode("ascii") + + +def _asn1_string_to_utf8(backend, asn1_string): + buf = backend._ffi.new("unsigned char **") + res = backend._lib.ASN1_STRING_to_UTF8(buf, asn1_string) + if res == -1: + raise ValueError( + "Unsupported ASN1 string type. Type: {0}".format(asn1_string.type) + ) + + backend.openssl_assert(buf[0] != backend._ffi.NULL) + buf = backend._ffi.gc( + buf, lambda buffer: backend._lib.OPENSSL_free(buffer[0]) + ) + return backend._ffi.buffer(buf[0], res)[:].decode('utf8') + + +def _parse_asn1_time(backend, asn1_time): + backend.openssl_assert(asn1_time != backend._ffi.NULL) + generalized_time = backend._lib.ASN1_TIME_to_generalizedtime( + asn1_time, backend._ffi.NULL + ) + backend.openssl_assert(generalized_time != backend._ffi.NULL) + generalized_time = backend._ffi.gc( + generalized_time, backend._lib.ASN1_GENERALIZEDTIME_free + ) + return _parse_asn1_generalized_time(backend, generalized_time) + + +def _parse_asn1_generalized_time(backend, generalized_time): + time = _asn1_string_to_ascii( + backend, backend._ffi.cast("ASN1_STRING *", generalized_time) + ) + return datetime.datetime.strptime(time, "%Y%m%d%H%M%SZ") + + +_EXTENSION_HANDLERS = { + ExtensionOID.BASIC_CONSTRAINTS: _decode_basic_constraints, + ExtensionOID.SUBJECT_KEY_IDENTIFIER: _decode_subject_key_identifier, + ExtensionOID.KEY_USAGE: _decode_key_usage, + ExtensionOID.SUBJECT_ALTERNATIVE_NAME: _decode_subject_alt_name, + ExtensionOID.EXTENDED_KEY_USAGE: _decode_extended_key_usage, + ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _decode_authority_key_identifier, + ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( + _decode_authority_information_access + ), + ExtensionOID.CERTIFICATE_POLICIES: _decode_certificate_policies, + ExtensionOID.CRL_DISTRIBUTION_POINTS: _decode_crl_distribution_points, + ExtensionOID.OCSP_NO_CHECK: _decode_ocsp_no_check, + ExtensionOID.INHIBIT_ANY_POLICY: _decode_inhibit_any_policy, + ExtensionOID.ISSUER_ALTERNATIVE_NAME: _decode_issuer_alt_name, + ExtensionOID.NAME_CONSTRAINTS: _decode_name_constraints, + ExtensionOID.POLICY_CONSTRAINTS: _decode_policy_constraints, +} + +_REVOKED_EXTENSION_HANDLERS = { + CRLEntryExtensionOID.CRL_REASON: _decode_crl_reason, + CRLEntryExtensionOID.INVALIDITY_DATE: _decode_invalidity_date, + CRLEntryExtensionOID.CERTIFICATE_ISSUER: _decode_cert_issuer, +} + +_CRL_EXTENSION_HANDLERS = { + ExtensionOID.CRL_NUMBER: _decode_crl_number, + ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _decode_authority_key_identifier, + ExtensionOID.ISSUER_ALTERNATIVE_NAME: _decode_issuer_alt_name, + ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( + _decode_authority_information_access + ), +} + +_CERTIFICATE_EXTENSION_PARSER = _X509ExtensionParser( + ext_count=lambda backend, x: backend._lib.X509_get_ext_count(x), + get_ext=lambda backend, x, i: backend._lib.X509_get_ext(x, i), + handlers=_EXTENSION_HANDLERS +) + +_CSR_EXTENSION_PARSER = _X509ExtensionParser( + ext_count=lambda backend, x: backend._lib.sk_X509_EXTENSION_num(x), + get_ext=lambda backend, x, i: backend._lib.sk_X509_EXTENSION_value(x, i), + handlers=_EXTENSION_HANDLERS +) + +_REVOKED_CERTIFICATE_EXTENSION_PARSER = _X509ExtensionParser( + ext_count=lambda backend, x: backend._lib.X509_REVOKED_get_ext_count(x), + get_ext=lambda backend, x, i: backend._lib.X509_REVOKED_get_ext(x, i), + handlers=_REVOKED_EXTENSION_HANDLERS, +) + +_CRL_EXTENSION_PARSER = _X509ExtensionParser( + ext_count=lambda backend, x: backend._lib.X509_CRL_get_ext_count(x), + get_ext=lambda backend, x, i: backend._lib.X509_CRL_get_ext(x, i), + handlers=_CRL_EXTENSION_HANDLERS, +) diff --git a/src/cryptography/hazmat/backends/openssl/dh.py b/src/cryptography/hazmat/backends/openssl/dh.py new file mode 100644 index 0000000..666429f --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/dh.py @@ -0,0 +1,182 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.hazmat.primitives.asymmetric import dh + + +def _dh_cdata_to_parameters(dh_cdata, backend): + lib = backend._lib + ffi = backend._ffi + + param_cdata = lib.DHparams_dup(dh_cdata) + backend.openssl_assert(param_cdata != ffi.NULL) + param_cdata = ffi.gc(param_cdata, lib.DH_free) + + return _DHParameters(backend, param_cdata) + + +@utils.register_interface(dh.DHParametersWithSerialization) +class _DHParameters(object): + def __init__(self, backend, dh_cdata): + self._backend = backend + self._dh_cdata = dh_cdata + + def parameter_numbers(self): + p = self._backend._ffi.new("BIGNUM **") + g = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_pqg(self._dh_cdata, + p, self._backend._ffi.NULL, g) + self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) + return dh.DHParameterNumbers( + p=self._backend._bn_to_int(p[0]), + g=self._backend._bn_to_int(g[0]) + ) + + def generate_private_key(self): + return self._backend.generate_dh_private_key(self) + + +def _handle_dh_compute_key_error(errors, backend): + lib = backend._lib + + backend.openssl_assert(errors[0][1:] == ( + lib.ERR_LIB_DH, + lib.DH_F_COMPUTE_KEY, + lib.DH_R_INVALID_PUBKEY + )) + + raise ValueError("Public key value is invalid for this exchange.") + + +def _get_dh_num_bits(backend, dh_cdata): + p = backend._ffi.new("BIGNUM **") + backend._lib.DH_get0_pqg(dh_cdata, p, + backend._ffi.NULL, + backend._ffi.NULL) + backend.openssl_assert(p[0] != backend._ffi.NULL) + return backend._lib.BN_num_bits(p[0]) + + +@utils.register_interface(dh.DHPrivateKeyWithSerialization) +class _DHPrivateKey(object): + def __init__(self, backend, dh_cdata): + self._backend = backend + self._dh_cdata = dh_cdata + self._key_size_bytes = self._backend._lib.DH_size(dh_cdata) + + @property + def key_size(self): + return _get_dh_num_bits(self._backend, self._dh_cdata) + + def private_numbers(self): + p = self._backend._ffi.new("BIGNUM **") + g = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_pqg(self._dh_cdata, + p, self._backend._ffi.NULL, g) + self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) + pub_key = self._backend._ffi.new("BIGNUM **") + priv_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_key(self._dh_cdata, pub_key, priv_key) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(priv_key[0] != self._backend._ffi.NULL) + return dh.DHPrivateNumbers( + public_numbers=dh.DHPublicNumbers( + parameter_numbers=dh.DHParameterNumbers( + p=self._backend._bn_to_int(p[0]), + g=self._backend._bn_to_int(g[0]) + ), + y=self._backend._bn_to_int(pub_key[0]) + ), + x=self._backend._bn_to_int(priv_key[0]) + ) + + def exchange(self, peer_public_key): + + buf = self._backend._ffi.new("unsigned char[]", self._key_size_bytes) + pub_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_key(peer_public_key._dh_cdata, pub_key, + self._backend._ffi.NULL) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + res = self._backend._lib.DH_compute_key( + buf, + pub_key[0], + self._dh_cdata + ) + + if res == -1: + errors = self._backend._consume_errors() + return _handle_dh_compute_key_error(errors, self._backend) + else: + self._backend.openssl_assert(res >= 1) + + key = self._backend._ffi.buffer(buf)[:res] + pad = self._key_size_bytes - len(key) + + if pad > 0: + key = (b"\x00" * pad) + key + + return key + + def public_key(self): + dh_cdata = self._backend._lib.DHparams_dup(self._dh_cdata) + self._backend.openssl_assert(dh_cdata != self._backend._ffi.NULL) + dh_cdata = self._backend._ffi.gc( + dh_cdata, self._backend._lib.DH_free + ) + + pub_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_key(self._dh_cdata, + pub_key, self._backend._ffi.NULL) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + pub_key_dup = self._backend._lib.BN_dup(pub_key[0]) + self._backend.openssl_assert(pub_key_dup != self._backend._ffi.NULL) + + res = self._backend._lib.DH_set0_key(dh_cdata, + pub_key_dup, + self._backend._ffi.NULL) + self._backend.openssl_assert(res == 1) + + return _DHPublicKey(self._backend, dh_cdata) + + def parameters(self): + return _dh_cdata_to_parameters(self._dh_cdata, self._backend) + + +@utils.register_interface(dh.DHPublicKeyWithSerialization) +class _DHPublicKey(object): + def __init__(self, backend, dh_cdata): + self._backend = backend + self._dh_cdata = dh_cdata + self._key_size_bits = _get_dh_num_bits(self._backend, self._dh_cdata) + + @property + def key_size(self): + return self._key_size_bits + + def public_numbers(self): + p = self._backend._ffi.new("BIGNUM **") + g = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_pqg(self._dh_cdata, + p, self._backend._ffi.NULL, g) + self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) + pub_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DH_get0_key(self._dh_cdata, + pub_key, self._backend._ffi.NULL) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + return dh.DHPublicNumbers( + parameter_numbers=dh.DHParameterNumbers( + p=self._backend._bn_to_int(p[0]), + g=self._backend._bn_to_int(g[0]) + ), + y=self._backend._bn_to_int(pub_key[0]) + ) + + def parameters(self): + return _dh_cdata_to_parameters(self._dh_cdata, self._backend) diff --git a/src/cryptography/hazmat/backends/openssl/dsa.py b/src/cryptography/hazmat/backends/openssl/dsa.py new file mode 100644 index 0000000..e82c043 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/dsa.py @@ -0,0 +1,290 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.backends.openssl.utils import ( + _calculate_digest_and_algorithm, _truncate_digest +) +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ( + AsymmetricSignatureContext, AsymmetricVerificationContext, dsa +) + + +def _truncate_digest_for_dsa(dsa_cdata, digest, backend): + """ + This function truncates digests that are longer than a given DS + key's length so they can be signed. OpenSSL does this for us in + 1.0.0c+, leaving us with three releases (1.0.0, 1.0.0a, and 1.0.0b) where + this is a problem. + """ + + q = backend._ffi.new("BIGNUM **") + backend._lib.DSA_get0_pqg( + dsa_cdata, backend._ffi.NULL, q, backend._ffi.NULL + ) + backend.openssl_assert(q[0] != backend._ffi.NULL) + + order_bits = backend._lib.BN_num_bits(q[0]) + return _truncate_digest(digest, order_bits) + + +def _dsa_sig_sign(backend, private_key, data): + sig_buf_len = backend._lib.DSA_size(private_key._dsa_cdata) + sig_buf = backend._ffi.new("unsigned char[]", sig_buf_len) + buflen = backend._ffi.new("unsigned int *") + + # The first parameter passed to DSA_sign is unused by OpenSSL but + # must be an integer. + res = backend._lib.DSA_sign( + 0, data, len(data), sig_buf, buflen, private_key._dsa_cdata + ) + backend.openssl_assert(res == 1) + backend.openssl_assert(buflen[0]) + + return backend._ffi.buffer(sig_buf)[:buflen[0]] + + +def _dsa_sig_verify(backend, public_key, signature, data): + # The first parameter passed to DSA_verify is unused by OpenSSL but + # must be an integer. + res = backend._lib.DSA_verify( + 0, data, len(data), signature, len(signature), public_key._dsa_cdata + ) + + if res != 1: + backend._consume_errors() + raise InvalidSignature + + +@utils.register_interface(AsymmetricVerificationContext) +class _DSAVerificationContext(object): + def __init__(self, backend, public_key, signature, algorithm): + self._backend = backend + self._public_key = public_key + self._signature = signature + self._algorithm = algorithm + + self._hash_ctx = hashes.Hash(self._algorithm, self._backend) + + def update(self, data): + self._hash_ctx.update(data) + + def verify(self): + data_to_verify = self._hash_ctx.finalize() + + data_to_verify = _truncate_digest_for_dsa( + self._public_key._dsa_cdata, data_to_verify, self._backend + ) + _dsa_sig_verify( + self._backend, self._public_key, self._signature, data_to_verify + ) + + +@utils.register_interface(AsymmetricSignatureContext) +class _DSASignatureContext(object): + def __init__(self, backend, private_key, algorithm): + self._backend = backend + self._private_key = private_key + self._algorithm = algorithm + self._hash_ctx = hashes.Hash(self._algorithm, self._backend) + + def update(self, data): + self._hash_ctx.update(data) + + def finalize(self): + data_to_sign = self._hash_ctx.finalize() + data_to_sign = _truncate_digest_for_dsa( + self._private_key._dsa_cdata, data_to_sign, self._backend + ) + return _dsa_sig_sign(self._backend, self._private_key, data_to_sign) + + +@utils.register_interface(dsa.DSAParametersWithNumbers) +class _DSAParameters(object): + def __init__(self, backend, dsa_cdata): + self._backend = backend + self._dsa_cdata = dsa_cdata + + def parameter_numbers(self): + p = self._backend._ffi.new("BIGNUM **") + q = self._backend._ffi.new("BIGNUM **") + g = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DSA_get0_pqg(self._dsa_cdata, p, q, g) + self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(q[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) + return dsa.DSAParameterNumbers( + p=self._backend._bn_to_int(p[0]), + q=self._backend._bn_to_int(q[0]), + g=self._backend._bn_to_int(g[0]) + ) + + def generate_private_key(self): + return self._backend.generate_dsa_private_key(self) + + +@utils.register_interface(dsa.DSAPrivateKeyWithSerialization) +class _DSAPrivateKey(object): + def __init__(self, backend, dsa_cdata, evp_pkey): + self._backend = backend + self._dsa_cdata = dsa_cdata + self._evp_pkey = evp_pkey + + p = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DSA_get0_pqg( + dsa_cdata, p, self._backend._ffi.NULL, self._backend._ffi.NULL + ) + self._backend.openssl_assert(p[0] != backend._ffi.NULL) + self._key_size = self._backend._lib.BN_num_bits(p[0]) + + key_size = utils.read_only_property("_key_size") + + def signer(self, signature_algorithm): + return _DSASignatureContext(self._backend, self, signature_algorithm) + + def private_numbers(self): + p = self._backend._ffi.new("BIGNUM **") + q = self._backend._ffi.new("BIGNUM **") + g = self._backend._ffi.new("BIGNUM **") + pub_key = self._backend._ffi.new("BIGNUM **") + priv_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DSA_get0_pqg(self._dsa_cdata, p, q, g) + self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(q[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) + self._backend._lib.DSA_get0_key(self._dsa_cdata, pub_key, priv_key) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(priv_key[0] != self._backend._ffi.NULL) + return dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=self._backend._bn_to_int(p[0]), + q=self._backend._bn_to_int(q[0]), + g=self._backend._bn_to_int(g[0]) + ), + y=self._backend._bn_to_int(pub_key[0]) + ), + x=self._backend._bn_to_int(priv_key[0]) + ) + + def public_key(self): + dsa_cdata = self._backend._lib.DSAparams_dup(self._dsa_cdata) + self._backend.openssl_assert(dsa_cdata != self._backend._ffi.NULL) + dsa_cdata = self._backend._ffi.gc( + dsa_cdata, self._backend._lib.DSA_free + ) + pub_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DSA_get0_key( + self._dsa_cdata, pub_key, self._backend._ffi.NULL + ) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + pub_key_dup = self._backend._lib.BN_dup(pub_key[0]) + res = self._backend._lib.DSA_set0_key( + dsa_cdata, pub_key_dup, self._backend._ffi.NULL + ) + self._backend.openssl_assert(res == 1) + evp_pkey = self._backend._dsa_cdata_to_evp_pkey(dsa_cdata) + return _DSAPublicKey(self._backend, dsa_cdata, evp_pkey) + + def parameters(self): + dsa_cdata = self._backend._lib.DSAparams_dup(self._dsa_cdata) + self._backend.openssl_assert(dsa_cdata != self._backend._ffi.NULL) + dsa_cdata = self._backend._ffi.gc( + dsa_cdata, self._backend._lib.DSA_free + ) + return _DSAParameters(self._backend, dsa_cdata) + + def private_bytes(self, encoding, format, encryption_algorithm): + return self._backend._private_key_bytes( + encoding, + format, + encryption_algorithm, + self._evp_pkey, + self._dsa_cdata + ) + + def sign(self, data, algorithm): + data, algorithm = _calculate_digest_and_algorithm( + self._backend, data, algorithm + ) + data = _truncate_digest_for_dsa(self._dsa_cdata, data, self._backend) + return _dsa_sig_sign(self._backend, self, data) + + +@utils.register_interface(dsa.DSAPublicKeyWithSerialization) +class _DSAPublicKey(object): + def __init__(self, backend, dsa_cdata, evp_pkey): + self._backend = backend + self._dsa_cdata = dsa_cdata + self._evp_pkey = evp_pkey + p = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DSA_get0_pqg( + dsa_cdata, p, self._backend._ffi.NULL, self._backend._ffi.NULL + ) + self._backend.openssl_assert(p[0] != backend._ffi.NULL) + self._key_size = self._backend._lib.BN_num_bits(p[0]) + + key_size = utils.read_only_property("_key_size") + + def verifier(self, signature, signature_algorithm): + if not isinstance(signature, bytes): + raise TypeError("signature must be bytes.") + + return _DSAVerificationContext( + self._backend, self, signature, signature_algorithm + ) + + def public_numbers(self): + p = self._backend._ffi.new("BIGNUM **") + q = self._backend._ffi.new("BIGNUM **") + g = self._backend._ffi.new("BIGNUM **") + pub_key = self._backend._ffi.new("BIGNUM **") + self._backend._lib.DSA_get0_pqg(self._dsa_cdata, p, q, g) + self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(q[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(g[0] != self._backend._ffi.NULL) + self._backend._lib.DSA_get0_key( + self._dsa_cdata, pub_key, self._backend._ffi.NULL + ) + self._backend.openssl_assert(pub_key[0] != self._backend._ffi.NULL) + return dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=self._backend._bn_to_int(p[0]), + q=self._backend._bn_to_int(q[0]), + g=self._backend._bn_to_int(g[0]) + ), + y=self._backend._bn_to_int(pub_key[0]) + ) + + def parameters(self): + dsa_cdata = self._backend._lib.DSAparams_dup(self._dsa_cdata) + dsa_cdata = self._backend._ffi.gc( + dsa_cdata, self._backend._lib.DSA_free + ) + return _DSAParameters(self._backend, dsa_cdata) + + def public_bytes(self, encoding, format): + if format is serialization.PublicFormat.PKCS1: + raise ValueError( + "DSA public keys do not support PKCS1 serialization" + ) + + return self._backend._public_key_bytes( + encoding, + format, + self, + self._evp_pkey, + None + ) + + def verify(self, signature, data, algorithm): + data, algorithm = _calculate_digest_and_algorithm( + self._backend, data, algorithm + ) + data = _truncate_digest_for_dsa(self._dsa_cdata, data, self._backend) + return _dsa_sig_verify(self._backend, self, signature, data) diff --git a/src/cryptography/hazmat/backends/openssl/ec.py b/src/cryptography/hazmat/backends/openssl/ec.py new file mode 100644 index 0000000..5969f2a --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/ec.py @@ -0,0 +1,323 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.exceptions import ( + InvalidSignature, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.openssl.utils import ( + _calculate_digest_and_algorithm, _truncate_digest +) +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ( + AsymmetricSignatureContext, AsymmetricVerificationContext, ec +) + + +def _truncate_digest_for_ecdsa(ec_key_cdata, digest, backend): + """ + This function truncates digests that are longer than a given elliptic + curve key's length so they can be signed. Since elliptic curve keys are + much shorter than RSA keys many digests (e.g. SHA-512) may require + truncation. + """ + + _lib = backend._lib + _ffi = backend._ffi + + group = _lib.EC_KEY_get0_group(ec_key_cdata) + + with backend._tmp_bn_ctx() as bn_ctx: + order = _lib.BN_CTX_get(bn_ctx) + backend.openssl_assert(order != _ffi.NULL) + + res = _lib.EC_GROUP_get_order(group, order, bn_ctx) + backend.openssl_assert(res == 1) + + order_bits = _lib.BN_num_bits(order) + + return _truncate_digest(digest, order_bits) + + +def _check_signature_algorithm(signature_algorithm): + if not isinstance(signature_algorithm, ec.ECDSA): + raise UnsupportedAlgorithm( + "Unsupported elliptic curve signature algorithm.", + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM) + + +def _ec_key_curve_sn(backend, ec_key): + group = backend._lib.EC_KEY_get0_group(ec_key) + backend.openssl_assert(group != backend._ffi.NULL) + + nid = backend._lib.EC_GROUP_get_curve_name(group) + # The following check is to find EC keys with unnamed curves and raise + # an error for now. + if nid == backend._lib.NID_undef: + raise NotImplementedError( + "ECDSA certificates with unnamed curves are unsupported " + "at this time" + ) + + curve_name = backend._lib.OBJ_nid2sn(nid) + backend.openssl_assert(curve_name != backend._ffi.NULL) + + sn = backend._ffi.string(curve_name).decode('ascii') + return sn + + +def _mark_asn1_named_ec_curve(backend, ec_cdata): + """ + Set the named curve flag on the EC_KEY. This causes OpenSSL to + serialize EC keys along with their curve OID which makes + deserialization easier. + """ + + backend._lib.EC_KEY_set_asn1_flag( + ec_cdata, backend._lib.OPENSSL_EC_NAMED_CURVE + ) + + +def _sn_to_elliptic_curve(backend, sn): + try: + return ec._CURVE_TYPES[sn]() + except KeyError: + raise UnsupportedAlgorithm( + "{0} is not a supported elliptic curve".format(sn), + _Reasons.UNSUPPORTED_ELLIPTIC_CURVE + ) + + +def _ecdsa_sig_sign(backend, private_key, data): + max_size = backend._lib.ECDSA_size(private_key._ec_key) + backend.openssl_assert(max_size > 0) + + sigbuf = backend._ffi.new("unsigned char[]", max_size) + siglen_ptr = backend._ffi.new("unsigned int[]", 1) + res = backend._lib.ECDSA_sign( + 0, data, len(data), sigbuf, siglen_ptr, private_key._ec_key + ) + backend.openssl_assert(res == 1) + return backend._ffi.buffer(sigbuf)[:siglen_ptr[0]] + + +def _ecdsa_sig_verify(backend, public_key, signature, data): + res = backend._lib.ECDSA_verify( + 0, data, len(data), signature, len(signature), public_key._ec_key + ) + if res != 1: + backend._consume_errors() + raise InvalidSignature + return True + + +@utils.register_interface(AsymmetricSignatureContext) +class _ECDSASignatureContext(object): + def __init__(self, backend, private_key, algorithm): + self._backend = backend + self._private_key = private_key + self._digest = hashes.Hash(algorithm, backend) + + def update(self, data): + self._digest.update(data) + + def finalize(self): + digest = self._digest.finalize() + + digest = _truncate_digest_for_ecdsa( + self._private_key._ec_key, digest, self._backend + ) + return _ecdsa_sig_sign(self._backend, self._private_key, digest) + + +@utils.register_interface(AsymmetricVerificationContext) +class _ECDSAVerificationContext(object): + def __init__(self, backend, public_key, signature, algorithm): + self._backend = backend + self._public_key = public_key + self._signature = signature + self._digest = hashes.Hash(algorithm, backend) + + def update(self, data): + self._digest.update(data) + + def verify(self): + digest = self._digest.finalize() + digest = _truncate_digest_for_ecdsa( + self._public_key._ec_key, digest, self._backend + ) + return _ecdsa_sig_verify( + self._backend, self._public_key, self._signature, digest + ) + + +@utils.register_interface(ec.EllipticCurvePrivateKeyWithSerialization) +class _EllipticCurvePrivateKey(object): + def __init__(self, backend, ec_key_cdata, evp_pkey): + self._backend = backend + _mark_asn1_named_ec_curve(backend, ec_key_cdata) + self._ec_key = ec_key_cdata + self._evp_pkey = evp_pkey + + sn = _ec_key_curve_sn(backend, ec_key_cdata) + self._curve = _sn_to_elliptic_curve(backend, sn) + + curve = utils.read_only_property("_curve") + + def signer(self, signature_algorithm): + _check_signature_algorithm(signature_algorithm) + return _ECDSASignatureContext( + self._backend, self, signature_algorithm.algorithm + ) + + def exchange(self, algorithm, peer_public_key): + if not ( + self._backend.elliptic_curve_exchange_algorithm_supported( + algorithm, self.curve + ) + ): + raise UnsupportedAlgorithm( + "This backend does not support the ECDH algorithm.", + _Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + ) + + if peer_public_key.curve.name != self.curve.name: + raise ValueError( + "peer_public_key and self are not on the same curve" + ) + + group = self._backend._lib.EC_KEY_get0_group(self._ec_key) + z_len = (self._backend._lib.EC_GROUP_get_degree(group) + 7) // 8 + self._backend.openssl_assert(z_len > 0) + z_buf = self._backend._ffi.new("uint8_t[]", z_len) + peer_key = self._backend._lib.EC_KEY_get0_public_key( + peer_public_key._ec_key + ) + + r = self._backend._lib.ECDH_compute_key( + z_buf, z_len, peer_key, self._ec_key, self._backend._ffi.NULL + ) + self._backend.openssl_assert(r > 0) + return self._backend._ffi.buffer(z_buf)[:z_len] + + def public_key(self): + group = self._backend._lib.EC_KEY_get0_group(self._ec_key) + self._backend.openssl_assert(group != self._backend._ffi.NULL) + + curve_nid = self._backend._lib.EC_GROUP_get_curve_name(group) + + public_ec_key = self._backend._lib.EC_KEY_new_by_curve_name(curve_nid) + self._backend.openssl_assert(public_ec_key != self._backend._ffi.NULL) + public_ec_key = self._backend._ffi.gc( + public_ec_key, self._backend._lib.EC_KEY_free + ) + + point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key) + self._backend.openssl_assert(point != self._backend._ffi.NULL) + + res = self._backend._lib.EC_KEY_set_public_key(public_ec_key, point) + self._backend.openssl_assert(res == 1) + + evp_pkey = self._backend._ec_cdata_to_evp_pkey(public_ec_key) + + return _EllipticCurvePublicKey(self._backend, public_ec_key, evp_pkey) + + def private_numbers(self): + bn = self._backend._lib.EC_KEY_get0_private_key(self._ec_key) + private_value = self._backend._bn_to_int(bn) + return ec.EllipticCurvePrivateNumbers( + private_value=private_value, + public_numbers=self.public_key().public_numbers() + ) + + def private_bytes(self, encoding, format, encryption_algorithm): + return self._backend._private_key_bytes( + encoding, + format, + encryption_algorithm, + self._evp_pkey, + self._ec_key + ) + + def sign(self, data, signature_algorithm): + _check_signature_algorithm(signature_algorithm) + data, algorithm = _calculate_digest_and_algorithm( + self._backend, data, signature_algorithm._algorithm + ) + data = _truncate_digest_for_ecdsa( + self._ec_key, data, self._backend + ) + return _ecdsa_sig_sign(self._backend, self, data) + + +@utils.register_interface(ec.EllipticCurvePublicKeyWithSerialization) +class _EllipticCurvePublicKey(object): + def __init__(self, backend, ec_key_cdata, evp_pkey): + self._backend = backend + _mark_asn1_named_ec_curve(backend, ec_key_cdata) + self._ec_key = ec_key_cdata + self._evp_pkey = evp_pkey + + sn = _ec_key_curve_sn(backend, ec_key_cdata) + self._curve = _sn_to_elliptic_curve(backend, sn) + + curve = utils.read_only_property("_curve") + + def verifier(self, signature, signature_algorithm): + if not isinstance(signature, bytes): + raise TypeError("signature must be bytes.") + + _check_signature_algorithm(signature_algorithm) + return _ECDSAVerificationContext( + self._backend, self, signature, signature_algorithm.algorithm + ) + + def public_numbers(self): + set_func, get_func, group = ( + self._backend._ec_key_determine_group_get_set_funcs(self._ec_key) + ) + point = self._backend._lib.EC_KEY_get0_public_key(self._ec_key) + self._backend.openssl_assert(point != self._backend._ffi.NULL) + + with self._backend._tmp_bn_ctx() as bn_ctx: + bn_x = self._backend._lib.BN_CTX_get(bn_ctx) + bn_y = self._backend._lib.BN_CTX_get(bn_ctx) + + res = get_func(group, point, bn_x, bn_y, bn_ctx) + self._backend.openssl_assert(res == 1) + + x = self._backend._bn_to_int(bn_x) + y = self._backend._bn_to_int(bn_y) + + return ec.EllipticCurvePublicNumbers( + x=x, + y=y, + curve=self._curve + ) + + def public_bytes(self, encoding, format): + if format is serialization.PublicFormat.PKCS1: + raise ValueError( + "EC public keys do not support PKCS1 serialization" + ) + + return self._backend._public_key_bytes( + encoding, + format, + self, + self._evp_pkey, + None + ) + + def verify(self, signature, data, signature_algorithm): + _check_signature_algorithm(signature_algorithm) + data, algorithm = _calculate_digest_and_algorithm( + self._backend, data, signature_algorithm._algorithm + ) + data = _truncate_digest_for_ecdsa( + self._ec_key, data, self._backend + ) + return _ecdsa_sig_verify(self._backend, self, signature, data) diff --git a/src/cryptography/hazmat/backends/openssl/encode_asn1.py b/src/cryptography/hazmat/backends/openssl/encode_asn1.py new file mode 100644 index 0000000..3b78486 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/encode_asn1.py @@ -0,0 +1,616 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import calendar +import ipaddress + +import idna + +import six + +from cryptography import utils, x509 +from cryptography.hazmat.backends.openssl.decode_asn1 import ( + _CRL_ENTRY_REASON_ENUM_TO_CODE, _DISTPOINT_TYPE_FULLNAME, + _DISTPOINT_TYPE_RELATIVENAME +) +from cryptography.x509.oid import CRLEntryExtensionOID, ExtensionOID, NameOID + + +def _encode_asn1_int(backend, x): + """ + Converts a python integer to an ASN1_INTEGER. The returned ASN1_INTEGER + will not be garbage collected (to support adding them to structs that take + ownership of the object). Be sure to register it for GC if it will be + discarded after use. + + """ + # Convert Python integer to OpenSSL "bignum" in case value exceeds + # machine's native integer limits (note: `int_to_bn` doesn't automatically + # GC). + i = backend._int_to_bn(x) + i = backend._ffi.gc(i, backend._lib.BN_free) + + # Wrap in an ASN.1 integer. Don't GC -- as documented. + i = backend._lib.BN_to_ASN1_INTEGER(i, backend._ffi.NULL) + backend.openssl_assert(i != backend._ffi.NULL) + return i + + +def _encode_asn1_int_gc(backend, x): + i = _encode_asn1_int(backend, x) + i = backend._ffi.gc(i, backend._lib.ASN1_INTEGER_free) + return i + + +def _encode_asn1_str(backend, data, length): + """ + Create an ASN1_OCTET_STRING from a Python byte string. + """ + s = backend._lib.ASN1_OCTET_STRING_new() + res = backend._lib.ASN1_OCTET_STRING_set(s, data, length) + backend.openssl_assert(res == 1) + return s + + +def _encode_asn1_utf8_str(backend, string): + """ + Create an ASN1_UTF8STRING from a Python unicode string. + This object will be an ASN1_STRING with UTF8 type in OpenSSL and + can be decoded with ASN1_STRING_to_UTF8. + """ + s = backend._lib.ASN1_UTF8STRING_new() + res = backend._lib.ASN1_STRING_set( + s, string.encode("utf8"), len(string.encode("utf8")) + ) + backend.openssl_assert(res == 1) + return s + + +def _encode_asn1_str_gc(backend, data, length): + s = _encode_asn1_str(backend, data, length) + s = backend._ffi.gc(s, backend._lib.ASN1_OCTET_STRING_free) + return s + + +def _encode_inhibit_any_policy(backend, inhibit_any_policy): + return _encode_asn1_int_gc(backend, inhibit_any_policy.skip_certs) + + +def _encode_name(backend, name): + """ + The X509_NAME created will not be gc'd. Use _encode_name_gc if needed. + """ + subject = backend._lib.X509_NAME_new() + for rdn in name.rdns: + set_flag = 0 # indicate whether to add to last RDN or create new RDN + for attribute in rdn: + name_entry = _encode_name_entry(backend, attribute) + # X509_NAME_add_entry dups the object so we need to gc this copy + name_entry = backend._ffi.gc( + name_entry, backend._lib.X509_NAME_ENTRY_free + ) + res = backend._lib.X509_NAME_add_entry( + subject, name_entry, -1, set_flag) + backend.openssl_assert(res == 1) + set_flag = -1 + return subject + + +def _encode_name_gc(backend, attributes): + subject = _encode_name(backend, attributes) + subject = backend._ffi.gc(subject, backend._lib.X509_NAME_free) + return subject + + +def _encode_sk_name_entry(backend, attributes): + """ + The sk_X50_NAME_ENTRY created will not be gc'd. + """ + stack = backend._lib.sk_X509_NAME_ENTRY_new_null() + for attribute in attributes: + name_entry = _encode_name_entry(backend, attribute) + res = backend._lib.sk_X509_NAME_ENTRY_push(stack, name_entry) + backend.openssl_assert(res == 1) + return stack + + +def _encode_name_entry(backend, attribute): + value = attribute.value.encode('utf8') + obj = _txt2obj_gc(backend, attribute.oid.dotted_string) + if attribute.oid == NameOID.COUNTRY_NAME: + # Per RFC5280 Appendix A.1 countryName should be encoded as + # PrintableString, not UTF8String + type = backend._lib.MBSTRING_ASC + else: + type = backend._lib.MBSTRING_UTF8 + name_entry = backend._lib.X509_NAME_ENTRY_create_by_OBJ( + backend._ffi.NULL, obj, type, value, -1 + ) + return name_entry + + +def _encode_crl_number(backend, crl_number): + return _encode_asn1_int_gc(backend, crl_number.crl_number) + + +def _encode_crl_reason(backend, crl_reason): + asn1enum = backend._lib.ASN1_ENUMERATED_new() + backend.openssl_assert(asn1enum != backend._ffi.NULL) + asn1enum = backend._ffi.gc(asn1enum, backend._lib.ASN1_ENUMERATED_free) + res = backend._lib.ASN1_ENUMERATED_set( + asn1enum, _CRL_ENTRY_REASON_ENUM_TO_CODE[crl_reason.reason] + ) + backend.openssl_assert(res == 1) + + return asn1enum + + +def _encode_invalidity_date(backend, invalidity_date): + time = backend._lib.ASN1_GENERALIZEDTIME_set( + backend._ffi.NULL, calendar.timegm( + invalidity_date.invalidity_date.timetuple() + ) + ) + backend.openssl_assert(time != backend._ffi.NULL) + time = backend._ffi.gc(time, backend._lib.ASN1_GENERALIZEDTIME_free) + + return time + + +def _encode_certificate_policies(backend, certificate_policies): + cp = backend._lib.sk_POLICYINFO_new_null() + backend.openssl_assert(cp != backend._ffi.NULL) + cp = backend._ffi.gc(cp, backend._lib.sk_POLICYINFO_free) + for policy_info in certificate_policies: + pi = backend._lib.POLICYINFO_new() + backend.openssl_assert(pi != backend._ffi.NULL) + res = backend._lib.sk_POLICYINFO_push(cp, pi) + backend.openssl_assert(res >= 1) + oid = _txt2obj(backend, policy_info.policy_identifier.dotted_string) + pi.policyid = oid + if policy_info.policy_qualifiers: + pqis = backend._lib.sk_POLICYQUALINFO_new_null() + backend.openssl_assert(pqis != backend._ffi.NULL) + for qualifier in policy_info.policy_qualifiers: + pqi = backend._lib.POLICYQUALINFO_new() + backend.openssl_assert(pqi != backend._ffi.NULL) + res = backend._lib.sk_POLICYQUALINFO_push(pqis, pqi) + backend.openssl_assert(res >= 1) + if isinstance(qualifier, six.text_type): + pqi.pqualid = _txt2obj( + backend, x509.OID_CPS_QUALIFIER.dotted_string + ) + pqi.d.cpsuri = _encode_asn1_str( + backend, + qualifier.encode("ascii"), + len(qualifier.encode("ascii")) + ) + else: + assert isinstance(qualifier, x509.UserNotice) + pqi.pqualid = _txt2obj( + backend, x509.OID_CPS_USER_NOTICE.dotted_string + ) + un = backend._lib.USERNOTICE_new() + backend.openssl_assert(un != backend._ffi.NULL) + pqi.d.usernotice = un + if qualifier.explicit_text: + un.exptext = _encode_asn1_utf8_str( + backend, qualifier.explicit_text + ) + + un.noticeref = _encode_notice_reference( + backend, qualifier.notice_reference + ) + + pi.qualifiers = pqis + + return cp + + +def _encode_notice_reference(backend, notice): + if notice is None: + return backend._ffi.NULL + else: + nr = backend._lib.NOTICEREF_new() + backend.openssl_assert(nr != backend._ffi.NULL) + # organization is a required field + nr.organization = _encode_asn1_utf8_str(backend, notice.organization) + + notice_stack = backend._lib.sk_ASN1_INTEGER_new_null() + nr.noticenos = notice_stack + for number in notice.notice_numbers: + num = _encode_asn1_int(backend, number) + res = backend._lib.sk_ASN1_INTEGER_push(notice_stack, num) + backend.openssl_assert(res >= 1) + + return nr + + +def _txt2obj(backend, name): + """ + Converts a Python string with an ASN.1 object ID in dotted form to a + ASN1_OBJECT. + """ + name = name.encode('ascii') + obj = backend._lib.OBJ_txt2obj(name, 1) + backend.openssl_assert(obj != backend._ffi.NULL) + return obj + + +def _txt2obj_gc(backend, name): + obj = _txt2obj(backend, name) + obj = backend._ffi.gc(obj, backend._lib.ASN1_OBJECT_free) + return obj + + +def _encode_ocsp_nocheck(backend, ext): + """ + The OCSP No Check extension is defined as a null ASN.1 value embedded in + an ASN.1 string. + """ + return _encode_asn1_str_gc(backend, b"\x05\x00", 2) + + +def _encode_key_usage(backend, key_usage): + set_bit = backend._lib.ASN1_BIT_STRING_set_bit + ku = backend._lib.ASN1_BIT_STRING_new() + ku = backend._ffi.gc(ku, backend._lib.ASN1_BIT_STRING_free) + res = set_bit(ku, 0, key_usage.digital_signature) + backend.openssl_assert(res == 1) + res = set_bit(ku, 1, key_usage.content_commitment) + backend.openssl_assert(res == 1) + res = set_bit(ku, 2, key_usage.key_encipherment) + backend.openssl_assert(res == 1) + res = set_bit(ku, 3, key_usage.data_encipherment) + backend.openssl_assert(res == 1) + res = set_bit(ku, 4, key_usage.key_agreement) + backend.openssl_assert(res == 1) + res = set_bit(ku, 5, key_usage.key_cert_sign) + backend.openssl_assert(res == 1) + res = set_bit(ku, 6, key_usage.crl_sign) + backend.openssl_assert(res == 1) + if key_usage.key_agreement: + res = set_bit(ku, 7, key_usage.encipher_only) + backend.openssl_assert(res == 1) + res = set_bit(ku, 8, key_usage.decipher_only) + backend.openssl_assert(res == 1) + else: + res = set_bit(ku, 7, 0) + backend.openssl_assert(res == 1) + res = set_bit(ku, 8, 0) + backend.openssl_assert(res == 1) + + return ku + + +def _encode_authority_key_identifier(backend, authority_keyid): + akid = backend._lib.AUTHORITY_KEYID_new() + backend.openssl_assert(akid != backend._ffi.NULL) + akid = backend._ffi.gc(akid, backend._lib.AUTHORITY_KEYID_free) + if authority_keyid.key_identifier is not None: + akid.keyid = _encode_asn1_str( + backend, + authority_keyid.key_identifier, + len(authority_keyid.key_identifier) + ) + + if authority_keyid.authority_cert_issuer is not None: + akid.issuer = _encode_general_names( + backend, authority_keyid.authority_cert_issuer + ) + + if authority_keyid.authority_cert_serial_number is not None: + akid.serial = _encode_asn1_int( + backend, authority_keyid.authority_cert_serial_number + ) + + return akid + + +def _encode_basic_constraints(backend, basic_constraints): + constraints = backend._lib.BASIC_CONSTRAINTS_new() + constraints = backend._ffi.gc( + constraints, backend._lib.BASIC_CONSTRAINTS_free + ) + constraints.ca = 255 if basic_constraints.ca else 0 + if basic_constraints.ca and basic_constraints.path_length is not None: + constraints.pathlen = _encode_asn1_int( + backend, basic_constraints.path_length + ) + + return constraints + + +def _encode_authority_information_access(backend, authority_info_access): + aia = backend._lib.sk_ACCESS_DESCRIPTION_new_null() + backend.openssl_assert(aia != backend._ffi.NULL) + aia = backend._ffi.gc( + aia, backend._lib.sk_ACCESS_DESCRIPTION_free + ) + for access_description in authority_info_access: + ad = backend._lib.ACCESS_DESCRIPTION_new() + method = _txt2obj( + backend, access_description.access_method.dotted_string + ) + gn = _encode_general_name(backend, access_description.access_location) + ad.method = method + ad.location = gn + res = backend._lib.sk_ACCESS_DESCRIPTION_push(aia, ad) + backend.openssl_assert(res >= 1) + + return aia + + +def _encode_general_names(backend, names): + general_names = backend._lib.GENERAL_NAMES_new() + backend.openssl_assert(general_names != backend._ffi.NULL) + for name in names: + gn = _encode_general_name(backend, name) + res = backend._lib.sk_GENERAL_NAME_push(general_names, gn) + backend.openssl_assert(res != 0) + + return general_names + + +def _encode_alt_name(backend, san): + general_names = _encode_general_names(backend, san) + general_names = backend._ffi.gc( + general_names, backend._lib.GENERAL_NAMES_free + ) + return general_names + + +def _encode_subject_key_identifier(backend, ski): + return _encode_asn1_str_gc(backend, ski.digest, len(ski.digest)) + + +def _idna_encode(value): + # Retain prefixes '*.' for common/alt names and '.' for name constraints + for prefix in ['*.', '.']: + if value.startswith(prefix): + value = value[len(prefix):] + return prefix.encode('ascii') + idna.encode(value) + return idna.encode(value) + + +def _encode_general_name(backend, name): + if isinstance(name, x509.DNSName): + gn = backend._lib.GENERAL_NAME_new() + backend.openssl_assert(gn != backend._ffi.NULL) + gn.type = backend._lib.GEN_DNS + + ia5 = backend._lib.ASN1_IA5STRING_new() + backend.openssl_assert(ia5 != backend._ffi.NULL) + value = _idna_encode(name.value) + + res = backend._lib.ASN1_STRING_set(ia5, value, len(value)) + backend.openssl_assert(res == 1) + gn.d.dNSName = ia5 + elif isinstance(name, x509.RegisteredID): + gn = backend._lib.GENERAL_NAME_new() + backend.openssl_assert(gn != backend._ffi.NULL) + gn.type = backend._lib.GEN_RID + obj = backend._lib.OBJ_txt2obj( + name.value.dotted_string.encode('ascii'), 1 + ) + backend.openssl_assert(obj != backend._ffi.NULL) + gn.d.registeredID = obj + elif isinstance(name, x509.DirectoryName): + gn = backend._lib.GENERAL_NAME_new() + backend.openssl_assert(gn != backend._ffi.NULL) + dir_name = _encode_name(backend, name.value) + gn.type = backend._lib.GEN_DIRNAME + gn.d.directoryName = dir_name + elif isinstance(name, x509.IPAddress): + gn = backend._lib.GENERAL_NAME_new() + backend.openssl_assert(gn != backend._ffi.NULL) + if isinstance(name.value, ipaddress.IPv4Network): + packed = ( + name.value.network_address.packed + + utils.int_to_bytes(((1 << 32) - name.value.num_addresses), 4) + ) + elif isinstance(name.value, ipaddress.IPv6Network): + packed = ( + name.value.network_address.packed + + utils.int_to_bytes((1 << 128) - name.value.num_addresses, 16) + ) + else: + packed = name.value.packed + ipaddr = _encode_asn1_str(backend, packed, len(packed)) + gn.type = backend._lib.GEN_IPADD + gn.d.iPAddress = ipaddr + elif isinstance(name, x509.OtherName): + gn = backend._lib.GENERAL_NAME_new() + backend.openssl_assert(gn != backend._ffi.NULL) + other_name = backend._lib.OTHERNAME_new() + backend.openssl_assert(other_name != backend._ffi.NULL) + + type_id = backend._lib.OBJ_txt2obj( + name.type_id.dotted_string.encode('ascii'), 1 + ) + backend.openssl_assert(type_id != backend._ffi.NULL) + data = backend._ffi.new("unsigned char[]", 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(name.value) + ) + if value == backend._ffi.NULL: + backend._consume_errors() + 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 + elif isinstance(name, x509.RFC822Name): + gn = backend._lib.GENERAL_NAME_new() + backend.openssl_assert(gn != backend._ffi.NULL) + asn1_str = _encode_asn1_str( + backend, name._encoded, len(name._encoded) + ) + gn.type = backend._lib.GEN_EMAIL + gn.d.rfc822Name = asn1_str + elif isinstance(name, x509.UniformResourceIdentifier): + gn = backend._lib.GENERAL_NAME_new() + backend.openssl_assert(gn != backend._ffi.NULL) + asn1_str = _encode_asn1_str( + backend, name._encoded, len(name._encoded) + ) + gn.type = backend._lib.GEN_URI + gn.d.uniformResourceIdentifier = asn1_str + else: + raise ValueError( + "{0} is an unknown GeneralName type".format(name) + ) + + return gn + + +def _encode_extended_key_usage(backend, extended_key_usage): + eku = backend._lib.sk_ASN1_OBJECT_new_null() + eku = backend._ffi.gc(eku, backend._lib.sk_ASN1_OBJECT_free) + for oid in extended_key_usage: + obj = _txt2obj(backend, oid.dotted_string) + res = backend._lib.sk_ASN1_OBJECT_push(eku, obj) + backend.openssl_assert(res >= 1) + + return eku + + +_CRLREASONFLAGS = { + x509.ReasonFlags.key_compromise: 1, + x509.ReasonFlags.ca_compromise: 2, + x509.ReasonFlags.affiliation_changed: 3, + x509.ReasonFlags.superseded: 4, + x509.ReasonFlags.cessation_of_operation: 5, + x509.ReasonFlags.certificate_hold: 6, + x509.ReasonFlags.privilege_withdrawn: 7, + x509.ReasonFlags.aa_compromise: 8, +} + + +def _encode_crl_distribution_points(backend, crl_distribution_points): + cdp = backend._lib.sk_DIST_POINT_new_null() + cdp = backend._ffi.gc(cdp, backend._lib.sk_DIST_POINT_free) + for point in crl_distribution_points: + dp = backend._lib.DIST_POINT_new() + backend.openssl_assert(dp != backend._ffi.NULL) + + if point.reasons: + bitmask = backend._lib.ASN1_BIT_STRING_new() + backend.openssl_assert(bitmask != backend._ffi.NULL) + dp.reasons = bitmask + for reason in point.reasons: + res = backend._lib.ASN1_BIT_STRING_set_bit( + bitmask, _CRLREASONFLAGS[reason], 1 + ) + backend.openssl_assert(res == 1) + + if point.full_name: + dpn = backend._lib.DIST_POINT_NAME_new() + backend.openssl_assert(dpn != backend._ffi.NULL) + dpn.type = _DISTPOINT_TYPE_FULLNAME + dpn.name.fullname = _encode_general_names(backend, point.full_name) + dp.distpoint = dpn + + if point.relative_name: + dpn = backend._lib.DIST_POINT_NAME_new() + backend.openssl_assert(dpn != backend._ffi.NULL) + dpn.type = _DISTPOINT_TYPE_RELATIVENAME + relativename = _encode_sk_name_entry(backend, point.relative_name) + backend.openssl_assert(relativename != backend._ffi.NULL) + dpn.name.relativename = relativename + dp.distpoint = dpn + + if point.crl_issuer: + dp.CRLissuer = _encode_general_names(backend, point.crl_issuer) + + res = backend._lib.sk_DIST_POINT_push(cdp, dp) + backend.openssl_assert(res >= 1) + + return cdp + + +def _encode_name_constraints(backend, name_constraints): + nc = backend._lib.NAME_CONSTRAINTS_new() + backend.openssl_assert(nc != backend._ffi.NULL) + nc = backend._ffi.gc(nc, backend._lib.NAME_CONSTRAINTS_free) + permitted = _encode_general_subtree( + backend, name_constraints.permitted_subtrees + ) + nc.permittedSubtrees = permitted + excluded = _encode_general_subtree( + backend, name_constraints.excluded_subtrees + ) + nc.excludedSubtrees = excluded + + return nc + + +def _encode_policy_constraints(backend, policy_constraints): + pc = backend._lib.POLICY_CONSTRAINTS_new() + backend.openssl_assert(pc != backend._ffi.NULL) + pc = backend._ffi.gc(pc, backend._lib.POLICY_CONSTRAINTS_free) + if policy_constraints.require_explicit_policy is not None: + pc.requireExplicitPolicy = _encode_asn1_int( + backend, policy_constraints.require_explicit_policy + ) + + if policy_constraints.inhibit_policy_mapping is not None: + pc.inhibitPolicyMapping = _encode_asn1_int( + backend, policy_constraints.inhibit_policy_mapping + ) + + return pc + + +def _encode_general_subtree(backend, subtrees): + if subtrees is None: + return backend._ffi.NULL + else: + general_subtrees = backend._lib.sk_GENERAL_SUBTREE_new_null() + for name in subtrees: + gs = backend._lib.GENERAL_SUBTREE_new() + gs.base = _encode_general_name(backend, name) + res = backend._lib.sk_GENERAL_SUBTREE_push(general_subtrees, gs) + assert res >= 1 + + return general_subtrees + + +_EXTENSION_ENCODE_HANDLERS = { + ExtensionOID.BASIC_CONSTRAINTS: _encode_basic_constraints, + ExtensionOID.SUBJECT_KEY_IDENTIFIER: _encode_subject_key_identifier, + ExtensionOID.KEY_USAGE: _encode_key_usage, + ExtensionOID.SUBJECT_ALTERNATIVE_NAME: _encode_alt_name, + ExtensionOID.ISSUER_ALTERNATIVE_NAME: _encode_alt_name, + ExtensionOID.EXTENDED_KEY_USAGE: _encode_extended_key_usage, + ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _encode_authority_key_identifier, + ExtensionOID.CERTIFICATE_POLICIES: _encode_certificate_policies, + ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( + _encode_authority_information_access + ), + ExtensionOID.CRL_DISTRIBUTION_POINTS: _encode_crl_distribution_points, + ExtensionOID.INHIBIT_ANY_POLICY: _encode_inhibit_any_policy, + ExtensionOID.OCSP_NO_CHECK: _encode_ocsp_nocheck, + ExtensionOID.NAME_CONSTRAINTS: _encode_name_constraints, + ExtensionOID.POLICY_CONSTRAINTS: _encode_policy_constraints, +} + +_CRL_EXTENSION_ENCODE_HANDLERS = { + ExtensionOID.ISSUER_ALTERNATIVE_NAME: _encode_alt_name, + ExtensionOID.AUTHORITY_KEY_IDENTIFIER: _encode_authority_key_identifier, + ExtensionOID.AUTHORITY_INFORMATION_ACCESS: ( + _encode_authority_information_access + ), + ExtensionOID.CRL_NUMBER: _encode_crl_number, +} + +_CRL_ENTRY_EXTENSION_ENCODE_HANDLERS = { + CRLEntryExtensionOID.CERTIFICATE_ISSUER: _encode_alt_name, + CRLEntryExtensionOID.CRL_REASON: _encode_crl_reason, + CRLEntryExtensionOID.INVALIDITY_DATE: _encode_invalidity_date, +} diff --git a/src/cryptography/hazmat/backends/openssl/hashes.py b/src/cryptography/hazmat/backends/openssl/hashes.py new file mode 100644 index 0000000..92ea53b --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/hashes.py @@ -0,0 +1,61 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + + +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.primitives import hashes + + +@utils.register_interface(hashes.HashContext) +class _HashContext(object): + def __init__(self, backend, algorithm, ctx=None): + self._algorithm = algorithm + + self._backend = backend + + if ctx is None: + ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new() + ctx = self._backend._ffi.gc( + ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free + ) + name = self._backend._build_openssl_digest_name(algorithm) + evp_md = self._backend._lib.EVP_get_digestbyname(name) + if evp_md == self._backend._ffi.NULL: + raise UnsupportedAlgorithm( + "{0} is not a supported hash on this backend.".format( + name), + _Reasons.UNSUPPORTED_HASH + ) + res = self._backend._lib.EVP_DigestInit_ex(ctx, evp_md, + self._backend._ffi.NULL) + self._backend.openssl_assert(res != 0) + + self._ctx = ctx + + algorithm = utils.read_only_property("_algorithm") + + def copy(self): + copied_ctx = self._backend._lib.Cryptography_EVP_MD_CTX_new() + copied_ctx = self._backend._ffi.gc( + copied_ctx, self._backend._lib.Cryptography_EVP_MD_CTX_free + ) + res = self._backend._lib.EVP_MD_CTX_copy_ex(copied_ctx, self._ctx) + self._backend.openssl_assert(res != 0) + return _HashContext(self._backend, self.algorithm, ctx=copied_ctx) + + def update(self, data): + res = self._backend._lib.EVP_DigestUpdate(self._ctx, data, len(data)) + self._backend.openssl_assert(res != 0) + + def finalize(self): + buf = self._backend._ffi.new("unsigned char[]", + self._backend._lib.EVP_MAX_MD_SIZE) + outlen = self._backend._ffi.new("unsigned int *") + res = self._backend._lib.EVP_DigestFinal_ex(self._ctx, buf, outlen) + self._backend.openssl_assert(res != 0) + self._backend.openssl_assert(outlen[0] == self.algorithm.digest_size) + return self._backend._ffi.buffer(buf)[:outlen[0]] diff --git a/src/cryptography/hazmat/backends/openssl/hmac.py b/src/cryptography/hazmat/backends/openssl/hmac.py new file mode 100644 index 0000000..dff3742 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/hmac.py @@ -0,0 +1,74 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + + +from cryptography import utils +from cryptography.exceptions import ( + InvalidSignature, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.primitives import constant_time, hashes, interfaces + + +@utils.register_interface(interfaces.MACContext) +@utils.register_interface(hashes.HashContext) +class _HMACContext(object): + def __init__(self, backend, key, algorithm, ctx=None): + self._algorithm = algorithm + self._backend = backend + + if ctx is None: + ctx = self._backend._lib.Cryptography_HMAC_CTX_new() + self._backend.openssl_assert(ctx != self._backend._ffi.NULL) + ctx = self._backend._ffi.gc( + ctx, self._backend._lib.Cryptography_HMAC_CTX_free + ) + evp_md = self._backend._lib.EVP_get_digestbyname( + algorithm.name.encode('ascii')) + if evp_md == self._backend._ffi.NULL: + raise UnsupportedAlgorithm( + "{0} is not a supported hash on this backend.".format( + algorithm.name), + _Reasons.UNSUPPORTED_HASH + ) + res = self._backend._lib.HMAC_Init_ex( + ctx, key, len(key), evp_md, self._backend._ffi.NULL + ) + self._backend.openssl_assert(res != 0) + + self._ctx = ctx + self._key = key + + algorithm = utils.read_only_property("_algorithm") + + def copy(self): + copied_ctx = self._backend._lib.Cryptography_HMAC_CTX_new() + self._backend.openssl_assert(copied_ctx != self._backend._ffi.NULL) + copied_ctx = self._backend._ffi.gc( + copied_ctx, self._backend._lib.Cryptography_HMAC_CTX_free + ) + res = self._backend._lib.HMAC_CTX_copy(copied_ctx, self._ctx) + self._backend.openssl_assert(res != 0) + return _HMACContext( + self._backend, self._key, self.algorithm, ctx=copied_ctx + ) + + def update(self, data): + res = self._backend._lib.HMAC_Update(self._ctx, data, len(data)) + self._backend.openssl_assert(res != 0) + + def finalize(self): + buf = self._backend._ffi.new("unsigned char[]", + self._backend._lib.EVP_MAX_MD_SIZE) + outlen = self._backend._ffi.new("unsigned int *") + res = self._backend._lib.HMAC_Final(self._ctx, buf, outlen) + self._backend.openssl_assert(res != 0) + self._backend.openssl_assert(outlen[0] == self.algorithm.digest_size) + return self._backend._ffi.buffer(buf)[:outlen[0]] + + def verify(self, signature): + digest = self.finalize() + if not constant_time.bytes_eq(digest, signature): + raise InvalidSignature("Signature did not match digest.") diff --git a/src/cryptography/hazmat/backends/openssl/rsa.py b/src/cryptography/hazmat/backends/openssl/rsa.py new file mode 100644 index 0000000..8996d88 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/rsa.py @@ -0,0 +1,527 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import math + +from cryptography import utils +from cryptography.exceptions import ( + InvalidSignature, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.openssl.utils import ( + _calculate_digest_and_algorithm +) +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ( + AsymmetricSignatureContext, AsymmetricVerificationContext, rsa +) +from cryptography.hazmat.primitives.asymmetric.padding import ( + AsymmetricPadding, MGF1, OAEP, PKCS1v15, PSS, calculate_max_pss_salt_length +) +from cryptography.hazmat.primitives.asymmetric.rsa import ( + RSAPrivateKeyWithSerialization, RSAPublicKeyWithSerialization +) + + +def _get_rsa_pss_salt_length(pss, key, hash_algorithm): + salt = pss._salt_length + + if salt is MGF1.MAX_LENGTH or salt is PSS.MAX_LENGTH: + return calculate_max_pss_salt_length(key, hash_algorithm) + else: + return salt + + +def _enc_dec_rsa(backend, key, data, padding): + if not isinstance(padding, AsymmetricPadding): + raise TypeError("Padding must be an instance of AsymmetricPadding.") + + if isinstance(padding, PKCS1v15): + padding_enum = backend._lib.RSA_PKCS1_PADDING + elif isinstance(padding, OAEP): + padding_enum = backend._lib.RSA_PKCS1_OAEP_PADDING + + if not isinstance(padding._mgf, MGF1): + raise UnsupportedAlgorithm( + "Only MGF1 is supported by this backend.", + _Reasons.UNSUPPORTED_MGF + ) + + if not backend.rsa_padding_supported(padding): + raise UnsupportedAlgorithm( + "This combination of padding and hash algorithm is not " + "supported by this backend.", + _Reasons.UNSUPPORTED_PADDING + ) + + if padding._label is not None and padding._label != b"": + raise ValueError("This backend does not support OAEP labels.") + + else: + raise UnsupportedAlgorithm( + "{0} is not supported by this backend.".format( + padding.name + ), + _Reasons.UNSUPPORTED_PADDING + ) + + return _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding) + + +def _enc_dec_rsa_pkey_ctx(backend, key, data, padding_enum, padding): + if isinstance(key, _RSAPublicKey): + init = backend._lib.EVP_PKEY_encrypt_init + crypt = backend._lib.EVP_PKEY_encrypt + else: + init = backend._lib.EVP_PKEY_decrypt_init + crypt = backend._lib.EVP_PKEY_decrypt + + pkey_ctx = backend._lib.EVP_PKEY_CTX_new( + key._evp_pkey, backend._ffi.NULL + ) + backend.openssl_assert(pkey_ctx != backend._ffi.NULL) + pkey_ctx = backend._ffi.gc(pkey_ctx, backend._lib.EVP_PKEY_CTX_free) + res = init(pkey_ctx) + backend.openssl_assert(res == 1) + res = backend._lib.EVP_PKEY_CTX_set_rsa_padding( + pkey_ctx, padding_enum) + backend.openssl_assert(res > 0) + buf_size = backend._lib.EVP_PKEY_size(key._evp_pkey) + backend.openssl_assert(buf_size > 0) + if ( + isinstance(padding, OAEP) and + backend._lib.Cryptography_HAS_RSA_OAEP_MD + ): + mgf1_md = backend._lib.EVP_get_digestbyname( + padding._mgf._algorithm.name.encode("ascii")) + backend.openssl_assert(mgf1_md != backend._ffi.NULL) + res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md(pkey_ctx, mgf1_md) + backend.openssl_assert(res > 0) + oaep_md = backend._lib.EVP_get_digestbyname( + padding._algorithm.name.encode("ascii")) + backend.openssl_assert(oaep_md != backend._ffi.NULL) + res = backend._lib.EVP_PKEY_CTX_set_rsa_oaep_md(pkey_ctx, oaep_md) + backend.openssl_assert(res > 0) + + outlen = backend._ffi.new("size_t *", buf_size) + buf = backend._ffi.new("unsigned char[]", buf_size) + res = crypt(pkey_ctx, buf, outlen, data, len(data)) + if res <= 0: + _handle_rsa_enc_dec_error(backend, key) + + return backend._ffi.buffer(buf)[:outlen[0]] + + +def _handle_rsa_enc_dec_error(backend, key): + errors = backend._consume_errors() + assert errors + assert errors[0].lib == backend._lib.ERR_LIB_RSA + if isinstance(key, _RSAPublicKey): + assert (errors[0].reason == + backend._lib.RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE) + raise ValueError( + "Data too long for key size. Encrypt less data or use a " + "larger key size." + ) + else: + decoding_errors = [ + backend._lib.RSA_R_BLOCK_TYPE_IS_NOT_01, + backend._lib.RSA_R_BLOCK_TYPE_IS_NOT_02, + backend._lib.RSA_R_OAEP_DECODING_ERROR, + # Though this error looks similar to the + # RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE, this occurs on decrypts, + # rather than on encrypts + backend._lib.RSA_R_DATA_TOO_LARGE_FOR_MODULUS, + ] + if backend._lib.Cryptography_HAS_RSA_R_PKCS_DECODING_ERROR: + decoding_errors.append(backend._lib.RSA_R_PKCS_DECODING_ERROR) + + assert errors[0].reason in decoding_errors + raise ValueError("Decryption failed.") + + +def _rsa_sig_determine_padding(backend, key, padding, algorithm): + if not isinstance(padding, AsymmetricPadding): + raise TypeError("Expected provider of AsymmetricPadding.") + + pkey_size = backend._lib.EVP_PKEY_size(key._evp_pkey) + backend.openssl_assert(pkey_size > 0) + + if isinstance(padding, PKCS1v15): + padding_enum = backend._lib.RSA_PKCS1_PADDING + elif isinstance(padding, PSS): + if not isinstance(padding._mgf, MGF1): + raise UnsupportedAlgorithm( + "Only MGF1 is supported by this backend.", + _Reasons.UNSUPPORTED_MGF + ) + + # Size of key in bytes - 2 is the maximum + # PSS signature length (salt length is checked later) + if pkey_size - algorithm.digest_size - 2 < 0: + raise ValueError("Digest too large for key size. Use a larger " + "key or different digest.") + + if not backend._pss_mgf1_hash_supported(padding._mgf._algorithm): + raise UnsupportedAlgorithm( + "When OpenSSL is older than 1.0.1 then only SHA1 is " + "supported with MGF1.", + _Reasons.UNSUPPORTED_HASH + ) + + padding_enum = backend._lib.RSA_PKCS1_PSS_PADDING + else: + raise UnsupportedAlgorithm( + "{0} is not supported by this backend.".format(padding.name), + _Reasons.UNSUPPORTED_PADDING + ) + + return padding_enum + + +def _rsa_sig_sign(backend, padding, padding_enum, algorithm, private_key, + data): + evp_md = backend._lib.EVP_get_digestbyname( + algorithm.name.encode("ascii")) + backend.openssl_assert(evp_md != backend._ffi.NULL) + + pkey_ctx = backend._lib.EVP_PKEY_CTX_new( + private_key._evp_pkey, backend._ffi.NULL + ) + backend.openssl_assert(pkey_ctx != backend._ffi.NULL) + pkey_ctx = backend._ffi.gc(pkey_ctx, backend._lib.EVP_PKEY_CTX_free) + res = backend._lib.EVP_PKEY_sign_init(pkey_ctx) + backend.openssl_assert(res == 1) + res = backend._lib.EVP_PKEY_CTX_set_signature_md( + pkey_ctx, evp_md) + backend.openssl_assert(res > 0) + + res = backend._lib.EVP_PKEY_CTX_set_rsa_padding( + pkey_ctx, padding_enum) + backend.openssl_assert(res > 0) + if isinstance(padding, PSS): + res = backend._lib.EVP_PKEY_CTX_set_rsa_pss_saltlen( + pkey_ctx, + _get_rsa_pss_salt_length( + padding, + private_key, + algorithm, + ) + ) + backend.openssl_assert(res > 0) + + if backend._lib.Cryptography_HAS_MGF1_MD: + # MGF1 MD is configurable in OpenSSL 1.0.1+ + mgf1_md = backend._lib.EVP_get_digestbyname( + padding._mgf._algorithm.name.encode("ascii")) + backend.openssl_assert( + mgf1_md != backend._ffi.NULL + ) + res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md( + pkey_ctx, mgf1_md + ) + backend.openssl_assert(res > 0) + + buflen = backend._ffi.new("size_t *") + res = backend._lib.EVP_PKEY_sign( + pkey_ctx, + backend._ffi.NULL, + buflen, + data, + len(data) + ) + backend.openssl_assert(res == 1) + buf = backend._ffi.new("unsigned char[]", buflen[0]) + res = backend._lib.EVP_PKEY_sign( + pkey_ctx, buf, buflen, data, len(data)) + if res != 1: + errors = backend._consume_errors() + assert errors[0].lib == backend._lib.ERR_LIB_RSA + reason = None + if (errors[0].reason == + backend._lib.RSA_R_DATA_TOO_LARGE_FOR_KEY_SIZE): + reason = ("Salt length too long for key size. Try using " + "MAX_LENGTH instead.") + else: + assert (errors[0].reason == + backend._lib.RSA_R_DIGEST_TOO_BIG_FOR_RSA_KEY) + reason = "Digest too large for key size. Use a larger key." + assert reason is not None + raise ValueError(reason) + + return backend._ffi.buffer(buf)[:] + + +def _rsa_sig_verify(backend, padding, padding_enum, algorithm, public_key, + signature, data): + evp_md = backend._lib.EVP_get_digestbyname( + algorithm.name.encode("ascii")) + backend.openssl_assert(evp_md != backend._ffi.NULL) + + pkey_ctx = backend._lib.EVP_PKEY_CTX_new( + public_key._evp_pkey, backend._ffi.NULL + ) + backend.openssl_assert(pkey_ctx != backend._ffi.NULL) + pkey_ctx = backend._ffi.gc(pkey_ctx, backend._lib.EVP_PKEY_CTX_free) + res = backend._lib.EVP_PKEY_verify_init(pkey_ctx) + backend.openssl_assert(res == 1) + res = backend._lib.EVP_PKEY_CTX_set_signature_md( + pkey_ctx, evp_md) + backend.openssl_assert(res > 0) + + res = backend._lib.EVP_PKEY_CTX_set_rsa_padding( + pkey_ctx, padding_enum) + backend.openssl_assert(res > 0) + if isinstance(padding, PSS): + res = backend._lib.EVP_PKEY_CTX_set_rsa_pss_saltlen( + pkey_ctx, + _get_rsa_pss_salt_length( + padding, + public_key, + algorithm, + ) + ) + backend.openssl_assert(res > 0) + if backend._lib.Cryptography_HAS_MGF1_MD: + # MGF1 MD is configurable in OpenSSL 1.0.1+ + mgf1_md = backend._lib.EVP_get_digestbyname( + padding._mgf._algorithm.name.encode("ascii")) + backend.openssl_assert( + mgf1_md != backend._ffi.NULL + ) + res = backend._lib.EVP_PKEY_CTX_set_rsa_mgf1_md( + pkey_ctx, mgf1_md + ) + backend.openssl_assert(res > 0) + + res = backend._lib.EVP_PKEY_verify( + pkey_ctx, + signature, + len(signature), + data, + len(data) + ) + # The previous call can return negative numbers in the event of an + # error. This is not a signature failure but we need to fail if it + # occurs. + backend.openssl_assert(res >= 0) + if res == 0: + errors = backend._consume_errors() + assert errors + raise InvalidSignature + + +@utils.register_interface(AsymmetricSignatureContext) +class _RSASignatureContext(object): + def __init__(self, backend, private_key, padding, algorithm): + self._backend = backend + self._private_key = private_key + + self._padding_enum = _rsa_sig_determine_padding( + backend, private_key, padding, algorithm + ) + self._padding = padding + self._algorithm = algorithm + self._hash_ctx = hashes.Hash(self._algorithm, self._backend) + + def update(self, data): + self._hash_ctx.update(data) + + def finalize(self): + return _rsa_sig_sign( + self._backend, + self._padding, + self._padding_enum, + self._algorithm, + self._private_key, + self._hash_ctx.finalize() + ) + + +@utils.register_interface(AsymmetricVerificationContext) +class _RSAVerificationContext(object): + def __init__(self, backend, public_key, signature, padding, algorithm): + self._backend = backend + self._public_key = public_key + self._signature = signature + self._padding = padding + + self._padding_enum = _rsa_sig_determine_padding( + backend, public_key, padding, algorithm + ) + + padding = padding + self._algorithm = algorithm + self._hash_ctx = hashes.Hash(self._algorithm, self._backend) + + def update(self, data): + self._hash_ctx.update(data) + + def verify(self): + return _rsa_sig_verify( + self._backend, + self._padding, + self._padding_enum, + self._algorithm, + self._public_key, + self._signature, + self._hash_ctx.finalize() + ) + + +@utils.register_interface(RSAPrivateKeyWithSerialization) +class _RSAPrivateKey(object): + def __init__(self, backend, rsa_cdata, evp_pkey): + self._backend = backend + self._rsa_cdata = rsa_cdata + self._evp_pkey = evp_pkey + + n = self._backend._ffi.new("BIGNUM **") + self._backend._lib.RSA_get0_key( + self._rsa_cdata, n, self._backend._ffi.NULL, + self._backend._ffi.NULL + ) + self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) + self._key_size = self._backend._lib.BN_num_bits(n[0]) + + key_size = utils.read_only_property("_key_size") + + def signer(self, padding, algorithm): + return _RSASignatureContext(self._backend, self, padding, algorithm) + + def decrypt(self, ciphertext, padding): + key_size_bytes = int(math.ceil(self.key_size / 8.0)) + if key_size_bytes != len(ciphertext): + raise ValueError("Ciphertext length must be equal to key size.") + + return _enc_dec_rsa(self._backend, self, ciphertext, padding) + + def public_key(self): + ctx = self._backend._lib.RSAPublicKey_dup(self._rsa_cdata) + self._backend.openssl_assert(ctx != self._backend._ffi.NULL) + ctx = self._backend._ffi.gc(ctx, self._backend._lib.RSA_free) + res = self._backend._lib.RSA_blinding_on(ctx, self._backend._ffi.NULL) + self._backend.openssl_assert(res == 1) + evp_pkey = self._backend._rsa_cdata_to_evp_pkey(ctx) + return _RSAPublicKey(self._backend, ctx, evp_pkey) + + def private_numbers(self): + n = self._backend._ffi.new("BIGNUM **") + e = self._backend._ffi.new("BIGNUM **") + d = self._backend._ffi.new("BIGNUM **") + p = self._backend._ffi.new("BIGNUM **") + q = self._backend._ffi.new("BIGNUM **") + dmp1 = self._backend._ffi.new("BIGNUM **") + dmq1 = self._backend._ffi.new("BIGNUM **") + iqmp = self._backend._ffi.new("BIGNUM **") + self._backend._lib.RSA_get0_key(self._rsa_cdata, n, e, d) + self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(e[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(d[0] != self._backend._ffi.NULL) + self._backend._lib.RSA_get0_factors(self._rsa_cdata, p, q) + self._backend.openssl_assert(p[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(q[0] != self._backend._ffi.NULL) + self._backend._lib.RSA_get0_crt_params( + self._rsa_cdata, dmp1, dmq1, iqmp + ) + self._backend.openssl_assert(dmp1[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(dmq1[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(iqmp[0] != self._backend._ffi.NULL) + return rsa.RSAPrivateNumbers( + p=self._backend._bn_to_int(p[0]), + q=self._backend._bn_to_int(q[0]), + d=self._backend._bn_to_int(d[0]), + dmp1=self._backend._bn_to_int(dmp1[0]), + dmq1=self._backend._bn_to_int(dmq1[0]), + iqmp=self._backend._bn_to_int(iqmp[0]), + public_numbers=rsa.RSAPublicNumbers( + e=self._backend._bn_to_int(e[0]), + n=self._backend._bn_to_int(n[0]), + ) + ) + + def private_bytes(self, encoding, format, encryption_algorithm): + return self._backend._private_key_bytes( + encoding, + format, + encryption_algorithm, + self._evp_pkey, + self._rsa_cdata + ) + + def sign(self, data, padding, algorithm): + padding_enum = _rsa_sig_determine_padding( + self._backend, self, padding, algorithm + ) + data, algorithm = _calculate_digest_and_algorithm( + self._backend, data, algorithm + ) + return _rsa_sig_sign( + self._backend, padding, padding_enum, + algorithm, self, data + ) + + +@utils.register_interface(RSAPublicKeyWithSerialization) +class _RSAPublicKey(object): + def __init__(self, backend, rsa_cdata, evp_pkey): + self._backend = backend + self._rsa_cdata = rsa_cdata + self._evp_pkey = evp_pkey + + n = self._backend._ffi.new("BIGNUM **") + self._backend._lib.RSA_get0_key( + self._rsa_cdata, n, self._backend._ffi.NULL, + self._backend._ffi.NULL + ) + self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) + self._key_size = self._backend._lib.BN_num_bits(n[0]) + + key_size = utils.read_only_property("_key_size") + + def verifier(self, signature, padding, algorithm): + if not isinstance(signature, bytes): + raise TypeError("signature must be bytes.") + + return _RSAVerificationContext( + self._backend, self, signature, padding, algorithm + ) + + def encrypt(self, plaintext, padding): + return _enc_dec_rsa(self._backend, self, plaintext, padding) + + def public_numbers(self): + n = self._backend._ffi.new("BIGNUM **") + e = self._backend._ffi.new("BIGNUM **") + self._backend._lib.RSA_get0_key( + self._rsa_cdata, n, e, self._backend._ffi.NULL + ) + self._backend.openssl_assert(n[0] != self._backend._ffi.NULL) + self._backend.openssl_assert(e[0] != self._backend._ffi.NULL) + return rsa.RSAPublicNumbers( + e=self._backend._bn_to_int(e[0]), + n=self._backend._bn_to_int(n[0]), + ) + + def public_bytes(self, encoding, format): + return self._backend._public_key_bytes( + encoding, + format, + self, + self._evp_pkey, + self._rsa_cdata + ) + + def verify(self, signature, data, padding, algorithm): + padding_enum = _rsa_sig_determine_padding( + self._backend, self, padding, algorithm + ) + data, algorithm = _calculate_digest_and_algorithm( + self._backend, data, algorithm + ) + return _rsa_sig_verify( + self._backend, padding, padding_enum, algorithm, self, + signature, data + ) diff --git a/src/cryptography/hazmat/backends/openssl/utils.py b/src/cryptography/hazmat/backends/openssl/utils.py new file mode 100644 index 0000000..c88e318 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/utils.py @@ -0,0 +1,46 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import six + +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric.utils import Prehashed + + +def _truncate_digest(digest, order_bits): + digest_len = len(digest) + + if 8 * digest_len > order_bits: + digest_len = (order_bits + 7) // 8 + digest = digest[:digest_len] + + if 8 * digest_len > order_bits: + rshift = 8 - (order_bits & 0x7) + assert 0 < rshift < 8 + + mask = 0xFF >> rshift << rshift + + # Set the bottom rshift bits to 0 + digest = digest[:-1] + six.int2byte(six.indexbytes(digest, -1) & mask) + + return digest + + +def _calculate_digest_and_algorithm(backend, data, algorithm): + if not isinstance(algorithm, Prehashed): + hash_ctx = hashes.Hash(algorithm, backend) + hash_ctx.update(data) + data = hash_ctx.finalize() + else: + algorithm = algorithm._algorithm + + if len(data) != algorithm.digest_size: + raise ValueError( + "The provided data must be the same length as the hash " + "algorithm's digest size." + ) + + return (data, algorithm) diff --git a/src/cryptography/hazmat/backends/openssl/x509.py b/src/cryptography/hazmat/backends/openssl/x509.py new file mode 100644 index 0000000..1f63d85 --- /dev/null +++ b/src/cryptography/hazmat/backends/openssl/x509.py @@ -0,0 +1,435 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import operator +import warnings + +from cryptography import utils, x509 +from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat.backends.openssl.decode_asn1 import ( + _CERTIFICATE_EXTENSION_PARSER, _CRL_EXTENSION_PARSER, + _CSR_EXTENSION_PARSER, _REVOKED_CERTIFICATE_EXTENSION_PARSER, + _asn1_integer_to_int, _asn1_string_to_bytes, _decode_x509_name, _obj2txt, + _parse_asn1_time +) +from cryptography.hazmat.primitives import hashes, serialization + + +@utils.register_interface(x509.Certificate) +class _Certificate(object): + def __init__(self, backend, x509): + self._backend = backend + self._x509 = x509 + + def __repr__(self): + return "".format(self.subject) + + def __eq__(self, other): + if not isinstance(other, x509.Certificate): + return NotImplemented + + res = self._backend._lib.X509_cmp(self._x509, other._x509) + return res == 0 + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash(self.public_bytes(serialization.Encoding.DER)) + + def fingerprint(self, algorithm): + h = hashes.Hash(algorithm, self._backend) + h.update(self.public_bytes(serialization.Encoding.DER)) + return h.finalize() + + @property + def version(self): + version = self._backend._lib.X509_get_version(self._x509) + if version == 0: + return x509.Version.v1 + elif version == 2: + return x509.Version.v3 + else: + raise x509.InvalidVersion( + "{0} is not a valid X509 version".format(version), version + ) + + @property + def serial(self): + warnings.warn( + "Certificate serial is deprecated, use serial_number instead.", + utils.DeprecatedIn14, + stacklevel=2 + ) + return self.serial_number + + @property + def serial_number(self): + asn1_int = self._backend._lib.X509_get_serialNumber(self._x509) + self._backend.openssl_assert(asn1_int != self._backend._ffi.NULL) + return _asn1_integer_to_int(self._backend, asn1_int) + + def public_key(self): + pkey = self._backend._lib.X509_get_pubkey(self._x509) + if pkey == self._backend._ffi.NULL: + # Remove errors from the stack. + self._backend._consume_errors() + raise ValueError("Certificate public key is of an unknown type") + + pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free) + + return self._backend._evp_pkey_to_public_key(pkey) + + @property + def not_valid_before(self): + asn1_time = self._backend._lib.X509_get_notBefore(self._x509) + return _parse_asn1_time(self._backend, asn1_time) + + @property + def not_valid_after(self): + asn1_time = self._backend._lib.X509_get_notAfter(self._x509) + return _parse_asn1_time(self._backend, asn1_time) + + @property + def issuer(self): + issuer = self._backend._lib.X509_get_issuer_name(self._x509) + self._backend.openssl_assert(issuer != self._backend._ffi.NULL) + return _decode_x509_name(self._backend, issuer) + + @property + def subject(self): + subject = self._backend._lib.X509_get_subject_name(self._x509) + self._backend.openssl_assert(subject != self._backend._ffi.NULL) + return _decode_x509_name(self._backend, subject) + + @property + def signature_hash_algorithm(self): + oid = self.signature_algorithm_oid + try: + return x509._SIG_OIDS_TO_HASH[oid] + except KeyError: + raise UnsupportedAlgorithm( + "Signature algorithm OID:{0} not recognized".format(oid) + ) + + @property + def signature_algorithm_oid(self): + alg = self._backend._ffi.new("X509_ALGOR **") + self._backend._lib.X509_get0_signature( + self._backend._ffi.NULL, alg, self._x509 + ) + self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL) + oid = _obj2txt(self._backend, alg[0].algorithm) + return x509.ObjectIdentifier(oid) + + @property + def extensions(self): + return _CERTIFICATE_EXTENSION_PARSER.parse(self._backend, self._x509) + + @property + def signature(self): + sig = self._backend._ffi.new("ASN1_BIT_STRING **") + self._backend._lib.X509_get0_signature( + sig, self._backend._ffi.NULL, self._x509 + ) + self._backend.openssl_assert(sig[0] != self._backend._ffi.NULL) + return _asn1_string_to_bytes(self._backend, sig[0]) + + @property + def tbs_certificate_bytes(self): + pp = self._backend._ffi.new("unsigned char **") + res = self._backend._lib.i2d_re_X509_tbs(self._x509, pp) + self._backend.openssl_assert(res > 0) + pp = self._backend._ffi.gc( + pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0]) + ) + return self._backend._ffi.buffer(pp[0], res)[:] + + def public_bytes(self, encoding): + bio = self._backend._create_mem_bio_gc() + if encoding is serialization.Encoding.PEM: + res = self._backend._lib.PEM_write_bio_X509(bio, self._x509) + elif encoding is serialization.Encoding.DER: + res = self._backend._lib.i2d_X509_bio(bio, self._x509) + else: + raise TypeError("encoding must be an item from the Encoding enum") + + self._backend.openssl_assert(res == 1) + return self._backend._read_mem_bio(bio) + + +@utils.register_interface(x509.RevokedCertificate) +class _RevokedCertificate(object): + def __init__(self, backend, crl, x509_revoked): + self._backend = backend + # The X509_REVOKED_value is a X509_REVOKED * that has + # no reference counting. This means when X509_CRL_free is + # called then the CRL and all X509_REVOKED * are freed. Since + # you can retain a reference to a single revoked certificate + # and let the CRL fall out of scope we need to retain a + # private reference to the CRL inside the RevokedCertificate + # object to prevent the gc from being called inappropriately. + self._crl = crl + self._x509_revoked = x509_revoked + + @property + def serial_number(self): + asn1_int = self._backend._lib.X509_REVOKED_get0_serialNumber( + self._x509_revoked + ) + self._backend.openssl_assert(asn1_int != self._backend._ffi.NULL) + return _asn1_integer_to_int(self._backend, asn1_int) + + @property + def revocation_date(self): + return _parse_asn1_time( + self._backend, + self._backend._lib.X509_REVOKED_get0_revocationDate( + self._x509_revoked + ) + ) + + @property + def extensions(self): + return _REVOKED_CERTIFICATE_EXTENSION_PARSER.parse( + self._backend, self._x509_revoked + ) + + +@utils.register_interface(x509.CertificateRevocationList) +class _CertificateRevocationList(object): + def __init__(self, backend, x509_crl): + self._backend = backend + self._x509_crl = x509_crl + + def __eq__(self, other): + if not isinstance(other, x509.CertificateRevocationList): + return NotImplemented + + res = self._backend._lib.X509_CRL_cmp(self._x509_crl, other._x509_crl) + return res == 0 + + def __ne__(self, other): + return not self == other + + def fingerprint(self, algorithm): + h = hashes.Hash(algorithm, self._backend) + bio = self._backend._create_mem_bio_gc() + res = self._backend._lib.i2d_X509_CRL_bio( + bio, self._x509_crl + ) + self._backend.openssl_assert(res == 1) + der = self._backend._read_mem_bio(bio) + h.update(der) + return h.finalize() + + @property + def signature_hash_algorithm(self): + oid = self.signature_algorithm_oid + try: + return x509._SIG_OIDS_TO_HASH[oid] + except KeyError: + raise UnsupportedAlgorithm( + "Signature algorithm OID:{0} not recognized".format(oid) + ) + + @property + def signature_algorithm_oid(self): + alg = self._backend._ffi.new("X509_ALGOR **") + self._backend._lib.X509_CRL_get0_signature( + self._x509_crl, self._backend._ffi.NULL, alg + ) + self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL) + oid = _obj2txt(self._backend, alg[0].algorithm) + return x509.ObjectIdentifier(oid) + + @property + def issuer(self): + issuer = self._backend._lib.X509_CRL_get_issuer(self._x509_crl) + self._backend.openssl_assert(issuer != self._backend._ffi.NULL) + return _decode_x509_name(self._backend, issuer) + + @property + def next_update(self): + nu = self._backend._lib.X509_CRL_get_nextUpdate(self._x509_crl) + self._backend.openssl_assert(nu != self._backend._ffi.NULL) + return _parse_asn1_time(self._backend, nu) + + @property + def last_update(self): + lu = self._backend._lib.X509_CRL_get_lastUpdate(self._x509_crl) + self._backend.openssl_assert(lu != self._backend._ffi.NULL) + return _parse_asn1_time(self._backend, lu) + + @property + def signature(self): + sig = self._backend._ffi.new("ASN1_BIT_STRING **") + self._backend._lib.X509_CRL_get0_signature( + self._x509_crl, sig, self._backend._ffi.NULL + ) + self._backend.openssl_assert(sig[0] != self._backend._ffi.NULL) + return _asn1_string_to_bytes(self._backend, sig[0]) + + @property + def tbs_certlist_bytes(self): + pp = self._backend._ffi.new("unsigned char **") + res = self._backend._lib.i2d_re_X509_CRL_tbs(self._x509_crl, pp) + self._backend.openssl_assert(res > 0) + pp = self._backend._ffi.gc( + pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0]) + ) + return self._backend._ffi.buffer(pp[0], res)[:] + + def public_bytes(self, encoding): + bio = self._backend._create_mem_bio_gc() + if encoding is serialization.Encoding.PEM: + res = self._backend._lib.PEM_write_bio_X509_CRL( + bio, self._x509_crl + ) + elif encoding is serialization.Encoding.DER: + res = self._backend._lib.i2d_X509_CRL_bio(bio, self._x509_crl) + else: + raise TypeError("encoding must be an item from the Encoding enum") + + self._backend.openssl_assert(res == 1) + return self._backend._read_mem_bio(bio) + + def _revoked_cert(self, idx): + revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl) + r = self._backend._lib.sk_X509_REVOKED_value(revoked, idx) + self._backend.openssl_assert(r != self._backend._ffi.NULL) + return _RevokedCertificate(self._backend, self, r) + + def __iter__(self): + for i in range(len(self)): + yield self._revoked_cert(i) + + def __getitem__(self, idx): + if isinstance(idx, slice): + start, stop, step = idx.indices(len(self)) + return [self._revoked_cert(i) for i in range(start, stop, step)] + else: + idx = operator.index(idx) + if idx < 0: + idx += len(self) + if not 0 <= idx < len(self): + raise IndexError + return self._revoked_cert(idx) + + def __len__(self): + revoked = self._backend._lib.X509_CRL_get_REVOKED(self._x509_crl) + if revoked == self._backend._ffi.NULL: + return 0 + else: + return self._backend._lib.sk_X509_REVOKED_num(revoked) + + @property + def extensions(self): + return _CRL_EXTENSION_PARSER.parse(self._backend, self._x509_crl) + + +@utils.register_interface(x509.CertificateSigningRequest) +class _CertificateSigningRequest(object): + def __init__(self, backend, x509_req): + self._backend = backend + self._x509_req = x509_req + + def __eq__(self, other): + if not isinstance(other, _CertificateSigningRequest): + return NotImplemented + + self_bytes = self.public_bytes(serialization.Encoding.DER) + other_bytes = other.public_bytes(serialization.Encoding.DER) + return self_bytes == other_bytes + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash(self.public_bytes(serialization.Encoding.DER)) + + def public_key(self): + pkey = self._backend._lib.X509_REQ_get_pubkey(self._x509_req) + self._backend.openssl_assert(pkey != self._backend._ffi.NULL) + pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free) + return self._backend._evp_pkey_to_public_key(pkey) + + @property + def subject(self): + subject = self._backend._lib.X509_REQ_get_subject_name(self._x509_req) + self._backend.openssl_assert(subject != self._backend._ffi.NULL) + return _decode_x509_name(self._backend, subject) + + @property + def signature_hash_algorithm(self): + oid = self.signature_algorithm_oid + try: + return x509._SIG_OIDS_TO_HASH[oid] + except KeyError: + raise UnsupportedAlgorithm( + "Signature algorithm OID:{0} not recognized".format(oid) + ) + + @property + def signature_algorithm_oid(self): + alg = self._backend._ffi.new("X509_ALGOR **") + self._backend._lib.X509_REQ_get0_signature( + self._x509_req, self._backend._ffi.NULL, alg + ) + self._backend.openssl_assert(alg[0] != self._backend._ffi.NULL) + oid = _obj2txt(self._backend, alg[0].algorithm) + return x509.ObjectIdentifier(oid) + + @property + def extensions(self): + x509_exts = self._backend._lib.X509_REQ_get_extensions(self._x509_req) + return _CSR_EXTENSION_PARSER.parse(self._backend, x509_exts) + + def public_bytes(self, encoding): + bio = self._backend._create_mem_bio_gc() + 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) + else: + raise TypeError("encoding must be an item from the Encoding enum") + + self._backend.openssl_assert(res == 1) + return self._backend._read_mem_bio(bio) + + @property + def tbs_certrequest_bytes(self): + pp = self._backend._ffi.new("unsigned char **") + res = self._backend._lib.i2d_re_X509_REQ_tbs(self._x509_req, pp) + self._backend.openssl_assert(res > 0) + pp = self._backend._ffi.gc( + pp, lambda pointer: self._backend._lib.OPENSSL_free(pointer[0]) + ) + return self._backend._ffi.buffer(pp[0], res)[:] + + @property + def signature(self): + sig = self._backend._ffi.new("ASN1_BIT_STRING **") + self._backend._lib.X509_REQ_get0_signature( + self._x509_req, sig, self._backend._ffi.NULL + ) + self._backend.openssl_assert(sig[0] != self._backend._ffi.NULL) + return _asn1_string_to_bytes(self._backend, sig[0]) + + @property + def is_signature_valid(self): + pkey = self._backend._lib.X509_REQ_get_pubkey(self._x509_req) + self._backend.openssl_assert(pkey != self._backend._ffi.NULL) + pkey = self._backend._ffi.gc(pkey, self._backend._lib.EVP_PKEY_free) + res = self._backend._lib.X509_REQ_verify(self._x509_req, pkey) + + if res != 1: + self._backend._consume_errors() + return False + + return True diff --git a/src/cryptography/hazmat/bindings/__init__.py b/src/cryptography/hazmat/bindings/__init__.py new file mode 100644 index 0000000..4b54088 --- /dev/null +++ b/src/cryptography/hazmat/bindings/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function diff --git a/src/cryptography/hazmat/bindings/commoncrypto/__init__.py b/src/cryptography/hazmat/bindings/commoncrypto/__init__.py new file mode 100644 index 0000000..4b54088 --- /dev/null +++ b/src/cryptography/hazmat/bindings/commoncrypto/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function diff --git a/src/cryptography/hazmat/bindings/commoncrypto/binding.py b/src/cryptography/hazmat/bindings/commoncrypto/binding.py new file mode 100644 index 0000000..dfe046b --- /dev/null +++ b/src/cryptography/hazmat/bindings/commoncrypto/binding.py @@ -0,0 +1,15 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography.hazmat.bindings._commoncrypto import ffi, lib + + +class Binding(object): + """ + CommonCrypto API wrapper. + """ + lib = lib + ffi = ffi diff --git a/src/cryptography/hazmat/bindings/openssl/__init__.py b/src/cryptography/hazmat/bindings/openssl/__init__.py new file mode 100644 index 0000000..4b54088 --- /dev/null +++ b/src/cryptography/hazmat/bindings/openssl/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function diff --git a/src/cryptography/hazmat/bindings/openssl/_conditional.py b/src/cryptography/hazmat/bindings/openssl/_conditional.py new file mode 100644 index 0000000..d1cebd8 --- /dev/null +++ b/src/cryptography/hazmat/bindings/openssl/_conditional.py @@ -0,0 +1,325 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +# This is a temporary copy of all the CONDITIONAL_NAMES from _cffi_src so +# we can loop over them and delete them at runtime. It will be removed when +# cffi supports #if in cdef + +CONDITIONAL_NAMES = { + "Cryptography_HAS_CMAC": [ + "CMAC_CTX_new", + "CMAC_Init", + "CMAC_Update", + "CMAC_Final", + "CMAC_CTX_copy", + "CMAC_CTX_free", + ], + "Cryptography_HAS_CMS": [ + "BIO_new_CMS", + "i2d_CMS_bio_stream", + "PEM_write_bio_CMS_stream", + "CMS_final", + "CMS_sign", + "CMS_verify", + "CMS_encrypt", + "CMS_decrypt", + "CMS_add1_signer", + "CMS_TEXT", + "CMS_NOCERTS", + "CMS_NO_CONTENT_VERIFY", + "CMS_NO_ATTR_VERIFY", + "CMS_NOSIGS", + "CMS_NOINTERN", + "CMS_NO_SIGNER_CERT_VERIFY", + "CMS_NOVERIFY", + "CMS_DETACHED", + "CMS_BINARY", + "CMS_NOATTR", + "CMS_NOSMIMECAP", + "CMS_NOOLDMIMETYPE", + "CMS_CRLFEOL", + "CMS_STREAM", + "CMS_NOCRL", + "CMS_PARTIAL", + "CMS_REUSE_DIGEST", + "CMS_USE_KEYID", + "CMS_DEBUG_DECRYPT", + ], + "Cryptography_HAS_EC": [ + "OPENSSL_EC_NAMED_CURVE", + "EC_GROUP_new", + "EC_GROUP_free", + "EC_GROUP_clear_free", + "EC_GROUP_new_curve_GFp", + "EC_GROUP_new_by_curve_name", + "EC_GROUP_set_curve_GFp", + "EC_GROUP_get_curve_GFp", + "EC_GROUP_method_of", + "EC_GROUP_get0_generator", + "EC_GROUP_get_curve_name", + "EC_GROUP_get_degree", + "EC_GROUP_set_asn1_flag", + "EC_GROUP_set_point_conversion_form", + "EC_KEY_new", + "EC_KEY_free", + "EC_get_builtin_curves", + "EC_KEY_new_by_curve_name", + "EC_KEY_copy", + "EC_KEY_dup", + "EC_KEY_up_ref", + "EC_KEY_set_group", + "EC_KEY_get0_private_key", + "EC_KEY_set_private_key", + "EC_KEY_set_public_key", + "EC_KEY_get_enc_flags", + "EC_KEY_set_enc_flags", + "EC_KEY_set_conv_form", + "EC_KEY_set_asn1_flag", + "EC_KEY_precompute_mult", + "EC_KEY_generate_key", + "EC_KEY_check_key", + "EC_POINT_new", + "EC_POINT_free", + "EC_POINT_clear_free", + "EC_POINT_copy", + "EC_POINT_dup", + "EC_POINT_method_of", + "EC_POINT_set_to_infinity", + "EC_POINT_set_Jprojective_coordinates_GFp", + "EC_POINT_get_Jprojective_coordinates_GFp", + "EC_POINT_set_affine_coordinates_GFp", + "EC_POINT_get_affine_coordinates_GFp", + "EC_POINT_set_compressed_coordinates_GFp", + "EC_POINT_point2oct", + "EC_POINT_oct2point", + "EC_POINT_point2bn", + "EC_POINT_bn2point", + "EC_POINT_point2hex", + "EC_POINT_hex2point", + "EC_POINT_add", + "EC_POINT_dbl", + "EC_POINT_invert", + "EC_POINT_is_at_infinity", + "EC_POINT_is_on_curve", + "EC_POINT_cmp", + "EC_POINT_make_affine", + "EC_POINTs_make_affine", + "EC_POINTs_mul", + "EC_POINT_mul", + "EC_GROUP_precompute_mult", + "EC_GROUP_have_precompute_mult", + "EC_GFp_simple_method", + "EC_GFp_mont_method", + "EC_GFp_nist_method", + "EC_METHOD_get_field_type", + "EVP_PKEY_assign_EC_KEY", + "EVP_PKEY_get1_EC_KEY", + "EVP_PKEY_set1_EC_KEY", + "PEM_write_bio_ECPrivateKey", + "i2d_EC_PUBKEY", + "d2i_EC_PUBKEY", + "d2i_EC_PUBKEY_bio", + "i2d_EC_PUBKEY_bio", + "d2i_ECPrivateKey", + "d2i_ECPrivateKey_bio", + "i2d_ECPrivateKey", + "i2d_ECPrivateKey_bio", + "i2o_ECPublicKey", + "o2i_ECPublicKey", + "SSL_CTX_set_tmp_ecdh", + "POINT_CONVERSION_COMPRESSED", + "POINT_CONVERSION_UNCOMPRESSED", + "POINT_CONVERSION_HYBRID", + ], + + "Cryptography_HAS_EC_1_0_1": [ + "EC_KEY_get_flags", + "EC_KEY_set_flags", + "EC_KEY_clear_flags", + "EC_KEY_set_public_key_affine_coordinates", + ], + + "Cryptography_HAS_EC2M": [ + "EC_GF2m_simple_method", + "EC_POINT_set_affine_coordinates_GF2m", + "EC_POINT_get_affine_coordinates_GF2m", + "EC_POINT_set_compressed_coordinates_GF2m", + "EC_GROUP_set_curve_GF2m", + "EC_GROUP_get_curve_GF2m", + "EC_GROUP_new_curve_GF2m", + ], + + "Cryptography_HAS_EC_1_0_2": [ + "EC_curve_nid2nist", + ], + "Cryptography_HAS_ECDH": [ + "ECDH_compute_key", + ], + "Cryptography_HAS_SET_ECDH_AUTO": [ + "SSL_CTX_set_ecdh_auto", + ], + "Cryptography_HAS_ECDSA": [ + "ECDSA_SIG_new", + "ECDSA_SIG_free", + "i2d_ECDSA_SIG", + "d2i_ECDSA_SIG", + "ECDSA_do_sign", + "ECDSA_do_sign_ex", + "ECDSA_do_verify", + "ECDSA_sign_setup", + "ECDSA_sign", + "ECDSA_sign_ex", + "ECDSA_verify", + "ECDSA_size", + ], + "Cryptography_HAS_ENGINE_CRYPTODEV": [ + "ENGINE_load_cryptodev" + ], + "Cryptography_HAS_EC_CODES": [ + "EC_R_UNKNOWN_GROUP", + "EC_F_EC_GROUP_NEW_BY_CURVE_NAME" + ], + "Cryptography_HAS_RSA_R_PKCS_DECODING_ERROR": [ + "RSA_R_PKCS_DECODING_ERROR" + ], + "Cryptography_HAS_GCM": [ + "EVP_CTRL_GCM_GET_TAG", + "EVP_CTRL_GCM_SET_TAG", + "EVP_CTRL_GCM_SET_IVLEN", + ], + "Cryptography_HAS_EGD": [ + "RAND_egd", + "RAND_egd_bytes", + "RAND_query_egd_bytes", + ], + "Cryptography_HAS_MGF1_MD": [ + "EVP_PKEY_CTX_set_rsa_mgf1_md", + ], + "Cryptography_HAS_RSA_OAEP_MD": [ + "EVP_PKEY_CTX_set_rsa_oaep_md", + ], + "Cryptography_HAS_TLSv1_1": [ + "SSL_OP_NO_TLSv1_1", + "TLSv1_1_method", + "TLSv1_1_server_method", + "TLSv1_1_client_method", + ], + + "Cryptography_HAS_TLSv1_2": [ + "SSL_OP_NO_TLSv1_2", + "TLSv1_2_method", + "TLSv1_2_server_method", + "TLSv1_2_client_method", + ], + + "Cryptography_HAS_SSL3_METHOD": [ + "SSLv3_method", + "SSLv3_client_method", + "SSLv3_server_method", + ], + + "Cryptography_HAS_RELEASE_BUFFERS": [ + "SSL_MODE_RELEASE_BUFFERS", + ], + + "Cryptography_HAS_OP_NO_COMPRESSION": [ + "SSL_OP_NO_COMPRESSION", + ], + + "Cryptography_HAS_SSL_OP_MSIE_SSLV2_RSA_PADDING": [ + "SSL_OP_MSIE_SSLV2_RSA_PADDING", + ], + + "Cryptography_HAS_SSL_OP_NO_TICKET": [ + "SSL_OP_NO_TICKET", + ], + + "Cryptography_HAS_NETBSD_D1_METH": [ + "DTLSv1_method", + ], + + "Cryptography_HAS_NEXTPROTONEG": [ + "SSL_CTX_set_next_protos_advertised_cb", + "SSL_CTX_set_next_proto_select_cb", + "SSL_select_next_proto", + "SSL_get0_next_proto_negotiated", + ], + + "Cryptography_HAS_ALPN": [ + "SSL_CTX_set_alpn_protos", + "SSL_set_alpn_protos", + "SSL_CTX_set_alpn_select_cb", + "SSL_get0_alpn_selected", + ], + + "Cryptography_HAS_COMPRESSION": [ + "SSL_get_current_compression", + "SSL_get_current_expansion", + "SSL_COMP_get_name", + ], + + "Cryptography_HAS_GET_SERVER_TMP_KEY": [ + "SSL_get_server_tmp_key", + ], + + "Cryptography_HAS_102_VERIFICATION_ERROR_CODES": [ + 'X509_V_ERR_SUITE_B_INVALID_VERSION', + 'X509_V_ERR_SUITE_B_INVALID_ALGORITHM', + 'X509_V_ERR_SUITE_B_INVALID_CURVE', + 'X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM', + 'X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED', + 'X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256', + 'X509_V_ERR_HOSTNAME_MISMATCH', + 'X509_V_ERR_EMAIL_MISMATCH', + 'X509_V_ERR_IP_ADDRESS_MISMATCH' + ], + "Cryptography_HAS_102_VERIFICATION_PARAMS": [ + "X509_V_FLAG_SUITEB_128_LOS_ONLY", + "X509_V_FLAG_SUITEB_192_LOS", + "X509_V_FLAG_SUITEB_128_LOS", + "X509_VERIFY_PARAM_set1_host", + "X509_VERIFY_PARAM_set1_email", + "X509_VERIFY_PARAM_set1_ip", + "X509_VERIFY_PARAM_set1_ip_asc", + "X509_VERIFY_PARAM_set_hostflags", + ], + "Cryptography_HAS_X509_V_FLAG_TRUSTED_FIRST": [ + "X509_V_FLAG_TRUSTED_FIRST", + ], + "Cryptography_HAS_X509_V_FLAG_PARTIAL_CHAIN": [ + "X509_V_FLAG_PARTIAL_CHAIN", + ], + "Cryptography_HAS_SET_CERT_CB": [ + "SSL_CTX_set_cert_cb", + "SSL_set_cert_cb", + ], + "Cryptography_HAS_AES_CTR128_ENCRYPT": [ + "AES_ctr128_encrypt", + ], + "Cryptography_HAS_SSL_ST": [ + "SSL_ST_BEFORE", + "SSL_ST_OK", + "SSL_ST_INIT", + "SSL_ST_RENEGOTIATE", + ], + "Cryptography_HAS_TLS_ST": [ + "TLS_ST_BEFORE", + "TLS_ST_OK", + ], + "Cryptography_HAS_LOCKING_CALLBACKS": [ + "CRYPTO_LOCK", + "CRYPTO_UNLOCK", + "CRYPTO_READ", + "CRYPTO_LOCK_SSL", + "CRYPTO_lock", + ], + "Cryptography_HAS_SCRYPT": [ + "EVP_PBE_scrypt", + ], + "Cryptography_HAS_NPN_NEGOTIATED": [ + "OPENSSL_NPN_NEGOTIATED", + ], +} diff --git a/src/cryptography/hazmat/bindings/openssl/binding.py b/src/cryptography/hazmat/bindings/openssl/binding.py new file mode 100644 index 0000000..39750ab --- /dev/null +++ b/src/cryptography/hazmat/bindings/openssl/binding.py @@ -0,0 +1,175 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import collections +import os +import threading +import types +import warnings + +from cryptography.exceptions import InternalError +from cryptography.hazmat.bindings._openssl import ffi, lib +from cryptography.hazmat.bindings.openssl._conditional import CONDITIONAL_NAMES + +_OpenSSLError = collections.namedtuple("_OpenSSLError", + ["code", "lib", "func", "reason"]) +_OpenSSLErrorWithText = collections.namedtuple( + "_OpenSSLErrorWithText", ["code", "lib", "func", "reason", "reason_text"] +) + + +def _consume_errors(lib): + errors = [] + while True: + code = lib.ERR_get_error() + if code == 0: + break + + err_lib = lib.ERR_GET_LIB(code) + err_func = lib.ERR_GET_FUNC(code) + err_reason = lib.ERR_GET_REASON(code) + + errors.append(_OpenSSLError(code, err_lib, err_func, err_reason)) + + return errors + + +def _openssl_assert(lib, ok): + if not ok: + errors = _consume_errors(lib) + errors_with_text = [] + for err in errors: + err_text_reason = ffi.string( + lib.ERR_error_string(err.code, ffi.NULL) + ) + errors_with_text.append( + _OpenSSLErrorWithText( + err.code, err.lib, err.func, err.reason, err_text_reason + ) + ) + + raise InternalError( + "Unknown OpenSSL error. This error is commonly encountered when " + "another library is not cleaning up the OpenSSL error stack. If " + "you are using cryptography with another library that uses " + "OpenSSL try disabling it before reporting a bug. Otherwise " + "please file an issue at https://github.com/pyca/cryptography/" + "issues with information on how to reproduce " + "this. ({0!r})".format(errors_with_text), + errors_with_text + ) + + +def ffi_callback(signature, name, **kwargs): + """Callback dispatcher + + The ffi_callback() dispatcher keeps callbacks compatible between dynamic + and static callbacks. + """ + def wrapper(func): + if lib.Cryptography_STATIC_CALLBACKS: + # def_extern() returns a decorator that sets the internal + # function pointer and returns the original function unmodified. + ffi.def_extern(name=name, **kwargs)(func) + callback = getattr(lib, name) + else: + # callback() wraps the function in a cdata function. + callback = ffi.callback(signature, **kwargs)(func) + return callback + return wrapper + + +def build_conditional_library(lib, conditional_names): + conditional_lib = types.ModuleType("lib") + excluded_names = set() + for condition, names in conditional_names.items(): + if not getattr(lib, condition): + excluded_names |= set(names) + + for attr in dir(lib): + if attr not in excluded_names: + setattr(conditional_lib, attr, getattr(lib, attr)) + + return conditional_lib + + +class Binding(object): + """ + OpenSSL API wrapper. + """ + lib = None + ffi = ffi + _lib_loaded = False + _init_lock = threading.Lock() + _lock_init_lock = threading.Lock() + + def __init__(self): + self._ensure_ffi_initialized() + + @classmethod + def _register_osrandom_engine(cls): + _openssl_assert(cls.lib, cls.lib.ERR_peek_error() == 0) + cls._osrandom_engine_id = cls.lib.Cryptography_osrandom_engine_id + cls._osrandom_engine_name = cls.lib.Cryptography_osrandom_engine_name + result = cls.lib.Cryptography_add_osrandom_engine() + _openssl_assert(cls.lib, result in (1, 2)) + + @classmethod + def _ensure_ffi_initialized(cls): + with cls._init_lock: + if not cls._lib_loaded: + cls.lib = build_conditional_library(lib, CONDITIONAL_NAMES) + cls._lib_loaded = True + # initialize the SSL library + cls.lib.SSL_library_init() + # adds all ciphers/digests for EVP + cls.lib.OpenSSL_add_all_algorithms() + # loads error strings for libcrypto and libssl functions + cls.lib.SSL_load_error_strings() + cls._register_osrandom_engine() + + @classmethod + def init_static_locks(cls): + with cls._lock_init_lock: + cls._ensure_ffi_initialized() + # Use Python's implementation if available, importing _ssl triggers + # the setup for this. + __import__("_ssl") + + if cls.lib.CRYPTO_get_locking_callback() != cls.ffi.NULL: + return + + # If nothing else has setup a locking callback already, we set up + # our own + res = lib._setup_ssl_threads() + _openssl_assert(cls.lib, res == 1) + + +def _verify_openssl_version(version): + if version < 0x10001000: + if os.environ.get("CRYPTOGRAPHY_ALLOW_OPENSSL_100"): + warnings.warn( + "OpenSSL version 1.0.0 is no longer supported by the OpenSSL " + "project, please upgrade. The next version of cryptography " + "will completely remove support for it.", + DeprecationWarning + ) + else: + raise RuntimeError( + "You are linking against OpenSSL 1.0.0, which is no longer " + "support by the OpenSSL project. You need to upgrade to a " + "newer version of OpenSSL." + ) + + +# OpenSSL is not thread safe until the locks are initialized. We call this +# method in module scope so that it executes with the import lock. On +# Pythons < 3.4 this import lock is a global lock, which can prevent a race +# condition registering the OpenSSL locks. On Python 3.4+ the import lock +# is per module so this approach will not work. +Binding.init_static_locks() + +_verify_openssl_version(Binding.lib.SSLeay()) diff --git a/src/cryptography/hazmat/primitives/__init__.py b/src/cryptography/hazmat/primitives/__init__.py new file mode 100644 index 0000000..4b54088 --- /dev/null +++ b/src/cryptography/hazmat/primitives/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function diff --git a/src/cryptography/hazmat/primitives/asymmetric/__init__.py b/src/cryptography/hazmat/primitives/asymmetric/__init__.py new file mode 100644 index 0000000..494a7a1 --- /dev/null +++ b/src/cryptography/hazmat/primitives/asymmetric/__init__.py @@ -0,0 +1,40 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc + +import six + + +@six.add_metaclass(abc.ABCMeta) +class AsymmetricSignatureContext(object): + @abc.abstractmethod + def update(self, data): + """ + Processes the provided bytes and returns nothing. + """ + + @abc.abstractmethod + def finalize(self): + """ + Returns the signature as bytes. + """ + + +@six.add_metaclass(abc.ABCMeta) +class AsymmetricVerificationContext(object): + @abc.abstractmethod + def update(self, data): + """ + Processes the provided bytes and returns nothing. + """ + + @abc.abstractmethod + def verify(self): + """ + Raises an exception if the bytes provided to update do not match the + signature or the signature does not match the public key. + """ diff --git a/src/cryptography/hazmat/primitives/asymmetric/dh.py b/src/cryptography/hazmat/primitives/asymmetric/dh.py new file mode 100644 index 0000000..ec044dd --- /dev/null +++ b/src/cryptography/hazmat/primitives/asymmetric/dh.py @@ -0,0 +1,189 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc + +import six + +from cryptography import utils + + +def generate_parameters(generator, key_size, backend): + return backend.generate_dh_parameters(generator, key_size) + + +class DHPrivateNumbers(object): + def __init__(self, x, public_numbers): + if not isinstance(x, six.integer_types): + raise TypeError("x must be an integer.") + + if not isinstance(public_numbers, DHPublicNumbers): + raise TypeError("public_numbers must be an instance of " + "DHPublicNumbers.") + + self._x = x + self._public_numbers = public_numbers + + def __eq__(self, other): + if not isinstance(other, DHPrivateNumbers): + return NotImplemented + + return ( + self._x == other._x and + self._public_numbers == other._public_numbers + ) + + def __ne__(self, other): + return not self == other + + def private_key(self, backend): + return backend.load_dh_private_numbers(self) + + public_numbers = utils.read_only_property("_public_numbers") + x = utils.read_only_property("_x") + + +class DHPublicNumbers(object): + def __init__(self, y, parameter_numbers): + if not isinstance(y, six.integer_types): + raise TypeError("y must be an integer.") + + if not isinstance(parameter_numbers, DHParameterNumbers): + raise TypeError( + "parameters must be an instance of DHParameterNumbers.") + + self._y = y + self._parameter_numbers = parameter_numbers + + def __eq__(self, other): + if not isinstance(other, DHPublicNumbers): + return NotImplemented + + return ( + self._y == other._y and + self._parameter_numbers == other._parameter_numbers + ) + + def __ne__(self, other): + return not self == other + + def public_key(self, backend): + return backend.load_dh_public_numbers(self) + + y = utils.read_only_property("_y") + parameter_numbers = utils.read_only_property("_parameter_numbers") + + +class DHParameterNumbers(object): + def __init__(self, p, g): + if ( + not isinstance(p, six.integer_types) or + not isinstance(g, six.integer_types) + ): + raise TypeError("p and g must be integers") + + if g not in (2, 5): + raise ValueError("DH generator must be 2 or 5") + + self._p = p + self._g = g + + def __eq__(self, other): + if not isinstance(other, DHParameterNumbers): + return NotImplemented + + return ( + self._p == other._p and + self._g == other._g + ) + + def __ne__(self, other): + return not self == other + + def parameters(self, backend): + return backend.load_dh_parameter_numbers(self) + + p = utils.read_only_property("_p") + g = utils.read_only_property("_g") + + +@six.add_metaclass(abc.ABCMeta) +class DHParameters(object): + @abc.abstractmethod + def generate_private_key(self): + """ + Generates and returns a DHPrivateKey. + """ + + +@six.add_metaclass(abc.ABCMeta) +class DHParametersWithSerialization(DHParameters): + @abc.abstractmethod + def parameter_numbers(self): + """ + Returns a DHParameterNumbers. + """ + + +@six.add_metaclass(abc.ABCMeta) +class DHPrivateKey(object): + @abc.abstractproperty + def key_size(self): + """ + The bit length of the prime modulus. + """ + + @abc.abstractmethod + def public_key(self): + """ + The DHPublicKey associated with this private key. + """ + + @abc.abstractmethod + def parameters(self): + """ + The DHParameters object associated with this private key. + """ + + +@six.add_metaclass(abc.ABCMeta) +class DHPrivateKeyWithSerialization(DHPrivateKey): + @abc.abstractmethod + def private_numbers(self): + """ + Returns a DHPrivateNumbers. + """ + + @abc.abstractmethod + def exchange(self, peer_public_key): + """ + Given peer's DHPublicKey, carry out the key exchange and + return shared key as bytes. + """ + + +@six.add_metaclass(abc.ABCMeta) +class DHPublicKey(object): + @abc.abstractproperty + def key_size(self): + """ + The bit length of the prime modulus. + """ + + @abc.abstractmethod + def parameters(self): + """ + The DHParameters object associated with this public key. + """ + + +@six.add_metaclass(abc.ABCMeta) +class DHPublicKeyWithSerialization(DHPublicKey): + @abc.abstractmethod + def public_numbers(self): + """ + Returns a DHPublicNumbers. + """ diff --git a/src/cryptography/hazmat/primitives/asymmetric/dsa.py b/src/cryptography/hazmat/primitives/asymmetric/dsa.py new file mode 100644 index 0000000..03e6a53 --- /dev/null +++ b/src/cryptography/hazmat/primitives/asymmetric/dsa.py @@ -0,0 +1,254 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc + +import six + +from cryptography import utils + + +@six.add_metaclass(abc.ABCMeta) +class DSAParameters(object): + @abc.abstractmethod + def generate_private_key(self): + """ + Generates and returns a DSAPrivateKey. + """ + + +@six.add_metaclass(abc.ABCMeta) +class DSAParametersWithNumbers(DSAParameters): + @abc.abstractmethod + def parameter_numbers(self): + """ + Returns a DSAParameterNumbers. + """ + + +@six.add_metaclass(abc.ABCMeta) +class DSAPrivateKey(object): + @abc.abstractproperty + def key_size(self): + """ + The bit length of the prime modulus. + """ + + @abc.abstractmethod + def public_key(self): + """ + The DSAPublicKey associated with this private key. + """ + + @abc.abstractmethod + def parameters(self): + """ + The DSAParameters object associated with this private key. + """ + + @abc.abstractmethod + def signer(self, signature_algorithm): + """ + Returns an AsymmetricSignatureContext used for signing data. + """ + + @abc.abstractmethod + def sign(self, data, algorithm): + """ + Signs the data + """ + + +@six.add_metaclass(abc.ABCMeta) +class DSAPrivateKeyWithSerialization(DSAPrivateKey): + @abc.abstractmethod + def private_numbers(self): + """ + Returns a DSAPrivateNumbers. + """ + + @abc.abstractmethod + def private_bytes(self, encoding, format, encryption_algorithm): + """ + Returns the key serialized as bytes. + """ + + +@six.add_metaclass(abc.ABCMeta) +class DSAPublicKey(object): + @abc.abstractproperty + def key_size(self): + """ + The bit length of the prime modulus. + """ + + @abc.abstractmethod + def parameters(self): + """ + The DSAParameters object associated with this public key. + """ + + @abc.abstractmethod + def verifier(self, signature, signature_algorithm): + """ + Returns an AsymmetricVerificationContext used for signing data. + """ + + @abc.abstractmethod + def public_numbers(self): + """ + Returns a DSAPublicNumbers. + """ + + @abc.abstractmethod + def public_bytes(self, encoding, format): + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def verify(self, signature, data, algorithm): + """ + Verifies the signature of the data. + """ + + +DSAPublicKeyWithSerialization = DSAPublicKey + + +def generate_parameters(key_size, backend): + return backend.generate_dsa_parameters(key_size) + + +def generate_private_key(key_size, backend): + return backend.generate_dsa_private_key_and_parameters(key_size) + + +def _check_dsa_parameters(parameters): + if utils.bit_length(parameters.p) not in [1024, 2048, 3072]: + raise ValueError("p must be exactly 1024, 2048, or 3072 bits long") + if utils.bit_length(parameters.q) not in [160, 256]: + raise ValueError("q must be exactly 160 or 256 bits long") + + if not (1 < parameters.g < parameters.p): + raise ValueError("g, p don't satisfy 1 < g < p.") + + +def _check_dsa_private_numbers(numbers): + parameters = numbers.public_numbers.parameter_numbers + _check_dsa_parameters(parameters) + if numbers.x <= 0 or numbers.x >= parameters.q: + raise ValueError("x must be > 0 and < q.") + + if numbers.public_numbers.y != pow(parameters.g, numbers.x, parameters.p): + raise ValueError("y must be equal to (g ** x % p).") + + +class DSAParameterNumbers(object): + def __init__(self, p, q, g): + if ( + not isinstance(p, six.integer_types) or + not isinstance(q, six.integer_types) or + not isinstance(g, six.integer_types) + ): + raise TypeError( + "DSAParameterNumbers p, q, and g arguments must be integers." + ) + + self._p = p + self._q = q + self._g = g + + p = utils.read_only_property("_p") + q = utils.read_only_property("_q") + g = utils.read_only_property("_g") + + def parameters(self, backend): + return backend.load_dsa_parameter_numbers(self) + + def __eq__(self, other): + if not isinstance(other, DSAParameterNumbers): + return NotImplemented + + return self.p == other.p and self.q == other.q and self.g == other.g + + def __ne__(self, other): + return not self == other + + def __repr__(self): + return ( + "".format( + self=self + ) + ) + + +class DSAPublicNumbers(object): + def __init__(self, y, parameter_numbers): + if not isinstance(y, six.integer_types): + raise TypeError("DSAPublicNumbers y argument must be an integer.") + + if not isinstance(parameter_numbers, DSAParameterNumbers): + raise TypeError( + "parameter_numbers must be a DSAParameterNumbers instance." + ) + + self._y = y + self._parameter_numbers = parameter_numbers + + y = utils.read_only_property("_y") + parameter_numbers = utils.read_only_property("_parameter_numbers") + + def public_key(self, backend): + return backend.load_dsa_public_numbers(self) + + def __eq__(self, other): + if not isinstance(other, DSAPublicNumbers): + return NotImplemented + + return ( + self.y == other.y and + self.parameter_numbers == other.parameter_numbers + ) + + def __ne__(self, other): + return not self == other + + def __repr__(self): + return ( + "".format(self=self) + ) + + +class DSAPrivateNumbers(object): + def __init__(self, x, public_numbers): + if not isinstance(x, six.integer_types): + raise TypeError("DSAPrivateNumbers x argument must be an integer.") + + if not isinstance(public_numbers, DSAPublicNumbers): + raise TypeError( + "public_numbers must be a DSAPublicNumbers instance." + ) + self._public_numbers = public_numbers + self._x = x + + x = utils.read_only_property("_x") + public_numbers = utils.read_only_property("_public_numbers") + + def private_key(self, backend): + return backend.load_dsa_private_numbers(self) + + def __eq__(self, other): + if not isinstance(other, DSAPrivateNumbers): + return NotImplemented + + return ( + self.x == other.x and self.public_numbers == other.public_numbers + ) + + def __ne__(self, other): + return not self == other diff --git a/src/cryptography/hazmat/primitives/asymmetric/ec.py b/src/cryptography/hazmat/primitives/asymmetric/ec.py new file mode 100644 index 0000000..a527387 --- /dev/null +++ b/src/cryptography/hazmat/primitives/asymmetric/ec.py @@ -0,0 +1,377 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc + +import six + +from cryptography import utils + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurve(object): + @abc.abstractproperty + def name(self): + """ + The name of the curve. e.g. secp256r1. + """ + + @abc.abstractproperty + def key_size(self): + """ + The bit length of the base point of the curve. + """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurveSignatureAlgorithm(object): + @abc.abstractproperty + def algorithm(self): + """ + The digest algorithm used with this signature. + """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurvePrivateKey(object): + @abc.abstractmethod + def signer(self, signature_algorithm): + """ + Returns an AsymmetricSignatureContext used for signing data. + """ + + @abc.abstractmethod + def exchange(self, algorithm, peer_public_key): + """ + Performs a key exchange operation using the provided algorithm with the + provided peer's public key. + """ + + @abc.abstractmethod + def public_key(self): + """ + The EllipticCurvePublicKey for this private key. + """ + + @abc.abstractproperty + def curve(self): + """ + The EllipticCurve that this key is on. + """ + + @abc.abstractproperty + def sign(self, data, signature_algorithm): + """ + Signs the data + """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurvePrivateKeyWithSerialization(EllipticCurvePrivateKey): + @abc.abstractmethod + def private_numbers(self): + """ + Returns an EllipticCurvePrivateNumbers. + """ + + @abc.abstractmethod + def private_bytes(self, encoding, format, encryption_algorithm): + """ + Returns the key serialized as bytes. + """ + + +@six.add_metaclass(abc.ABCMeta) +class EllipticCurvePublicKey(object): + @abc.abstractmethod + def verifier(self, signature, signature_algorithm): + """ + Returns an AsymmetricVerificationContext used for signing data. + """ + + @abc.abstractproperty + def curve(self): + """ + The EllipticCurve that this key is on. + """ + + @abc.abstractmethod + def public_numbers(self): + """ + Returns an EllipticCurvePublicNumbers. + """ + + @abc.abstractmethod + def public_bytes(self, encoding, format): + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def verify(self, signature, data, signature_algorithm): + """ + Verifies the signature of the data. + """ + + +EllipticCurvePublicKeyWithSerialization = EllipticCurvePublicKey + + +@utils.register_interface(EllipticCurve) +class SECT571R1(object): + name = "sect571r1" + key_size = 571 + + +@utils.register_interface(EllipticCurve) +class SECT409R1(object): + name = "sect409r1" + key_size = 409 + + +@utils.register_interface(EllipticCurve) +class SECT283R1(object): + name = "sect283r1" + key_size = 283 + + +@utils.register_interface(EllipticCurve) +class SECT233R1(object): + name = "sect233r1" + key_size = 233 + + +@utils.register_interface(EllipticCurve) +class SECT163R2(object): + name = "sect163r2" + key_size = 163 + + +@utils.register_interface(EllipticCurve) +class SECT571K1(object): + name = "sect571k1" + key_size = 571 + + +@utils.register_interface(EllipticCurve) +class SECT409K1(object): + name = "sect409k1" + key_size = 409 + + +@utils.register_interface(EllipticCurve) +class SECT283K1(object): + name = "sect283k1" + key_size = 283 + + +@utils.register_interface(EllipticCurve) +class SECT233K1(object): + name = "sect233k1" + key_size = 233 + + +@utils.register_interface(EllipticCurve) +class SECT163K1(object): + name = "sect163k1" + key_size = 163 + + +@utils.register_interface(EllipticCurve) +class SECP521R1(object): + name = "secp521r1" + key_size = 521 + + +@utils.register_interface(EllipticCurve) +class SECP384R1(object): + name = "secp384r1" + key_size = 384 + + +@utils.register_interface(EllipticCurve) +class SECP256R1(object): + name = "secp256r1" + key_size = 256 + + +@utils.register_interface(EllipticCurve) +class SECP256K1(object): + name = "secp256k1" + key_size = 256 + + +@utils.register_interface(EllipticCurve) +class SECP224R1(object): + name = "secp224r1" + key_size = 224 + + +@utils.register_interface(EllipticCurve) +class SECP192R1(object): + name = "secp192r1" + key_size = 192 + + +_CURVE_TYPES = { + "prime192v1": SECP192R1, + "prime256v1": SECP256R1, + + "secp192r1": SECP192R1, + "secp224r1": SECP224R1, + "secp256r1": SECP256R1, + "secp384r1": SECP384R1, + "secp521r1": SECP521R1, + "secp256k1": SECP256K1, + + "sect163k1": SECT163K1, + "sect233k1": SECT233K1, + "sect283k1": SECT283K1, + "sect409k1": SECT409K1, + "sect571k1": SECT571K1, + + "sect163r2": SECT163R2, + "sect233r1": SECT233R1, + "sect283r1": SECT283R1, + "sect409r1": SECT409R1, + "sect571r1": SECT571R1, +} + + +@utils.register_interface(EllipticCurveSignatureAlgorithm) +class ECDSA(object): + def __init__(self, algorithm): + self._algorithm = algorithm + + algorithm = utils.read_only_property("_algorithm") + + +def generate_private_key(curve, backend): + return backend.generate_elliptic_curve_private_key(curve) + + +def derive_private_key(private_value, curve, backend): + if not isinstance(private_value, six.integer_types): + raise TypeError("private_value must be an integer type.") + + if private_value <= 0: + raise ValueError("private_value must be a positive integer.") + + if not isinstance(curve, EllipticCurve): + raise TypeError("curve must provide the EllipticCurve interface.") + + return backend.derive_elliptic_curve_private_key(private_value, curve) + + +class EllipticCurvePublicNumbers(object): + def __init__(self, x, y, curve): + if ( + not isinstance(x, six.integer_types) or + not isinstance(y, six.integer_types) + ): + raise TypeError("x and y must be integers.") + + if not isinstance(curve, EllipticCurve): + raise TypeError("curve must provide the EllipticCurve interface.") + + self._y = y + self._x = x + self._curve = curve + + def public_key(self, backend): + return backend.load_elliptic_curve_public_numbers(self) + + def encode_point(self): + # key_size is in bits. Convert to bytes and round up + byte_length = (self.curve.key_size + 7) // 8 + return ( + b'\x04' + utils.int_to_bytes(self.x, byte_length) + + utils.int_to_bytes(self.y, byte_length) + ) + + @classmethod + def from_encoded_point(cls, curve, data): + if not isinstance(curve, EllipticCurve): + raise TypeError("curve must be an EllipticCurve instance") + + if data.startswith(b'\x04'): + # key_size is in bits. Convert to bytes and round up + byte_length = (curve.key_size + 7) // 8 + if len(data) == 2 * byte_length + 1: + x = utils.int_from_bytes(data[1:byte_length + 1], 'big') + y = utils.int_from_bytes(data[byte_length + 1:], 'big') + return cls(x, y, curve) + else: + raise ValueError('Invalid elliptic curve point data length') + else: + raise ValueError('Unsupported elliptic curve point type') + + curve = utils.read_only_property("_curve") + x = utils.read_only_property("_x") + y = utils.read_only_property("_y") + + def __eq__(self, other): + if not isinstance(other, EllipticCurvePublicNumbers): + return NotImplemented + + return ( + self.x == other.x and + self.y == other.y and + self.curve.name == other.curve.name and + self.curve.key_size == other.curve.key_size + ) + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.x, self.y, self.curve.name, self.curve.key_size)) + + def __repr__(self): + return ( + "".format(self) + ) + + +class EllipticCurvePrivateNumbers(object): + def __init__(self, private_value, public_numbers): + if not isinstance(private_value, six.integer_types): + raise TypeError("private_value must be an integer.") + + if not isinstance(public_numbers, EllipticCurvePublicNumbers): + raise TypeError( + "public_numbers must be an EllipticCurvePublicNumbers " + "instance." + ) + + self._private_value = private_value + self._public_numbers = public_numbers + + def private_key(self, backend): + return backend.load_elliptic_curve_private_numbers(self) + + private_value = utils.read_only_property("_private_value") + public_numbers = utils.read_only_property("_public_numbers") + + def __eq__(self, other): + if not isinstance(other, EllipticCurvePrivateNumbers): + return NotImplemented + + return ( + self.private_value == other.private_value and + self.public_numbers == other.public_numbers + ) + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.private_value, self.public_numbers)) + + +class ECDH(object): + pass diff --git a/src/cryptography/hazmat/primitives/asymmetric/padding.py b/src/cryptography/hazmat/primitives/asymmetric/padding.py new file mode 100644 index 0000000..a37c3f9 --- /dev/null +++ b/src/cryptography/hazmat/primitives/asymmetric/padding.py @@ -0,0 +1,79 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc +import math + +import six + +from cryptography import utils +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import rsa + + +@six.add_metaclass(abc.ABCMeta) +class AsymmetricPadding(object): + @abc.abstractproperty + def name(self): + """ + A string naming this padding (e.g. "PSS", "PKCS1"). + """ + + +@utils.register_interface(AsymmetricPadding) +class PKCS1v15(object): + name = "EMSA-PKCS1-v1_5" + + +@utils.register_interface(AsymmetricPadding) +class PSS(object): + MAX_LENGTH = object() + name = "EMSA-PSS" + + def __init__(self, mgf, salt_length): + self._mgf = mgf + + if (not isinstance(salt_length, six.integer_types) and + salt_length is not self.MAX_LENGTH): + raise TypeError("salt_length must be an integer.") + + if salt_length is not self.MAX_LENGTH and salt_length < 0: + raise ValueError("salt_length must be zero or greater.") + + self._salt_length = salt_length + + +@utils.register_interface(AsymmetricPadding) +class OAEP(object): + name = "EME-OAEP" + + def __init__(self, mgf, algorithm, label): + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError("Expected instance of hashes.HashAlgorithm.") + + self._mgf = mgf + self._algorithm = algorithm + self._label = label + + +class MGF1(object): + MAX_LENGTH = object() + + def __init__(self, algorithm): + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError("Expected instance of hashes.HashAlgorithm.") + + self._algorithm = algorithm + + +def calculate_max_pss_salt_length(key, hash_algorithm): + if not isinstance(key, (rsa.RSAPrivateKey, rsa.RSAPublicKey)): + raise TypeError("key must be an RSA public or private key") + # bit length - 1 per RFC 3447 + emlen = int(math.ceil((key.key_size - 1) / 8.0)) + salt_length = emlen - hash_algorithm.digest_size - 2 + assert salt_length >= 0 + return salt_length diff --git a/src/cryptography/hazmat/primitives/asymmetric/rsa.py b/src/cryptography/hazmat/primitives/asymmetric/rsa.py new file mode 100644 index 0000000..3157aed --- /dev/null +++ b/src/cryptography/hazmat/primitives/asymmetric/rsa.py @@ -0,0 +1,364 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc +from fractions import gcd + +import six + +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.backends.interfaces import RSABackend + + +@six.add_metaclass(abc.ABCMeta) +class RSAPrivateKey(object): + @abc.abstractmethod + def signer(self, padding, algorithm): + """ + Returns an AsymmetricSignatureContext used for signing data. + """ + + @abc.abstractmethod + def decrypt(self, ciphertext, padding): + """ + Decrypts the provided ciphertext. + """ + + @abc.abstractproperty + def key_size(self): + """ + The bit length of the public modulus. + """ + + @abc.abstractmethod + def public_key(self): + """ + The RSAPublicKey associated with this private key. + """ + + @abc.abstractmethod + def sign(self, data, padding, algorithm): + """ + Signs the data. + """ + + +@six.add_metaclass(abc.ABCMeta) +class RSAPrivateKeyWithSerialization(RSAPrivateKey): + @abc.abstractmethod + def private_numbers(self): + """ + Returns an RSAPrivateNumbers. + """ + + @abc.abstractmethod + def private_bytes(self, encoding, format, encryption_algorithm): + """ + Returns the key serialized as bytes. + """ + + +@six.add_metaclass(abc.ABCMeta) +class RSAPublicKey(object): + @abc.abstractmethod + def verifier(self, signature, padding, algorithm): + """ + Returns an AsymmetricVerificationContext used for verifying signatures. + """ + + @abc.abstractmethod + def encrypt(self, plaintext, padding): + """ + Encrypts the given plaintext. + """ + + @abc.abstractproperty + def key_size(self): + """ + The bit length of the public modulus. + """ + + @abc.abstractmethod + def public_numbers(self): + """ + Returns an RSAPublicNumbers + """ + + @abc.abstractmethod + def public_bytes(self, encoding, format): + """ + Returns the key serialized as bytes. + """ + + @abc.abstractmethod + def verify(self, signature, data, padding, algorithm): + """ + Verifies the signature of the data. + """ + + +RSAPublicKeyWithSerialization = RSAPublicKey + + +def generate_private_key(public_exponent, key_size, backend): + if not isinstance(backend, RSABackend): + raise UnsupportedAlgorithm( + "Backend object does not implement RSABackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + _verify_rsa_parameters(public_exponent, key_size) + return backend.generate_rsa_private_key(public_exponent, key_size) + + +def _verify_rsa_parameters(public_exponent, key_size): + if public_exponent < 3: + raise ValueError("public_exponent must be >= 3.") + + if public_exponent & 1 == 0: + raise ValueError("public_exponent must be odd.") + + if key_size < 512: + raise ValueError("key_size must be at least 512-bits.") + + +def _check_private_key_components(p, q, private_exponent, dmp1, dmq1, iqmp, + public_exponent, modulus): + if modulus < 3: + raise ValueError("modulus must be >= 3.") + + if p >= modulus: + raise ValueError("p must be < modulus.") + + if q >= modulus: + raise ValueError("q must be < modulus.") + + if dmp1 >= modulus: + raise ValueError("dmp1 must be < modulus.") + + if dmq1 >= modulus: + raise ValueError("dmq1 must be < modulus.") + + if iqmp >= modulus: + raise ValueError("iqmp must be < modulus.") + + if private_exponent >= modulus: + raise ValueError("private_exponent must be < modulus.") + + if public_exponent < 3 or public_exponent >= modulus: + raise ValueError("public_exponent must be >= 3 and < modulus.") + + if public_exponent & 1 == 0: + raise ValueError("public_exponent must be odd.") + + if dmp1 & 1 == 0: + raise ValueError("dmp1 must be odd.") + + if dmq1 & 1 == 0: + raise ValueError("dmq1 must be odd.") + + if p * q != modulus: + raise ValueError("p*q must equal modulus.") + + +def _check_public_key_components(e, n): + if n < 3: + raise ValueError("n must be >= 3.") + + if e < 3 or e >= n: + raise ValueError("e must be >= 3 and < n.") + + if e & 1 == 0: + raise ValueError("e must be odd.") + + +def _modinv(e, m): + """ + Modular Multiplicative Inverse. Returns x such that: (x*e) mod m == 1 + """ + x1, y1, x2, y2 = 1, 0, 0, 1 + a, b = e, m + while b > 0: + q, r = divmod(a, b) + xn, yn = x1 - q * x2, y1 - q * y2 + a, b, x1, y1, x2, y2 = b, r, x2, y2, xn, yn + return x1 % m + + +def rsa_crt_iqmp(p, q): + """ + Compute the CRT (q ** -1) % p value from RSA primes p and q. + """ + return _modinv(q, p) + + +def rsa_crt_dmp1(private_exponent, p): + """ + Compute the CRT private_exponent % (p - 1) value from the RSA + private_exponent (d) and p. + """ + return private_exponent % (p - 1) + + +def rsa_crt_dmq1(private_exponent, q): + """ + Compute the CRT private_exponent % (q - 1) value from the RSA + private_exponent (d) and q. + """ + return private_exponent % (q - 1) + + +# Controls the number of iterations rsa_recover_prime_factors will perform +# to obtain the prime factors. Each iteration increments by 2 so the actual +# maximum attempts is half this number. +_MAX_RECOVERY_ATTEMPTS = 1000 + + +def rsa_recover_prime_factors(n, e, d): + """ + Compute factors p and q from the private exponent d. We assume that n has + no more than two factors. This function is adapted from code in PyCrypto. + """ + # See 8.2.2(i) in Handbook of Applied Cryptography. + ktot = d * e - 1 + # The quantity d*e-1 is a multiple of phi(n), even, + # and can be represented as t*2^s. + t = ktot + while t % 2 == 0: + t = t // 2 + # Cycle through all multiplicative inverses in Zn. + # The algorithm is non-deterministic, but there is a 50% chance + # any candidate a leads to successful factoring. + # See "Digitalized Signatures and Public Key Functions as Intractable + # as Factorization", M. Rabin, 1979 + spotted = False + a = 2 + while not spotted and a < _MAX_RECOVERY_ATTEMPTS: + k = t + # Cycle through all values a^{t*2^i}=a^k + while k < ktot: + cand = pow(a, k, n) + # Check if a^k is a non-trivial root of unity (mod n) + if cand != 1 and cand != (n - 1) and pow(cand, 2, n) == 1: + # We have found a number such that (cand-1)(cand+1)=0 (mod n). + # Either of the terms divides n. + p = gcd(cand + 1, n) + spotted = True + break + k *= 2 + # This value was not any good... let's try another! + a += 2 + if not spotted: + raise ValueError("Unable to compute factors p and q from exponent d.") + # Found ! + q, r = divmod(n, p) + assert r == 0 + p, q = sorted((p, q), reverse=True) + return (p, q) + + +class RSAPrivateNumbers(object): + def __init__(self, p, q, d, dmp1, dmq1, iqmp, + public_numbers): + if ( + not isinstance(p, six.integer_types) or + not isinstance(q, six.integer_types) or + not isinstance(d, six.integer_types) or + not isinstance(dmp1, six.integer_types) or + not isinstance(dmq1, six.integer_types) or + not isinstance(iqmp, six.integer_types) + ): + raise TypeError( + "RSAPrivateNumbers p, q, d, dmp1, dmq1, iqmp arguments must" + " all be an integers." + ) + + if not isinstance(public_numbers, RSAPublicNumbers): + raise TypeError( + "RSAPrivateNumbers public_numbers must be an RSAPublicNumbers" + " instance." + ) + + self._p = p + self._q = q + self._d = d + self._dmp1 = dmp1 + self._dmq1 = dmq1 + self._iqmp = iqmp + self._public_numbers = public_numbers + + p = utils.read_only_property("_p") + q = utils.read_only_property("_q") + d = utils.read_only_property("_d") + dmp1 = utils.read_only_property("_dmp1") + dmq1 = utils.read_only_property("_dmq1") + iqmp = utils.read_only_property("_iqmp") + public_numbers = utils.read_only_property("_public_numbers") + + def private_key(self, backend): + return backend.load_rsa_private_numbers(self) + + def __eq__(self, other): + if not isinstance(other, RSAPrivateNumbers): + return NotImplemented + + return ( + self.p == other.p and + self.q == other.q and + self.d == other.d and + self.dmp1 == other.dmp1 and + self.dmq1 == other.dmq1 and + self.iqmp == other.iqmp and + self.public_numbers == other.public_numbers + ) + + 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): + if ( + not isinstance(e, six.integer_types) or + not isinstance(n, six.integer_types) + ): + raise TypeError("RSAPublicNumbers arguments must be integers.") + + self._e = e + self._n = n + + e = utils.read_only_property("_e") + n = utils.read_only_property("_n") + + def public_key(self, backend): + return backend.load_rsa_public_numbers(self) + + def __repr__(self): + return "".format(self) + + def __eq__(self, other): + if not isinstance(other, RSAPublicNumbers): + return NotImplemented + + return self.e == other.e and self.n == other.n + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.e, self.n)) diff --git a/src/cryptography/hazmat/primitives/asymmetric/utils.py b/src/cryptography/hazmat/primitives/asymmetric/utils.py new file mode 100644 index 0000000..44bf59d --- /dev/null +++ b/src/cryptography/hazmat/primitives/asymmetric/utils.py @@ -0,0 +1,83 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import warnings + +from pyasn1.codec.der import decoder, encoder +from pyasn1.error import PyAsn1Error +from pyasn1.type import namedtype, univ + +import six + +from cryptography import utils +from cryptography.hazmat.primitives import hashes + + +class _DSSSigValue(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('r', univ.Integer()), + namedtype.NamedType('s', univ.Integer()) + ) + + +def decode_rfc6979_signature(signature): + warnings.warn( + "decode_rfc6979_signature is deprecated and will " + "be removed in a future version, use decode_dss_signature instead.", + utils.DeprecatedIn10, + stacklevel=2 + ) + return decode_dss_signature(signature) + + +def decode_dss_signature(signature): + try: + data, remaining = decoder.decode(signature, asn1Spec=_DSSSigValue()) + except PyAsn1Error: + raise ValueError("Invalid signature data. Unable to decode ASN.1") + + if remaining: + raise ValueError( + "The signature contains bytes after the end of the ASN.1 sequence." + ) + + r = int(data.getComponentByName('r')) + s = int(data.getComponentByName('s')) + return (r, s) + + +def encode_rfc6979_signature(r, s): + warnings.warn( + "encode_rfc6979_signature is deprecated and will " + "be removed in a future version, use encode_dss_signature instead.", + utils.DeprecatedIn10, + stacklevel=2 + ) + return encode_dss_signature(r, s) + + +def encode_dss_signature(r, s): + if ( + not isinstance(r, six.integer_types) or + not isinstance(s, six.integer_types) + ): + raise ValueError("Both r and s must be integers") + + sig = _DSSSigValue() + sig.setComponentByName('r', r) + sig.setComponentByName('s', s) + return encoder.encode(sig) + + +class Prehashed(object): + def __init__(self, algorithm): + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError("Expected instance of HashAlgorithm.") + + self._algorithm = algorithm + self._digest_size = algorithm.digest_size + + digest_size = utils.read_only_property("_digest_size") diff --git a/src/cryptography/hazmat/primitives/ciphers/__init__.py b/src/cryptography/hazmat/primitives/ciphers/__init__.py new file mode 100644 index 0000000..b5dd0ed --- /dev/null +++ b/src/cryptography/hazmat/primitives/ciphers/__init__.py @@ -0,0 +1,20 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography.hazmat.primitives.ciphers.base import ( + AEADCipherContext, AEADEncryptionContext, BlockCipherAlgorithm, Cipher, + CipherAlgorithm, CipherContext +) + + +__all__ = [ + "Cipher", + "CipherAlgorithm", + "BlockCipherAlgorithm", + "CipherContext", + "AEADCipherContext", + "AEADEncryptionContext", +] diff --git a/src/cryptography/hazmat/primitives/ciphers/algorithms.py b/src/cryptography/hazmat/primitives/ciphers/algorithms.py new file mode 100644 index 0000000..c193f79 --- /dev/null +++ b/src/cryptography/hazmat/primitives/ciphers/algorithms.py @@ -0,0 +1,140 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.hazmat.primitives.ciphers import ( + BlockCipherAlgorithm, CipherAlgorithm +) + + +def _verify_key_size(algorithm, key): + # Verify that the key size matches the expected key size + if len(key) * 8 not in algorithm.key_sizes: + raise ValueError("Invalid key size ({0}) for {1}.".format( + len(key) * 8, algorithm.name + )) + return key + + +@utils.register_interface(BlockCipherAlgorithm) +@utils.register_interface(CipherAlgorithm) +class AES(object): + name = "AES" + block_size = 128 + key_sizes = frozenset([128, 192, 256]) + + def __init__(self, key): + self.key = _verify_key_size(self, key) + + @property + def key_size(self): + return len(self.key) * 8 + + +@utils.register_interface(BlockCipherAlgorithm) +@utils.register_interface(CipherAlgorithm) +class Camellia(object): + name = "camellia" + block_size = 128 + key_sizes = frozenset([128, 192, 256]) + + def __init__(self, key): + self.key = _verify_key_size(self, key) + + @property + def key_size(self): + return len(self.key) * 8 + + +@utils.register_interface(BlockCipherAlgorithm) +@utils.register_interface(CipherAlgorithm) +class TripleDES(object): + name = "3DES" + block_size = 64 + key_sizes = frozenset([64, 128, 192]) + + def __init__(self, key): + if len(key) == 8: + key += key + key + elif len(key) == 16: + key += key[:8] + self.key = _verify_key_size(self, key) + + @property + def key_size(self): + return len(self.key) * 8 + + +@utils.register_interface(BlockCipherAlgorithm) +@utils.register_interface(CipherAlgorithm) +class Blowfish(object): + name = "Blowfish" + block_size = 64 + key_sizes = frozenset(range(32, 449, 8)) + + def __init__(self, key): + self.key = _verify_key_size(self, key) + + @property + def key_size(self): + return len(self.key) * 8 + + +@utils.register_interface(BlockCipherAlgorithm) +@utils.register_interface(CipherAlgorithm) +class CAST5(object): + name = "CAST5" + block_size = 64 + key_sizes = frozenset(range(40, 129, 8)) + + def __init__(self, key): + self.key = _verify_key_size(self, key) + + @property + def key_size(self): + return len(self.key) * 8 + + +@utils.register_interface(CipherAlgorithm) +class ARC4(object): + name = "RC4" + key_sizes = frozenset([40, 56, 64, 80, 128, 160, 192, 256]) + + def __init__(self, key): + self.key = _verify_key_size(self, key) + + @property + def key_size(self): + return len(self.key) * 8 + + +@utils.register_interface(CipherAlgorithm) +class IDEA(object): + name = "IDEA" + block_size = 64 + key_sizes = frozenset([128]) + + def __init__(self, key): + self.key = _verify_key_size(self, key) + + @property + def key_size(self): + return len(self.key) * 8 + + +@utils.register_interface(BlockCipherAlgorithm) +@utils.register_interface(CipherAlgorithm) +class SEED(object): + name = "SEED" + block_size = 128 + key_sizes = frozenset([128]) + + def __init__(self, key): + self.key = _verify_key_size(self, key) + + @property + def key_size(self): + return len(self.key) * 8 diff --git a/src/cryptography/hazmat/primitives/ciphers/base.py b/src/cryptography/hazmat/primitives/ciphers/base.py new file mode 100644 index 0000000..496975a --- /dev/null +++ b/src/cryptography/hazmat/primitives/ciphers/base.py @@ -0,0 +1,203 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc + +import six + +from cryptography import utils +from cryptography.exceptions import ( + AlreadyFinalized, AlreadyUpdated, NotYetFinalized, UnsupportedAlgorithm, + _Reasons +) +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers import modes + + +@six.add_metaclass(abc.ABCMeta) +class CipherAlgorithm(object): + @abc.abstractproperty + def name(self): + """ + A string naming this mode (e.g. "AES", "Camellia"). + """ + + @abc.abstractproperty + def key_size(self): + """ + The size of the key being used as an integer in bits (e.g. 128, 256). + """ + + +@six.add_metaclass(abc.ABCMeta) +class BlockCipherAlgorithm(object): + @abc.abstractproperty + def block_size(self): + """ + The size of a block as an integer in bits (e.g. 64, 128). + """ + + +@six.add_metaclass(abc.ABCMeta) +class CipherContext(object): + @abc.abstractmethod + def update(self, data): + """ + Processes the provided bytes through the cipher and returns the results + as bytes. + """ + + @abc.abstractmethod + def finalize(self): + """ + Returns the results of processing the final block as bytes. + """ + + +@six.add_metaclass(abc.ABCMeta) +class AEADCipherContext(object): + @abc.abstractmethod + def authenticate_additional_data(self, data): + """ + Authenticates the provided bytes. + """ + + +@six.add_metaclass(abc.ABCMeta) +class AEADEncryptionContext(object): + @abc.abstractproperty + def tag(self): + """ + Returns tag bytes. This is only available after encryption is + finalized. + """ + + +class Cipher(object): + def __init__(self, algorithm, mode, backend): + if not isinstance(backend, CipherBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement CipherBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + if not isinstance(algorithm, CipherAlgorithm): + raise TypeError("Expected interface of CipherAlgorithm.") + + if mode is not None: + mode.validate_for_algorithm(algorithm) + + self.algorithm = algorithm + self.mode = mode + self._backend = backend + + def encryptor(self): + if isinstance(self.mode, modes.ModeWithAuthenticationTag): + if self.mode.tag is not None: + raise ValueError( + "Authentication tag must be None when encrypting." + ) + ctx = self._backend.create_symmetric_encryption_ctx( + self.algorithm, self.mode + ) + return self._wrap_ctx(ctx, encrypt=True) + + def decryptor(self): + if isinstance(self.mode, modes.ModeWithAuthenticationTag): + if self.mode.tag is None: + raise ValueError( + "Authentication tag must be provided when decrypting." + ) + ctx = self._backend.create_symmetric_decryption_ctx( + self.algorithm, self.mode + ) + return self._wrap_ctx(ctx, encrypt=False) + + def _wrap_ctx(self, ctx, encrypt): + if isinstance(self.mode, modes.ModeWithAuthenticationTag): + if encrypt: + return _AEADEncryptionContext(ctx) + else: + return _AEADCipherContext(ctx) + else: + return _CipherContext(ctx) + + +@utils.register_interface(CipherContext) +class _CipherContext(object): + def __init__(self, ctx): + self._ctx = ctx + + def update(self, data): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + return self._ctx.update(data) + + def finalize(self): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + data = self._ctx.finalize() + self._ctx = None + return data + + +@utils.register_interface(AEADCipherContext) +@utils.register_interface(CipherContext) +class _AEADCipherContext(object): + def __init__(self, ctx): + self._ctx = ctx + self._bytes_processed = 0 + self._aad_bytes_processed = 0 + self._tag = None + self._updated = False + + def update(self, data): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + self._updated = True + self._bytes_processed += len(data) + if self._bytes_processed > self._ctx._mode._MAX_ENCRYPTED_BYTES: + raise ValueError( + "{0} has a maximum encrypted byte limit of {1}".format( + self._ctx._mode.name, self._ctx._mode._MAX_ENCRYPTED_BYTES + ) + ) + + return self._ctx.update(data) + + def finalize(self): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + data = self._ctx.finalize() + self._tag = self._ctx.tag + self._ctx = None + return data + + def authenticate_additional_data(self, data): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + if self._updated: + raise AlreadyUpdated("Update has been called on this context.") + + self._aad_bytes_processed += len(data) + if self._aad_bytes_processed > self._ctx._mode._MAX_AAD_BYTES: + raise ValueError( + "{0} has a maximum AAD byte limit of {1}".format( + self._ctx._mode.name, self._ctx._mode._MAX_AAD_BYTES + ) + ) + + self._ctx.authenticate_additional_data(data) + + +@utils.register_interface(AEADEncryptionContext) +class _AEADEncryptionContext(_AEADCipherContext): + @property + def tag(self): + if self._ctx is not None: + raise NotYetFinalized("You must finalize encryption before " + "getting the tag.") + return self._tag diff --git a/src/cryptography/hazmat/primitives/ciphers/modes.py b/src/cryptography/hazmat/primitives/ciphers/modes.py new file mode 100644 index 0000000..802e544 --- /dev/null +++ b/src/cryptography/hazmat/primitives/ciphers/modes.py @@ -0,0 +1,185 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc + +import six + +from cryptography import utils + + +@six.add_metaclass(abc.ABCMeta) +class Mode(object): + @abc.abstractproperty + def name(self): + """ + A string naming this mode (e.g. "ECB", "CBC"). + """ + + @abc.abstractmethod + def validate_for_algorithm(self, algorithm): + """ + Checks that all the necessary invariants of this (mode, algorithm) + combination are met. + """ + + +@six.add_metaclass(abc.ABCMeta) +class ModeWithInitializationVector(object): + @abc.abstractproperty + def initialization_vector(self): + """ + The value of the initialization vector for this mode as bytes. + """ + + +@six.add_metaclass(abc.ABCMeta) +class ModeWithNonce(object): + @abc.abstractproperty + def nonce(self): + """ + The value of the nonce for this mode as bytes. + """ + + +@six.add_metaclass(abc.ABCMeta) +class ModeWithAuthenticationTag(object): + @abc.abstractproperty + def tag(self): + """ + The value of the tag supplied to the constructor of this mode. + """ + + +def _check_iv_length(self, algorithm): + if len(self.initialization_vector) * 8 != algorithm.block_size: + raise ValueError("Invalid IV size ({0}) for {1}.".format( + len(self.initialization_vector), self.name + )) + + +@utils.register_interface(Mode) +@utils.register_interface(ModeWithInitializationVector) +class CBC(object): + name = "CBC" + + def __init__(self, initialization_vector): + if not isinstance(initialization_vector, bytes): + raise TypeError("initialization_vector must be bytes") + + self._initialization_vector = initialization_vector + + initialization_vector = utils.read_only_property("_initialization_vector") + validate_for_algorithm = _check_iv_length + + +@utils.register_interface(Mode) +class ECB(object): + name = "ECB" + + def validate_for_algorithm(self, algorithm): + pass + + +@utils.register_interface(Mode) +@utils.register_interface(ModeWithInitializationVector) +class OFB(object): + name = "OFB" + + def __init__(self, initialization_vector): + if not isinstance(initialization_vector, bytes): + raise TypeError("initialization_vector must be bytes") + + self._initialization_vector = initialization_vector + + initialization_vector = utils.read_only_property("_initialization_vector") + validate_for_algorithm = _check_iv_length + + +@utils.register_interface(Mode) +@utils.register_interface(ModeWithInitializationVector) +class CFB(object): + name = "CFB" + + def __init__(self, initialization_vector): + if not isinstance(initialization_vector, bytes): + raise TypeError("initialization_vector must be bytes") + + self._initialization_vector = initialization_vector + + initialization_vector = utils.read_only_property("_initialization_vector") + validate_for_algorithm = _check_iv_length + + +@utils.register_interface(Mode) +@utils.register_interface(ModeWithInitializationVector) +class CFB8(object): + name = "CFB8" + + def __init__(self, initialization_vector): + if not isinstance(initialization_vector, bytes): + raise TypeError("initialization_vector must be bytes") + + self._initialization_vector = initialization_vector + + initialization_vector = utils.read_only_property("_initialization_vector") + validate_for_algorithm = _check_iv_length + + +@utils.register_interface(Mode) +@utils.register_interface(ModeWithNonce) +class CTR(object): + name = "CTR" + + def __init__(self, nonce): + if not isinstance(nonce, bytes): + raise TypeError("nonce must be bytes") + + self._nonce = nonce + + nonce = utils.read_only_property("_nonce") + + def validate_for_algorithm(self, algorithm): + if len(self.nonce) * 8 != algorithm.block_size: + raise ValueError("Invalid nonce size ({0}) for {1}.".format( + len(self.nonce), self.name + )) + + +@utils.register_interface(Mode) +@utils.register_interface(ModeWithInitializationVector) +@utils.register_interface(ModeWithAuthenticationTag) +class GCM(object): + name = "GCM" + _MAX_ENCRYPTED_BYTES = (2 ** 39 - 256) // 8 + _MAX_AAD_BYTES = (2 ** 64) // 8 + + def __init__(self, initialization_vector, tag=None, min_tag_length=16): + # len(initialization_vector) must in [1, 2 ** 64), but it's impossible + # to actually construct a bytes object that large, so we don't check + # for it + if min_tag_length < 4: + raise ValueError("min_tag_length must be >= 4") + if tag is not None and len(tag) < min_tag_length: + raise ValueError( + "Authentication tag must be {0} bytes or longer.".format( + min_tag_length) + ) + + if not isinstance(initialization_vector, bytes): + raise TypeError("initialization_vector must be bytes") + + if tag is not None and not isinstance(tag, bytes): + raise TypeError("tag must be bytes or None") + + self._initialization_vector = initialization_vector + self._tag = tag + + tag = utils.read_only_property("_tag") + initialization_vector = utils.read_only_property("_initialization_vector") + + def validate_for_algorithm(self, algorithm): + pass diff --git a/src/cryptography/hazmat/primitives/cmac.py b/src/cryptography/hazmat/primitives/cmac.py new file mode 100644 index 0000000..c2038a3 --- /dev/null +++ b/src/cryptography/hazmat/primitives/cmac.py @@ -0,0 +1,66 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.exceptions import ( + AlreadyFinalized, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import CMACBackend +from cryptography.hazmat.primitives import ciphers, interfaces + + +@utils.register_interface(interfaces.MACContext) +class CMAC(object): + def __init__(self, algorithm, backend, ctx=None): + if not isinstance(backend, CMACBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement CMACBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + if not isinstance(algorithm, ciphers.BlockCipherAlgorithm): + raise TypeError( + "Expected instance of BlockCipherAlgorithm." + ) + self._algorithm = algorithm + + self._backend = backend + if ctx is None: + self._ctx = self._backend.create_cmac_ctx(self._algorithm) + else: + self._ctx = ctx + + def update(self, data): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + if not isinstance(data, bytes): + raise TypeError("data must be bytes.") + self._ctx.update(data) + + def finalize(self): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + digest = self._ctx.finalize() + self._ctx = None + return digest + + def verify(self, signature): + if not isinstance(signature, bytes): + raise TypeError("signature must be bytes.") + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + + ctx, self._ctx = self._ctx, None + ctx.verify(signature) + + def copy(self): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + return CMAC( + self._algorithm, + backend=self._backend, + ctx=self._ctx.copy() + ) diff --git a/src/cryptography/hazmat/primitives/constant_time.py b/src/cryptography/hazmat/primitives/constant_time.py new file mode 100644 index 0000000..5a682ca --- /dev/null +++ b/src/cryptography/hazmat/primitives/constant_time.py @@ -0,0 +1,26 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import hmac + +from cryptography.hazmat.bindings._constant_time import lib + + +if hasattr(hmac, "compare_digest"): + def bytes_eq(a, b): + if not isinstance(a, bytes) or not isinstance(b, bytes): + raise TypeError("a and b must be bytes.") + + return hmac.compare_digest(a, b) + +else: + def bytes_eq(a, b): + if not isinstance(a, bytes) or not isinstance(b, bytes): + raise TypeError("a and b must be bytes.") + + return lib.Cryptography_constant_time_bytes_eq( + a, len(a), b, len(b) + ) == 1 diff --git a/src/cryptography/hazmat/primitives/hashes.py b/src/cryptography/hazmat/primitives/hashes.py new file mode 100644 index 0000000..0714c11 --- /dev/null +++ b/src/cryptography/hazmat/primitives/hashes.py @@ -0,0 +1,205 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc + +import six + +from cryptography import utils +from cryptography.exceptions import ( + AlreadyFinalized, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import HashBackend + + +@six.add_metaclass(abc.ABCMeta) +class HashAlgorithm(object): + @abc.abstractproperty + def name(self): + """ + A string naming this algorithm (e.g. "sha256", "md5"). + """ + + @abc.abstractproperty + def digest_size(self): + """ + The size of the resulting digest in bytes. + """ + + @abc.abstractproperty + def block_size(self): + """ + The internal block size of the hash algorithm in bytes. + """ + + +@six.add_metaclass(abc.ABCMeta) +class HashContext(object): + @abc.abstractproperty + def algorithm(self): + """ + A HashAlgorithm that will be used by this context. + """ + + @abc.abstractmethod + def update(self, data): + """ + Processes the provided bytes through the hash. + """ + + @abc.abstractmethod + def finalize(self): + """ + Finalizes the hash context and returns the hash digest as bytes. + """ + + @abc.abstractmethod + def copy(self): + """ + Return a HashContext that is a copy of the current context. + """ + + +@utils.register_interface(HashContext) +class Hash(object): + def __init__(self, algorithm, backend, ctx=None): + if not isinstance(backend, HashBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement HashBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + if not isinstance(algorithm, HashAlgorithm): + raise TypeError("Expected instance of hashes.HashAlgorithm.") + self._algorithm = algorithm + + self._backend = backend + + if ctx is None: + self._ctx = self._backend.create_hash_ctx(self.algorithm) + else: + self._ctx = ctx + + algorithm = utils.read_only_property("_algorithm") + + def update(self, data): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + if not isinstance(data, bytes): + raise TypeError("data must be bytes.") + self._ctx.update(data) + + def copy(self): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + return Hash( + self.algorithm, backend=self._backend, ctx=self._ctx.copy() + ) + + def finalize(self): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + digest = self._ctx.finalize() + self._ctx = None + return digest + + +@utils.register_interface(HashAlgorithm) +class SHA1(object): + name = "sha1" + digest_size = 20 + block_size = 64 + + +@utils.register_interface(HashAlgorithm) +class SHA224(object): + name = "sha224" + digest_size = 28 + block_size = 64 + + +@utils.register_interface(HashAlgorithm) +class SHA256(object): + name = "sha256" + digest_size = 32 + block_size = 64 + + +@utils.register_interface(HashAlgorithm) +class SHA384(object): + name = "sha384" + digest_size = 48 + block_size = 128 + + +@utils.register_interface(HashAlgorithm) +class SHA512(object): + name = "sha512" + digest_size = 64 + block_size = 128 + + +@utils.register_interface(HashAlgorithm) +class RIPEMD160(object): + name = "ripemd160" + digest_size = 20 + block_size = 64 + + +@utils.register_interface(HashAlgorithm) +class Whirlpool(object): + name = "whirlpool" + digest_size = 64 + block_size = 64 + + +@utils.register_interface(HashAlgorithm) +class MD5(object): + name = "md5" + digest_size = 16 + block_size = 64 + + +@utils.register_interface(HashAlgorithm) +class BLAKE2b(object): + name = "blake2b" + _max_digest_size = 64 + _min_digest_size = 1 + block_size = 128 + + def __init__(self, digest_size): + if ( + digest_size > self._max_digest_size or + digest_size < self._min_digest_size + ): + raise ValueError("Digest size must be {0}-{1}".format( + self._min_digest_size, self._max_digest_size) + ) + + self._digest_size = digest_size + + digest_size = utils.read_only_property("_digest_size") + + +@utils.register_interface(HashAlgorithm) +class BLAKE2s(object): + name = "blake2s" + block_size = 64 + _max_digest_size = 32 + _min_digest_size = 1 + + def __init__(self, digest_size): + if ( + digest_size > self._max_digest_size or + digest_size < self._min_digest_size + ): + raise ValueError("Digest size must be {0}-{1}".format( + self._min_digest_size, self._max_digest_size) + ) + + self._digest_size = digest_size + + digest_size = utils.read_only_property("_digest_size") diff --git a/src/cryptography/hazmat/primitives/hmac.py b/src/cryptography/hazmat/primitives/hmac.py new file mode 100644 index 0000000..15b9ee6 --- /dev/null +++ b/src/cryptography/hazmat/primitives/hmac.py @@ -0,0 +1,69 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.exceptions import ( + AlreadyFinalized, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.primitives import hashes, interfaces + + +@utils.register_interface(interfaces.MACContext) +@utils.register_interface(hashes.HashContext) +class HMAC(object): + def __init__(self, key, algorithm, backend, ctx=None): + if not isinstance(backend, HMACBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement HMACBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + if not isinstance(algorithm, hashes.HashAlgorithm): + raise TypeError("Expected instance of hashes.HashAlgorithm.") + self._algorithm = algorithm + + self._backend = backend + self._key = key + if ctx is None: + self._ctx = self._backend.create_hmac_ctx(key, self.algorithm) + else: + self._ctx = ctx + + algorithm = utils.read_only_property("_algorithm") + + def update(self, data): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + if not isinstance(data, bytes): + raise TypeError("data must be bytes.") + self._ctx.update(data) + + def copy(self): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + return HMAC( + self._key, + self.algorithm, + backend=self._backend, + ctx=self._ctx.copy() + ) + + def finalize(self): + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + digest = self._ctx.finalize() + self._ctx = None + return digest + + def verify(self, signature): + if not isinstance(signature, bytes): + raise TypeError("signature must be bytes.") + if self._ctx is None: + raise AlreadyFinalized("Context was already finalized.") + + ctx, self._ctx = self._ctx, None + ctx.verify(signature) diff --git a/src/cryptography/hazmat/primitives/interfaces/__init__.py b/src/cryptography/hazmat/primitives/interfaces/__init__.py new file mode 100644 index 0000000..4c95190 --- /dev/null +++ b/src/cryptography/hazmat/primitives/interfaces/__init__.py @@ -0,0 +1,37 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc + +import six + + +@six.add_metaclass(abc.ABCMeta) +class MACContext(object): + @abc.abstractmethod + def update(self, data): + """ + Processes the provided bytes. + """ + + @abc.abstractmethod + def finalize(self): + """ + Returns the message authentication code as bytes. + """ + + @abc.abstractmethod + def copy(self): + """ + Return a MACContext that is a copy of the current context. + """ + + @abc.abstractmethod + def verify(self, signature): + """ + Checks if the generated message authentication code matches the + signature. + """ diff --git a/src/cryptography/hazmat/primitives/kdf/__init__.py b/src/cryptography/hazmat/primitives/kdf/__init__.py new file mode 100644 index 0000000..2d0724e --- /dev/null +++ b/src/cryptography/hazmat/primitives/kdf/__init__.py @@ -0,0 +1,26 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc + +import six + + +@six.add_metaclass(abc.ABCMeta) +class KeyDerivationFunction(object): + @abc.abstractmethod + def derive(self, key_material): + """ + Deterministically generates and returns a new key based on the existing + key material. + """ + + @abc.abstractmethod + def verify(self, key_material, expected_key): + """ + Checks whether the key generated by the key material matches the + expected derived key. Raises an exception if they do not match. + """ diff --git a/src/cryptography/hazmat/primitives/kdf/concatkdf.py b/src/cryptography/hazmat/primitives/kdf/concatkdf.py new file mode 100644 index 0000000..c6399e4 --- /dev/null +++ b/src/cryptography/hazmat/primitives/kdf/concatkdf.py @@ -0,0 +1,125 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import struct + +from cryptography import utils +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.hazmat.primitives import constant_time, hashes, hmac +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + + +def _int_to_u32be(n): + return struct.pack('>I', n) + + +def _common_args_checks(algorithm, length, otherinfo): + max_length = algorithm.digest_size * (2 ** 32 - 1) + if length > max_length: + raise ValueError( + "Can not derive keys larger than {0} bits.".format( + max_length + )) + if not (otherinfo is None or isinstance(otherinfo, bytes)): + raise TypeError("otherinfo must be bytes.") + + +def _concatkdf_derive(key_material, length, auxfn, otherinfo): + if not isinstance(key_material, bytes): + raise TypeError("key_material must be bytes.") + + output = [b""] + outlen = 0 + counter = 1 + + while (length > outlen): + h = auxfn() + h.update(_int_to_u32be(counter)) + h.update(key_material) + h.update(otherinfo) + output.append(h.finalize()) + outlen += len(output[-1]) + counter += 1 + + return b"".join(output)[:length] + + +@utils.register_interface(KeyDerivationFunction) +class ConcatKDFHash(object): + def __init__(self, algorithm, length, otherinfo, backend): + + _common_args_checks(algorithm, length, otherinfo) + self._algorithm = algorithm + self._length = length + self._otherinfo = otherinfo + if self._otherinfo is None: + self._otherinfo = b"" + + if not isinstance(backend, HashBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement HashBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + self._backend = backend + self._used = False + + def _hash(self): + return hashes.Hash(self._algorithm, self._backend) + + def derive(self, key_material): + if self._used: + raise AlreadyFinalized + self._used = True + return _concatkdf_derive(key_material, self._length, + self._hash, self._otherinfo) + + def verify(self, key_material, expected_key): + if not constant_time.bytes_eq(self.derive(key_material), expected_key): + raise InvalidKey + + +@utils.register_interface(KeyDerivationFunction) +class ConcatKDFHMAC(object): + def __init__(self, algorithm, length, salt, otherinfo, backend): + + _common_args_checks(algorithm, length, otherinfo) + self._algorithm = algorithm + self._length = length + self._otherinfo = otherinfo + if self._otherinfo is None: + self._otherinfo = b"" + + if not (salt is None or isinstance(salt, bytes)): + raise TypeError("salt must be bytes.") + if salt is None: + salt = b"\x00" * algorithm.block_size + self._salt = salt + + if not isinstance(backend, HMACBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement HMACBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + self._backend = backend + self._used = False + + def _hmac(self): + return hmac.HMAC(self._salt, self._algorithm, self._backend) + + def derive(self, key_material): + if self._used: + raise AlreadyFinalized + self._used = True + return _concatkdf_derive(key_material, self._length, + self._hmac, self._otherinfo) + + def verify(self, key_material, expected_key): + if not constant_time.bytes_eq(self.derive(key_material), expected_key): + raise InvalidKey diff --git a/src/cryptography/hazmat/primitives/kdf/hkdf.py b/src/cryptography/hazmat/primitives/kdf/hkdf.py new file mode 100644 index 0000000..82ed9b1 --- /dev/null +++ b/src/cryptography/hazmat/primitives/kdf/hkdf.py @@ -0,0 +1,116 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import six + +from cryptography import utils +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.primitives import constant_time, hmac +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + + +@utils.register_interface(KeyDerivationFunction) +class HKDF(object): + def __init__(self, algorithm, length, salt, info, backend): + if not isinstance(backend, HMACBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement HMACBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + self._algorithm = algorithm + + if not (salt is None or isinstance(salt, bytes)): + raise TypeError("salt must be bytes.") + + if salt is None: + salt = b"\x00" * (self._algorithm.digest_size // 8) + + self._salt = salt + + self._backend = backend + + self._hkdf_expand = HKDFExpand(self._algorithm, length, info, backend) + + def _extract(self, key_material): + h = hmac.HMAC(self._salt, self._algorithm, backend=self._backend) + h.update(key_material) + return h.finalize() + + def derive(self, key_material): + if not isinstance(key_material, bytes): + raise TypeError("key_material must be bytes.") + + return self._hkdf_expand.derive(self._extract(key_material)) + + def verify(self, key_material, expected_key): + if not constant_time.bytes_eq(self.derive(key_material), expected_key): + raise InvalidKey + + +@utils.register_interface(KeyDerivationFunction) +class HKDFExpand(object): + def __init__(self, algorithm, length, info, backend): + if not isinstance(backend, HMACBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement HMACBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + self._algorithm = algorithm + + self._backend = backend + + max_length = 255 * (algorithm.digest_size // 8) + + if length > max_length: + raise ValueError( + "Can not derive keys larger than {0} octets.".format( + max_length + )) + + self._length = length + + if not (info is None or isinstance(info, bytes)): + raise TypeError("info must be bytes.") + + if info is None: + info = b"" + + self._info = info + + self._used = False + + def _expand(self, key_material): + output = [b""] + counter = 1 + + while self._algorithm.digest_size * (len(output) - 1) < self._length: + h = hmac.HMAC(key_material, self._algorithm, backend=self._backend) + h.update(output[-1]) + h.update(self._info) + h.update(six.int2byte(counter)) + output.append(h.finalize()) + counter += 1 + + return b"".join(output)[:self._length] + + def derive(self, key_material): + if not isinstance(key_material, bytes): + raise TypeError("key_material must be bytes.") + + if self._used: + raise AlreadyFinalized + + self._used = True + return self._expand(key_material) + + def verify(self, key_material, expected_key): + if not constant_time.bytes_eq(self.derive(key_material), expected_key): + raise InvalidKey diff --git a/src/cryptography/hazmat/primitives/kdf/kbkdf.py b/src/cryptography/hazmat/primitives/kdf/kbkdf.py new file mode 100644 index 0000000..29ac0fa --- /dev/null +++ b/src/cryptography/hazmat/primitives/kdf/kbkdf.py @@ -0,0 +1,148 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from enum import Enum + +from six.moves import range + +from cryptography import utils +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.primitives import constant_time, hashes, hmac +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + + +class Mode(Enum): + CounterMode = "ctr" + + +class CounterLocation(Enum): + BeforeFixed = "before_fixed" + AfterFixed = "after_fixed" + + +@utils.register_interface(KeyDerivationFunction) +class KBKDFHMAC(object): + def __init__(self, algorithm, mode, length, rlen, llen, + location, label, context, fixed, backend): + if not isinstance(backend, HMACBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement HMACBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + if not isinstance(algorithm, hashes.HashAlgorithm): + raise UnsupportedAlgorithm( + "Algorithm supplied is not a supported hash algorithm.", + _Reasons.UNSUPPORTED_HASH + ) + + if not backend.hmac_supported(algorithm): + raise UnsupportedAlgorithm( + "Algorithm supplied is not a supported hmac algorithm.", + _Reasons.UNSUPPORTED_HASH + ) + + if not isinstance(mode, Mode): + raise TypeError("mode must be of type Mode") + + if not isinstance(location, CounterLocation): + raise TypeError("location must be of type CounterLocation") + + if (label or context) and fixed: + raise ValueError("When supplying fixed data, " + "label and context are ignored.") + + if rlen is None or not self._valid_byte_length(rlen): + raise ValueError("rlen must be between 1 and 4") + + if llen is None and fixed is None: + raise ValueError("Please specify an llen") + + if llen is not None and not isinstance(llen, int): + raise TypeError("llen must be an integer") + + if label is None: + label = b'' + + if context is None: + context = b'' + + if (not isinstance(label, bytes) or + not isinstance(context, bytes)): + raise TypeError('label and context must be of type bytes') + + self._algorithm = algorithm + self._mode = mode + self._length = length + self._rlen = rlen + self._llen = llen + self._location = location + self._label = label + self._context = context + self._backend = backend + self._used = False + self._fixed_data = fixed + + def _valid_byte_length(self, value): + if not isinstance(value, int): + raise TypeError('value must be of type int') + + value_bin = utils.int_to_bytes(1, value) + if not 1 <= len(value_bin) <= 4: + return False + return True + + def derive(self, key_material): + if self._used: + raise AlreadyFinalized + + if not isinstance(key_material, bytes): + raise TypeError('key_material must be bytes') + self._used = True + + # inverse floor division (equivalent to ceiling) + rounds = -(-self._length // self._algorithm.digest_size) + + output = [b''] + + # For counter mode, the number of iterations shall not be + # larger than 2^r-1, where r <= 32 is the binary length of the counter + # This ensures that the counter values used as an input to the + # PRF will not repeat during a particular call to the KDF function. + r_bin = utils.int_to_bytes(1, self._rlen) + if rounds > pow(2, len(r_bin) * 8) - 1: + raise ValueError('There are too many iterations.') + + for i in range(1, rounds + 1): + h = hmac.HMAC(key_material, self._algorithm, backend=self._backend) + + counter = utils.int_to_bytes(i, self._rlen) + if self._location == CounterLocation.BeforeFixed: + h.update(counter) + + h.update(self._generate_fixed_input()) + + if self._location == CounterLocation.AfterFixed: + h.update(counter) + + output.append(h.finalize()) + + return b''.join(output)[:self._length] + + def _generate_fixed_input(self): + if self._fixed_data and isinstance(self._fixed_data, bytes): + return self._fixed_data + + l = utils.int_to_bytes(self._length * 8, self._llen) + + return b"".join([self._label, b"\x00", self._context, l]) + + def verify(self, key_material, expected_key): + if not constant_time.bytes_eq(self.derive(key_material), expected_key): + raise InvalidKey diff --git a/src/cryptography/hazmat/primitives/kdf/pbkdf2.py b/src/cryptography/hazmat/primitives/kdf/pbkdf2.py new file mode 100644 index 0000000..f8ce7a3 --- /dev/null +++ b/src/cryptography/hazmat/primitives/kdf/pbkdf2.py @@ -0,0 +1,58 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import PBKDF2HMACBackend +from cryptography.hazmat.primitives import constant_time +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + + +@utils.register_interface(KeyDerivationFunction) +class PBKDF2HMAC(object): + def __init__(self, algorithm, length, salt, iterations, backend): + if not isinstance(backend, PBKDF2HMACBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement PBKDF2HMACBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + if not backend.pbkdf2_hmac_supported(algorithm): + raise UnsupportedAlgorithm( + "{0} is not supported for PBKDF2 by this backend.".format( + algorithm.name), + _Reasons.UNSUPPORTED_HASH + ) + self._used = False + self._algorithm = algorithm + self._length = length + if not isinstance(salt, bytes): + raise TypeError("salt must be bytes.") + self._salt = salt + self._iterations = iterations + self._backend = backend + + def derive(self, key_material): + if self._used: + raise AlreadyFinalized("PBKDF2 instances can only be used once.") + self._used = True + + if not isinstance(key_material, bytes): + raise TypeError("key_material must be bytes.") + return self._backend.derive_pbkdf2_hmac( + self._algorithm, + self._length, + self._salt, + self._iterations, + key_material + ) + + def verify(self, key_material, expected_key): + derived_key = self.derive(key_material) + if not constant_time.bytes_eq(derived_key, expected_key): + raise InvalidKey("Keys do not match.") diff --git a/src/cryptography/hazmat/primitives/kdf/scrypt.py b/src/cryptography/hazmat/primitives/kdf/scrypt.py new file mode 100644 index 0000000..2093540 --- /dev/null +++ b/src/cryptography/hazmat/primitives/kdf/scrypt.py @@ -0,0 +1,59 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import ScryptBackend +from cryptography.hazmat.primitives import constant_time +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + + +@utils.register_interface(KeyDerivationFunction) +class Scrypt(object): + def __init__(self, salt, length, n, r, p, backend): + if not isinstance(backend, ScryptBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement ScryptBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + self._length = length + if not isinstance(salt, bytes): + raise TypeError("salt must be bytes.") + + if n < 2 or (n & (n - 1)) != 0: + raise ValueError("n must be greater than 1 and be a power of 2.") + + if r < 1: + raise ValueError("r must be greater than or equal to 1.") + + if p < 1: + raise ValueError("p must be greater than or equal to 1.") + + self._used = False + self._salt = salt + self._n = n + self._r = r + self._p = p + self._backend = backend + + def derive(self, key_material): + if self._used: + raise AlreadyFinalized("Scrypt instances can only be used once.") + self._used = True + + if not isinstance(key_material, bytes): + raise TypeError("key_material must be bytes.") + return self._backend.derive_scrypt( + key_material, self._salt, self._length, self._n, self._r, self._p + ) + + def verify(self, key_material, expected_key): + derived_key = self.derive(key_material) + if not constant_time.bytes_eq(derived_key, expected_key): + raise InvalidKey("Keys do not match.") diff --git a/src/cryptography/hazmat/primitives/kdf/x963kdf.py b/src/cryptography/hazmat/primitives/kdf/x963kdf.py new file mode 100644 index 0000000..83789b3 --- /dev/null +++ b/src/cryptography/hazmat/primitives/kdf/x963kdf.py @@ -0,0 +1,70 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import struct + +from cryptography import utils +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.hazmat.primitives import constant_time, hashes +from cryptography.hazmat.primitives.kdf import KeyDerivationFunction + + +def _int_to_u32be(n): + return struct.pack('>I', n) + + +@utils.register_interface(KeyDerivationFunction) +class X963KDF(object): + def __init__(self, algorithm, length, sharedinfo, backend): + + max_len = algorithm.digest_size * (2 ** 32 - 1) + if length > max_len: + raise ValueError( + "Can not derive keys larger than {0} bits.".format(max_len)) + if not (sharedinfo is None or isinstance(sharedinfo, bytes)): + raise TypeError("sharedinfo must be bytes.") + self._algorithm = algorithm + self._length = length + self._sharedinfo = sharedinfo + + if not isinstance(backend, HashBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement HashBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + self._backend = backend + self._used = False + + def derive(self, key_material): + if self._used: + raise AlreadyFinalized + self._used = True + + if not isinstance(key_material, bytes): + raise TypeError("key_material must be bytes.") + + output = [b""] + outlen = 0 + counter = 1 + + while self._length > outlen: + h = hashes.Hash(self._algorithm, self._backend) + h.update(key_material) + h.update(_int_to_u32be(counter)) + if self._sharedinfo is not None: + h.update(self._sharedinfo) + output.append(h.finalize()) + outlen += len(output[-1]) + counter += 1 + + return b"".join(output)[:self._length] + + def verify(self, key_material, expected_key): + if not constant_time.bytes_eq(self.derive(key_material), expected_key): + raise InvalidKey diff --git a/src/cryptography/hazmat/primitives/keywrap.py b/src/cryptography/hazmat/primitives/keywrap.py new file mode 100644 index 0000000..6e79ab6 --- /dev/null +++ b/src/cryptography/hazmat/primitives/keywrap.py @@ -0,0 +1,85 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import struct + +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers.algorithms import AES +from cryptography.hazmat.primitives.ciphers.modes import ECB +from cryptography.hazmat.primitives.constant_time import bytes_eq + + +def aes_key_wrap(wrapping_key, key_to_wrap, backend): + if len(wrapping_key) not in [16, 24, 32]: + raise ValueError("The wrapping key must be a valid AES key length") + + if len(key_to_wrap) < 16: + raise ValueError("The key to wrap must be at least 16 bytes") + + if len(key_to_wrap) % 8 != 0: + raise ValueError("The key to wrap must be a multiple of 8 bytes") + + # RFC 3394 Key Wrap - 2.2.1 (index method) + encryptor = Cipher(AES(wrapping_key), ECB(), backend).encryptor() + a = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6" + r = [key_to_wrap[i:i + 8] for i in range(0, len(key_to_wrap), 8)] + n = len(r) + for j in range(6): + for i in range(n): + # every encryption operation is a discrete 16 byte chunk (because + # AES has a 128-bit block size) and since we're using ECB it is + # safe to reuse the encryptor for the entire operation + b = encryptor.update(a + r[i]) + # pack/unpack are safe as these are always 64-bit chunks + a = struct.pack( + ">Q", struct.unpack(">Q", b[:8])[0] ^ ((n * j) + i + 1) + ) + r[i] = b[-8:] + + assert encryptor.finalize() == b"" + + return a + b"".join(r) + + +def aes_key_unwrap(wrapping_key, wrapped_key, backend): + if len(wrapped_key) < 24: + raise ValueError("Must be at least 24 bytes") + + if len(wrapped_key) % 8 != 0: + raise ValueError("The wrapped key must be a multiple of 8 bytes") + + if len(wrapping_key) not in [16, 24, 32]: + raise ValueError("The wrapping key must be a valid AES key length") + + # Implement RFC 3394 Key Unwrap - 2.2.2 (index method) + decryptor = Cipher(AES(wrapping_key), ECB(), backend).decryptor() + aiv = b"\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6" + + r = [wrapped_key[i:i + 8] for i in range(0, len(wrapped_key), 8)] + a = r.pop(0) + n = len(r) + for j in reversed(range(6)): + for i in reversed(range(n)): + # pack/unpack are safe as these are always 64-bit chunks + atr = struct.pack( + ">Q", struct.unpack(">Q", a)[0] ^ ((n * j) + i + 1) + ) + r[i] + # every decryption operation is a discrete 16 byte chunk so + # it is safe to reuse the decryptor for the entire operation + b = decryptor.update(atr) + a = b[:8] + r[i] = b[-8:] + + assert decryptor.finalize() == b"" + + if not bytes_eq(a, aiv): + raise InvalidUnwrap() + + return b"".join(r) + + +class InvalidUnwrap(Exception): + pass diff --git a/src/cryptography/hazmat/primitives/padding.py b/src/cryptography/hazmat/primitives/padding.py new file mode 100644 index 0000000..a081976 --- /dev/null +++ b/src/cryptography/hazmat/primitives/padding.py @@ -0,0 +1,202 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc + +import six + +from cryptography import utils +from cryptography.exceptions import AlreadyFinalized +from cryptography.hazmat.bindings._padding import lib + + +@six.add_metaclass(abc.ABCMeta) +class PaddingContext(object): + @abc.abstractmethod + def update(self, data): + """ + Pads the provided bytes and returns any available data as bytes. + """ + + @abc.abstractmethod + def finalize(self): + """ + Finalize the padding, returns bytes. + """ + + +def _byte_padding_check(block_size): + if not (0 <= block_size <= 2040): + raise ValueError("block_size must be in range(0, 2041).") + + if block_size % 8 != 0: + raise ValueError("block_size must be a multiple of 8.") + + +def _byte_padding_update(buffer_, data, block_size): + if buffer_ is None: + raise AlreadyFinalized("Context was already finalized.") + + if not isinstance(data, bytes): + raise TypeError("data must be bytes.") + + buffer_ += data + + finished_blocks = len(buffer_) // (block_size // 8) + + result = buffer_[:finished_blocks * (block_size // 8)] + buffer_ = buffer_[finished_blocks * (block_size // 8):] + + return buffer_, result + + +def _byte_padding_pad(buffer_, block_size, paddingfn): + if buffer_ is None: + raise AlreadyFinalized("Context was already finalized.") + + pad_size = block_size // 8 - len(buffer_) + return buffer_ + paddingfn(pad_size) + + +def _byte_unpadding_update(buffer_, data, block_size): + if buffer_ is None: + raise AlreadyFinalized("Context was already finalized.") + + if not isinstance(data, bytes): + raise TypeError("data must be bytes.") + + buffer_ += data + + finished_blocks = max(len(buffer_) // (block_size // 8) - 1, 0) + + result = buffer_[:finished_blocks * (block_size // 8)] + buffer_ = buffer_[finished_blocks * (block_size // 8):] + + return buffer_, result + + +def _byte_unpadding_check(buffer_, block_size, checkfn): + if buffer_ is None: + raise AlreadyFinalized("Context was already finalized.") + + if len(buffer_) != block_size // 8: + raise ValueError("Invalid padding bytes.") + + valid = checkfn(buffer_, block_size // 8) + + if not valid: + raise ValueError("Invalid padding bytes.") + + pad_size = six.indexbytes(buffer_, -1) + return buffer_[:-pad_size] + + +class PKCS7(object): + def __init__(self, block_size): + _byte_padding_check(block_size) + self.block_size = block_size + + def padder(self): + return _PKCS7PaddingContext(self.block_size) + + def unpadder(self): + return _PKCS7UnpaddingContext(self.block_size) + + +@utils.register_interface(PaddingContext) +class _PKCS7PaddingContext(object): + def __init__(self, block_size): + self.block_size = block_size + # TODO: more copies than necessary, we should use zero-buffer (#193) + self._buffer = b"" + + def update(self, data): + self._buffer, result = _byte_padding_update( + self._buffer, data, self.block_size) + return result + + def _padding(self, size): + return six.int2byte(size) * size + + def finalize(self): + result = _byte_padding_pad( + self._buffer, self.block_size, self._padding) + self._buffer = None + return result + + +@utils.register_interface(PaddingContext) +class _PKCS7UnpaddingContext(object): + def __init__(self, block_size): + self.block_size = block_size + # TODO: more copies than necessary, we should use zero-buffer (#193) + self._buffer = b"" + + def update(self, data): + self._buffer, result = _byte_unpadding_update( + self._buffer, data, self.block_size) + return result + + def finalize(self): + result = _byte_unpadding_check( + self._buffer, self.block_size, + lib.Cryptography_check_pkcs7_padding) + self._buffer = None + return result + + +class ANSIX923(object): + def __init__(self, block_size): + _byte_padding_check(block_size) + self.block_size = block_size + + def padder(self): + return _ANSIX923PaddingContext(self.block_size) + + def unpadder(self): + return _ANSIX923UnpaddingContext(self.block_size) + + +@utils.register_interface(PaddingContext) +class _ANSIX923PaddingContext(object): + def __init__(self, block_size): + self.block_size = block_size + # TODO: more copies than necessary, we should use zero-buffer (#193) + self._buffer = b"" + + def update(self, data): + self._buffer, result = _byte_padding_update( + self._buffer, data, self.block_size) + return result + + def _padding(self, size): + return six.int2byte(0) * (size - 1) + six.int2byte(size) + + def finalize(self): + result = _byte_padding_pad( + self._buffer, self.block_size, self._padding) + self._buffer = None + return result + + +@utils.register_interface(PaddingContext) +class _ANSIX923UnpaddingContext(object): + def __init__(self, block_size): + self.block_size = block_size + # TODO: more copies than necessary, we should use zero-buffer (#193) + self._buffer = b"" + + def update(self, data): + self._buffer, result = _byte_unpadding_update( + self._buffer, data, self.block_size) + return result + + def finalize(self): + result = _byte_unpadding_check( + self._buffer, self.block_size, + lib.Cryptography_check_ansix923_padding) + self._buffer = None + return result diff --git a/src/cryptography/hazmat/primitives/serialization.py b/src/cryptography/hazmat/primitives/serialization.py new file mode 100644 index 0000000..992fd42 --- /dev/null +++ b/src/cryptography/hazmat/primitives/serialization.py @@ -0,0 +1,197 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc +import base64 +import struct +from enum import Enum + +import six + +from cryptography import utils +from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa + + +def load_pem_private_key(data, password, backend): + return backend.load_pem_private_key(data, password) + + +def load_pem_public_key(data, backend): + return backend.load_pem_public_key(data) + + +def load_der_private_key(data, password, backend): + return backend.load_der_private_key(data, password) + + +def load_der_public_key(data, backend): + return backend.load_der_public_key(data) + + +def load_ssh_public_key(data, backend): + key_parts = data.split(b' ', 2) + + if len(key_parts) < 2: + raise ValueError( + 'Key is not in the proper format or contains extra data.') + + key_type = key_parts[0] + + if key_type == b'ssh-rsa': + loader = _load_ssh_rsa_public_key + elif key_type == b'ssh-dss': + loader = _load_ssh_dss_public_key + elif key_type in [ + b'ecdsa-sha2-nistp256', b'ecdsa-sha2-nistp384', b'ecdsa-sha2-nistp521', + ]: + loader = _load_ssh_ecdsa_public_key + else: + raise UnsupportedAlgorithm('Key type is not supported.') + + key_body = key_parts[1] + + try: + decoded_data = base64.b64decode(key_body) + except TypeError: + raise ValueError('Key is not in the proper format.') + + inner_key_type, rest = _ssh_read_next_string(decoded_data) + + if inner_key_type != key_type: + raise ValueError( + 'Key header and key body contain different key type values.' + ) + + return loader(key_type, rest, backend) + + +def _load_ssh_rsa_public_key(key_type, decoded_data, backend): + e, rest = _ssh_read_next_mpint(decoded_data) + n, rest = _ssh_read_next_mpint(rest) + + if rest: + raise ValueError('Key body contains extra bytes.') + + return rsa.RSAPublicNumbers(e, n).public_key(backend) + + +def _load_ssh_dss_public_key(key_type, decoded_data, backend): + p, rest = _ssh_read_next_mpint(decoded_data) + q, rest = _ssh_read_next_mpint(rest) + g, rest = _ssh_read_next_mpint(rest) + y, rest = _ssh_read_next_mpint(rest) + + if rest: + raise ValueError('Key body contains extra bytes.') + + parameter_numbers = dsa.DSAParameterNumbers(p, q, g) + public_numbers = dsa.DSAPublicNumbers(y, parameter_numbers) + + return public_numbers.public_key(backend) + + +def _load_ssh_ecdsa_public_key(expected_key_type, decoded_data, backend): + curve_name, rest = _ssh_read_next_string(decoded_data) + data, rest = _ssh_read_next_string(rest) + + if expected_key_type != b"ecdsa-sha2-" + curve_name: + raise ValueError( + 'Key header and key body contain different key type values.' + ) + + if rest: + raise ValueError('Key body contains extra bytes.') + + curve = { + b"nistp256": ec.SECP256R1, + b"nistp384": ec.SECP384R1, + b"nistp521": ec.SECP521R1, + }[curve_name]() + + if six.indexbytes(data, 0) != 4: + raise NotImplementedError( + "Compressed elliptic curve points are not supported" + ) + + numbers = ec.EllipticCurvePublicNumbers.from_encoded_point(curve, data) + return numbers.public_key(backend) + + +def _ssh_read_next_string(data): + """ + Retrieves the next RFC 4251 string value from the data. + + While the RFC calls these strings, in Python they are bytes objects. + """ + if len(data) < 4: + raise ValueError("Key is not in the proper format") + + str_len, = struct.unpack('>I', data[:4]) + if len(data) < str_len + 4: + raise ValueError("Key is not in the proper format") + + return data[4:4 + str_len], data[4 + str_len:] + + +def _ssh_read_next_mpint(data): + """ + Reads the next mpint from the data. + + Currently, all mpints are interpreted as unsigned. + """ + mpint_data, rest = _ssh_read_next_string(data) + + return ( + utils.int_from_bytes(mpint_data, byteorder='big', signed=False), rest + ) + + +def _ssh_write_string(data): + return struct.pack(">I", len(data)) + data + + +def _ssh_write_mpint(value): + data = utils.int_to_bytes(value) + if six.indexbytes(data, 0) & 0x80: + data = b"\x00" + data + return _ssh_write_string(data) + + +class Encoding(Enum): + PEM = "PEM" + DER = "DER" + OpenSSH = "OpenSSH" + + +class PrivateFormat(Enum): + PKCS8 = "PKCS8" + TraditionalOpenSSL = "TraditionalOpenSSL" + + +class PublicFormat(Enum): + SubjectPublicKeyInfo = "X.509 subjectPublicKeyInfo with PKCS#1" + PKCS1 = "Raw PKCS#1" + OpenSSH = "OpenSSH" + + +@six.add_metaclass(abc.ABCMeta) +class KeySerializationEncryption(object): + pass + + +@utils.register_interface(KeySerializationEncryption) +class BestAvailableEncryption(object): + def __init__(self, password): + if not isinstance(password, bytes) or len(password) == 0: + raise ValueError("Password must be 1 or more bytes.") + + self.password = password + + +@utils.register_interface(KeySerializationEncryption) +class NoEncryption(object): + pass diff --git a/src/cryptography/hazmat/primitives/twofactor/__init__.py b/src/cryptography/hazmat/primitives/twofactor/__init__.py new file mode 100644 index 0000000..e71f9e6 --- /dev/null +++ b/src/cryptography/hazmat/primitives/twofactor/__init__.py @@ -0,0 +1,9 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + + +class InvalidToken(Exception): + pass diff --git a/src/cryptography/hazmat/primitives/twofactor/hotp.py b/src/cryptography/hazmat/primitives/twofactor/hotp.py new file mode 100644 index 0000000..4ad1bdc --- /dev/null +++ b/src/cryptography/hazmat/primitives/twofactor/hotp.py @@ -0,0 +1,68 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import struct + +import six + +from cryptography.exceptions import ( + UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.primitives import constant_time, hmac +from cryptography.hazmat.primitives.hashes import SHA1, SHA256, SHA512 +from cryptography.hazmat.primitives.twofactor import InvalidToken +from cryptography.hazmat.primitives.twofactor.utils import _generate_uri + + +class HOTP(object): + def __init__(self, key, length, algorithm, backend, + enforce_key_length=True): + if not isinstance(backend, HMACBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement HMACBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + if len(key) < 16 and enforce_key_length is True: + raise ValueError("Key length has to be at least 128 bits.") + + if not isinstance(length, six.integer_types): + raise TypeError("Length parameter must be an integer type.") + + if length < 6 or length > 8: + raise ValueError("Length of HOTP has to be between 6 to 8.") + + if not isinstance(algorithm, (SHA1, SHA256, SHA512)): + raise TypeError("Algorithm must be SHA1, SHA256 or SHA512.") + + self._key = key + self._length = length + self._algorithm = algorithm + self._backend = backend + + def generate(self, counter): + truncated_value = self._dynamic_truncate(counter) + hotp = truncated_value % (10 ** self._length) + return "{0:0{1}}".format(hotp, self._length).encode() + + def verify(self, hotp, counter): + if not constant_time.bytes_eq(self.generate(counter), hotp): + raise InvalidToken("Supplied HOTP value does not match.") + + def _dynamic_truncate(self, counter): + ctx = hmac.HMAC(self._key, self._algorithm, self._backend) + ctx.update(struct.pack(">Q", counter)) + hmac_value = ctx.finalize() + + offset = six.indexbytes(hmac_value, len(hmac_value) - 1) & 0b1111 + p = hmac_value[offset:offset + 4] + return struct.unpack(">I", p)[0] & 0x7fffffff + + def get_provisioning_uri(self, account_name, counter, issuer): + return _generate_uri(self, "hotp", account_name, issuer, [ + ("counter", int(counter)), + ]) diff --git a/src/cryptography/hazmat/primitives/twofactor/totp.py b/src/cryptography/hazmat/primitives/twofactor/totp.py new file mode 100644 index 0000000..499f282 --- /dev/null +++ b/src/cryptography/hazmat/primitives/twofactor/totp.py @@ -0,0 +1,40 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography.exceptions import ( + UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.primitives import constant_time +from cryptography.hazmat.primitives.twofactor import InvalidToken +from cryptography.hazmat.primitives.twofactor.hotp import HOTP +from cryptography.hazmat.primitives.twofactor.utils import _generate_uri + + +class TOTP(object): + def __init__(self, key, length, algorithm, time_step, backend, + enforce_key_length=True): + if not isinstance(backend, HMACBackend): + raise UnsupportedAlgorithm( + "Backend object does not implement HMACBackend.", + _Reasons.BACKEND_MISSING_INTERFACE + ) + + self._time_step = time_step + self._hotp = HOTP(key, length, algorithm, backend, enforce_key_length) + + def generate(self, time): + counter = int(time / self._time_step) + return self._hotp.generate(counter) + + def verify(self, totp, time): + if not constant_time.bytes_eq(self.generate(time), totp): + raise InvalidToken("Supplied TOTP value does not match.") + + def get_provisioning_uri(self, account_name, issuer): + return _generate_uri(self._hotp, "totp", account_name, issuer, [ + ("period", int(self._time_step)), + ]) diff --git a/src/cryptography/hazmat/primitives/twofactor/utils.py b/src/cryptography/hazmat/primitives/twofactor/utils.py new file mode 100644 index 0000000..0ed8c4c --- /dev/null +++ b/src/cryptography/hazmat/primitives/twofactor/utils.py @@ -0,0 +1,30 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import base64 + +from six.moves.urllib.parse import quote, urlencode + + +def _generate_uri(hotp, type_name, account_name, issuer, extra_parameters): + parameters = [ + ("digits", hotp._length), + ("secret", base64.b32encode(hotp._key)), + ("algorithm", hotp._algorithm.name.upper()), + ] + + if issuer is not None: + parameters.append(("issuer", issuer)) + + parameters.extend(extra_parameters) + + uriparts = { + "type": type_name, + "label": ("%s:%s" % (quote(issuer), quote(account_name)) if issuer + else quote(account_name)), + "parameters": urlencode(parameters), + } + return "otpauth://{type}/{label}?{parameters}".format(**uriparts) diff --git a/src/cryptography/utils.py b/src/cryptography/utils.py new file mode 100644 index 0000000..f16b7ef --- /dev/null +++ b/src/cryptography/utils.py @@ -0,0 +1,137 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc +import binascii +import inspect +import sys +import warnings + + +# the functions deprecated in 1.0 and 1.4 are on an arbitrarily extended +# deprecation cycle and should not be removed until we agree on when that cycle +# ends. +DeprecatedIn10 = DeprecationWarning +DeprecatedIn14 = DeprecationWarning +DeprecatedIn16 = DeprecationWarning + + +def read_only_property(name): + return property(lambda self: getattr(self, name)) + + +def register_interface(iface): + def register_decorator(klass): + verify_interface(iface, klass) + iface.register(klass) + return klass + return register_decorator + + +def register_interface_if(predicate, iface): + def register_decorator(klass): + if predicate: + verify_interface(iface, klass) + iface.register(klass) + return klass + return register_decorator + + +if hasattr(int, "from_bytes"): + int_from_bytes = int.from_bytes +else: + def int_from_bytes(data, byteorder, signed=False): + assert byteorder == 'big' + assert not signed + + # call bytes() on data to allow the use of bytearrays + return int(bytes(data).encode('hex'), 16) + + +def int_to_bytes(integer, length=None): + hex_string = '%x' % integer + if length is None: + n = len(hex_string) + else: + n = length * 2 + return binascii.unhexlify(hex_string.zfill(n + (n & 1))) + + +class InterfaceNotImplemented(Exception): + pass + + +if hasattr(inspect, "signature"): + signature = inspect.signature +else: + signature = inspect.getargspec + + +def verify_interface(iface, klass): + for method in iface.__abstractmethods__: + if not hasattr(klass, method): + raise InterfaceNotImplemented( + "{0} is missing a {1!r} method".format(klass, method) + ) + if isinstance(getattr(iface, method), abc.abstractproperty): + # Can't properly verify these yet. + continue + sig = signature(getattr(iface, method)) + actual = signature(getattr(klass, method)) + if sig != actual: + raise InterfaceNotImplemented( + "{0}.{1}'s signature differs from the expected. Expected: " + "{2!r}. Received: {3!r}".format( + klass, method, sig, actual + ) + ) + + +if sys.version_info >= (2, 7): + def bit_length(x): + return x.bit_length() +else: + def bit_length(x): + return len(bin(x)) - (2 + (x <= 0)) + + +class _DeprecatedValue(object): + def __init__(self, value, message, warning_class): + self.value = value + self.message = message + self.warning_class = warning_class + + +class _ModuleWithDeprecations(object): + def __init__(self, module): + self.__dict__["_module"] = module + + def __getattr__(self, attr): + obj = getattr(self._module, attr) + if isinstance(obj, _DeprecatedValue): + warnings.warn(obj.message, obj.warning_class, stacklevel=2) + obj = obj.value + return obj + + def __setattr__(self, attr, value): + setattr(self._module, attr, value) + + def __delattr__(self, attr): + obj = getattr(self._module, attr) + if isinstance(obj, _DeprecatedValue): + warnings.warn(obj.message, obj.warning_class, stacklevel=2) + + delattr(self._module, attr) + + def __dir__(self): + return ["_module"] + dir(self._module) + + +def deprecated(value, module_name, message, warning_class): + module = sys.modules[module_name] + if not isinstance(module, _ModuleWithDeprecations): + sys.modules[module_name] = module = _ModuleWithDeprecations(module) + return _DeprecatedValue(value, message, warning_class) diff --git a/src/cryptography/x509/__init__.py b/src/cryptography/x509/__init__.py new file mode 100644 index 0000000..51914e1 --- /dev/null +++ b/src/cryptography/x509/__init__.py @@ -0,0 +1,179 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography.x509.base import ( + Certificate, CertificateBuilder, CertificateRevocationList, + CertificateRevocationListBuilder, + CertificateSigningRequest, CertificateSigningRequestBuilder, + InvalidVersion, RevokedCertificate, RevokedCertificateBuilder, + Version, load_der_x509_certificate, load_der_x509_crl, load_der_x509_csr, + load_pem_x509_certificate, load_pem_x509_crl, load_pem_x509_csr, + random_serial_number, +) +from cryptography.x509.extensions import ( + AccessDescription, AuthorityInformationAccess, + AuthorityKeyIdentifier, BasicConstraints, CRLDistributionPoints, + CRLNumber, CRLReason, CertificateIssuer, CertificatePolicies, + DistributionPoint, DuplicateExtension, ExtendedKeyUsage, Extension, + ExtensionNotFound, ExtensionType, Extensions, GeneralNames, + InhibitAnyPolicy, InvalidityDate, IssuerAlternativeName, KeyUsage, + NameConstraints, NoticeReference, OCSPNoCheck, PolicyConstraints, + PolicyInformation, ReasonFlags, SubjectAlternativeName, + SubjectKeyIdentifier, UnrecognizedExtension, UnsupportedExtension, + UserNotice +) +from cryptography.x509.general_name import ( + DNSName, DirectoryName, GeneralName, IPAddress, OtherName, RFC822Name, + RegisteredID, UniformResourceIdentifier, UnsupportedGeneralNameType, + _GENERAL_NAMES +) +from cryptography.x509.name import ( + Name, NameAttribute, RelativeDistinguishedName +) +from cryptography.x509.oid import ( + AuthorityInformationAccessOID, CRLEntryExtensionOID, + CertificatePoliciesOID, ExtendedKeyUsageOID, ExtensionOID, NameOID, + ObjectIdentifier, SignatureAlgorithmOID, _SIG_OIDS_TO_HASH +) + + +OID_AUTHORITY_INFORMATION_ACCESS = ExtensionOID.AUTHORITY_INFORMATION_ACCESS +OID_AUTHORITY_KEY_IDENTIFIER = ExtensionOID.AUTHORITY_KEY_IDENTIFIER +OID_BASIC_CONSTRAINTS = ExtensionOID.BASIC_CONSTRAINTS +OID_CERTIFICATE_POLICIES = ExtensionOID.CERTIFICATE_POLICIES +OID_CRL_DISTRIBUTION_POINTS = ExtensionOID.CRL_DISTRIBUTION_POINTS +OID_EXTENDED_KEY_USAGE = ExtensionOID.EXTENDED_KEY_USAGE +OID_FRESHEST_CRL = ExtensionOID.FRESHEST_CRL +OID_INHIBIT_ANY_POLICY = ExtensionOID.INHIBIT_ANY_POLICY +OID_ISSUER_ALTERNATIVE_NAME = ExtensionOID.ISSUER_ALTERNATIVE_NAME +OID_KEY_USAGE = ExtensionOID.KEY_USAGE +OID_NAME_CONSTRAINTS = ExtensionOID.NAME_CONSTRAINTS +OID_OCSP_NO_CHECK = ExtensionOID.OCSP_NO_CHECK +OID_POLICY_CONSTRAINTS = ExtensionOID.POLICY_CONSTRAINTS +OID_POLICY_MAPPINGS = ExtensionOID.POLICY_MAPPINGS +OID_SUBJECT_ALTERNATIVE_NAME = ExtensionOID.SUBJECT_ALTERNATIVE_NAME +OID_SUBJECT_DIRECTORY_ATTRIBUTES = ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES +OID_SUBJECT_INFORMATION_ACCESS = ExtensionOID.SUBJECT_INFORMATION_ACCESS +OID_SUBJECT_KEY_IDENTIFIER = ExtensionOID.SUBJECT_KEY_IDENTIFIER + +OID_DSA_WITH_SHA1 = SignatureAlgorithmOID.DSA_WITH_SHA1 +OID_DSA_WITH_SHA224 = SignatureAlgorithmOID.DSA_WITH_SHA224 +OID_DSA_WITH_SHA256 = SignatureAlgorithmOID.DSA_WITH_SHA256 +OID_ECDSA_WITH_SHA1 = SignatureAlgorithmOID.ECDSA_WITH_SHA1 +OID_ECDSA_WITH_SHA224 = SignatureAlgorithmOID.ECDSA_WITH_SHA224 +OID_ECDSA_WITH_SHA256 = SignatureAlgorithmOID.ECDSA_WITH_SHA256 +OID_ECDSA_WITH_SHA384 = SignatureAlgorithmOID.ECDSA_WITH_SHA384 +OID_ECDSA_WITH_SHA512 = SignatureAlgorithmOID.ECDSA_WITH_SHA512 +OID_RSA_WITH_MD5 = SignatureAlgorithmOID.RSA_WITH_MD5 +OID_RSA_WITH_SHA1 = SignatureAlgorithmOID.RSA_WITH_SHA1 +OID_RSA_WITH_SHA224 = SignatureAlgorithmOID.RSA_WITH_SHA224 +OID_RSA_WITH_SHA256 = SignatureAlgorithmOID.RSA_WITH_SHA256 +OID_RSA_WITH_SHA384 = SignatureAlgorithmOID.RSA_WITH_SHA384 +OID_RSA_WITH_SHA512 = SignatureAlgorithmOID.RSA_WITH_SHA512 + +OID_COMMON_NAME = NameOID.COMMON_NAME +OID_COUNTRY_NAME = NameOID.COUNTRY_NAME +OID_DOMAIN_COMPONENT = NameOID.DOMAIN_COMPONENT +OID_DN_QUALIFIER = NameOID.DN_QUALIFIER +OID_EMAIL_ADDRESS = NameOID.EMAIL_ADDRESS +OID_GENERATION_QUALIFIER = NameOID.GENERATION_QUALIFIER +OID_GIVEN_NAME = NameOID.GIVEN_NAME +OID_LOCALITY_NAME = NameOID.LOCALITY_NAME +OID_ORGANIZATIONAL_UNIT_NAME = NameOID.ORGANIZATIONAL_UNIT_NAME +OID_ORGANIZATION_NAME = NameOID.ORGANIZATION_NAME +OID_PSEUDONYM = NameOID.PSEUDONYM +OID_SERIAL_NUMBER = NameOID.SERIAL_NUMBER +OID_STATE_OR_PROVINCE_NAME = NameOID.STATE_OR_PROVINCE_NAME +OID_SURNAME = NameOID.SURNAME +OID_TITLE = NameOID.TITLE + +OID_CLIENT_AUTH = ExtendedKeyUsageOID.CLIENT_AUTH +OID_CODE_SIGNING = ExtendedKeyUsageOID.CODE_SIGNING +OID_EMAIL_PROTECTION = ExtendedKeyUsageOID.EMAIL_PROTECTION +OID_OCSP_SIGNING = ExtendedKeyUsageOID.OCSP_SIGNING +OID_SERVER_AUTH = ExtendedKeyUsageOID.SERVER_AUTH +OID_TIME_STAMPING = ExtendedKeyUsageOID.TIME_STAMPING + +OID_ANY_POLICY = CertificatePoliciesOID.ANY_POLICY +OID_CPS_QUALIFIER = CertificatePoliciesOID.CPS_QUALIFIER +OID_CPS_USER_NOTICE = CertificatePoliciesOID.CPS_USER_NOTICE + +OID_CERTIFICATE_ISSUER = CRLEntryExtensionOID.CERTIFICATE_ISSUER +OID_CRL_REASON = CRLEntryExtensionOID.CRL_REASON +OID_INVALIDITY_DATE = CRLEntryExtensionOID.INVALIDITY_DATE + +OID_CA_ISSUERS = AuthorityInformationAccessOID.CA_ISSUERS +OID_OCSP = AuthorityInformationAccessOID.OCSP + + +__all__ = [ + "load_pem_x509_certificate", + "load_der_x509_certificate", + "load_pem_x509_csr", + "load_der_x509_csr", + "load_pem_x509_crl", + "load_der_x509_crl", + "random_serial_number", + "InvalidVersion", + "DuplicateExtension", + "UnsupportedExtension", + "ExtensionNotFound", + "UnsupportedGeneralNameType", + "NameAttribute", + "Name", + "RelativeDistinguishedName", + "ObjectIdentifier", + "ExtensionType", + "Extensions", + "Extension", + "ExtendedKeyUsage", + "OCSPNoCheck", + "BasicConstraints", + "CRLNumber", + "KeyUsage", + "AuthorityInformationAccess", + "AccessDescription", + "CertificatePolicies", + "PolicyInformation", + "UserNotice", + "NoticeReference", + "SubjectKeyIdentifier", + "NameConstraints", + "CRLDistributionPoints", + "DistributionPoint", + "ReasonFlags", + "InhibitAnyPolicy", + "SubjectAlternativeName", + "IssuerAlternativeName", + "AuthorityKeyIdentifier", + "GeneralNames", + "GeneralName", + "RFC822Name", + "DNSName", + "UniformResourceIdentifier", + "RegisteredID", + "DirectoryName", + "IPAddress", + "OtherName", + "Certificate", + "CertificateRevocationList", + "CertificateRevocationListBuilder", + "CertificateSigningRequest", + "RevokedCertificate", + "RevokedCertificateBuilder", + "CertificateSigningRequestBuilder", + "CertificateBuilder", + "Version", + "_SIG_OIDS_TO_HASH", + "OID_CA_ISSUERS", + "OID_OCSP", + "_GENERAL_NAMES", + "CertificateIssuer", + "CRLReason", + "InvalidityDate", + "UnrecognizedExtension", + "PolicyConstraints", +] diff --git a/src/cryptography/x509/base.py b/src/cryptography/x509/base.py new file mode 100644 index 0000000..ffa7191 --- /dev/null +++ b/src/cryptography/x509/base.py @@ -0,0 +1,730 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc +import datetime +import os +from enum import Enum + +import six + +from cryptography import utils +from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa +from cryptography.x509.extensions import Extension, ExtensionType +from cryptography.x509.name import Name + + +_UNIX_EPOCH = datetime.datetime(1970, 1, 1) + + +def _convert_to_naive_utc_time(time): + """Normalizes a datetime to a naive datetime in UTC. + + time -- datetime to normalize. Assumed to be in UTC if not timezone + aware. + """ + if time.tzinfo is not None: + offset = time.utcoffset() + offset = offset if offset else datetime.timedelta() + return time.replace(tzinfo=None) - offset + else: + return time + + +class Version(Enum): + v1 = 0 + v3 = 2 + + +def load_pem_x509_certificate(data, backend): + return backend.load_pem_x509_certificate(data) + + +def load_der_x509_certificate(data, backend): + return backend.load_der_x509_certificate(data) + + +def load_pem_x509_csr(data, backend): + return backend.load_pem_x509_csr(data) + + +def load_der_x509_csr(data, backend): + return backend.load_der_x509_csr(data) + + +def load_pem_x509_crl(data, backend): + return backend.load_pem_x509_crl(data) + + +def load_der_x509_crl(data, backend): + return backend.load_der_x509_crl(data) + + +class InvalidVersion(Exception): + def __init__(self, msg, parsed_version): + super(InvalidVersion, self).__init__(msg) + self.parsed_version = parsed_version + + +@six.add_metaclass(abc.ABCMeta) +class Certificate(object): + @abc.abstractmethod + def fingerprint(self, algorithm): + """ + Returns bytes using digest passed. + """ + + @abc.abstractproperty + def serial_number(self): + """ + Returns certificate serial number + """ + + @abc.abstractproperty + def version(self): + """ + Returns the certificate version + """ + + @abc.abstractmethod + def public_key(self): + """ + Returns the public key + """ + + @abc.abstractproperty + def not_valid_before(self): + """ + Not before time (represented as UTC datetime) + """ + + @abc.abstractproperty + def not_valid_after(self): + """ + Not after time (represented as UTC datetime) + """ + + @abc.abstractproperty + def issuer(self): + """ + Returns the issuer name object. + """ + + @abc.abstractproperty + def subject(self): + """ + Returns the subject name object. + """ + + @abc.abstractproperty + def signature_hash_algorithm(self): + """ + Returns a HashAlgorithm corresponding to the type of the digest signed + in the certificate. + """ + + @abc.abstractproperty + def signature_algorithm_oid(self): + """ + Returns the ObjectIdentifier of the signature algorithm. + """ + + @abc.abstractproperty + def extensions(self): + """ + Returns an Extensions object. + """ + + @abc.abstractproperty + def signature(self): + """ + Returns the signature bytes. + """ + + @abc.abstractproperty + def tbs_certificate_bytes(self): + """ + Returns the tbsCertificate payload bytes as defined in RFC 5280. + """ + + @abc.abstractmethod + def __eq__(self, other): + """ + Checks equality. + """ + + @abc.abstractmethod + def __ne__(self, other): + """ + Checks not equal. + """ + + @abc.abstractmethod + def __hash__(self): + """ + Computes a hash. + """ + + @abc.abstractmethod + def public_bytes(self, encoding): + """ + Serializes the certificate to PEM or DER format. + """ + + +@six.add_metaclass(abc.ABCMeta) +class CertificateRevocationList(object): + @abc.abstractmethod + def public_bytes(self, encoding): + """ + Serializes the CRL to PEM or DER format. + """ + + @abc.abstractmethod + def fingerprint(self, algorithm): + """ + Returns bytes using digest passed. + """ + + @abc.abstractproperty + def signature_hash_algorithm(self): + """ + Returns a HashAlgorithm corresponding to the type of the digest signed + in the certificate. + """ + + @abc.abstractproperty + def signature_algorithm_oid(self): + """ + Returns the ObjectIdentifier of the signature algorithm. + """ + + @abc.abstractproperty + def issuer(self): + """ + Returns the X509Name with the issuer of this CRL. + """ + + @abc.abstractproperty + def next_update(self): + """ + Returns the date of next update for this CRL. + """ + + @abc.abstractproperty + def last_update(self): + """ + Returns the date of last update for this CRL. + """ + + @abc.abstractproperty + def extensions(self): + """ + Returns an Extensions object containing a list of CRL extensions. + """ + + @abc.abstractproperty + def signature(self): + """ + Returns the signature bytes. + """ + + @abc.abstractproperty + def tbs_certlist_bytes(self): + """ + Returns the tbsCertList payload bytes as defined in RFC 5280. + """ + + @abc.abstractmethod + def __eq__(self, other): + """ + Checks equality. + """ + + @abc.abstractmethod + def __ne__(self, other): + """ + Checks not equal. + """ + + +@six.add_metaclass(abc.ABCMeta) +class CertificateSigningRequest(object): + @abc.abstractmethod + def __eq__(self, other): + """ + Checks equality. + """ + + @abc.abstractmethod + def __ne__(self, other): + """ + Checks not equal. + """ + + @abc.abstractmethod + def __hash__(self): + """ + Computes a hash. + """ + + @abc.abstractmethod + def public_key(self): + """ + Returns the public key + """ + + @abc.abstractproperty + def subject(self): + """ + Returns the subject name object. + """ + + @abc.abstractproperty + def signature_hash_algorithm(self): + """ + Returns a HashAlgorithm corresponding to the type of the digest signed + in the certificate. + """ + + @abc.abstractproperty + def signature_algorithm_oid(self): + """ + Returns the ObjectIdentifier of the signature algorithm. + """ + + @abc.abstractproperty + def extensions(self): + """ + Returns the extensions in the signing request. + """ + + @abc.abstractmethod + def public_bytes(self, encoding): + """ + Encodes the request to PEM or DER format. + """ + + @abc.abstractproperty + def signature(self): + """ + Returns the signature bytes. + """ + + @abc.abstractproperty + def tbs_certrequest_bytes(self): + """ + Returns the PKCS#10 CertificationRequestInfo bytes as defined in RFC + 2986. + """ + + @abc.abstractproperty + def is_signature_valid(self): + """ + Verifies signature of signing request. + """ + + +@six.add_metaclass(abc.ABCMeta) +class RevokedCertificate(object): + @abc.abstractproperty + def serial_number(self): + """ + Returns the serial number of the revoked certificate. + """ + + @abc.abstractproperty + def revocation_date(self): + """ + Returns the date of when this certificate was revoked. + """ + + @abc.abstractproperty + def extensions(self): + """ + Returns an Extensions object containing a list of Revoked extensions. + """ + + +class CertificateSigningRequestBuilder(object): + def __init__(self, subject_name=None, extensions=[]): + """ + Creates an empty X.509 certificate request (v1). + """ + self._subject_name = subject_name + self._extensions = extensions + + def subject_name(self, name): + """ + Sets the certificate requestor's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError('Expecting x509.Name object.') + if self._subject_name is not None: + raise ValueError('The subject name may only be set once.') + return CertificateSigningRequestBuilder(name, self._extensions) + + def add_extension(self, extension, critical): + """ + Adds an X.509 extension to the certificate request. + """ + if not isinstance(extension, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extension.oid, critical, extension) + + # TODO: This is quadratic in the number of extensions + for e in self._extensions: + if e.oid == extension.oid: + raise ValueError('This extension has already been set.') + return CertificateSigningRequestBuilder( + self._subject_name, self._extensions + [extension] + ) + + def sign(self, private_key, algorithm, backend): + """ + Signs the request using the requestor's private key. + """ + if self._subject_name is None: + raise ValueError("A CertificateSigningRequest must have a subject") + return backend.create_x509_csr(self, private_key, algorithm) + + +class CertificateBuilder(object): + def __init__(self, issuer_name=None, subject_name=None, + public_key=None, serial_number=None, not_valid_before=None, + not_valid_after=None, extensions=[]): + self._version = Version.v3 + self._issuer_name = issuer_name + self._subject_name = subject_name + self._public_key = public_key + self._serial_number = serial_number + self._not_valid_before = not_valid_before + self._not_valid_after = not_valid_after + self._extensions = extensions + + def issuer_name(self, name): + """ + Sets the CA's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError('Expecting x509.Name object.') + if self._issuer_name is not None: + raise ValueError('The issuer name may only be set once.') + return CertificateBuilder( + name, self._subject_name, self._public_key, + self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + ) + + def subject_name(self, name): + """ + Sets the requestor's distinguished name. + """ + if not isinstance(name, Name): + raise TypeError('Expecting x509.Name object.') + if self._subject_name is not None: + raise ValueError('The subject name may only be set once.') + return CertificateBuilder( + self._issuer_name, name, self._public_key, + self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + ) + + def public_key(self, key): + """ + Sets the requestor's public key (as found in the signing request). + """ + if not isinstance(key, (dsa.DSAPublicKey, rsa.RSAPublicKey, + ec.EllipticCurvePublicKey)): + raise TypeError('Expecting one of DSAPublicKey, RSAPublicKey,' + ' or EllipticCurvePublicKey.') + if self._public_key is not None: + raise ValueError('The public key may only be set once.') + return CertificateBuilder( + self._issuer_name, self._subject_name, key, + self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + ) + + def serial_number(self, number): + """ + Sets the certificate serial number. + """ + if not isinstance(number, six.integer_types): + raise TypeError('Serial number must be of integral type.') + if self._serial_number is not None: + raise ValueError('The serial number may only be set once.') + if number <= 0: + raise ValueError('The serial number should be positive.') + + # ASN.1 integers are always signed, so most significant bit must be + # zero. + if utils.bit_length(number) >= 160: # As defined in RFC 5280 + raise ValueError('The serial number should not be more than 159 ' + 'bits.') + return CertificateBuilder( + self._issuer_name, self._subject_name, + self._public_key, number, self._not_valid_before, + self._not_valid_after, self._extensions + ) + + def not_valid_before(self, time): + """ + Sets the certificate activation time. + """ + if not isinstance(time, datetime.datetime): + raise TypeError('Expecting datetime object.') + if self._not_valid_before is not None: + raise ValueError('The not valid before may only be set once.') + time = _convert_to_naive_utc_time(time) + if time <= _UNIX_EPOCH: + raise ValueError('The not valid before date must be after the unix' + ' epoch (1970 January 1).') + if self._not_valid_after is not None and time > self._not_valid_after: + raise ValueError( + 'The not valid before date must be before the not valid after ' + 'date.' + ) + return CertificateBuilder( + self._issuer_name, self._subject_name, + self._public_key, self._serial_number, time, + self._not_valid_after, self._extensions + ) + + def not_valid_after(self, time): + """ + Sets the certificate expiration time. + """ + if not isinstance(time, datetime.datetime): + raise TypeError('Expecting datetime object.') + if self._not_valid_after is not None: + raise ValueError('The not valid after may only be set once.') + time = _convert_to_naive_utc_time(time) + if time <= _UNIX_EPOCH: + raise ValueError('The not valid after date must be after the unix' + ' epoch (1970 January 1).') + if (self._not_valid_before is not None and + time < self._not_valid_before): + raise ValueError( + 'The not valid after date must be after the not valid before ' + 'date.' + ) + return CertificateBuilder( + self._issuer_name, self._subject_name, + self._public_key, self._serial_number, self._not_valid_before, + time, self._extensions + ) + + def add_extension(self, extension, critical): + """ + Adds an X.509 extension to the certificate. + """ + if not isinstance(extension, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extension.oid, critical, extension) + + # TODO: This is quadratic in the number of extensions + for e in self._extensions: + if e.oid == extension.oid: + raise ValueError('This extension has already been set.') + + return CertificateBuilder( + self._issuer_name, self._subject_name, + self._public_key, self._serial_number, self._not_valid_before, + self._not_valid_after, self._extensions + [extension] + ) + + def sign(self, private_key, algorithm, backend): + """ + Signs the certificate using the CA's private key. + """ + if self._subject_name is None: + raise ValueError("A certificate must have a subject name") + + if self._issuer_name is None: + raise ValueError("A certificate must have an issuer name") + + if self._serial_number is None: + raise ValueError("A certificate must have a serial number") + + if self._not_valid_before is None: + raise ValueError("A certificate must have a not valid before time") + + if self._not_valid_after is None: + raise ValueError("A certificate must have a not valid after time") + + if self._public_key is None: + raise ValueError("A certificate must have a public key") + + return backend.create_x509_certificate(self, private_key, algorithm) + + +class CertificateRevocationListBuilder(object): + def __init__(self, issuer_name=None, last_update=None, next_update=None, + extensions=[], revoked_certificates=[]): + self._issuer_name = issuer_name + self._last_update = last_update + self._next_update = next_update + self._extensions = extensions + self._revoked_certificates = revoked_certificates + + def issuer_name(self, issuer_name): + if not isinstance(issuer_name, Name): + raise TypeError('Expecting x509.Name object.') + if self._issuer_name is not None: + raise ValueError('The issuer name may only be set once.') + return CertificateRevocationListBuilder( + issuer_name, self._last_update, self._next_update, + self._extensions, self._revoked_certificates + ) + + def last_update(self, last_update): + if not isinstance(last_update, datetime.datetime): + raise TypeError('Expecting datetime object.') + if self._last_update is not None: + raise ValueError('Last update may only be set once.') + last_update = _convert_to_naive_utc_time(last_update) + if last_update <= _UNIX_EPOCH: + raise ValueError('The last update date must be after the unix' + ' epoch (1970 January 1).') + if self._next_update is not None and last_update > self._next_update: + raise ValueError( + 'The last update date must be before the next update date.' + ) + return CertificateRevocationListBuilder( + self._issuer_name, last_update, self._next_update, + self._extensions, self._revoked_certificates + ) + + def next_update(self, next_update): + if not isinstance(next_update, datetime.datetime): + raise TypeError('Expecting datetime object.') + if self._next_update is not None: + raise ValueError('Last update may only be set once.') + next_update = _convert_to_naive_utc_time(next_update) + if next_update <= _UNIX_EPOCH: + raise ValueError('The last update date must be after the unix' + ' epoch (1970 January 1).') + if self._last_update is not None and next_update < self._last_update: + raise ValueError( + 'The next update date must be after the last update date.' + ) + return CertificateRevocationListBuilder( + self._issuer_name, self._last_update, next_update, + self._extensions, self._revoked_certificates + ) + + def add_extension(self, extension, critical): + """ + Adds an X.509 extension to the certificate revocation list. + """ + if not isinstance(extension, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extension.oid, critical, extension) + + # TODO: This is quadratic in the number of extensions + for e in self._extensions: + if e.oid == extension.oid: + raise ValueError('This extension has already been set.') + return CertificateRevocationListBuilder( + self._issuer_name, self._last_update, self._next_update, + self._extensions + [extension], self._revoked_certificates + ) + + def add_revoked_certificate(self, revoked_certificate): + """ + Adds a revoked certificate to the CRL. + """ + if not isinstance(revoked_certificate, RevokedCertificate): + raise TypeError("Must be an instance of RevokedCertificate") + + return CertificateRevocationListBuilder( + self._issuer_name, self._last_update, + self._next_update, self._extensions, + self._revoked_certificates + [revoked_certificate] + ) + + def sign(self, private_key, algorithm, backend): + if self._issuer_name is None: + raise ValueError("A CRL must have an issuer name") + + if self._last_update is None: + raise ValueError("A CRL must have a last update time") + + if self._next_update is None: + raise ValueError("A CRL must have a next update time") + + return backend.create_x509_crl(self, private_key, algorithm) + + +class RevokedCertificateBuilder(object): + def __init__(self, serial_number=None, revocation_date=None, + extensions=[]): + self._serial_number = serial_number + self._revocation_date = revocation_date + self._extensions = extensions + + def serial_number(self, number): + if not isinstance(number, six.integer_types): + raise TypeError('Serial number must be of integral type.') + if self._serial_number is not None: + raise ValueError('The serial number may only be set once.') + if number <= 0: + raise ValueError('The serial number should be positive') + + # ASN.1 integers are always signed, so most significant bit must be + # zero. + if utils.bit_length(number) >= 160: # As defined in RFC 5280 + raise ValueError('The serial number should not be more than 159 ' + 'bits.') + return RevokedCertificateBuilder( + number, self._revocation_date, self._extensions + ) + + def revocation_date(self, time): + if not isinstance(time, datetime.datetime): + raise TypeError('Expecting datetime object.') + if self._revocation_date is not None: + raise ValueError('The revocation date may only be set once.') + time = _convert_to_naive_utc_time(time) + if time <= _UNIX_EPOCH: + raise ValueError('The revocation date must be after the unix' + ' epoch (1970 January 1).') + return RevokedCertificateBuilder( + self._serial_number, time, self._extensions + ) + + def add_extension(self, extension, critical): + if not isinstance(extension, ExtensionType): + raise TypeError("extension must be an ExtensionType") + + extension = Extension(extension.oid, critical, extension) + + # TODO: This is quadratic in the number of extensions + for e in self._extensions: + if e.oid == extension.oid: + raise ValueError('This extension has already been set.') + return RevokedCertificateBuilder( + self._serial_number, self._revocation_date, + self._extensions + [extension] + ) + + def build(self, backend): + if self._serial_number is None: + raise ValueError("A revoked certificate must have a serial number") + if self._revocation_date is None: + raise ValueError( + "A revoked certificate must have a revocation date" + ) + + return backend.create_x509_revoked_certificate(self) + + +def random_serial_number(): + return utils.int_from_bytes(os.urandom(20), "big") >> 1 diff --git a/src/cryptography/x509/extensions.py b/src/cryptography/x509/extensions.py new file mode 100644 index 0000000..f7f6fcd --- /dev/null +++ b/src/cryptography/x509/extensions.py @@ -0,0 +1,1212 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc +import datetime +import hashlib +import ipaddress +import warnings +from enum import Enum + +from pyasn1.codec.der import decoder +from pyasn1.type import namedtype, univ + +import six + +from cryptography import utils +from cryptography.hazmat.primitives import constant_time, serialization +from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from cryptography.x509.general_name import GeneralName, IPAddress, OtherName +from cryptography.x509.name import Name, RelativeDistinguishedName +from cryptography.x509.oid import ( + CRLEntryExtensionOID, ExtensionOID, ObjectIdentifier +) + + +class _SubjectPublicKeyInfo(univ.Sequence): + componentType = namedtype.NamedTypes( + namedtype.NamedType('algorithm', univ.Sequence()), + namedtype.NamedType('subjectPublicKey', univ.BitString()) + ) + + +def _key_identifier_from_public_key(public_key): + if isinstance(public_key, RSAPublicKey): + data = public_key.public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.PKCS1, + ) + elif isinstance(public_key, EllipticCurvePublicKey): + data = public_key.public_numbers().encode_point() + else: + # This is a very slow way to do this. + serialized = public_key.public_bytes( + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + spki, remaining = decoder.decode( + serialized, asn1Spec=_SubjectPublicKeyInfo() + ) + assert not remaining + # the univ.BitString object is a tuple of bits. We need bytes and + # pyasn1 really doesn't want to give them to us. To get it we'll + # build an integer and convert that to bytes. + bits = 0 + for bit in spki.getComponentByName("subjectPublicKey"): + bits = bits << 1 | bit + + data = utils.int_to_bytes(bits) + + return hashlib.sha1(data).digest() + + +class DuplicateExtension(Exception): + def __init__(self, msg, oid): + super(DuplicateExtension, self).__init__(msg) + self.oid = oid + + +class UnsupportedExtension(Exception): + def __init__(self, msg, oid): + super(UnsupportedExtension, self).__init__(msg) + self.oid = oid + + +class ExtensionNotFound(Exception): + def __init__(self, msg, oid): + super(ExtensionNotFound, self).__init__(msg) + self.oid = oid + + +@six.add_metaclass(abc.ABCMeta) +class ExtensionType(object): + @abc.abstractproperty + def oid(self): + """ + Returns the oid associated with the given extension type. + """ + + +class Extensions(object): + def __init__(self, extensions): + self._extensions = extensions + + def get_extension_for_oid(self, oid): + for ext in self: + if ext.oid == oid: + return ext + + raise ExtensionNotFound("No {0} extension was found".format(oid), oid) + + def get_extension_for_class(self, extclass): + if extclass is UnrecognizedExtension: + raise TypeError( + "UnrecognizedExtension can't be used with " + "get_extension_for_class because more than one instance of the" + " class may be present." + ) + + for ext in self: + if isinstance(ext.value, extclass): + return ext + + raise ExtensionNotFound( + "No {0} extension was found".format(extclass), extclass.oid + ) + + def __iter__(self): + return iter(self._extensions) + + def __len__(self): + return len(self._extensions) + + def __getitem__(self, idx): + return self._extensions[idx] + + def __repr__(self): + return ( + "".format(self._extensions) + ) + + +@utils.register_interface(ExtensionType) +class CRLNumber(object): + oid = ExtensionOID.CRL_NUMBER + + def __init__(self, crl_number): + if not isinstance(crl_number, six.integer_types): + raise TypeError("crl_number must be an integer") + + self._crl_number = crl_number + + def __eq__(self, other): + if not isinstance(other, CRLNumber): + return NotImplemented + + return self.crl_number == other.crl_number + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash(self.crl_number) + + def __repr__(self): + return "".format(self.crl_number) + + crl_number = utils.read_only_property("_crl_number") + + +@utils.register_interface(ExtensionType) +class AuthorityKeyIdentifier(object): + oid = ExtensionOID.AUTHORITY_KEY_IDENTIFIER + + def __init__(self, key_identifier, authority_cert_issuer, + authority_cert_serial_number): + if (authority_cert_issuer is None) != ( + authority_cert_serial_number is None + ): + raise ValueError( + "authority_cert_issuer and authority_cert_serial_number " + "must both be present or both None" + ) + + if authority_cert_issuer is not None: + authority_cert_issuer = list(authority_cert_issuer) + if not all( + isinstance(x, GeneralName) for x in authority_cert_issuer + ): + raise TypeError( + "authority_cert_issuer must be a list of GeneralName " + "objects" + ) + + if authority_cert_serial_number is not None and not isinstance( + authority_cert_serial_number, six.integer_types + ): + raise TypeError( + "authority_cert_serial_number must be an integer" + ) + + self._key_identifier = key_identifier + self._authority_cert_issuer = authority_cert_issuer + self._authority_cert_serial_number = authority_cert_serial_number + + @classmethod + def from_issuer_public_key(cls, public_key): + digest = _key_identifier_from_public_key(public_key) + return cls( + key_identifier=digest, + authority_cert_issuer=None, + authority_cert_serial_number=None + ) + + @classmethod + def from_issuer_subject_key_identifier(cls, ski): + return cls( + key_identifier=ski.value.digest, + authority_cert_issuer=None, + authority_cert_serial_number=None + ) + + def __repr__(self): + return ( + "".format(self) + ) + + def __eq__(self, other): + if not isinstance(other, AuthorityKeyIdentifier): + return NotImplemented + + return ( + self.key_identifier == other.key_identifier and + self.authority_cert_issuer == other.authority_cert_issuer and + self.authority_cert_serial_number == + other.authority_cert_serial_number + ) + + def __ne__(self, other): + return not self == other + + key_identifier = utils.read_only_property("_key_identifier") + authority_cert_issuer = utils.read_only_property("_authority_cert_issuer") + authority_cert_serial_number = utils.read_only_property( + "_authority_cert_serial_number" + ) + + +@utils.register_interface(ExtensionType) +class SubjectKeyIdentifier(object): + oid = ExtensionOID.SUBJECT_KEY_IDENTIFIER + + def __init__(self, digest): + self._digest = digest + + @classmethod + def from_public_key(cls, public_key): + return cls(_key_identifier_from_public_key(public_key)) + + digest = utils.read_only_property("_digest") + + def __repr__(self): + return "".format(self.digest) + + def __eq__(self, other): + if not isinstance(other, SubjectKeyIdentifier): + return NotImplemented + + return constant_time.bytes_eq(self.digest, other.digest) + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash(self.digest) + + +@utils.register_interface(ExtensionType) +class AuthorityInformationAccess(object): + oid = ExtensionOID.AUTHORITY_INFORMATION_ACCESS + + def __init__(self, descriptions): + descriptions = list(descriptions) + if not all(isinstance(x, AccessDescription) for x in descriptions): + raise TypeError( + "Every item in the descriptions list must be an " + "AccessDescription" + ) + + self._descriptions = descriptions + + def __iter__(self): + return iter(self._descriptions) + + def __len__(self): + return len(self._descriptions) + + def __repr__(self): + return "".format(self._descriptions) + + def __eq__(self, other): + if not isinstance(other, AuthorityInformationAccess): + return NotImplemented + + return self._descriptions == other._descriptions + + def __ne__(self, other): + return not self == other + + def __getitem__(self, idx): + return self._descriptions[idx] + + +class AccessDescription(object): + def __init__(self, access_method, access_location): + if not isinstance(access_method, ObjectIdentifier): + raise TypeError("access_method must be an ObjectIdentifier") + + if not isinstance(access_location, GeneralName): + raise TypeError("access_location must be a GeneralName") + + self._access_method = access_method + self._access_location = access_location + + def __repr__(self): + return ( + "".format(self) + ) + + def __eq__(self, other): + if not isinstance(other, AccessDescription): + return NotImplemented + + return ( + self.access_method == other.access_method and + self.access_location == other.access_location + ) + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.access_method, self.access_location)) + + access_method = utils.read_only_property("_access_method") + access_location = utils.read_only_property("_access_location") + + +@utils.register_interface(ExtensionType) +class BasicConstraints(object): + oid = ExtensionOID.BASIC_CONSTRAINTS + + def __init__(self, ca, path_length): + if not isinstance(ca, bool): + raise TypeError("ca must be a boolean value") + + if path_length is not None and not ca: + raise ValueError("path_length must be None when ca is False") + + if ( + path_length is not None and + (not isinstance(path_length, six.integer_types) or path_length < 0) + ): + raise TypeError( + "path_length must be a non-negative integer or None" + ) + + self._ca = ca + self._path_length = path_length + + ca = utils.read_only_property("_ca") + path_length = utils.read_only_property("_path_length") + + def __repr__(self): + return ("").format(self) + + def __eq__(self, other): + if not isinstance(other, BasicConstraints): + return NotImplemented + + return self.ca == other.ca and self.path_length == other.path_length + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.ca, self.path_length)) + + +@utils.register_interface(ExtensionType) +class CRLDistributionPoints(object): + oid = ExtensionOID.CRL_DISTRIBUTION_POINTS + + def __init__(self, distribution_points): + distribution_points = list(distribution_points) + if not all( + isinstance(x, DistributionPoint) for x in distribution_points + ): + raise TypeError( + "distribution_points must be a list of DistributionPoint " + "objects" + ) + + self._distribution_points = distribution_points + + def __iter__(self): + return iter(self._distribution_points) + + def __len__(self): + return len(self._distribution_points) + + def __repr__(self): + return "".format(self._distribution_points) + + def __eq__(self, other): + if not isinstance(other, CRLDistributionPoints): + return NotImplemented + + return self._distribution_points == other._distribution_points + + def __ne__(self, other): + return not self == other + + def __getitem__(self, idx): + return self._distribution_points[idx] + + +class DistributionPoint(object): + def __init__(self, full_name, relative_name, reasons, crl_issuer): + if full_name and relative_name: + raise ValueError( + "You cannot provide both full_name and relative_name, at " + "least one must be None." + ) + + if full_name: + full_name = list(full_name) + if not all(isinstance(x, GeneralName) for x in full_name): + raise TypeError( + "full_name must be a list of GeneralName objects" + ) + + if relative_name: + if isinstance(relative_name, Name): + warnings.warn( + "relative_name= is deprecated and will " + "be removed in a future version; use " + " instead.", + utils.DeprecatedIn16, + stacklevel=2 + ) + relative_name = RelativeDistinguishedName(relative_name) + elif not isinstance(relative_name, RelativeDistinguishedName): + raise TypeError( + "relative_name must be a RelativeDistinguishedName" + ) + + if crl_issuer: + crl_issuer = list(crl_issuer) + if not all(isinstance(x, GeneralName) for x in crl_issuer): + raise TypeError( + "crl_issuer must be None or a list of general names" + ) + + if reasons and (not isinstance(reasons, frozenset) or not all( + isinstance(x, ReasonFlags) for x in reasons + )): + raise TypeError("reasons must be None or frozenset of ReasonFlags") + + if reasons and ( + ReasonFlags.unspecified in reasons or + ReasonFlags.remove_from_crl in reasons + ): + raise ValueError( + "unspecified and remove_from_crl are not valid reasons in a " + "DistributionPoint" + ) + + if reasons and not crl_issuer and not (full_name or relative_name): + raise ValueError( + "You must supply crl_issuer, full_name, or relative_name when " + "reasons is not None" + ) + + self._full_name = full_name + self._relative_name = relative_name + self._reasons = reasons + self._crl_issuer = crl_issuer + + def __repr__(self): + return ( + "".format(self) + ) + + def __eq__(self, other): + if not isinstance(other, DistributionPoint): + return NotImplemented + + return ( + self.full_name == other.full_name and + self.relative_name == other.relative_name and + self.reasons == other.reasons and + self.crl_issuer == other.crl_issuer + ) + + def __ne__(self, other): + return not self == other + + full_name = utils.read_only_property("_full_name") + relative_name = utils.read_only_property("_relative_name") + reasons = utils.read_only_property("_reasons") + crl_issuer = utils.read_only_property("_crl_issuer") + + +class ReasonFlags(Enum): + unspecified = "unspecified" + key_compromise = "keyCompromise" + ca_compromise = "cACompromise" + affiliation_changed = "affiliationChanged" + superseded = "superseded" + cessation_of_operation = "cessationOfOperation" + certificate_hold = "certificateHold" + privilege_withdrawn = "privilegeWithdrawn" + aa_compromise = "aACompromise" + remove_from_crl = "removeFromCRL" + + +@utils.register_interface(ExtensionType) +class PolicyConstraints(object): + oid = ExtensionOID.POLICY_CONSTRAINTS + + def __init__(self, require_explicit_policy, inhibit_policy_mapping): + if require_explicit_policy is not None and not isinstance( + require_explicit_policy, six.integer_types + ): + raise TypeError( + "require_explicit_policy must be a non-negative integer or " + "None" + ) + + if inhibit_policy_mapping is not None and not isinstance( + inhibit_policy_mapping, six.integer_types + ): + raise TypeError( + "inhibit_policy_mapping must be a non-negative integer or None" + ) + + if inhibit_policy_mapping is None and require_explicit_policy is None: + raise ValueError( + "At least one of require_explicit_policy and " + "inhibit_policy_mapping must not be None" + ) + + self._require_explicit_policy = require_explicit_policy + self._inhibit_policy_mapping = inhibit_policy_mapping + + def __repr__(self): + return ( + u"".format(self) + ) + + def __eq__(self, other): + if not isinstance(other, PolicyConstraints): + return NotImplemented + + return ( + self.require_explicit_policy == other.require_explicit_policy and + self.inhibit_policy_mapping == other.inhibit_policy_mapping + ) + + def __ne__(self, other): + return not self == other + + require_explicit_policy = utils.read_only_property( + "_require_explicit_policy" + ) + inhibit_policy_mapping = utils.read_only_property( + "_inhibit_policy_mapping" + ) + + +@utils.register_interface(ExtensionType) +class CertificatePolicies(object): + oid = ExtensionOID.CERTIFICATE_POLICIES + + def __init__(self, policies): + policies = list(policies) + if not all(isinstance(x, PolicyInformation) for x in policies): + raise TypeError( + "Every item in the policies list must be a " + "PolicyInformation" + ) + + self._policies = policies + + def __iter__(self): + return iter(self._policies) + + def __len__(self): + return len(self._policies) + + def __repr__(self): + return "".format(self._policies) + + def __eq__(self, other): + if not isinstance(other, CertificatePolicies): + return NotImplemented + + return self._policies == other._policies + + def __ne__(self, other): + return not self == other + + def __getitem__(self, idx): + return self._policies[idx] + + +class PolicyInformation(object): + def __init__(self, policy_identifier, policy_qualifiers): + if not isinstance(policy_identifier, ObjectIdentifier): + raise TypeError("policy_identifier must be an ObjectIdentifier") + + self._policy_identifier = policy_identifier + + if policy_qualifiers: + policy_qualifiers = list(policy_qualifiers) + if not all( + isinstance(x, (six.text_type, UserNotice)) + for x in policy_qualifiers + ): + raise TypeError( + "policy_qualifiers must be a list of strings and/or " + "UserNotice objects or None" + ) + + self._policy_qualifiers = policy_qualifiers + + def __repr__(self): + return ( + "".format(self) + ) + + def __eq__(self, other): + if not isinstance(other, PolicyInformation): + return NotImplemented + + return ( + self.policy_identifier == other.policy_identifier and + self.policy_qualifiers == other.policy_qualifiers + ) + + def __ne__(self, other): + return not self == other + + policy_identifier = utils.read_only_property("_policy_identifier") + policy_qualifiers = utils.read_only_property("_policy_qualifiers") + + +class UserNotice(object): + def __init__(self, notice_reference, explicit_text): + if notice_reference and not isinstance( + notice_reference, NoticeReference + ): + raise TypeError( + "notice_reference must be None or a NoticeReference" + ) + + self._notice_reference = notice_reference + self._explicit_text = explicit_text + + def __repr__(self): + return ( + "".format(self) + ) + + def __eq__(self, other): + if not isinstance(other, UserNotice): + return NotImplemented + + return ( + self.notice_reference == other.notice_reference and + self.explicit_text == other.explicit_text + ) + + def __ne__(self, other): + return not self == other + + notice_reference = utils.read_only_property("_notice_reference") + explicit_text = utils.read_only_property("_explicit_text") + + +class NoticeReference(object): + def __init__(self, organization, notice_numbers): + self._organization = organization + notice_numbers = list(notice_numbers) + if not all(isinstance(x, int) for x in notice_numbers): + raise TypeError( + "notice_numbers must be a list of integers" + ) + + self._notice_numbers = notice_numbers + + def __repr__(self): + return ( + "".format(self) + ) + + def __eq__(self, other): + if not isinstance(other, NoticeReference): + return NotImplemented + + return ( + self.organization == other.organization and + self.notice_numbers == other.notice_numbers + ) + + def __ne__(self, other): + return not self == other + + organization = utils.read_only_property("_organization") + notice_numbers = utils.read_only_property("_notice_numbers") + + +@utils.register_interface(ExtensionType) +class ExtendedKeyUsage(object): + oid = ExtensionOID.EXTENDED_KEY_USAGE + + def __init__(self, usages): + usages = list(usages) + if not all(isinstance(x, ObjectIdentifier) for x in usages): + raise TypeError( + "Every item in the usages list must be an ObjectIdentifier" + ) + + self._usages = usages + + def __iter__(self): + return iter(self._usages) + + def __len__(self): + return len(self._usages) + + def __repr__(self): + return "".format(self._usages) + + def __eq__(self, other): + if not isinstance(other, ExtendedKeyUsage): + return NotImplemented + + return self._usages == other._usages + + def __ne__(self, other): + return not self == other + + +@utils.register_interface(ExtensionType) +class OCSPNoCheck(object): + oid = ExtensionOID.OCSP_NO_CHECK + + +@utils.register_interface(ExtensionType) +class InhibitAnyPolicy(object): + oid = ExtensionOID.INHIBIT_ANY_POLICY + + def __init__(self, skip_certs): + if not isinstance(skip_certs, six.integer_types): + raise TypeError("skip_certs must be an integer") + + if skip_certs < 0: + raise ValueError("skip_certs must be a non-negative integer") + + self._skip_certs = skip_certs + + def __repr__(self): + return "".format(self) + + def __eq__(self, other): + if not isinstance(other, InhibitAnyPolicy): + return NotImplemented + + return self.skip_certs == other.skip_certs + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash(self.skip_certs) + + skip_certs = utils.read_only_property("_skip_certs") + + +@utils.register_interface(ExtensionType) +class KeyUsage(object): + oid = ExtensionOID.KEY_USAGE + + def __init__(self, digital_signature, content_commitment, key_encipherment, + data_encipherment, key_agreement, key_cert_sign, crl_sign, + encipher_only, decipher_only): + if not key_agreement and (encipher_only or decipher_only): + raise ValueError( + "encipher_only and decipher_only can only be true when " + "key_agreement is true" + ) + + self._digital_signature = digital_signature + self._content_commitment = content_commitment + self._key_encipherment = key_encipherment + self._data_encipherment = data_encipherment + self._key_agreement = key_agreement + self._key_cert_sign = key_cert_sign + self._crl_sign = crl_sign + self._encipher_only = encipher_only + self._decipher_only = decipher_only + + digital_signature = utils.read_only_property("_digital_signature") + content_commitment = utils.read_only_property("_content_commitment") + key_encipherment = utils.read_only_property("_key_encipherment") + data_encipherment = utils.read_only_property("_data_encipherment") + key_agreement = utils.read_only_property("_key_agreement") + key_cert_sign = utils.read_only_property("_key_cert_sign") + crl_sign = utils.read_only_property("_crl_sign") + + @property + def encipher_only(self): + if not self.key_agreement: + raise ValueError( + "encipher_only is undefined unless key_agreement is true" + ) + else: + return self._encipher_only + + @property + def decipher_only(self): + if not self.key_agreement: + raise ValueError( + "decipher_only is undefined unless key_agreement is true" + ) + else: + return self._decipher_only + + def __repr__(self): + try: + encipher_only = self.encipher_only + decipher_only = self.decipher_only + except ValueError: + encipher_only = None + decipher_only = None + + return ("").format( + self, encipher_only, decipher_only) + + def __eq__(self, other): + if not isinstance(other, KeyUsage): + return NotImplemented + + return ( + self.digital_signature == other.digital_signature and + self.content_commitment == other.content_commitment and + self.key_encipherment == other.key_encipherment and + self.data_encipherment == other.data_encipherment and + self.key_agreement == other.key_agreement and + self.key_cert_sign == other.key_cert_sign and + self.crl_sign == other.crl_sign and + self._encipher_only == other._encipher_only and + self._decipher_only == other._decipher_only + ) + + def __ne__(self, other): + return not self == other + + +@utils.register_interface(ExtensionType) +class NameConstraints(object): + oid = ExtensionOID.NAME_CONSTRAINTS + + def __init__(self, permitted_subtrees, excluded_subtrees): + if permitted_subtrees is not None: + permitted_subtrees = list(permitted_subtrees) + if not all( + isinstance(x, GeneralName) for x in permitted_subtrees + ): + raise TypeError( + "permitted_subtrees must be a list of GeneralName objects " + "or None" + ) + + self._validate_ip_name(permitted_subtrees) + + if excluded_subtrees is not None: + excluded_subtrees = list(excluded_subtrees) + if not all( + isinstance(x, GeneralName) for x in excluded_subtrees + ): + raise TypeError( + "excluded_subtrees must be a list of GeneralName objects " + "or None" + ) + + self._validate_ip_name(excluded_subtrees) + + if permitted_subtrees is None and excluded_subtrees is None: + raise ValueError( + "At least one of permitted_subtrees and excluded_subtrees " + "must not be None" + ) + + self._permitted_subtrees = permitted_subtrees + self._excluded_subtrees = excluded_subtrees + + def __eq__(self, other): + if not isinstance(other, NameConstraints): + return NotImplemented + + return ( + self.excluded_subtrees == other.excluded_subtrees and + self.permitted_subtrees == other.permitted_subtrees + ) + + def __ne__(self, other): + return not self == other + + def _validate_ip_name(self, tree): + if any(isinstance(name, IPAddress) and not isinstance( + name.value, (ipaddress.IPv4Network, ipaddress.IPv6Network) + ) for name in tree): + raise TypeError( + "IPAddress name constraints must be an IPv4Network or" + " IPv6Network object" + ) + + def __repr__(self): + return ( + u"".format(self) + ) + + permitted_subtrees = utils.read_only_property("_permitted_subtrees") + excluded_subtrees = utils.read_only_property("_excluded_subtrees") + + +class Extension(object): + def __init__(self, oid, critical, value): + if not isinstance(oid, ObjectIdentifier): + raise TypeError( + "oid argument must be an ObjectIdentifier instance." + ) + + if not isinstance(critical, bool): + raise TypeError("critical must be a boolean value") + + self._oid = oid + self._critical = critical + self._value = value + + oid = utils.read_only_property("_oid") + critical = utils.read_only_property("_critical") + value = utils.read_only_property("_value") + + def __repr__(self): + return ("").format(self) + + def __eq__(self, other): + if not isinstance(other, Extension): + return NotImplemented + + return ( + self.oid == other.oid and + self.critical == other.critical and + self.value == other.value + ) + + def __ne__(self, other): + return not self == other + + +class GeneralNames(object): + def __init__(self, general_names): + general_names = list(general_names) + if not all(isinstance(x, GeneralName) for x in general_names): + raise TypeError( + "Every item in the general_names list must be an " + "object conforming to the GeneralName interface" + ) + + self._general_names = general_names + + def __iter__(self): + return iter(self._general_names) + + def __len__(self): + return len(self._general_names) + + def get_values_for_type(self, type): + # Return the value of each GeneralName, except for OtherName instances + # which we return directly because it has two important properties not + # just one value. + objs = (i for i in self if isinstance(i, type)) + if type != OtherName: + objs = (i.value for i in objs) + return list(objs) + + def __repr__(self): + return "".format(self._general_names) + + def __eq__(self, other): + if not isinstance(other, GeneralNames): + return NotImplemented + + return self._general_names == other._general_names + + def __ne__(self, other): + return not self == other + + def __getitem__(self, idx): + return self._general_names[idx] + + +@utils.register_interface(ExtensionType) +class SubjectAlternativeName(object): + oid = ExtensionOID.SUBJECT_ALTERNATIVE_NAME + + def __init__(self, general_names): + self._general_names = GeneralNames(general_names) + + def __iter__(self): + return iter(self._general_names) + + def __len__(self): + return len(self._general_names) + + def get_values_for_type(self, type): + return self._general_names.get_values_for_type(type) + + def __repr__(self): + return "".format(self._general_names) + + def __eq__(self, other): + if not isinstance(other, SubjectAlternativeName): + return NotImplemented + + return self._general_names == other._general_names + + def __getitem__(self, idx): + return self._general_names[idx] + + def __ne__(self, other): + return not self == other + + +@utils.register_interface(ExtensionType) +class IssuerAlternativeName(object): + oid = ExtensionOID.ISSUER_ALTERNATIVE_NAME + + def __init__(self, general_names): + self._general_names = GeneralNames(general_names) + + def __iter__(self): + return iter(self._general_names) + + def __len__(self): + return len(self._general_names) + + def get_values_for_type(self, type): + return self._general_names.get_values_for_type(type) + + def __repr__(self): + return "".format(self._general_names) + + def __eq__(self, other): + if not isinstance(other, IssuerAlternativeName): + return NotImplemented + + return self._general_names == other._general_names + + def __ne__(self, other): + return not self == other + + def __getitem__(self, idx): + return self._general_names[idx] + + +@utils.register_interface(ExtensionType) +class CertificateIssuer(object): + oid = CRLEntryExtensionOID.CERTIFICATE_ISSUER + + def __init__(self, general_names): + self._general_names = GeneralNames(general_names) + + def __iter__(self): + return iter(self._general_names) + + def __len__(self): + return len(self._general_names) + + def get_values_for_type(self, type): + return self._general_names.get_values_for_type(type) + + def __repr__(self): + return "".format(self._general_names) + + def __eq__(self, other): + if not isinstance(other, CertificateIssuer): + return NotImplemented + + return self._general_names == other._general_names + + def __ne__(self, other): + return not self == other + + def __getitem__(self, idx): + return self._general_names[idx] + + +@utils.register_interface(ExtensionType) +class CRLReason(object): + oid = CRLEntryExtensionOID.CRL_REASON + + def __init__(self, reason): + if not isinstance(reason, ReasonFlags): + raise TypeError("reason must be an element from ReasonFlags") + + self._reason = reason + + def __repr__(self): + return "".format(self._reason) + + def __eq__(self, other): + if not isinstance(other, CRLReason): + return NotImplemented + + return self.reason == other.reason + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash(self.reason) + + reason = utils.read_only_property("_reason") + + +@utils.register_interface(ExtensionType) +class InvalidityDate(object): + oid = CRLEntryExtensionOID.INVALIDITY_DATE + + def __init__(self, invalidity_date): + if not isinstance(invalidity_date, datetime.datetime): + raise TypeError("invalidity_date must be a datetime.datetime") + + self._invalidity_date = invalidity_date + + def __repr__(self): + return "".format( + self._invalidity_date + ) + + def __eq__(self, other): + if not isinstance(other, InvalidityDate): + return NotImplemented + + return self.invalidity_date == other.invalidity_date + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash(self.invalidity_date) + + invalidity_date = utils.read_only_property("_invalidity_date") + + +@utils.register_interface(ExtensionType) +class UnrecognizedExtension(object): + def __init__(self, oid, value): + if not isinstance(oid, ObjectIdentifier): + raise TypeError("oid must be an ObjectIdentifier") + self._oid = oid + self._value = value + + oid = utils.read_only_property("_oid") + value = utils.read_only_property("_value") + + def __repr__(self): + return ( + "".format( + self + ) + ) + + def __eq__(self, other): + if not isinstance(other, UnrecognizedExtension): + return NotImplemented + + return self.oid == other.oid and self.value == other.value + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.oid, self.value)) diff --git a/src/cryptography/x509/general_name.py b/src/cryptography/x509/general_name.py new file mode 100644 index 0000000..6745243 --- /dev/null +++ b/src/cryptography/x509/general_name.py @@ -0,0 +1,271 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import abc +import ipaddress +from email.utils import parseaddr + +import idna + +import six + +from six.moves import urllib_parse + +from cryptography import utils +from cryptography.x509.name import Name +from cryptography.x509.oid import ObjectIdentifier + + +_GENERAL_NAMES = { + 0: "otherName", + 1: "rfc822Name", + 2: "dNSName", + 3: "x400Address", + 4: "directoryName", + 5: "ediPartyName", + 6: "uniformResourceIdentifier", + 7: "iPAddress", + 8: "registeredID", +} + + +class UnsupportedGeneralNameType(Exception): + def __init__(self, msg, type): + super(UnsupportedGeneralNameType, self).__init__(msg) + self.type = type + + +@six.add_metaclass(abc.ABCMeta) +class GeneralName(object): + @abc.abstractproperty + def value(self): + """ + Return the value of the object + """ + + +@utils.register_interface(GeneralName) +class RFC822Name(object): + def __init__(self, value): + 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 ) 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") + + def __repr__(self): + return "".format(self.value) + + def __eq__(self, other): + if not isinstance(other, RFC822Name): + return NotImplemented + + return self.value == other.value + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash(self.value) + + +@utils.register_interface(GeneralName) +class DNSName(object): + def __init__(self, value): + if not isinstance(value, six.text_type): + raise TypeError("value must be a unicode string") + + self._value = value + + value = utils.read_only_property("_value") + + def __repr__(self): + return "".format(self.value) + + def __eq__(self, other): + if not isinstance(other, DNSName): + return NotImplemented + + return self.value == other.value + + def __ne__(self, other): + return not self == other + + +@utils.register_interface(GeneralName) +class UniformResourceIdentifier(object): + def __init__(self, value): + if not isinstance(value, six.text_type): + raise TypeError("value must be a unicode string") + + parsed = urllib_parse.urlparse(value) + if not parsed.hostname: + netloc = "" + elif parsed.port: + netloc = ( + idna.encode(parsed.hostname) + + ":{0}".format(parsed.port).encode("ascii") + ).decode("ascii") + else: + netloc = idna.encode(parsed.hostname).decode("ascii") + + # Note that building a URL in this fashion means it should be + # semantically indistinguishable from the original but is not + # guaranteed to be exactly the same. + uri = urllib_parse.urlunparse(( + parsed.scheme, + netloc, + parsed.path, + parsed.params, + parsed.query, + parsed.fragment + )).encode("ascii") + + self._value = value + self._encoded = uri + + value = utils.read_only_property("_value") + + def __repr__(self): + return "".format(self.value) + + def __eq__(self, other): + if not isinstance(other, UniformResourceIdentifier): + return NotImplemented + + return self.value == other.value + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash(self.value) + + +@utils.register_interface(GeneralName) +class DirectoryName(object): + def __init__(self, value): + if not isinstance(value, Name): + raise TypeError("value must be a Name") + + self._value = value + + value = utils.read_only_property("_value") + + def __repr__(self): + return "".format(self.value) + + def __eq__(self, other): + if not isinstance(other, DirectoryName): + return NotImplemented + + return self.value == other.value + + def __ne__(self, other): + return not self == other + + +@utils.register_interface(GeneralName) +class RegisteredID(object): + def __init__(self, value): + if not isinstance(value, ObjectIdentifier): + raise TypeError("value must be an ObjectIdentifier") + + self._value = value + + value = utils.read_only_property("_value") + + def __repr__(self): + return "".format(self.value) + + def __eq__(self, other): + if not isinstance(other, RegisteredID): + return NotImplemented + + return self.value == other.value + + def __ne__(self, other): + return not self == other + + +@utils.register_interface(GeneralName) +class IPAddress(object): + def __init__(self, value): + if not isinstance( + value, + ( + ipaddress.IPv4Address, + ipaddress.IPv6Address, + ipaddress.IPv4Network, + ipaddress.IPv6Network + ) + ): + raise TypeError( + "value must be an instance of ipaddress.IPv4Address, " + "ipaddress.IPv6Address, ipaddress.IPv4Network, or " + "ipaddress.IPv6Network" + ) + + self._value = value + + value = utils.read_only_property("_value") + + def __repr__(self): + return "".format(self.value) + + def __eq__(self, other): + if not isinstance(other, IPAddress): + return NotImplemented + + return self.value == other.value + + def __ne__(self, other): + return not self == other + + +@utils.register_interface(GeneralName) +class OtherName(object): + def __init__(self, type_id, value): + if not isinstance(type_id, ObjectIdentifier): + raise TypeError("type_id must be an ObjectIdentifier") + if not isinstance(value, bytes): + raise TypeError("value must be a binary string") + + self._type_id = type_id + self._value = value + + type_id = utils.read_only_property("_type_id") + value = utils.read_only_property("_value") + + def __repr__(self): + return "".format( + self.type_id, self.value) + + def __eq__(self, other): + if not isinstance(other, OtherName): + return NotImplemented + + return self.type_id == other.type_id and self.value == other.value + + def __ne__(self, other): + return not self == other diff --git a/src/cryptography/x509/name.py b/src/cryptography/x509/name.py new file mode 100644 index 0000000..277128f --- /dev/null +++ b/src/cryptography/x509/name.py @@ -0,0 +1,138 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import six + +from cryptography import utils +from cryptography.x509.oid import NameOID, ObjectIdentifier + + +class NameAttribute(object): + def __init__(self, oid, value): + if not isinstance(oid, ObjectIdentifier): + raise TypeError( + "oid argument must be an ObjectIdentifier instance." + ) + + if not isinstance(value, six.text_type): + raise TypeError( + "value argument must be a text type." + ) + + if oid == NameOID.COUNTRY_NAME and len(value.encode("utf8")) != 2: + raise ValueError( + "Country name must be a 2 character country code" + ) + + self._oid = oid + self._value = value + + oid = utils.read_only_property("_oid") + value = utils.read_only_property("_value") + + def __eq__(self, other): + if not isinstance(other, NameAttribute): + return NotImplemented + + return ( + self.oid == other.oid and + self.value == other.value + ) + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash((self.oid, self.value)) + + def __repr__(self): + return "".format(self) + + +class RelativeDistinguishedName(object): + def __init__(self, attributes): + attributes = frozenset(attributes) + if not attributes: + raise ValueError("a relative distinguished name cannot be empty") + if not all(isinstance(x, NameAttribute) for x in attributes): + raise TypeError("attributes must be an iterable of NameAttribute") + + self._attributes = attributes + + def get_attributes_for_oid(self, oid): + return [i for i in self if i.oid == oid] + + def __eq__(self, other): + if not isinstance(other, RelativeDistinguishedName): + return NotImplemented + + return self._attributes == other._attributes + + def __ne__(self, other): + return not self == other + + def __hash__(self): + return hash(self._attributes) + + def __iter__(self): + return iter(self._attributes) + + def __len__(self): + return len(self._attributes) + + def __repr__(self): + return "".format(list(self)) + + +class Name(object): + def __init__(self, attributes): + attributes = list(attributes) + if all(isinstance(x, NameAttribute) for x in attributes): + self._attributes = [ + RelativeDistinguishedName([x]) for x in attributes + ] + elif all(isinstance(x, RelativeDistinguishedName) for x in attributes): + self._attributes = attributes + else: + raise TypeError( + "attributes must be a list of NameAttribute" + " or a list RelativeDistinguishedName" + ) + + def get_attributes_for_oid(self, oid): + return [i for i in self if i.oid == oid] + + @property + def rdns(self): + return self._attributes + + def public_bytes(self, backend): + return backend.x509_name_bytes(self) + + def __eq__(self, other): + if not isinstance(other, Name): + return NotImplemented + + return self._attributes == other._attributes + + def __ne__(self, other): + return not self == other + + def __hash__(self): + # TODO: this is relatively expensive, if this looks like a bottleneck + # for you, consider optimizing! + return hash(tuple(self._attributes)) + + def __iter__(self): + for rdn in self._attributes: + for ava in rdn: + yield ava + + def __len__(self): + return sum(len(rdn) for rdn in self._attributes) + + def __repr__(self): + return "".format(list(self)) diff --git a/src/cryptography/x509/oid.py b/src/cryptography/x509/oid.py new file mode 100644 index 0000000..3edac72 --- /dev/null +++ b/src/cryptography/x509/oid.py @@ -0,0 +1,258 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.hazmat.primitives import hashes + + +class ObjectIdentifier(object): + def __init__(self, dotted_string): + self._dotted_string = dotted_string + + nodes = self._dotted_string.split(".") + intnodes = [] + + # There must be at least 2 nodes, the first node must be 0..2, and + # if less than 2, the second node cannot have a value outside the + # range 0..39. All nodes must be integers. + for node in nodes: + try: + intnodes.append(int(node, 0)) + except ValueError: + raise ValueError( + "Malformed OID: %s (non-integer nodes)" % ( + self._dotted_string)) + + if len(nodes) < 2: + raise ValueError( + "Malformed OID: %s (insufficient number of nodes)" % ( + self._dotted_string)) + + if intnodes[0] > 2: + raise ValueError( + "Malformed OID: %s (first node outside valid range)" % ( + self._dotted_string)) + + if intnodes[0] < 2 and intnodes[1] >= 40: + raise ValueError( + "Malformed OID: %s (second node outside valid range)" % ( + self._dotted_string)) + + def __eq__(self, other): + if not isinstance(other, ObjectIdentifier): + return NotImplemented + + return self.dotted_string == other.dotted_string + + def __ne__(self, other): + return not self == other + + def __repr__(self): + return "".format( + self.dotted_string, + self._name + ) + + def __hash__(self): + return hash(self.dotted_string) + + @property + def _name(self): + return _OID_NAMES.get(self, "Unknown OID") + + dotted_string = utils.read_only_property("_dotted_string") + + +class ExtensionOID(object): + SUBJECT_DIRECTORY_ATTRIBUTES = ObjectIdentifier("2.5.29.9") + SUBJECT_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.14") + KEY_USAGE = ObjectIdentifier("2.5.29.15") + SUBJECT_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.17") + ISSUER_ALTERNATIVE_NAME = ObjectIdentifier("2.5.29.18") + BASIC_CONSTRAINTS = ObjectIdentifier("2.5.29.19") + NAME_CONSTRAINTS = ObjectIdentifier("2.5.29.30") + CRL_DISTRIBUTION_POINTS = ObjectIdentifier("2.5.29.31") + CERTIFICATE_POLICIES = ObjectIdentifier("2.5.29.32") + POLICY_MAPPINGS = ObjectIdentifier("2.5.29.33") + AUTHORITY_KEY_IDENTIFIER = ObjectIdentifier("2.5.29.35") + POLICY_CONSTRAINTS = ObjectIdentifier("2.5.29.36") + EXTENDED_KEY_USAGE = ObjectIdentifier("2.5.29.37") + FRESHEST_CRL = ObjectIdentifier("2.5.29.46") + INHIBIT_ANY_POLICY = ObjectIdentifier("2.5.29.54") + AUTHORITY_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.1") + SUBJECT_INFORMATION_ACCESS = ObjectIdentifier("1.3.6.1.5.5.7.1.11") + OCSP_NO_CHECK = ObjectIdentifier("1.3.6.1.5.5.7.48.1.5") + CRL_NUMBER = ObjectIdentifier("2.5.29.20") + + +class CRLEntryExtensionOID(object): + CERTIFICATE_ISSUER = ObjectIdentifier("2.5.29.29") + CRL_REASON = ObjectIdentifier("2.5.29.21") + INVALIDITY_DATE = ObjectIdentifier("2.5.29.24") + + +class NameOID(object): + COMMON_NAME = ObjectIdentifier("2.5.4.3") + COUNTRY_NAME = ObjectIdentifier("2.5.4.6") + LOCALITY_NAME = ObjectIdentifier("2.5.4.7") + STATE_OR_PROVINCE_NAME = ObjectIdentifier("2.5.4.8") + STREET_ADDRESS = ObjectIdentifier("2.5.4.9") + ORGANIZATION_NAME = ObjectIdentifier("2.5.4.10") + ORGANIZATIONAL_UNIT_NAME = ObjectIdentifier("2.5.4.11") + SERIAL_NUMBER = ObjectIdentifier("2.5.4.5") + SURNAME = ObjectIdentifier("2.5.4.4") + GIVEN_NAME = ObjectIdentifier("2.5.4.42") + TITLE = ObjectIdentifier("2.5.4.12") + GENERATION_QUALIFIER = ObjectIdentifier("2.5.4.44") + X500_UNIQUE_IDENTIFIER = ObjectIdentifier("2.5.4.45") + DN_QUALIFIER = ObjectIdentifier("2.5.4.46") + PSEUDONYM = ObjectIdentifier("2.5.4.65") + USER_ID = ObjectIdentifier("0.9.2342.19200300.100.1.1") + DOMAIN_COMPONENT = ObjectIdentifier("0.9.2342.19200300.100.1.25") + EMAIL_ADDRESS = ObjectIdentifier("1.2.840.113549.1.9.1") + JURISDICTION_COUNTRY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.3") + JURISDICTION_LOCALITY_NAME = ObjectIdentifier("1.3.6.1.4.1.311.60.2.1.1") + JURISDICTION_STATE_OR_PROVINCE_NAME = ObjectIdentifier( + "1.3.6.1.4.1.311.60.2.1.2" + ) + BUSINESS_CATEGORY = ObjectIdentifier("2.5.4.15") + POSTAL_ADDRESS = ObjectIdentifier("2.5.4.16") + POSTAL_CODE = ObjectIdentifier("2.5.4.17") + + +class SignatureAlgorithmOID(object): + RSA_WITH_MD5 = ObjectIdentifier("1.2.840.113549.1.1.4") + RSA_WITH_SHA1 = ObjectIdentifier("1.2.840.113549.1.1.5") + # This is an alternate OID for RSA with SHA1 that is occasionally seen + _RSA_WITH_SHA1 = ObjectIdentifier("1.3.14.3.2.29") + RSA_WITH_SHA224 = ObjectIdentifier("1.2.840.113549.1.1.14") + RSA_WITH_SHA256 = ObjectIdentifier("1.2.840.113549.1.1.11") + RSA_WITH_SHA384 = ObjectIdentifier("1.2.840.113549.1.1.12") + RSA_WITH_SHA512 = ObjectIdentifier("1.2.840.113549.1.1.13") + ECDSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10045.4.1") + ECDSA_WITH_SHA224 = ObjectIdentifier("1.2.840.10045.4.3.1") + ECDSA_WITH_SHA256 = ObjectIdentifier("1.2.840.10045.4.3.2") + ECDSA_WITH_SHA384 = ObjectIdentifier("1.2.840.10045.4.3.3") + ECDSA_WITH_SHA512 = ObjectIdentifier("1.2.840.10045.4.3.4") + DSA_WITH_SHA1 = ObjectIdentifier("1.2.840.10040.4.3") + DSA_WITH_SHA224 = ObjectIdentifier("2.16.840.1.101.3.4.3.1") + DSA_WITH_SHA256 = ObjectIdentifier("2.16.840.1.101.3.4.3.2") + + +_SIG_OIDS_TO_HASH = { + SignatureAlgorithmOID.RSA_WITH_MD5: hashes.MD5(), + SignatureAlgorithmOID.RSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID._RSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID.RSA_WITH_SHA224: hashes.SHA224(), + SignatureAlgorithmOID.RSA_WITH_SHA256: hashes.SHA256(), + SignatureAlgorithmOID.RSA_WITH_SHA384: hashes.SHA384(), + SignatureAlgorithmOID.RSA_WITH_SHA512: hashes.SHA512(), + SignatureAlgorithmOID.ECDSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID.ECDSA_WITH_SHA224: hashes.SHA224(), + SignatureAlgorithmOID.ECDSA_WITH_SHA256: hashes.SHA256(), + SignatureAlgorithmOID.ECDSA_WITH_SHA384: hashes.SHA384(), + SignatureAlgorithmOID.ECDSA_WITH_SHA512: hashes.SHA512(), + SignatureAlgorithmOID.DSA_WITH_SHA1: hashes.SHA1(), + SignatureAlgorithmOID.DSA_WITH_SHA224: hashes.SHA224(), + SignatureAlgorithmOID.DSA_WITH_SHA256: hashes.SHA256() +} + + +class ExtendedKeyUsageOID(object): + SERVER_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.1") + CLIENT_AUTH = ObjectIdentifier("1.3.6.1.5.5.7.3.2") + CODE_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.3") + EMAIL_PROTECTION = ObjectIdentifier("1.3.6.1.5.5.7.3.4") + TIME_STAMPING = ObjectIdentifier("1.3.6.1.5.5.7.3.8") + OCSP_SIGNING = ObjectIdentifier("1.3.6.1.5.5.7.3.9") + + +class AuthorityInformationAccessOID(object): + CA_ISSUERS = ObjectIdentifier("1.3.6.1.5.5.7.48.2") + OCSP = ObjectIdentifier("1.3.6.1.5.5.7.48.1") + + +class CertificatePoliciesOID(object): + CPS_QUALIFIER = ObjectIdentifier("1.3.6.1.5.5.7.2.1") + CPS_USER_NOTICE = ObjectIdentifier("1.3.6.1.5.5.7.2.2") + ANY_POLICY = ObjectIdentifier("2.5.29.32.0") + + +_OID_NAMES = { + NameOID.COMMON_NAME: "commonName", + NameOID.COUNTRY_NAME: "countryName", + NameOID.LOCALITY_NAME: "localityName", + NameOID.STATE_OR_PROVINCE_NAME: "stateOrProvinceName", + NameOID.STREET_ADDRESS: "streetAddress", + NameOID.ORGANIZATION_NAME: "organizationName", + NameOID.ORGANIZATIONAL_UNIT_NAME: "organizationalUnitName", + NameOID.SERIAL_NUMBER: "serialNumber", + NameOID.SURNAME: "surname", + NameOID.GIVEN_NAME: "givenName", + NameOID.TITLE: "title", + NameOID.GENERATION_QUALIFIER: "generationQualifier", + NameOID.X500_UNIQUE_IDENTIFIER: "x500UniqueIdentifier", + NameOID.DN_QUALIFIER: "dnQualifier", + NameOID.PSEUDONYM: "pseudonym", + NameOID.USER_ID: "userID", + NameOID.DOMAIN_COMPONENT: "domainComponent", + NameOID.EMAIL_ADDRESS: "emailAddress", + NameOID.JURISDICTION_COUNTRY_NAME: "jurisdictionCountryName", + NameOID.JURISDICTION_LOCALITY_NAME: "jurisdictionLocalityName", + NameOID.JURISDICTION_STATE_OR_PROVINCE_NAME: ( + "jurisdictionStateOrProvinceName" + ), + NameOID.BUSINESS_CATEGORY: "businessCategory", + NameOID.POSTAL_ADDRESS: "postalAddress", + NameOID.POSTAL_CODE: "postalCode", + + SignatureAlgorithmOID.RSA_WITH_MD5: "md5WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA1: "sha1WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA224: "sha224WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA256: "sha256WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA384: "sha384WithRSAEncryption", + SignatureAlgorithmOID.RSA_WITH_SHA512: "sha512WithRSAEncryption", + SignatureAlgorithmOID.ECDSA_WITH_SHA1: "ecdsa-with-SHA1", + SignatureAlgorithmOID.ECDSA_WITH_SHA224: "ecdsa-with-SHA224", + SignatureAlgorithmOID.ECDSA_WITH_SHA256: "ecdsa-with-SHA256", + SignatureAlgorithmOID.ECDSA_WITH_SHA384: "ecdsa-with-SHA384", + SignatureAlgorithmOID.ECDSA_WITH_SHA512: "ecdsa-with-SHA512", + SignatureAlgorithmOID.DSA_WITH_SHA1: "dsa-with-sha1", + SignatureAlgorithmOID.DSA_WITH_SHA224: "dsa-with-sha224", + SignatureAlgorithmOID.DSA_WITH_SHA256: "dsa-with-sha256", + ExtendedKeyUsageOID.SERVER_AUTH: "serverAuth", + ExtendedKeyUsageOID.CLIENT_AUTH: "clientAuth", + ExtendedKeyUsageOID.CODE_SIGNING: "codeSigning", + ExtendedKeyUsageOID.EMAIL_PROTECTION: "emailProtection", + ExtendedKeyUsageOID.TIME_STAMPING: "timeStamping", + ExtendedKeyUsageOID.OCSP_SIGNING: "OCSPSigning", + ExtensionOID.SUBJECT_DIRECTORY_ATTRIBUTES: "subjectDirectoryAttributes", + ExtensionOID.SUBJECT_KEY_IDENTIFIER: "subjectKeyIdentifier", + ExtensionOID.KEY_USAGE: "keyUsage", + ExtensionOID.SUBJECT_ALTERNATIVE_NAME: "subjectAltName", + ExtensionOID.ISSUER_ALTERNATIVE_NAME: "issuerAltName", + ExtensionOID.BASIC_CONSTRAINTS: "basicConstraints", + CRLEntryExtensionOID.CRL_REASON: "cRLReason", + CRLEntryExtensionOID.INVALIDITY_DATE: "invalidityDate", + CRLEntryExtensionOID.CERTIFICATE_ISSUER: "certificateIssuer", + ExtensionOID.NAME_CONSTRAINTS: "nameConstraints", + ExtensionOID.CRL_DISTRIBUTION_POINTS: "cRLDistributionPoints", + ExtensionOID.CERTIFICATE_POLICIES: "certificatePolicies", + ExtensionOID.POLICY_MAPPINGS: "policyMappings", + ExtensionOID.AUTHORITY_KEY_IDENTIFIER: "authorityKeyIdentifier", + ExtensionOID.POLICY_CONSTRAINTS: "policyConstraints", + ExtensionOID.EXTENDED_KEY_USAGE: "extendedKeyUsage", + ExtensionOID.FRESHEST_CRL: "freshestCRL", + ExtensionOID.INHIBIT_ANY_POLICY: "inhibitAnyPolicy", + ExtensionOID.AUTHORITY_INFORMATION_ACCESS: "authorityInfoAccess", + ExtensionOID.SUBJECT_INFORMATION_ACCESS: "subjectInfoAccess", + ExtensionOID.OCSP_NO_CHECK: "OCSPNoCheck", + ExtensionOID.CRL_NUMBER: "cRLNumber", + AuthorityInformationAccessOID.OCSP: "OCSP", + AuthorityInformationAccessOID.CA_ISSUERS: "caIssuers", + CertificatePoliciesOID.CPS_QUALIFIER: "id-qt-cps", + CertificatePoliciesOID.CPS_USER_NOTICE: "id-qt-unotice", +} diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..4b54088 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function diff --git a/tests/conftest.py b/tests/conftest.py new file mode 100644 index 0000000..1952ec1 --- /dev/null +++ b/tests/conftest.py @@ -0,0 +1,51 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography.hazmat.backends import _available_backends +from cryptography.hazmat.backends.openssl import backend as openssl_backend + +from .utils import check_backend_support, select_backends, skip_if_empty + + +def pytest_report_header(config): + return "OpenSSL: {0}".format(openssl_backend.openssl_version_text()) + + +def pytest_generate_tests(metafunc): + 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 = [ + mark.kwargs["interface"] for mark in required + ] + for backend in selected_backends: + if all( + isinstance(backend, iface) for iface in required_interfaces + ): + filtered_backends.append(backend) + + # If you pass an empty list to parametrize Bad Things(tm) happen + # as of pytest 2.6.4 when the test also has a parametrize decorator + skip_if_empty(filtered_backends, required_interfaces) + + metafunc.parametrize("backend", filtered_backends) + + +@pytest.mark.trylast +def pytest_runtest_setup(item): + check_backend_support(item) + + +def pytest_addoption(parser): + parser.addoption( + "--backend", action="store", metavar="NAME", + help="Only run tests matching the backend NAME." + ) diff --git a/tests/doubles.py b/tests/doubles.py new file mode 100644 index 0000000..2ff1942 --- /dev/null +++ b/tests/doubles.py @@ -0,0 +1,43 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.hazmat.primitives.ciphers import CipherAlgorithm +from cryptography.hazmat.primitives.ciphers.modes import Mode + + +@utils.register_interface(CipherAlgorithm) +class DummyCipherAlgorithm(object): + name = "dummy-cipher" + block_size = 128 + key_size = None + + +@utils.register_interface(Mode) +class DummyMode(object): + name = "dummy-mode" + + def validate_for_algorithm(self, algorithm): + pass + + +@utils.register_interface(hashes.HashAlgorithm) +class DummyHashAlgorithm(object): + name = "dummy-hash" + block_size = None + digest_size = None + + +@utils.register_interface(serialization.KeySerializationEncryption) +class DummyKeySerializationEncryption(object): + pass + + +@utils.register_interface(padding.AsymmetricPadding) +class DummyAsymmetricPadding(object): + name = "dummy-padding" diff --git a/tests/hazmat/__init__.py b/tests/hazmat/__init__.py new file mode 100644 index 0000000..4b54088 --- /dev/null +++ b/tests/hazmat/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function diff --git a/tests/hazmat/backends/__init__.py b/tests/hazmat/backends/__init__.py new file mode 100644 index 0000000..4b54088 --- /dev/null +++ b/tests/hazmat/backends/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function diff --git a/tests/hazmat/backends/test_backendinit.py b/tests/hazmat/backends/test_backendinit.py new file mode 100644 index 0000000..b930f99 --- /dev/null +++ b/tests/hazmat/backends/test_backendinit.py @@ -0,0 +1,17 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography.hazmat.backends import _backend_import_fallback + + +def test_backend_import_fallback_empty_backends(): + backends = _backend_import_fallback([]) + assert len(backends) >= 1 + + +def test_backend_import_fallback_existing_backends(): + backend_list = [1, 2, 3, 4] + assert backend_list == _backend_import_fallback(backend_list) diff --git a/tests/hazmat/backends/test_commoncrypto.py b/tests/hazmat/backends/test_commoncrypto.py new file mode 100644 index 0000000..2b730e9 --- /dev/null +++ b/tests/hazmat/backends/test_commoncrypto.py @@ -0,0 +1,54 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography.exceptions import InternalError, _Reasons +from cryptography.hazmat.backends import _available_backends +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers.algorithms import AES +from cryptography.hazmat.primitives.ciphers.modes import CBC, GCM + +from ...doubles import DummyCipherAlgorithm +from ...utils import raises_unsupported_algorithm + + +@pytest.mark.skipif("commoncrypto" not in + [i.name for i in _available_backends()], + reason="CommonCrypto not available") +class TestCommonCrypto(object): + def test_supports_cipher(self): + from cryptography.hazmat.backends.commoncrypto.backend import backend + assert backend.cipher_supported(None, None) is False + + def test_register_duplicate_cipher_adapter(self): + from cryptography.hazmat.backends.commoncrypto.backend import backend + with pytest.raises(ValueError): + backend._register_cipher_adapter( + AES, backend._lib.kCCAlgorithmAES128, + CBC, backend._lib.kCCModeCBC + ) + + def test_handle_response(self): + from cryptography.hazmat.backends.commoncrypto.backend import backend + + with pytest.raises(ValueError): + backend._check_cipher_response(backend._lib.kCCAlignmentError) + + with pytest.raises(InternalError): + backend._check_cipher_response(backend._lib.kCCMemoryFailure) + + with pytest.raises(InternalError): + backend._check_cipher_response(backend._lib.kCCDecodeError) + + def test_nonexistent_aead_cipher(self): + from cryptography.hazmat.backends.commoncrypto.backend import Backend + b = Backend() + cipher = Cipher( + DummyCipherAlgorithm(), GCM(b"fake_iv_here"), backend=b, + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + cipher.encryptor() diff --git a/tests/hazmat/backends/test_multibackend.py b/tests/hazmat/backends/test_multibackend.py new file mode 100644 index 0000000..bd80673 --- /dev/null +++ b/tests/hazmat/backends/test_multibackend.py @@ -0,0 +1,648 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography import utils +from cryptography.exceptions import ( + UnsupportedAlgorithm, _Reasons +) +from cryptography.hazmat.backends.interfaces import ( + CMACBackend, CipherBackend, DERSerializationBackend, DHBackend, + DSABackend, EllipticCurveBackend, HMACBackend, HashBackend, + PBKDF2HMACBackend, PEMSerializationBackend, RSABackend, ScryptBackend, + X509Backend +) +from cryptography.hazmat.backends.multibackend import MultiBackend +from cryptography.hazmat.primitives import cmac, hashes, hmac +from cryptography.hazmat.primitives.asymmetric import ec, padding +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes + +from ...utils import raises_unsupported_algorithm + + +class DummyBackend(object): + pass + + +@utils.register_interface(ec.EllipticCurve) +class DummyCurve(object): + name = "dummy-curve" + key_size = 1 + + +@utils.register_interface(CipherBackend) +class DummyCipherBackend(object): + def __init__(self, supported_ciphers): + self._ciphers = supported_ciphers + + def cipher_supported(self, cipher, mode): + return (type(cipher), type(mode)) in self._ciphers + + def create_symmetric_encryption_ctx(self, cipher, mode): + if not self.cipher_supported(cipher, mode): + raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_CIPHER) + + def create_symmetric_decryption_ctx(self, cipher, mode): + if not self.cipher_supported(cipher, mode): + raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_CIPHER) + + +@utils.register_interface(HashBackend) +class DummyHashBackend(object): + def __init__(self, supported_algorithms): + self._algorithms = supported_algorithms + + def hash_supported(self, algorithm): + return type(algorithm) in self._algorithms + + def create_hash_ctx(self, algorithm): + if not self.hash_supported(algorithm): + raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_HASH) + + +@utils.register_interface(HMACBackend) +class DummyHMACBackend(object): + def __init__(self, supported_algorithms): + self._algorithms = supported_algorithms + + def hmac_supported(self, algorithm): + return type(algorithm) in self._algorithms + + def create_hmac_ctx(self, key, algorithm): + if not self.hmac_supported(algorithm): + raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_HASH) + + +@utils.register_interface(PBKDF2HMACBackend) +class DummyPBKDF2HMACBackend(object): + def __init__(self, supported_algorithms): + self._algorithms = supported_algorithms + + def pbkdf2_hmac_supported(self, algorithm): + return type(algorithm) in self._algorithms + + def derive_pbkdf2_hmac(self, algorithm, length, salt, iterations, + key_material): + if not self.pbkdf2_hmac_supported(algorithm): + raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_HASH) + + +@utils.register_interface(RSABackend) +class DummyRSABackend(object): + def generate_rsa_private_key(self, public_exponent, key_size): + pass + + def rsa_padding_supported(self, padding): + pass + + def generate_rsa_parameters_supported(self, public_exponent, key_size): + pass + + def load_rsa_private_numbers(self, numbers): + pass + + def load_rsa_public_numbers(self, numbers): + pass + + +@utils.register_interface(DSABackend) +class DummyDSABackend(object): + def generate_dsa_parameters(self, key_size): + pass + + def generate_dsa_private_key(self, parameters): + pass + + def generate_dsa_private_key_and_parameters(self, key_size): + pass + + def dsa_hash_supported(self, algorithm): + pass + + def dsa_parameters_supported(self, p, q, g): + pass + + def load_dsa_private_numbers(self, numbers): + pass + + def load_dsa_public_numbers(self, numbers): + pass + + def load_dsa_parameter_numbers(self, numbers): + pass + + +@utils.register_interface(CMACBackend) +class DummyCMACBackend(object): + def __init__(self, supported_algorithms): + self._algorithms = supported_algorithms + + def cmac_algorithm_supported(self, algorithm): + return type(algorithm) in self._algorithms + + def create_cmac_ctx(self, algorithm): + if not self.cmac_algorithm_supported(algorithm): + raise UnsupportedAlgorithm("", _Reasons.UNSUPPORTED_CIPHER) + + +@utils.register_interface(EllipticCurveBackend) +class DummyEllipticCurveBackend(object): + def __init__(self, supported_curves): + self._curves = supported_curves + + def elliptic_curve_supported(self, curve): + return any( + isinstance(curve, curve_type) + for curve_type in self._curves + ) + + def elliptic_curve_signature_algorithm_supported( + self, signature_algorithm, curve + ): + return ( + isinstance(signature_algorithm, ec.ECDSA) and + self.elliptic_curve_supported(curve) + ) + + def generate_elliptic_curve_private_key(self, curve): + if not self.elliptic_curve_supported(curve): + raise UnsupportedAlgorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE) + + def load_elliptic_curve_private_numbers(self, numbers): + if not self.elliptic_curve_supported(numbers.public_numbers.curve): + raise UnsupportedAlgorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE) + + def load_elliptic_curve_public_numbers(self, numbers): + if not self.elliptic_curve_supported(numbers.curve): + raise UnsupportedAlgorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE) + + def elliptic_curve_exchange_algorithm_supported(self, algorithm, curve): + return ( + isinstance(algorithm, ec.ECDH) and + self.elliptic_curve_supported(curve) + ) + + def derive_elliptic_curve_private_key(self, private_value, curve): + if not self.elliptic_curve_supported(curve): + raise UnsupportedAlgorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE) + + +@utils.register_interface(PEMSerializationBackend) +class DummyPEMSerializationBackend(object): + def load_pem_private_key(self, data, password): + pass + + def load_pem_public_key(self, data): + pass + + +@utils.register_interface(DERSerializationBackend) +class DummyDERSerializationBackend(object): + def load_der_private_key(self, data, password): + pass + + def load_der_public_key(self, data): + pass + + +@utils.register_interface(X509Backend) +class DummyX509Backend(object): + def load_pem_x509_certificate(self, data): + pass + + def load_der_x509_certificate(self, data): + pass + + def load_pem_x509_crl(self, data): + pass + + def load_der_x509_crl(self, data): + pass + + def load_pem_x509_csr(self, data): + pass + + def load_der_x509_csr(self, data): + pass + + def create_x509_csr(self, builder, private_key, algorithm): + pass + + def create_x509_certificate(self, builder, private_key, algorithm): + pass + + def create_x509_crl(self, builder, private_key, algorithm): + pass + + def create_x509_revoked_certificate(self, builder): + pass + + def x509_name_bytes(self, name): + pass + + +@utils.register_interface(DHBackend) +class DummyDHBackend(object): + def generate_dh_parameters(self, generator, key_size): + pass + + def load_dh_parameter_numbers(self, numbers): + pass + + def generate_dh_private_key(self, parameters): + pass + + def load_dh_private_numbers(self, numbers): + pass + + def load_dh_public_numbers(self, numbers): + pass + + def generate_dh_private_key_and_parameters(self, generator, key_size): + pass + + def dh_parameters_supported(self, p, g): + pass + + +@utils.register_interface(ScryptBackend) +class DummyScryptBackend(object): + def derive_scrypt(self, key_material, salt, length, n, r, p): + pass + + +class TestMultiBackend(object): + def test_raises_error_with_empty_list(self): + with pytest.raises(ValueError): + MultiBackend([]) + + def test_ciphers(self): + backend = MultiBackend([ + DummyHashBackend([]), + DummyCipherBackend([ + (algorithms.AES, modes.CBC), + ]) + ]) + assert backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.CBC(b"\x00" * 16) + ) + assert not backend.cipher_supported( + algorithms.TripleDES(b"\x00" * 16), modes.CBC(b"\x00" * 16) + ) + + cipher = Cipher( + algorithms.AES(b"\x00" * 16), + modes.CBC(b"\x00" * 16), + backend=backend + ) + cipher.encryptor() + cipher.decryptor() + + cipher = Cipher( + algorithms.Camellia(b"\x00" * 16), + modes.CBC(b"\x00" * 16), + backend=backend + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + cipher.encryptor() + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + cipher.decryptor() + + def test_hashes(self): + backend = MultiBackend([ + DummyHashBackend([hashes.MD5]) + ]) + assert backend.hash_supported(hashes.MD5()) + assert not backend.hash_supported(hashes.SHA256()) + + hashes.Hash(hashes.MD5(), backend=backend) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + hashes.Hash(hashes.SHA1(), backend=backend) + + def test_hmac(self): + backend = MultiBackend([ + DummyHMACBackend([hashes.MD5]) + ]) + assert backend.hmac_supported(hashes.MD5()) + assert not backend.hmac_supported(hashes.SHA256()) + + hmac.HMAC(b"", hashes.MD5(), backend=backend) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + hmac.HMAC(b"", hashes.SHA1(), backend=backend) + + def test_pbkdf2(self): + backend = MultiBackend([ + DummyPBKDF2HMACBackend([hashes.MD5]) + ]) + assert backend.pbkdf2_hmac_supported(hashes.MD5()) + + backend.derive_pbkdf2_hmac(hashes.MD5(), 10, b"", 10, b"") + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + backend.derive_pbkdf2_hmac(hashes.SHA1(), 10, b"", 10, b"") + + def test_rsa(self): + backend = MultiBackend([ + DummyRSABackend() + ]) + + backend.generate_rsa_private_key( + key_size=1024, public_exponent=65537 + ) + + backend.rsa_padding_supported(padding.PKCS1v15()) + + backend.generate_rsa_parameters_supported(65537, 1024) + + backend.load_rsa_private_numbers("private_numbers") + + backend.load_rsa_public_numbers("public_numbers") + + backend = MultiBackend([DummyBackend()]) + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.generate_rsa_private_key(key_size=1024, public_exponent=3) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.rsa_padding_supported(padding.PKCS1v15()) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.generate_rsa_parameters_supported(65537, 1024) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.load_rsa_private_numbers("private_numbers") + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.load_rsa_public_numbers("public_numbers") + + def test_dsa(self): + backend = MultiBackend([ + DummyDSABackend() + ]) + + backend.generate_dsa_parameters(key_size=1024) + + parameters = object() + backend.generate_dsa_private_key(parameters) + backend.generate_dsa_private_key_and_parameters(key_size=1024) + + backend.dsa_hash_supported(hashes.SHA1()) + backend.dsa_parameters_supported(1, 2, 3) + backend.load_dsa_private_numbers("numbers") + backend.load_dsa_public_numbers("numbers") + backend.load_dsa_parameter_numbers("numbers") + + backend = MultiBackend([DummyBackend()]) + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.generate_dsa_parameters(key_size=1024) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.generate_dsa_private_key(parameters) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.generate_dsa_private_key_and_parameters(key_size=1024) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.dsa_hash_supported(hashes.SHA1()) + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.dsa_parameters_supported('p', 'q', 'g') + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.load_dsa_private_numbers("numbers") + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.load_dsa_public_numbers("numbers") + + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + backend.load_dsa_parameter_numbers("numbers") + + def test_cmac(self): + backend = MultiBackend([ + DummyCMACBackend([algorithms.AES]) + ]) + + fake_key = b"\x00" * 16 + + assert backend.cmac_algorithm_supported(algorithms.AES(fake_key)) + assert not backend.cmac_algorithm_supported( + algorithms.TripleDES(fake_key) + ) + + cmac.CMAC(algorithms.AES(fake_key), backend) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + cmac.CMAC(algorithms.TripleDES(fake_key), backend) + + def test_elliptic_curve(self): + backend = MultiBackend([ + DummyEllipticCurveBackend([ + ec.SECT283K1 + ]) + ]) + + assert backend.elliptic_curve_supported(ec.SECT283K1()) is True + + assert backend.elliptic_curve_signature_algorithm_supported( + ec.ECDSA(hashes.SHA256()), + ec.SECT283K1() + ) is True + + backend.generate_elliptic_curve_private_key(ec.SECT283K1()) + + backend.load_elliptic_curve_private_numbers( + ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + 2, + 3, + ec.SECT283K1() + ) + ) + ) + + backend.load_elliptic_curve_public_numbers( + ec.EllipticCurvePublicNumbers( + 2, + 3, + ec.SECT283K1() + ) + ) + + assert backend.elliptic_curve_supported(ec.SECT163K1()) is False + + assert backend.elliptic_curve_signature_algorithm_supported( + ec.ECDSA(hashes.SHA256()), + ec.SECT163K1() + ) is False + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE): + backend.generate_elliptic_curve_private_key(ec.SECT163K1()) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE): + backend.load_elliptic_curve_private_numbers( + ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + 2, + 3, + ec.SECT163K1() + ) + ) + ) + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE): + backend.load_elliptic_curve_public_numbers( + ec.EllipticCurvePublicNumbers( + 2, + 3, + ec.SECT163K1() + ) + ) + + assert backend.elliptic_curve_exchange_algorithm_supported( + ec.ECDH(), ec.SECT283K1() + ) + backend2 = MultiBackend([DummyEllipticCurveBackend([])]) + assert not backend2.elliptic_curve_exchange_algorithm_supported( + ec.ECDH(), ec.SECT163K1() + ) + + with pytest.raises(UnsupportedAlgorithm): + backend.derive_elliptic_curve_private_key(123, DummyCurve()) + + assert backend.derive_elliptic_curve_private_key( + 123, ec.SECT283K1()) is None + + def test_pem_serialization_backend(self): + backend = MultiBackend([DummyPEMSerializationBackend()]) + + backend.load_pem_private_key(b"keydata", None) + backend.load_pem_public_key(b"keydata") + + backend = MultiBackend([DummyBackend()]) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): + backend.load_pem_private_key(b"keydata", None) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): + backend.load_pem_public_key(b"keydata") + + def test_der_serialization_backend(self): + backend = MultiBackend([DummyDERSerializationBackend()]) + + backend.load_der_private_key(b"keydata", None) + backend.load_der_public_key(b"keydata") + + backend = MultiBackend([DummyBackend()]) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): + backend.load_der_private_key(b"keydata", None) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_SERIALIZATION): + backend.load_der_public_key(b"keydata") + + def test_x509_backend(self): + backend = MultiBackend([DummyX509Backend()]) + + backend.load_pem_x509_certificate(b"certdata") + backend.load_der_x509_certificate(b"certdata") + backend.load_pem_x509_crl(b"crldata") + backend.load_der_x509_crl(b"crldata") + backend.load_pem_x509_csr(b"reqdata") + backend.load_der_x509_csr(b"reqdata") + backend.create_x509_csr(object(), b"privatekey", hashes.SHA1()) + backend.create_x509_certificate(object(), b"privatekey", hashes.SHA1()) + backend.create_x509_crl(object(), b"privatekey", hashes.SHA1()) + backend.create_x509_revoked_certificate(object()) + backend.x509_name_bytes(object()) + + backend = MultiBackend([DummyBackend()]) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.load_pem_x509_certificate(b"certdata") + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.load_der_x509_certificate(b"certdata") + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.load_pem_x509_crl(b"crldata") + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.load_der_x509_crl(b"crldata") + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.load_pem_x509_csr(b"reqdata") + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.load_der_x509_csr(b"reqdata") + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.create_x509_csr(object(), b"privatekey", hashes.SHA1()) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.create_x509_certificate( + object(), b"privatekey", hashes.SHA1() + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.create_x509_crl( + object(), b"privatekey", hashes.SHA1() + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.create_x509_revoked_certificate(object()) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_X509): + backend.x509_name_bytes(object()) + + def test_dh_backend(self): + backend = MultiBackend([DummyDHBackend()]) + + backend.generate_dh_parameters(2, 512) + backend.load_dh_parameter_numbers(object()) + backend.generate_dh_private_key(object()) + backend.load_dh_private_numbers(object()) + backend.load_dh_public_numbers(object()) + backend.generate_dh_private_key_and_parameters(2, 512) + backend.dh_parameters_supported(2, 3) + + backend = MultiBackend([DummyBackend()]) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.generate_dh_parameters(2, 512) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.load_dh_parameter_numbers(object()) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.generate_dh_private_key(object()) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.load_dh_private_numbers(object()) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.load_dh_public_numbers(object()) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.generate_dh_private_key_and_parameters(2, 512) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_DIFFIE_HELLMAN): + backend.dh_parameters_supported(2, 3) + + def test_scrypt(self): + backend = MultiBackend([DummyScryptBackend()]) + backend.derive_scrypt(b"key", b"salt", 1, 1, 1, 1) + + backend = MultiBackend([DummyBackend]) + with pytest.raises(UnsupportedAlgorithm): + backend.derive_scrypt(b"key", b"salt", 1, 1, 1, 1) diff --git a/tests/hazmat/backends/test_openssl.py b/tests/hazmat/backends/test_openssl.py new file mode 100644 index 0000000..47c4606 --- /dev/null +++ b/tests/hazmat/backends/test_openssl.py @@ -0,0 +1,751 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import datetime +import itertools +import os +import subprocess +import sys +import textwrap + +from pkg_resources import parse_version + +import pytest + +from cryptography import utils, x509 +from cryptography.exceptions import InternalError, _Reasons +from cryptography.hazmat.backends.interfaces import RSABackend +from cryptography.hazmat.backends.openssl.backend import ( + Backend, backend +) +from cryptography.hazmat.backends.openssl.ec import _sn_to_elliptic_curve +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import dsa, ec, padding +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.ciphers.algorithms import AES +from cryptography.hazmat.primitives.ciphers.modes import CBC, CTR + +from ..primitives.fixtures_dsa import DSA_KEY_2048 +from ..primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 +from ..primitives.test_ec import _skip_curve_unsupported +from ...doubles import ( + DummyAsymmetricPadding, DummyCipherAlgorithm, DummyHashAlgorithm, DummyMode +) +from ...test_x509 import _load_cert +from ...utils import load_vectors_from_file, raises_unsupported_algorithm + + +def skip_if_libre_ssl(openssl_version): + if u'LibreSSL' in openssl_version: + pytest.skip("LibreSSL hard-codes RAND_bytes to use arc4random.") + + +class TestLibreSkip(object): + def test_skip_no(self): + assert skip_if_libre_ssl(u"OpenSSL 1.0.2h 3 May 2016") is None + + def test_skip_yes(self): + with pytest.raises(pytest.skip.Exception): + skip_if_libre_ssl(u"LibreSSL 2.1.6") + + +class DummyMGF(object): + _salt_length = 0 + + +class TestOpenSSL(object): + def test_backend_exists(self): + assert backend + + def test_openssl_version_text(self): + """ + This test checks the value of OPENSSL_VERSION_TEXT. + + Unfortunately, this define does not appear to have a + formal content definition, so for now we'll test to see + if it starts with OpenSSL or LibreSSL as that appears + to be true for every OpenSSL-alike. + """ + assert ( + backend.openssl_version_text().startswith("OpenSSL") or + backend.openssl_version_text().startswith("LibreSSL") + ) + + def test_supports_cipher(self): + assert backend.cipher_supported(None, None) is False + + def test_aes_ctr_always_available(self): + # AES CTR should always be available, even in 1.0.0. + assert backend.cipher_supported(AES(b"\x00" * 16), + CTR(b"\x00" * 16)) is True + + def test_register_duplicate_cipher_adapter(self): + with pytest.raises(ValueError): + backend.register_cipher_adapter(AES, CBC, None) + + @pytest.mark.parametrize("mode", [DummyMode(), None]) + def test_nonexistent_cipher(self, mode): + b = Backend() + b.register_cipher_adapter( + DummyCipherAlgorithm, + type(mode), + lambda backend, cipher, mode: backend._ffi.NULL + ) + cipher = Cipher( + DummyCipherAlgorithm(), mode, backend=b, + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + cipher.encryptor() + + def test_openssl_assert(self): + backend.openssl_assert(True) + with pytest.raises(InternalError): + backend.openssl_assert(False) + + def test_consume_errors(self): + for i in range(10): + backend._lib.ERR_put_error(backend._lib.ERR_LIB_EVP, 0, 0, + b"test_openssl.py", -1) + + assert backend._lib.ERR_peek_error() != 0 + + errors = backend._consume_errors() + + assert backend._lib.ERR_peek_error() == 0 + assert len(errors) == 10 + + def test_ssl_ciphers_registered(self): + meth = backend._lib.TLSv1_method() + ctx = backend._lib.SSL_CTX_new(meth) + assert ctx != backend._ffi.NULL + backend._lib.SSL_CTX_free(ctx) + + def test_evp_ciphers_registered(self): + cipher = backend._lib.EVP_get_cipherbyname(b"aes-256-cbc") + assert cipher != backend._ffi.NULL + + def test_error_strings_loaded(self): + # returns a value in a static buffer + err = backend._lib.ERR_error_string(101183626, backend._ffi.NULL) + assert backend._ffi.string(err) == ( + b"error:0607F08A:digital envelope routines:EVP_EncryptFinal_ex:" + b"data not multiple of block length" + ) + + def test_unknown_error_in_cipher_finalize(self): + cipher = Cipher(AES(b"\0" * 16), CBC(b"\0" * 16), backend=backend) + enc = cipher.encryptor() + enc.update(b"\0") + backend._lib.ERR_put_error(0, 0, 1, + b"test_openssl.py", -1) + with pytest.raises(InternalError): + enc.finalize() + + def test_large_key_size_on_new_openssl(self): + parameters = dsa.generate_parameters(2048, backend) + param_num = parameters.parameter_numbers() + assert utils.bit_length(param_num.p) == 2048 + parameters = dsa.generate_parameters(3072, backend) + param_num = parameters.parameter_numbers() + assert utils.bit_length(param_num.p) == 3072 + + def test_int_to_bn(self): + value = (2 ** 4242) - 4242 + bn = backend._int_to_bn(value) + assert bn != backend._ffi.NULL + bn = backend._ffi.gc(bn, backend._lib.BN_free) + + assert bn + assert backend._bn_to_int(bn) == value + + def test_int_to_bn_inplace(self): + value = (2 ** 4242) - 4242 + bn_ptr = backend._lib.BN_new() + assert bn_ptr != backend._ffi.NULL + bn_ptr = backend._ffi.gc(bn_ptr, backend._lib.BN_free) + bn = backend._int_to_bn(value, bn_ptr) + + assert bn == bn_ptr + assert backend._bn_to_int(bn_ptr) == value + + def test_bn_to_int(self): + bn = backend._int_to_bn(0) + assert backend._bn_to_int(bn) == 0 + + +class TestOpenSSLRandomEngine(object): + def setup(self): + # The default RAND engine is global and shared between + # tests. We make sure that the default engine is osrandom + # before we start each test and restore the global state to + # that engine in teardown. + current_default = backend._lib.ENGINE_get_default_RAND() + name = backend._lib.ENGINE_get_name(current_default) + assert name == backend._binding._osrandom_engine_name + + def teardown(self): + # we need to reset state to being default. backend is a shared global + # for all these tests. + backend.activate_osrandom_engine() + current_default = backend._lib.ENGINE_get_default_RAND() + name = backend._lib.ENGINE_get_name(current_default) + assert name == backend._binding._osrandom_engine_name + + @pytest.mark.skipif(sys.executable is None, + reason="No Python interpreter available.") + def test_osrandom_engine_is_default(self, tmpdir): + engine_printer = textwrap.dedent( + """ + import sys + from cryptography.hazmat.backends.openssl.backend import backend + + e = backend._lib.ENGINE_get_default_RAND() + name = backend._lib.ENGINE_get_name(e) + sys.stdout.write(backend._ffi.string(name).decode('ascii')) + res = backend._lib.ENGINE_free(e) + assert res == 1 + """ + ) + engine_name = tmpdir.join('engine_name') + + # If we're running tests via ``python setup.py test`` in a clean + # environment then all of our dependencies are going to be installed + # into either the current directory or the .eggs directory. However the + # subprocess won't know to activate these dependencies, so we'll get it + # to do so by passing our entire sys.path into the subprocess via the + # PYTHONPATH environment variable. + env = os.environ.copy() + env["PYTHONPATH"] = os.pathsep.join(sys.path) + + with engine_name.open('w') as out: + subprocess.check_call( + [sys.executable, "-c", engine_printer], + env=env, + stdout=out, + stderr=subprocess.PIPE, + ) + + osrandom_engine_name = backend._ffi.string( + backend._binding._osrandom_engine_name + ) + + assert engine_name.read().encode('ascii') == osrandom_engine_name + + def test_osrandom_sanity_check(self): + # This test serves as a check against catastrophic failure. + buf = backend._ffi.new("unsigned char[]", 500) + res = backend._lib.RAND_bytes(buf, 500) + assert res == 1 + assert backend._ffi.buffer(buf)[:] != "\x00" * 500 + + def test_activate_osrandom_no_default(self): + backend.activate_builtin_random() + e = backend._lib.ENGINE_get_default_RAND() + assert e == backend._ffi.NULL + backend.activate_osrandom_engine() + e = backend._lib.ENGINE_get_default_RAND() + name = backend._lib.ENGINE_get_name(e) + assert name == backend._binding._osrandom_engine_name + res = backend._lib.ENGINE_free(e) + assert res == 1 + + def test_activate_builtin_random(self): + e = backend._lib.ENGINE_get_default_RAND() + assert e != backend._ffi.NULL + name = backend._lib.ENGINE_get_name(e) + assert name == backend._binding._osrandom_engine_name + res = backend._lib.ENGINE_free(e) + assert res == 1 + backend.activate_builtin_random() + e = backend._lib.ENGINE_get_default_RAND() + assert e == backend._ffi.NULL + + def test_activate_builtin_random_already_active(self): + backend.activate_builtin_random() + e = backend._lib.ENGINE_get_default_RAND() + assert e == backend._ffi.NULL + backend.activate_builtin_random() + e = backend._lib.ENGINE_get_default_RAND() + assert e == backend._ffi.NULL + + def test_osrandom_engine_implementation(self): + name = backend.osrandom_engine_implementation() + assert name in ['/dev/urandom', 'CryptGenRandom', 'getentropy', + 'getrandom'] + if sys.platform.startswith('linux'): + assert name in ['getrandom', '/dev/urandom'] + if sys.platform == 'darwin': + # macOS 10.12+ supports getentropy + if parse_version(os.uname()[2]) >= parse_version("16.0"): + assert name == 'getentropy' + else: + assert name == '/dev/urandom' + if 'bsd' in sys.platform: + assert name in ['getentropy', '/dev/urandom'] + if sys.platform == 'win32': + assert name == 'CryptGenRandom' + + def test_activate_osrandom_already_default(self): + e = backend._lib.ENGINE_get_default_RAND() + name = backend._lib.ENGINE_get_name(e) + assert name == backend._binding._osrandom_engine_name + res = backend._lib.ENGINE_free(e) + assert res == 1 + backend.activate_osrandom_engine() + e = backend._lib.ENGINE_get_default_RAND() + name = backend._lib.ENGINE_get_name(e) + assert name == backend._binding._osrandom_engine_name + res = backend._lib.ENGINE_free(e) + assert res == 1 + + +class TestOpenSSLRSA(object): + def test_generate_rsa_parameters_supported(self): + assert backend.generate_rsa_parameters_supported(1, 1024) is False + assert backend.generate_rsa_parameters_supported(4, 1024) is False + assert backend.generate_rsa_parameters_supported(3, 1024) is True + assert backend.generate_rsa_parameters_supported(3, 511) is False + + def test_generate_bad_public_exponent(self): + with pytest.raises(ValueError): + backend.generate_rsa_private_key(public_exponent=1, key_size=2048) + + with pytest.raises(ValueError): + backend.generate_rsa_private_key(public_exponent=4, key_size=2048) + + def test_cant_generate_insecure_tiny_key(self): + with pytest.raises(ValueError): + backend.generate_rsa_private_key(public_exponent=65537, + key_size=511) + + with pytest.raises(ValueError): + backend.generate_rsa_private_key(public_exponent=65537, + key_size=256) + + @pytest.mark.skipif( + backend._lib.CRYPTOGRAPHY_OPENSSL_101_OR_GREATER, + reason="Requires an older OpenSSL. Must be < 1.0.1" + ) + def test_non_sha1_pss_mgf1_hash_algorithm_on_old_openssl(self): + private_key = RSA_KEY_512.private_key(backend) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + private_key.signer( + padding.PSS( + mgf=padding.MGF1( + algorithm=hashes.SHA256(), + ), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) + public_key = private_key.public_key() + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + public_key.verifier( + b"sig", + padding.PSS( + mgf=padding.MGF1( + algorithm=hashes.SHA256(), + ), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) + + def test_rsa_padding_unsupported_pss_mgf1_hash(self): + assert backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(DummyHashAlgorithm()), salt_length=0) + ) is False + + def test_rsa_padding_unsupported(self): + assert backend.rsa_padding_supported(DummyAsymmetricPadding()) is False + + def test_rsa_padding_supported_pkcs1v15(self): + assert backend.rsa_padding_supported(padding.PKCS1v15()) is True + + def test_rsa_padding_supported_pss(self): + assert backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ) is True + + def test_rsa_padding_supported_oaep(self): + assert backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ), + ) is True + + @pytest.mark.skipif( + backend._lib.Cryptography_HAS_RSA_OAEP_MD == 0, + reason="Requires OpenSSL with rsa_oaep_md (1.0.2+)" + ) + def test_rsa_padding_supported_oaep_sha2_combinations(self): + hashalgs = [ + hashes.SHA1(), + hashes.SHA224(), + hashes.SHA256(), + hashes.SHA384(), + hashes.SHA512(), + ] + for mgf1alg, oaepalg in itertools.product(hashalgs, hashalgs): + assert backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=mgf1alg), + algorithm=oaepalg, + label=None + ), + ) is True + + def test_rsa_padding_unsupported_oaep_ripemd160_sha1(self): + assert backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.RIPEMD160()), + algorithm=hashes.SHA1(), + label=None + ), + ) is False + + def test_rsa_padding_unsupported_oaep_sha1_ripemd160(self): + assert backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.RIPEMD160(), + label=None + ), + ) is False + + def test_rsa_padding_unsupported_mgf(self): + assert backend.rsa_padding_supported( + padding.OAEP( + mgf=DummyMGF(), + algorithm=hashes.SHA1(), + label=None + ), + ) is False + + assert backend.rsa_padding_supported( + padding.PSS(mgf=DummyMGF(), salt_length=0) + ) is False + + @pytest.mark.skipif( + backend._lib.Cryptography_HAS_RSA_OAEP_MD == 1, + reason="Requires OpenSSL without rsa_oaep_md (< 1.0.2)" + ) + def test_unsupported_mgf1_hash_algorithm_decrypt(self): + private_key = RSA_KEY_512.private_key(backend) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + private_key.decrypt( + b"0" * 64, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA1(), + label=None + ) + ) + + @pytest.mark.skipif( + backend._lib.Cryptography_HAS_RSA_OAEP_MD == 1, + reason="Requires OpenSSL without rsa_oaep_md (< 1.0.2)" + ) + def test_unsupported_oaep_hash_algorithm_decrypt(self): + private_key = RSA_KEY_512.private_key(backend) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + private_key.decrypt( + b"0" * 64, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA256(), + label=None + ) + ) + + def test_unsupported_mgf1_hash_algorithm_ripemd160_decrypt(self): + private_key = RSA_KEY_512.private_key(backend) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + private_key.decrypt( + b"0" * 64, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.RIPEMD160()), + algorithm=hashes.RIPEMD160(), + label=None + ) + ) + + def test_unsupported_mgf1_hash_algorithm_whirlpool_decrypt(self): + private_key = RSA_KEY_512.private_key(backend) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + private_key.decrypt( + b"0" * 64, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.Whirlpool()), + algorithm=hashes.Whirlpool(), + label=None + ) + ) + + def test_unsupported_oaep_label_decrypt(self): + private_key = RSA_KEY_512.private_key(backend) + with pytest.raises(ValueError): + private_key.decrypt( + b"0" * 64, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=b"label" + ) + ) + + +@pytest.mark.skipif( + backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_101, + reason="Requires an OpenSSL version >= 1.0.1" +) +class TestOpenSSLCMAC(object): + def test_unsupported_cipher(self): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + backend.create_cmac_ctx(DummyCipherAlgorithm()) + + +class TestOpenSSLCreateX509CSR(object): + @pytest.mark.skipif( + backend._lib.CRYPTOGRAPHY_OPENSSL_101_OR_GREATER, + reason="Requires an older OpenSSL. Must be < 1.0.1" + ) + def test_unsupported_dsa_keys(self): + private_key = DSA_KEY_2048.private_key(backend) + + with pytest.raises(NotImplementedError): + backend.create_x509_csr(object(), private_key, hashes.SHA1()) + + @pytest.mark.skipif( + backend._lib.CRYPTOGRAPHY_OPENSSL_101_OR_GREATER, + reason="Requires an older OpenSSL. Must be < 1.0.1" + ) + def test_unsupported_ec_keys(self): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + + with pytest.raises(NotImplementedError): + backend.create_x509_csr(object(), private_key, hashes.SHA1()) + + +class TestOpenSSLSignX509Certificate(object): + def test_requires_certificate_builder(self): + private_key = RSA_KEY_2048.private_key(backend) + + with pytest.raises(TypeError): + backend.create_x509_certificate( + object(), private_key, DummyHashAlgorithm() + ) + + @pytest.mark.skipif( + backend._lib.CRYPTOGRAPHY_OPENSSL_101_OR_GREATER, + reason="Requires an older OpenSSL. Must be < 1.0.1" + ) + def test_sign_with_dsa_private_key_is_unsupported(self): + private_key = DSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2032, 1, 1, 12, 1) + ) + + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA512(), backend) + + @pytest.mark.skipif( + backend._lib.CRYPTOGRAPHY_OPENSSL_101_OR_GREATER, + reason="Requires an older OpenSSL. Must be < 1.0.1" + ) + def test_sign_with_ec_private_key_is_unsupported(self): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + builder = x509.CertificateBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2032, 1, 1, 12, 1) + ) + + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA512(), backend) + + +class TestOpenSSLSignX509CertificateRevocationList(object): + def test_invalid_builder(self): + private_key = RSA_KEY_2048.private_key(backend) + + with pytest.raises(TypeError): + backend.create_x509_crl(object(), private_key, hashes.SHA256()) + + @pytest.mark.skipif( + backend._lib.CRYPTOGRAPHY_OPENSSL_101_OR_GREATER, + reason="Requires an older OpenSSL. Must be < 1.0.1" + ) + def test_sign_with_dsa_private_key_is_unsupported(self): + private_key = DSA_KEY_2048.private_key(backend) + builder = x509.CertificateRevocationListBuilder() + builder = builder.issuer_name( + x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')]) + ).last_update( + datetime.datetime(2002, 1, 1, 12, 1) + ).next_update( + datetime.datetime(2032, 1, 1, 12, 1) + ) + + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA1(), backend) + + @pytest.mark.skipif( + backend._lib.CRYPTOGRAPHY_OPENSSL_101_OR_GREATER, + reason="Requires an older OpenSSL. Must be < 1.0.1" + ) + def test_sign_with_ec_private_key_is_unsupported(self): + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + builder = x509.CertificateRevocationListBuilder() + builder = builder.issuer_name( + x509.Name([x509.NameAttribute(x509.NameOID.COUNTRY_NAME, u'US')]) + ).last_update( + datetime.datetime(2002, 1, 1, 12, 1) + ).next_update( + datetime.datetime(2032, 1, 1, 12, 1) + ) + + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA512(), backend) + + +class TestOpenSSLCreateRevokedCertificate(object): + def test_invalid_builder(self): + with pytest.raises(TypeError): + backend.create_x509_revoked_certificate(object()) + + +class TestOpenSSLSerializationWithOpenSSL(object): + def test_pem_password_cb_buffer_too_small(self): + ffi_cb, userdata = backend._pem_password_cb(b"aa") + handle = backend._ffi.new_handle(userdata) + buf = backend._ffi.new('char *') + assert ffi_cb(buf, 1, False, handle) == 0 + assert userdata.called == 1 + assert isinstance(userdata.exception, ValueError) + + def test_pem_password_cb(self): + password = b'abcdefg' + buf_size = len(password) + 1 + ffi_cb, userdata = backend._pem_password_cb(password) + handle = backend._ffi.new_handle(userdata) + buf = backend._ffi.new('char[]', buf_size) + assert ffi_cb(buf, buf_size, False, handle) == len(password) + assert userdata.called == 1 + assert backend._ffi.string(buf, len(password)) == password + + def test_unsupported_evp_pkey_type(self): + key = backend._create_evp_pkey_gc() + with raises_unsupported_algorithm(None): + backend._evp_pkey_to_private_key(key) + with raises_unsupported_algorithm(None): + backend._evp_pkey_to_public_key(key) + + def test_very_long_pem_serialization_password(self): + password = "x" * 1024 + + with pytest.raises(ValueError): + load_vectors_from_file( + os.path.join( + "asymmetric", "Traditional_OpenSSL_Serialization", + "key1.pem" + ), + lambda pemfile: ( + backend.load_pem_private_key( + pemfile.read().encode(), password + ) + ) + ) + + +class DummyLibrary(object): + Cryptography_HAS_EC = 0 + + +class TestOpenSSLEllipticCurve(object): + def test_elliptic_curve_supported(self, monkeypatch): + monkeypatch.setattr(backend, "_lib", DummyLibrary()) + + assert backend.elliptic_curve_supported(None) is False + + def test_elliptic_curve_signature_algorithm_supported(self, monkeypatch): + monkeypatch.setattr(backend, "_lib", DummyLibrary()) + + assert backend.elliptic_curve_signature_algorithm_supported( + None, None + ) is False + + def test_sn_to_elliptic_curve_not_supported(self): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_ELLIPTIC_CURVE): + _sn_to_elliptic_curve(backend, b"fake") + + def test_elliptic_curve_exchange_algorithm_supported(self, monkeypatch): + monkeypatch.setattr(backend, "_lib", DummyLibrary()) + assert not backend.elliptic_curve_exchange_algorithm_supported( + ec.ECDH(), ec.SECP256R1() + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +class TestRSAPEMSerialization(object): + def test_password_length_limit(self): + password = b"x" * 1024 + key = RSA_KEY_2048.private_key(backend) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.BestAvailableEncryption(password) + ) + + +class TestGOSTCertificate(object): + def test_numeric_string_x509_name_entry(self): + cert = _load_cert( + os.path.join("x509", "e-trust.ru.der"), + x509.load_der_x509_certificate, + backend + ) + if ( + backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_102I or + backend._lib.CRYPTOGRAPHY_IS_LIBRESSL + ): + with pytest.raises(ValueError) as exc: + cert.subject + + # We assert on the message in this case because if the certificate + # fails to load it will also raise a ValueError and this test could + # erroneously pass. + assert str(exc.value) == "Unsupported ASN1 string type. Type: 18" + else: + assert cert.subject.get_attributes_for_oid( + x509.ObjectIdentifier("1.2.643.3.131.1.1") + )[0].value == "007710474375" diff --git a/tests/hazmat/bindings/test_commoncrypto.py b/tests/hazmat/bindings/test_commoncrypto.py new file mode 100644 index 0000000..b0a2dc4 --- /dev/null +++ b/tests/hazmat/bindings/test_commoncrypto.py @@ -0,0 +1,26 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest + + +ccbinding = pytest.importorskip( + "cryptography.hazmat.bindings.commoncrypto.binding" +) + + +class TestCommonCrypto(object): + def test_binding_loads(self): + binding = ccbinding.Binding() + assert binding + assert binding.lib + assert binding.ffi + + def test_binding_returns_same_lib(self): + binding = ccbinding.Binding() + binding2 = ccbinding.Binding() + assert binding.lib == binding2.lib + assert binding.ffi == binding2.ffi diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py new file mode 100644 index 0000000..3e01717 --- /dev/null +++ b/tests/hazmat/bindings/test_openssl.py @@ -0,0 +1,115 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography.exceptions import InternalError +from cryptography.hazmat.bindings.openssl.binding import ( + Binding, _OpenSSLErrorWithText, _openssl_assert, _verify_openssl_version +) + + +class TestOpenSSL(object): + def test_binding_loads(self): + binding = Binding() + assert binding + assert binding.lib + assert binding.ffi + + def test_crypto_lock_init(self): + b = Binding() + if ( + b.lib.CRYPTOGRAPHY_OPENSSL_110_OR_GREATER and + not b.lib.CRYPTOGRAPHY_IS_LIBRESSL + ): + pytest.skip("Requires an older OpenSSL. Must be < 1.1.0") + + b.init_static_locks() + lock_cb = b.lib.CRYPTO_get_locking_callback() + assert lock_cb != b.ffi.NULL + + def test_add_engine_more_than_once(self): + b = Binding() + b._register_osrandom_engine() + assert b.lib.ERR_get_error() == 0 + + def test_ssl_ctx_options(self): + # Test that we're properly handling 32-bit unsigned on all platforms. + b = Binding() + assert b.lib.SSL_OP_ALL > 0 + ctx = b.lib.SSL_CTX_new(b.lib.TLSv1_method()) + ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free) + current_options = b.lib.SSL_CTX_get_options(ctx) + resp = b.lib.SSL_CTX_set_options(ctx, b.lib.SSL_OP_ALL) + expected_options = current_options | b.lib.SSL_OP_ALL + assert resp == expected_options + assert b.lib.SSL_CTX_get_options(ctx) == expected_options + + def test_ssl_options(self): + # Test that we're properly handling 32-bit unsigned on all platforms. + b = Binding() + assert b.lib.SSL_OP_ALL > 0 + ctx = b.lib.SSL_CTX_new(b.lib.TLSv1_method()) + ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free) + ssl = b.lib.SSL_new(ctx) + ssl = b.ffi.gc(ssl, b.lib.SSL_free) + current_options = b.lib.SSL_get_options(ssl) + resp = b.lib.SSL_set_options(ssl, b.lib.SSL_OP_ALL) + expected_options = current_options | b.lib.SSL_OP_ALL + assert resp == expected_options + assert b.lib.SSL_get_options(ssl) == expected_options + + def test_ssl_mode(self): + # Test that we're properly handling 32-bit unsigned on all platforms. + b = Binding() + assert b.lib.SSL_OP_ALL > 0 + ctx = b.lib.SSL_CTX_new(b.lib.TLSv1_method()) + ctx = b.ffi.gc(ctx, b.lib.SSL_CTX_free) + ssl = b.lib.SSL_new(ctx) + ssl = b.ffi.gc(ssl, b.lib.SSL_free) + current_options = b.lib.SSL_get_mode(ssl) + resp = b.lib.SSL_set_mode(ssl, b.lib.SSL_OP_ALL) + expected_options = current_options | b.lib.SSL_OP_ALL + assert resp == expected_options + assert b.lib.SSL_get_mode(ssl) == expected_options + + def test_conditional_removal(self): + b = Binding() + + if b.lib.CRYPTOGRAPHY_OPENSSL_101_OR_GREATER: + assert b.lib.CMAC_Init + else: + with pytest.raises(AttributeError): + b.lib.CMAC_Init + + def test_openssl_assert_error_on_stack(self): + b = Binding() + b.lib.ERR_put_error( + b.lib.ERR_LIB_EVP, + b.lib.EVP_F_EVP_ENCRYPTFINAL_EX, + b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH, + b"", + -1 + ) + with pytest.raises(InternalError) as exc_info: + _openssl_assert(b.lib, False) + + assert exc_info.value.err_code == [_OpenSSLErrorWithText( + code=101183626, + lib=b.lib.ERR_LIB_EVP, + func=b.lib.EVP_F_EVP_ENCRYPTFINAL_EX, + reason=b.lib.EVP_R_DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH, + reason_text=( + b'error:0607F08A:digital envelope routines:EVP_EncryptFinal_' + b'ex:data not multiple of block length' + ) + )] + + def test_verify_openssl_version(self, monkeypatch): + monkeypatch.delenv("CRYPTOGRAPHY_ALLOW_OPENSSL_100", raising=False) + with pytest.raises(RuntimeError): + # OpenSSL 1.0.0 + _verify_openssl_version(0x100000F) diff --git a/tests/hazmat/primitives/__init__.py b/tests/hazmat/primitives/__init__.py new file mode 100644 index 0000000..4b54088 --- /dev/null +++ b/tests/hazmat/primitives/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function diff --git a/tests/hazmat/primitives/fixtures_dsa.py b/tests/hazmat/primitives/fixtures_dsa.py new file mode 100644 index 0000000..dd947ae --- /dev/null +++ b/tests/hazmat/primitives/fixtures_dsa.py @@ -0,0 +1,152 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography.hazmat.primitives.asymmetric.dsa import ( + DSAParameterNumbers, DSAPrivateNumbers, DSAPublicNumbers +) + + +DSA_KEY_1024 = DSAPrivateNumbers( + public_numbers=DSAPublicNumbers( + parameter_numbers=DSAParameterNumbers( + p=int( + 'd38311e2cd388c3ed698e82fdf88eb92b5a9a483dc88005d4b725ef34' + '1eabb47cf8a7a8a41e792a156b7ce97206c4f9c5ce6fc5ae7912102b6' + 'b502e59050b5b21ce263dddb2044b652236f4d42ab4b5d6aa73189cef' + '1ace778d7845a5c1c1c7147123188f8dc551054ee162b634d60f097f7' + '19076640e20980a0093113a8bd73', 16 + ), + q=int('96c5390a8b612c0e422bb2b0ea194a3ec935a281', 16), + g=int( + '06b7861abbd35cc89e79c52f68d20875389b127361ca66822138ce499' + '1d2b862259d6b4548a6495b195aa0e0b6137ca37eb23b94074d3c3d30' + '0042bdf15762812b6333ef7b07ceba78607610fcc9ee68491dbc1e34c' + 'd12615474e52b18bc934fb00c61d39e7da8902291c4434a4e2224c3f4' + 'fd9f93cd6f4f17fc076341a7e7d9', 16 + ) + ), + y=int( + '6f26d98d41de7d871b6381851c9d91fa03942092ab6097e76422070edb71d' + 'b44ff568280fdb1709f8fc3feab39f1f824adaeb2a298088156ac31af1aa0' + '4bf54f475bdcfdcf2f8a2dd973e922d83e76f016558617603129b21c70bf7' + 'd0e5dc9e68fe332e295b65876eb9a12fe6fca9f1a1ce80204646bf99b5771' + 'd249a6fea627', 16 + ) + ), + x=int('8185fee9cc7c0e91fd85503274f1cd5a3fd15a49', 16) +) + +DSA_KEY_2048 = DSAPrivateNumbers( + public_numbers=DSAPublicNumbers( + parameter_numbers=DSAParameterNumbers( + p=int( + 'ea1fb1af22881558ef93be8a5f8653c5a559434c49c8c2c12ace5e9c4' + '1434c9cf0a8e9498acb0f4663c08b4484eace845f6fb17dac62c98e70' + '6af0fc74e4da1c6c2b3fbf5a1d58ff82fc1a66f3e8b12252c40278fff' + '9dd7f102eed2cb5b7323ebf1908c234d935414dded7f8d244e54561b0' + 'dca39b301de8c49da9fb23df33c6182e3f983208c560fb5119fbf78eb' + 'e3e6564ee235c6a15cbb9ac247baba5a423bc6582a1a9d8a2b4f0e9e3' + 'd9dbac122f750dd754325135257488b1f6ecabf21bff2947fe0d3b2cb' + '7ffe67f4e7fcdf1214f6053e72a5bb0dd20a0e9fe6db2df0a908c36e9' + '5e60bf49ca4368b8b892b9c79f61ef91c47567c40e1f80ac5aa66ef7', + 16 + ), + q=int( + '8ec73f3761caf5fdfe6e4e82098bf10f898740dcb808204bf6b18f507' + '192c19d', 16 + ), + g=int( + 'e4c4eca88415b23ecf811c96e48cd24200fe916631a68a684e6ccb6b1' + '913413d344d1d8d84a333839d88eee431521f6e357c16e6a93be111a9' + '8076739cd401bab3b9d565bf4fb99e9d185b1e14d61c93700133f908b' + 'ae03e28764d107dcd2ea7674217622074bb19efff482f5f5c1a86d555' + '1b2fc68d1c6e9d8011958ef4b9c2a3a55d0d3c882e6ad7f9f0f3c6156' + '8f78d0706b10a26f23b4f197c322b825002284a0aca91807bba98ece9' + '12b80e10cdf180cf99a35f210c1655fbfdd74f13b1b5046591f840387' + '3d12239834dd6c4eceb42bf7482e1794a1601357b629ddfa971f2ed27' + '3b146ec1ca06d0adf55dd91d65c37297bda78c6d210c0bc26e558302', + 16 + ) + ), + y=int( + '6b32e31ab9031dc4dd0b5039a78d07826687ab087ae6de4736f5b0434e125' + '3092e8a0b231f9c87f3fc8a4cb5634eb194bf1b638b7a7889620ce6711567' + 'e36aa36cda4604cfaa601a45918371d4ccf68d8b10a50a0460eb1dc0fff62' + 'ef5e6ee4d473e18ea4a66c196fb7e677a49b48241a0b4a97128eff30fa437' + '050501a584f8771e7280d26d5af30784039159c11ebfea10b692fd0a58215' + 'eeb18bff117e13f08db792ed4151a218e4bed8dddfb0793225bd1e9773505' + '166f4bd8cedbb286ea28232972da7bae836ba97329ba6b0a36508e50a52a7' + '675e476d4d4137eae13f22a9d2fefde708ba8f34bf336c6e76331761e4b06' + '17633fe7ec3f23672fb19d27', 16 + ) + ), + x=int( + '405772da6e90d809e77d5de796562a2dd4dfd10ef00a83a3aba6bd818a0348a1', + 16 + ) +) + +DSA_KEY_3072 = DSAPrivateNumbers( + public_numbers=DSAPublicNumbers( + parameter_numbers=DSAParameterNumbers( + p=int( + 'f335666dd1339165af8b9a5e3835adfe15c158e4c3c7bd53132e7d582' + '8c352f593a9a787760ce34b789879941f2f01f02319f6ae0b756f1a84' + '2ba54c85612ed632ee2d79ef17f06b77c641b7b080aff52a03fc2462e' + '80abc64d223723c236deeb7d201078ec01ca1fbc1763139e25099a84e' + 'c389159c409792080736bd7caa816b92edf23f2c351f90074aa5ea265' + '1b372f8b58a0a65554db2561d706a63685000ac576b7e4562e262a142' + '85a9c6370b290e4eb7757527d80b6c0fd5df831d36f3d1d35f12ab060' + '548de1605fd15f7c7aafed688b146a02c945156e284f5b71282045aba' + '9844d48b5df2e9e7a5887121eae7d7b01db7cdf6ff917cd8eb50c6bf1' + 'd54f90cce1a491a9c74fea88f7e7230b047d16b5a6027881d6f154818' + 'f06e513faf40c8814630e4e254f17a47bfe9cb519b98289935bf17673' + 'ae4c8033504a20a898d0032ee402b72d5986322f3bdfb27400561f747' + '6cd715eaabb7338b854e51fc2fa026a5a579b6dcea1b1c0559c13d3c1' + '136f303f4b4d25ad5b692229957', 16 + ), + q=int( + 'd3eba6521240694015ef94412e08bf3cf8d635a455a398d6f210f6169' + '041653b', 16 + ), + g=int( + 'ce84b30ddf290a9f787a7c2f1ce92c1cbf4ef400e3cd7ce4978db2104' + 'd7394b493c18332c64cec906a71c3778bd93341165dee8e6cd4ca6f13' + 'afff531191194ada55ecf01ff94d6cf7c4768b82dd29cd131aaf202ae' + 'fd40e564375285c01f3220af4d70b96f1395420d778228f1461f5d0b8' + 'e47357e87b1fe3286223b553e3fc9928f16ae3067ded6721bedf1d1a0' + '1bfd22b9ae85fce77820d88cdf50a6bde20668ad77a707d1c60fcc5d5' + '1c9de488610d0285eb8ff721ff141f93a9fb23c1d1f7654c07c46e588' + '36d1652828f71057b8aff0b0778ef2ca934ea9d0f37daddade2d823a4' + 'd8e362721082e279d003b575ee59fd050d105dfd71cd63154efe431a0' + '869178d9811f4f231dc5dcf3b0ec0f2b0f9896c32ec6c7ee7d60aa971' + '09e09224907328d4e6acd10117e45774406c4c947da8020649c3168f6' + '90e0bd6e91ac67074d1d436b58ae374523deaf6c93c1e6920db4a080b' + '744804bb073cecfe83fa9398cf150afa286dc7eb7949750cf5001ce10' + '4e9187f7e16859afa8fd0d775ae', 16 + ) + ), + y=int( + '814824e435e1e6f38daa239aad6dad21033afce6a3ebd35c1359348a0f241' + '8871968c2babfc2baf47742148828f8612183178f126504da73566b6bab33' + 'ba1f124c15aa461555c2451d86c94ee21c3e3fc24c55527e01b1f03adcdd8' + 'ec5cb08082803a7b6a829c3e99eeb332a2cf5c035b0ce0078d3d414d31fa4' + '7e9726be2989b8d06da2e6cd363f5a7d1515e3f4925e0b32adeae3025cc5a' + '996f6fd27494ea408763de48f3bb39f6a06514b019899b312ec570851637b' + '8865cff3a52bf5d54ad5a19e6e400a2d33251055d0a440b50d53f4791391d' + 'c754ad02b9eab74c46b4903f9d76f824339914db108057af7cde657d41766' + 'a99991ac8787694f4185d6f91d7627048f827b405ec67bf2fe56141c4c581' + 'd8c317333624e073e5879a82437cb0c7b435c0ce434e15965db1315d64895' + '991e6bbe7dac040c42052408bbc53423fd31098248a58f8a67da3a39895cd' + '0cc927515d044c1e3cb6a3259c3d0da354cce89ea3552c59609db10ee9899' + '86527436af21d9485ddf25f90f7dff6d2bae', 16 + ) + ), + x=int( + 'b2764c46113983777d3e7e97589f1303806d14ad9f2f1ef033097de954b17706', + 16 + ) +) diff --git a/tests/hazmat/primitives/fixtures_ec.py b/tests/hazmat/primitives/fixtures_ec.py new file mode 100644 index 0000000..21c6903 --- /dev/null +++ b/tests/hazmat/primitives/fixtures_ec.py @@ -0,0 +1,296 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography.hazmat.primitives.asymmetric import ec + + +EC_KEY_SECT571R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '213997069697108634621868251335076179190383272087548888968788698953' + '131928375431570122753130054966269038244076049869476736547896549201' + '7388482714521707824160638375437887802901' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT571R1(), + x=int( + '42585672410900520895287019432267514156432686681290164230262278' + '54789182447139054594501570747809649335533486119017169439209005' + '883737780433424425566023654583165324498640038089' + ), + y=int( + '13822523320209387572500458104799806851658024537477228250738334' + '46977851514777531296572763848253279034733550774927720436494321' + '97281333379623823457479233585424800362717541750' + ) + ) +) + +EC_KEY_SECT409R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '604993237916498765317587097853603474519114726157206838874832379003' + '281871982139714656205843929472002062791572217653118715727' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT409R1(), + x=int( + '76237701339268928039087238870073679814646664010783544301589269' + '2272579213400205907766385199643053767195204247826349822350081' + ), + y=int( + '10056668929618383045204866060110626563392345494925302478351744' + '01475129090774493235522729123877384838835703483224447476728811' + ) + ) +) + +EC_KEY_SECT283R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '589705077255658434962118789801402573495547207239917043241273753671' + '0603230261342427657' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT283R1(), + x=int( + '10694213430317013187241490088760888472172922291550831393222973' + '531614941756901942108493' + ), + y=int( + '11461553100313943515373601367527399649593366728262918214942116' + '4359557613202950705170' + ) + ) +) + +EC_KEY_SECT233R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '343470067105388144757135261232658742142830154753739648095101899829' + '8288' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT233R1(), + x=int( + '74494951569151557692195071465128140646140765188698294062550374' + '71118267' + ), + y=int( + '48699150823022962508544923825876164485917001162461401797511748' + '44872205' + ) + ) +) + +EC_KEY_SECT163R2 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '11788436193853888218177032687141056784083668635' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT163R2(), + x=int( + '5247234453330640212490501030772203801908103222463' + ), + y=int( + '3172513801099088785224248292142866317754124455206' + ) + ) +) + +EC_KEY_SECT571K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '592811051234886966121888758661314648311634839499582476726008738218' + '165015048237934517672316204181933804884636855291118594744334592153' + '883208936227914544246799490897169723387' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT571K1(), + x=int( + '81362471461936552203898455874182916939857774872643607884250052' + '29301336524105230729653881789373412990921493551253481866317181' + '50644729351721577822595637058949405764944491655' + ), + y=int( + '14058041260812945396067821061063618047896814719828637241661260' + '31235681542401975593036630733881695595289523801041910183736211' + '587294494888450327374439795428519848065589000434' + ) + ) +) + +EC_KEY_SECT409K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '110321743150399087059465162400463719641470113494908091197354523708' + '934106732952992153105338671368548199643686444619485307877' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT409K1(), + x=int( + '62280214209410363493525178797944995742119600145953755916426161' + '0790364158569265348038207313261547476506319796469776797725796' + ), + y=int( + '46653883749102474289095010108777579907422472804577185369332018' + '7318642669590280811057512951467298158275464566214288556375885' + ) + ) +) + +EC_KEY_SECT283K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '182508394415444014156574733141549331538128234395356466108310015130' + '3868915489347291850' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT283K1(), + x=int( + '31141647206111886426350703123670451554123180910379592764773885' + '2959123367428352287032' + ), + y=int( + '71787460144483665964585187837283963089964760704065205376175384' + '58957627834444017112582' + ) + ) +) + +EC_KEY_SECT233K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '172670089647474613734091436081960550801254775902629891892394471086' + '2070' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT233K1(), + x=int( + '55693911474339510991521579392202889561373678973929426354737048' + '68129172' + ), + y=int( + '11025856248546376145959939911850923631416718241836051344384802' + '737277815' + ) + ) +) + +EC_KEY_SECT163K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '3699303791425402204035307605170569820290317991287' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECT163K1(), + x=int( + '4479755902310063321544063130576409926980094120721' + ), + y=int( + '3051218481937171839039826690648109285113977745779' + ) + ) +) + +EC_KEY_SECP521R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '662751235215460886290293902658128847495347691199214706697089140769' + '672273950767961331442265530524063943548846724348048614239791498442' + '5997823106818915698960565' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP521R1(), + x=int( + '12944742826257420846659527752683763193401384271391513286022917' + '29910013082920512632908350502247952686156279140016049549948975' + '670668730618745449113644014505462' + ), + y=int( + '10784108810271976186737587749436295782985563640368689081052886' + '16296815984553198866894145509329328086635278430266482551941240' + '591605833440825557820439734509311' + ) + ) +) + +EC_KEY_SECP384R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '280814107134858470598753916394807521398239633534281633982576099083' + '35787109896602102090002196616273211495718603965098' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP384R1(), + x=int( + '10036914308591746758780165503819213553101287571902957054148542' + '504671046744460374996612408381962208627004841444205030' + ), + y=int( + '17337335659928075994560513699823544906448896792102247714689323' + '575406618073069185107088229463828921069465902299522926' + ) + ) +) + +EC_KEY_SECP256R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '271032978511595617649844168316234344656921218699414461240502635010' + '25776962849' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP256R1(), + x=int( + '49325986169170464532722748935508337546545346352733747948730305' + '442770101441241' + ), + y=int( + '51709162888529903487188595007092772817469799707382623884187518' + '455962250433661' + ) + ) +) + +EC_KEY_SECP256K1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '683341569008473593765879222774207677458810362976327530563215318048' + '64380736732' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP256K1(), + x=int( + '59251322975795306609293064274738085741081547489119277536110995' + '120127593127884' + ), + y=int( + '10334192001480392039227801832201340147605940717841294644187071' + '8261641142297801' + ) + ) +) + +EC_KEY_SECP224R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '234854340492774342642505519082413233282383066880756900834047566251' + '50' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP224R1(), + x=int( + '51165676638271204691095081341581621487998422645261573824239666' + '1214' + ), + y=int( + '14936601450555711309158397172719963843891926209168533453717969' + '1265' + ) + ) +) + +EC_KEY_SECP192R1 = ec.EllipticCurvePrivateNumbers( + private_value=int( + '4534766128536179420071447168915990251715442361606049349869' + ), + public_numbers=ec.EllipticCurvePublicNumbers( + curve=ec.SECP192R1(), + x=int( + '5415069751170397888083674339683360671310515485781457536999' + ), + y=int( + '18671605334415960797751252911958331304288357195986572776' + ) + ) +) diff --git a/tests/hazmat/primitives/fixtures_rsa.py b/tests/hazmat/primitives/fixtures_rsa.py new file mode 100644 index 0000000..a531783 --- /dev/null +++ b/tests/hazmat/primitives/fixtures_rsa.py @@ -0,0 +1,603 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography.hazmat.primitives.asymmetric.rsa import ( + RSAPrivateNumbers, RSAPublicNumbers +) + + +RSA_KEY_512 = RSAPrivateNumbers( + p=int( + "d57846898d5c0de249c08467586cb458fa9bc417cdf297f73cfc52281b787cd9", 16 + ), + q=int( + "d10f71229e87e010eb363db6a85fd07df72d985b73c42786191f2ce9134afb2d", 16 + ), + d=int( + "272869352cacf9c866c4e107acc95d4c608ca91460a93d28588d51cfccc07f449" + "18bbe7660f9f16adc2b4ed36ca310ef3d63b79bd447456e3505736a45a6ed21", 16 + ), + dmp1=int( + "addff2ec7564c6b64bc670d250b6f24b0b8db6b2810099813b7e7658cecf5c39", 16 + ), + dmq1=int( + "463ae9c6b77aedcac1397781e50e4afc060d4b216dc2778494ebe42a6850c81", 16 + ), + iqmp=int( + "54deef8548f65cad1d411527a32dcb8e712d3e128e4e0ff118663fae82a758f4", 16 + ), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "ae5411f963c50e3267fafcf76381c8b1e5f7b741fdb2a544bcf48bd607b10c991" + "90caeb8011dc22cf83d921da55ec32bd05cac3ee02ca5e1dbef93952850b525", + 16 + ), + ) +) + +RSA_KEY_512_ALT = RSAPrivateNumbers( + p=int( + "febe19c29a0b50fefa4f7b1832f84df1caf9be8242da25c9d689e18226e67ce5", + 16), + q=int( + "eb616c639dd999feda26517e1c77b6878f363fe828c4e6670ec1787f28b1e731", + 16), + d=int( + "80edecfde704a806445a4cc782b85d3f36f17558f385654ea767f006470fdfcbda5e2" + "206839289d3f419b4e4fb8e1acee1b4fb9c591f69b64ec83937f5829241", 16), + dmp1=int( + "7f4fa06e2a3077a54691cc5216bf13ad40a4b9fa3dd0ea4bca259487484baea5", + 16), + dmq1=int( + "35eaa70d5a8711c352ed1c15ab27b0e3f46614d575214535ae279b166597fac1", + 16), + iqmp=int( + "cc1f272de6846851ec80cb89a02dbac78f44b47bc08f53b67b4651a3acde8b19", + 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "ea397388b999ef0f7e7416fa000367efd9a0ba0deddd3f8160d1c36d62267f210" + "fbd9c97abeb6654450ff03e7601b8caa6c6f4cba18f0b52c179d17e8f258ad5", + 16), + ) +) + +RSA_KEY_522 = RSAPrivateNumbers( + p=int( + "1a8aab9a069f92b52fdf05824f2846223dc27adfc806716a247a77d4c36885e4bf", + 16), + q=int( + "19e8d620d177ec54cdb733bb1915e72ef644b1202b889ceb524613efa49c07eb4f", + 16), + d=int( + "10b8a7c0a92c1ae2d678097d69db3bfa966b541fb857468291d48d1b52397ea2bac0d" + "4370c159015c7219e3806a01bbafaffdd46f86e3da1e2d1fe80a0369ccd745", 16), + dmp1=int( + "3eb6277f66e6e2dcf89f1b8529431f730839dbd9a3e49555159bc8470eee886e5", + 16), + dmq1=int( + "184b4d74aa54c361e51eb23fee4eae5e4786b37b11b6e0447af9c0b9c4e4953c5b", + 16), + iqmp=int( + "f80e9ab4fa7b35d0d232ef51c4736d1f2dcf2c7b1dd8716211b1bf1337e74f8ae", + 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "2afaea0e0bb6fca037da7d190b5270a6c665bc18e7a456f7e69beaac4433db748" + "ba99acdd14697e453bca596eb35b47f2d48f1f85ef08ce5109dad557a9cf85ebf" + "1", 16), + ), +) + +RSA_KEY_599 = RSAPrivateNumbers( + p=int( + "cf95d20be0c7af69f4b3d909f65d858c26d1a7ef34da8e3977f4fa230580e58814b54" + "24be99", 16), + q=int( + "6052be4b28debd4265fe12ace5aa4a0c4eb8d63ff8853c66824b35622161eb48a3bc8" + "c3ada5", 16), + d=int( + "69d9adc465e61585d3142d7cc8dd30605e8d1cbbf31009bc2cd5538dc40528d5d68ee" + "fe6a42d23674b6ec76e192351bf368c8968f0392110bf1c2825dbcff071270b80adcc" + "fa1d19d00a1", 16), + dmp1=int( + "a86d10edde456687fba968b1f298d2e07226adb1221b2a466a93f3d83280f0bb46c20" + "2b6811", 16), + dmq1=int( + "40d570e08611e6b1da94b95d46f8e7fe80be48f7a5ff8838375b08039514a399b11c2" + "80735", 16), + iqmp=int( + "cd051cb0ea68b88765c041262ace2ec4db11dab14afd192742e34d5da3328637fabdf" + "bae26e", 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "4e1b470fe00642426f3808e74c959632dd67855a4c503c5b7876ccf4dc7f6a1a4" + "9107b90d26daf0a7879a6858218345fbc6e59f01cd095ca5647c27c25265e6c47" + "4fea89537191c7073d9d", 16), + ) +) + +RSA_KEY_745 = RSAPrivateNumbers( + p=int( + "1c5a0cfe9a86debd19eca33ba961f15bc598aa7983a545ce775b933afc89eb51bcf90" + "836257fdd060d4b383240241d", 16 + ), + q=int( + "fb2634f657f82ee6b70553382c4e2ed26b947c97ce2f0016f1b282cf2998184ad0527" + "a9eead826dd95fe06b57a025", 16 + ), + d=int( + "402f30f976bc07d15ff0779abff127b20a8b6b1d0024cc2ad8b6762d38f174f81e792" + "3b49d80bdbdd80d9675cbc7b2793ec199a0430eb5c84604dacfdb29259ae6a1a44676" + "22f0b23d4cb0f5cb1db4b8173c8d9d3e57a74dbd200d2141", 16), + dmp1=int( + "e5e95b7751a6649f199be21bef7a51c9e49821d945b6fc5f538b4a670d8762c375b00" + "8e70f31d52b3ea2bd14c3101", 16), + dmq1=int( + "12b85d5843645f72990fcf8d2f58408b34b3a3b9d9078dd527fceb5d2fb7839008092" + "dd4aca2a1fb00542801dcef5", 16), + iqmp=int( + "5672740d947f621fc7969e3a44ec26736f3f819863d330e63e9409e139d20753551ac" + "c16544dd2bdadb9dee917440", 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "1bd085f92237774d34013b477ceebbb2f2feca71118db9b7429341477947e7b1d" + "04e8c43ede3c52bb25781af58d4ff81289f301eac62dc3bcd7dafd7a4d5304e9f" + "308e766952fbf2b62373e66611fa53189987dbef9f7243dcbbeb25831", 16), + ) +) + +RSA_KEY_768 = RSAPrivateNumbers( + p=int( + "f80c0061b607f93206b68e208906498d68c6e396faf457150cf975c8f849848465869" + "7ecd402313397088044c4c2071b", 16), + q=int( + "e5b5dbecc93c6d306fc14e6aa9737f9be2728bc1a326a8713d2849b34c1cb54c63468" + "3a68abb1d345dbf15a3c492cf55", 16), + d=int( + "d44601442255ffa331212c60385b5e898555c75c0272632ff42d57c4b16ca97dbca9f" + "d6d99cd2c9fd298df155ed5141b4be06c651934076133331d4564d73faed7ce98e283" + "2f7ce3949bc183be7e7ca34f6dd04a9098b6c73649394b0a76c541", 16), + dmp1=int( + "a5763406fa0b65929661ce7b2b8c73220e43a5ebbfe99ff15ddf464fd238105ad4f2a" + "c83818518d70627d8908703bb03", 16), + dmq1=int( + "cb467a9ef899a39a685aecd4d0ad27b0bfdc53b68075363c373d8eb2bed8eccaf3533" + "42f4db735a9e087b7539c21ba9d", 16), + iqmp=int( + "5fe86bd3aee0c4d09ef11e0530a78a4534c9b833422813b5c934a450c8e564d8097a0" + "6fd74f1ebe2d5573782093f587a", 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "de92f1eb5f4abf426b6cac9dd1e9bf57132a4988b4ed3f8aecc15e251028bd6df" + "46eb97c711624af7db15e6430894d1b640c13929329241ee094f5a4fe1a20bc9b" + "75232320a72bc567207ec54d6b48dccb19737cf63acc1021abb337f19130f7", + 16), + ) +) + +RSA_KEY_1024 = RSAPrivateNumbers( + p=int( + "ea4d9d9a1a068be44b9a5f8f6de0512b2c5ba1fb804a4655babba688e6e890b347c1a" + "7426685a929337f513ae4256f0b7e5022d642237f960c5b24b96bee8e51", 16), + q=int( + "cffb33e400d6f08b410d69deb18a85cf0ed88fcca9f32d6f2f66c62143d49aff92c11" + "4de937d4f1f62d4635ee89af99ce86d38a2b05310f3857c7b5d586ac8f9", 16), + d=int( + "3d12d46d04ce942fb99be7bf30587b8cd3e21d75a2720e7bda1b867f1d418d91d8b9f" + "e1c00181fdde94f2faf33b4e6f800a1b3ae3b972ccb6d5079dcb6c794070ac8306d59" + "c00b58b7a9a81122a6b055832de7c72334a07494d8e7c9fbeed2cc37e011d9e6bfc6e" + "9bcddbef7f0f5771d9cf82cd4b268c97ec684575c24b6c881", 16), + dmp1=int( + "470f2b11257b7ec9ca34136f487f939e6861920ad8a9ae132a02e74af5dceaa5b4c98" + "2949ccb44b67e2bcad2f58674db237fe250e0d62b47b28fa1dfaa603b41", 16), + dmq1=int( + "c616e8317d6b3ae8272973709b80e8397256697ff14ea03389de454f619f99915a617" + "45319fefbe154ec1d49441a772c2f63f7d15c478199afc60469bfd0d561", 16), + iqmp=int( + "d15e7c9ad357dfcd5dbdc8427680daf1006761bcfba93a7f86589ad88832a8d564b1c" + "d4291a658c96fbaea7ca588795820902d85caebd49c2d731e3fe0243130", 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "be5aac07456d990133ebce69c06b48845b972ab1ad9f134bc5683c6b5489b5119" + "ede07be3bed0e355d48e0dfab1e4fb5187adf42d7d3fb0401c082acb8481bf17f" + "0e871f8877be04c3a1197d40aa260e2e0c48ed3fd2b93dc3fc0867591f67f3cd6" + "0a77adee1d68a8c3730a5702485f6ac9ede7f0fd2918e037ee4cc1fc1b4c9", + 16), + ) +) + +RSA_KEY_1025 = RSAPrivateNumbers( + p=int( + "18e9bfb7071725da04d31c103fa3563648c69def43a204989214eb57b0c8b299f9ef3" + "5dda79a62d8d67fd2a9b69fbd8d0490aa2edc1e111a2b8eb7c737bb691a5", 16), + q=int( + "d8eccaeeb95815f3079d13685f3f72ca2bf2550b349518049421375df88ca9bbb4ba8" + "cb0e3502203c9eeae174112509153445d251313e4711a102818c66fcbb7", 16), + d=int( + "fe9ac54910b8b1bc948a03511c54cab206a1d36d50d591124109a48abb7480977ccb0" + "47b4d4f1ce7b0805df2d4fa3fe425f49b78535a11f4b87a4eba0638b3340c23d4e6b2" + "1ecebe9d5364ea6ead2d47b27836019e6ecb407000a50dc95a8614c9d0031a6e3a524" + "d2345cfb76e15c1f69d5ba35bdfb6ec63bcb115a757ef79d9", 16), + dmp1=int( + "18537e81006a68ea76d590cc88e73bd26bc38d09c977959748e5265c0ce21c0b5fd26" + "53d975f97ef759b809f791487a8fff1264bf561627fb4527a3f0bbb72c85", 16), + dmq1=int( + "c807eac5a1f1e1239f04b04dd16eff9a00565127a91046fa89e1eb5d6301cace85447" + "4d1f47b0332bd35b4214b66e9166953241538f761f30d969272ee214f17", 16), + iqmp=int( + "133aa74dd41fe70fa244f07d0c4091a22f8c8f0134fe6aea9ec8b55383b758fefe358" + "2beec36eca91715eee7d21931f24fa9e97e8e3a50f9cd0f731574a5eafcc", 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "151c44fed756370fb2d4a0e6ec7dcac84068ca459b6aaf22daf902dca72c77563" + "bf276fe3523f38f5ddaf3ea9aa88486a9d8760ff732489075862bee0e599de5c5" + "f509b4519f4f446521bad15cd279a498fe1e89107ce0d237e3103d7c5eb801666" + "42e2924b152aebff97b71fdd2d68ebb45034cc784e2e822ff6d1edf98af3f3", + 16), + ) +) + +RSA_KEY_1026 = RSAPrivateNumbers( + p=int( + "1fcbfb8719c5bdb5fe3eb0937c76bb096e750b9442dfe31d6a877a13aed2a6a4e9f79" + "40f815f1c307dd6bc2b4b207bb6fe5be3a15bd2875a957492ce197cdedb1", 16), + q=int( + "1f704a0f6b8966dd52582fdc08227dd3dbaeaa781918b41144b692711091b4ca4eb62" + "985c3513853828ce8739001dfba9a9a7f1a23cbcaf74280be925e2e7b50d", 16), + d=int( + "c67975e35a1d0d0b3ebfca736262cf91990cb31cf4ac473c0c816f3bc2720bcba2475" + "e8d0de8535d257816c0fc53afc1b597eada8b229069d6ef2792fc23f59ffb4dc6c3d9" + "0a3c462082025a4cba7561296dd3d8870c4440d779406f00879afe2c681e7f5ee055e" + "ff829e6e55883ec20830c72300762e6e3a333d94b4dbe4501", 16), + dmp1=int( + "314730ca7066c55d086a9fbdf3670ef7cef816b9efea8b514b882ae9d647217cf41d7" + "e9989269dc9893d02e315cb81f058c49043c2cac47adea58bdf5e20e841", 16), + dmq1=int( + "1da28a9d687ff7cfeebc2439240de7505a8796376968c8ec723a2b669af8ce53d9c88" + "af18540bd78b2da429014923fa435f22697ac60812d7ca9c17a557f394cd", 16), + iqmp=int( + "727947b57b8a36acd85180522f1b381bce5fdbd962743b3b14af98a36771a80f58ddd" + "62675d72a5935190da9ddc6fd6d6d5e9e9f805a2e92ab8d56b820493cdf", 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "3e7a5e6483e55eb8b723f9c46732d21b0af9e06a4a1099962d67a35ee3f62e312" + "9cfae6ab0446da18e26f33e1d753bc1cc03585c100cf0ab5ef056695706fc8b0c" + "9c710cd73fe6e5beda70f515a96fabd3cc5ac49efcb2594b220ff3b603fcd927f" + "6a0838ef04bf52f3ed9eab801f09e5aed1613ddeb946ed0fbb02060b3a36fd", + 16), + ) +) + +RSA_KEY_1027 = RSAPrivateNumbers( + p=int( + "30135e54cfb072c3d3eaf2000f3ed92ceafc85efc867b9d4bf5612f2978c432040093" + "4829f741c0f002b54af2a4433ff872b6321ef00ff1e72cba4e0ced937c7d", 16), + q=int( + "1d01a8aead6f86b78c875f18edd74214e06535d65da054aeb8e1851d6f3319b4fb6d8" + "6b01e07d19f8261a1ded7dc08116345509ab9790e3f13e65c037e5bb7e27", 16), + d=int( + "21cf4477df79561c7818731da9b9c88cd793f1b4b8e175bd0bfb9c0941a4dc648ecf1" + "6d96b35166c9ea116f4c2eb33ce1c231e641a37c25e54c17027bdec08ddafcb83642e" + "795a0dd133155ccc5eed03b6e745930d9ac7cfe91f9045149f33295af03a2198c660f" + "08d8150d13ce0e2eb02f21ac75d63b55822f77bd5be8d07619", 16), + dmp1=int( + "173fb695931e845179511c18b546b265cb79b517c135902377281bdf9f34205e1f399" + "4603ad63e9f6e7885ea73a929f03fa0d6bed943051ce76cddde2d89d434d", 16), + dmq1=int( + "10956b387b2621327da0c3c8ffea2af8be967ee25163222746c28115a406e632a7f12" + "5a9397224f1fa5c116cd3a313e5c508d31db2deb83b6e082d213e33f7fcf", 16), + iqmp=int( + "234f833949f2c0d797bc6a0e906331e17394fa8fbc8449395766d3a8d222cf6167c48" + "8e7fe1fe9721d3e3b699a595c8e6f063d92bd840dbc84d763b2b37002109", 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "57281707d7f9b1369c117911758980e32c05b133ac52c225bcf68b79157ff47ea" + "0a5ae9f579ef1fd7e42937f921eb3123c4a045cc47a2159fbbf904783e654954c" + "42294c30a95c15db7c7b91f136244e548f62474b137087346c5522e54f226f49d" + "6c93bc58cb39972e41bde452bb3ae9d60eb93e5e1ce91d222138d9890c7d0b", + 16), + ) +) + +RSA_KEY_1028 = RSAPrivateNumbers( + p=int( + "359d17378fae8e9160097daee78a206bd52efe1b757c12a6da8026cc4fc4bb2620f12" + "b8254f4db6aed8228be8ee3e5a27ec7d31048602f01edb00befd209e8c75", 16), + q=int( + "33a2e70b93d397c46e63b273dcd3dcfa64291342a6ce896e1ec8f1c0edc44106550f3" + "c06e7d3ca6ea29eccf3f6ab5ac6235c265313d6ea8e8767e6a343f616581", 16), + d=int( + "880640088d331aa5c0f4cf2887809a420a2bc086e671e6ffe4e47a8c80792c038a314" + "9a8e45ef9a72816ab45b36e3af6800351067a6b2751843d4232413146bb575491463a" + "8addd06ce3d1bcf7028ec6c5d938c545a20f0a40214b5c574ca7e840062b2b5f8ed49" + "4b144bb2113677c4b10519177fee1d4f5fb8a1c159b0b47c01", 16), + dmp1=int( + "75f8c52dad2c1cea26b8bba63236ee4059489e3d2db766136098bcc6b67fde8f77cd3" + "640035107bfb1ffc6480983cfb84fe0c3be008424ebc968a7db7e01f005", 16), + dmq1=int( + "3893c59469e4ede5cd0e6ff9837ca023ba9b46ff40c60ccf1bec10f7d38db5b1ba817" + "6c41a3f750ec4203b711455aca06d1e0adffc5cffa42bb92c7cb77a6c01", 16), + iqmp=int( + "ad32aafae3c962ac25459856dc8ef1f733c3df697eced29773677f435d186cf759d1a" + "5563dd421ec47b4d7e7f12f29647c615166d9c43fc49001b29089344f65", 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "ad0696bef71597eb3a88e135d83c596930cac73868fbd7e6b2d64f34eea5c28cc" + "e3510c68073954d3ba4deb38643e7a820a4cf06e75f7f82eca545d412bd637819" + "45c28d406e95a6cced5ae924a8bfa4f3def3e0250d91246c269ec40c89c93a85a" + "cd3770ba4d2e774732f43abe94394de43fb57f93ca25f7a59d75d400a3eff5", + 16), + ) +) + +RSA_KEY_1029 = RSAPrivateNumbers( + p=int( + "66f33e513c0b6b6adbf041d037d9b1f0ebf8de52812a3ac397a963d3f71ba64b3ad04" + "e4d4b5e377e6fa22febcac292c907dc8dcfe64c807fd9a7e3a698850d983", 16), + q=int( + "3b47a89a19022461dcc2d3c05b501ee76955e8ce3cf821beb4afa85a21a26fd7203db" + "deb8941f1c60ada39fd6799f6c07eb8554113f1020460ec40e93cd5f6b21", 16), + d=int( + "280c42af8b1c719821f2f6e2bf5f3dd53c81b1f3e1e7cc4fce6e2f830132da0665bde" + "bc1e307106b112b52ad5754867dddd028116cf4471bc14a58696b99524b1ad8f05b31" + "cf47256e54ab4399b6a073b2c0452441438dfddf47f3334c13c5ec86ece4d33409056" + "139328fafa992fb5f5156f25f9b21d3e1c37f156d963d97e41", 16), + dmp1=int( + "198c7402a4ec10944c50ab8488d7b5991c767e75eb2817bd427dff10335ae141fa2e8" + "7c016dc22d975cac229b9ffdf7d943ddfd3a04b8bf82e83c3b32c5698b11", 16), + dmq1=int( + "15fd30c7687b68ef7c2a30cdeb913ec56c4757c218cf9a04d995470797ee5f3a17558" + "fbb6d00af245d2631d893b382da48a72bc8a613024289895952ab245b0c1", 16), + iqmp=int( + "4f8fde17e84557a3f4e242d889e898545ab55a1a8e075c9bb0220173ccffe84659abe" + "a235104f82e32750309389d4a52af57dbb6e48d831917b6efeb190176570", 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "17d6e0a09aa5b2d003e51f43b9c37ffde74688f5e3b709fd02ef375cb6b8d15e2" + "99a9f74981c3eeaaf947d5c2d64a1a80f5c5108a49a715c3f7be95a016b8d3300" + "965ead4a4df76e642d761526803e9434d4ec61b10cb50526d4dcaef02593085de" + "d8c331c1b27b200a45628403065efcb2c0a0ca1f75d648d40a007fbfbf2cae3", + 16), + ) +) + +RSA_KEY_1030 = RSAPrivateNumbers( + p=int( + "6f4ac8a8172ef1154cf7f80b5e91de723c35a4c512860bfdbafcc3b994a2384bf7796" + "3a2dd0480c7e04d5d418629651a0de8979add6f47b23da14c27a682b69c9", 16), + q=int( + "65a9f83e07dea5b633e036a9dccfb32c46bf53c81040a19c574c3680838fc6d28bde9" + "55c0ff18b30481d4ab52a9f5e9f835459b1348bbb563ad90b15a682fadb3", 16), + d=int( + "290db707b3e1a96445ae8ea93af55a9f211a54ebe52995c2eb28085d1e3f09c986e73" + "a00010c8e4785786eaaa5c85b98444bd93b585d0c24363ccc22c482e150a3fd900176" + "86968e4fa20423ae72823b0049defceccb39bb34aa4ef64e6b14463b76d6a871c859e" + "37285455b94b8e1527d1525b1682ac6f7c8fd79d576c55318c1", 16), + dmp1=int( + "23f7fa84010225dea98297032dac5d45745a2e07976605681acfe87e0920a8ab3caf5" + "9d9602f3d63dc0584f75161fd8fff20c626c21c5e02a85282276a74628a9", 16), + dmq1=int( + "18ebb657765464a8aa44bf019a882b72a2110a77934c54915f70e6375088b10331982" + "962bce1c7edd8ef9d3d95aa2566d2a99da6ebab890b95375919408d00f33", 16), + iqmp=int( + "3d59d208743c74054151002d77dcdfc55af3d41357e89af88d7eef2767be54c290255" + "9258d85cf2a1083c035a33e65a1ca46dc8b706847c1c6434cef7b71a9dae", 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "2c326574320818a6a8cb6b3328e2d6c1ba2a3f09b6eb2bc543c03ab18eb5efdaa" + "8fcdbb6b4e12168304f587999f9d96a421fc80cb933a490df85d25883e6a88750" + "d6bd8b3d4117251eee8f45e70e6daac7dbbd92a9103c623a09355cf00e3f16168" + "e38b9c4cb5b368deabbed8df466bc6835eaba959bc1c2f4ec32a09840becc8b", + 16), + ) +) + +RSA_KEY_1031 = RSAPrivateNumbers( + p=int( + "c0958c08e50137db989fb7cc93abf1984543e2f955d4f43fb2967f40105e79274c852" + "293fa06ce63ca8436155e475ed6d1f73fea4c8e2516cc79153e3dc83e897", 16), + q=int( + "78cae354ea5d6862e5d71d20273b7cddb8cdfab25478fe865180676b04250685c4d03" + "30c216574f7876a7b12dfe69f1661d3b0cea6c2c0dcfb84050f817afc28d", 16), + d=int( + "1d55cc02b17a5d25bfb39f2bc58389004d0d7255051507f75ef347cdf5519d1a00f4b" + "d235ce4171bfab7bdb7a6dcfae1cf41433fb7da5923cc84f15a675c0b83492c95dd99" + "a9fc157aea352ffdcbb5d59dbc3662171d5838d69f130678ee27841a79ef64f679ce9" + "3821fa69c03f502244c04b737edad8967def8022a144feaab29", 16), + dmp1=int( + "5b1c2504ec3a984f86b4414342b5bcf59a0754f13adf25b2a0edbc43f5ba8c3cc061d" + "80b03e5866d059968f0d10a98deaeb4f7830436d76b22cf41f2914e13eff", 16), + dmq1=int( + "6c361e1819691ab5d67fb2a8f65c958d301cdf24d90617c68ec7005edfb4a7b638cde" + "79d4b61cfba5c86e8c0ccf296bc7f611cb8d4ae0e072a0f68552ec2d5995", 16), + iqmp=int( + "b7d61945fdc8b92e075b15554bab507fa8a18edd0a18da373ec6c766c71eece61136a" + "84b90b6d01741d40458bfad17a9bee9d4a8ed2f6e270782dc3bf5d58b56e", 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "5adebaa926ea11fb635879487fdd53dcfbb391a11ac7279bb3b4877c9b811370a" + "9f73da0690581691626d8a7cf5d972cced9c2091ccf999024b23b4e6dc6d99f80" + "a454737dec0caffaebe4a3fac250ed02079267c8f39620b5ae3e125ca35338522" + "dc9353ecac19cb2fe3b9e3a9291619dbb1ea3a7c388e9ee6469fbf5fb22892b", + 16), + ) +) + +RSA_KEY_1536 = RSAPrivateNumbers( + p=int( + "f1a65fa4e2aa6e7e2b560251e8a4cd65b625ad9f04f6571785782d1c213d91c961637" + "0c572f2783caf2899f7fb690cf99a0184257fbd4b071b212c88fb348279a5387e61f1" + "17e9c62980c45ea863fa9292087c0f66ecdcde6443d5a37268bf71", 16), + q=int( + "e54c2cbc3839b1da6ae6fea45038d986d6f523a3ae76051ba20583aab711ea5965cf5" + "3cf54128cc9573f7460bba0fd6758a57aaf240c391790fb38ab473d83ef735510c53d" + "1d10c31782e8fd7da42615e33565745c30a5e6ceb2a3ae0666cc35", 16), + d=int( + "7bcad87e23da2cb2a8c328883fabce06e1f8e9b776c8bf253ad9884e6200e3bd9bd3b" + "a2cbe87d3854527bf005ba5d878c5b0fa20cfb0a2a42884ae95ca12bf7304285e9214" + "5e992f7006c7c0ae839ad550da495b143bec0f4806c7f44caed45f3ccc6dc44cfaf30" + "7abdb757e3d28e41c2d21366835c0a41e50a95af490ac03af061d2feb36ac0afb87be" + "a13fb0f0c5a410727ebedb286c77f9469473fae27ef2c836da6071ef7efc1647f1233" + "4009a89eecb09a8287abc8c2afd1ddd9a1b0641", 16), + dmp1=int( + "a845366cd6f9df1f34861bef7594ed025aa83a12759e245f58adaa9bdff9c3befb760" + "75d3701e90038e888eec9bf092df63400152cb25fc07effc6c74c45f0654ccbde15cd" + "90dd5504298a946fa5cf22a956072da27a6602e6c6e5c97f2db9c1", 16), + dmq1=int( + "28b0c1e78cdac03310717992d321a3888830ec6829978c048156152d805b4f8919c61" + "70b5dd204e5ddf3c6c53bc6aff15d0bd09faff7f351b94abb9db980b31f150a6d7573" + "08eb66938f89a5225cb4dd817a824c89e7a0293b58fc2eefb7e259", 16), + iqmp=int( + "6c1536c0e16e42a094b6caaf50231ba81916871497d73dcbbbd4bdeb9e60cae0413b3" + "8143b5d680275b29ed7769fe5577e4f9b3647ddb064941120914526d64d80016d2eb7" + "dc362da7c569623157f3d7cff8347f11494bf5c048d77e28d3f515", 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "d871bb2d27672e54fc62c4680148cbdf848438da804e2c48b5a9c9f9daf6cc6e8" + "ea7d2296f25064537a9a542aef3dd449ea75774238d4da02c353d1bee70013dcc" + "c248ceef4050160705c188043c8559bf6dbfb6c4bb382eda4e9547575a8227d5b" + "3c0a7088391364cf9f018d8bea053b226ec65e8cdbeaf48a071d0074860a734b1" + "cb7d2146d43014b20776dea42f7853a54690e6cbbf3331a9f43763cfe2a51c329" + "3bea3b2eebec0d8e43eb317a443afe541107d886e5243c096091543ae65", 16), + ) +) + +RSA_KEY_2048 = RSAPrivateNumbers( + p=int( + "e14202e58c5f7446648d75e5dc465781f661f6b73000c080368afcfb21377f4ef19da" + "845d4ef9bc6b151f6d9f34629103f2e57615f9ba0a3a2fbb035069e1d63b4bb0e78ad" + "dad1ec3c6f87e25c877a1c4c1972098e09158ef7b9bc163852a18d44a70b7b31a03dc" + "2614fd9ab7bf002cba79054544af3bfbdb6aed06c7b24e6ab", 16), + q=int( + "dbe2bea1ff92599bd19f9d045d6ce62250c05cfeac5117f3cf3e626cb696e3d886379" + "557d5a57b7476f9cf886accfd40508a805fe3b45a78e1a8a125e516cda91640ee6398" + "ec5a39d3e6b177ef12ab00d07907a17640e4ca454fd8487da3c4ffa0d5c2a5edb1221" + "1c8e33c7ee9fa6753771fd111ec04b8317f86693eb2928c89", 16), + d=int( + "aef17f80f2653bc30539f26dd4c82ed6abc1d1b53bc0abcdbee47e9a8ab433abde865" + "9fcfae1244d22de6ad333c95aee7d47f30b6815065ac3322744d3ea75058002cd1b29" + "3141ee2a6dc682342432707080071bd2131d6262cab07871c28aa5238b87173fb78c3" + "7f9c7bcd18c12e8971bb77fd9fa3e0792fec18d8d9bed0b03ba02b263606f24dbace1" + "c8263ce2802a769a090e993fd49abc50c3d3c78c29bee2de0c98055d2f102f1c5684b" + "8dddee611d5205392d8e8dd61a15bf44680972a87f040a611a149271eeb2573f8bf6f" + "627dfa70e77def2ee6584914fa0290e041349ea0999cdff3e493365885b906cbcf195" + "843345809a85098cca90fea014a21", 16), + dmp1=int( + "9ba56522ffcfa5244eae805c87cc0303461f82be29691b9a7c15a5a050df6c143c575" + "7c288d3d7ab7f32c782e9d9fcddc10a604e6425c0e5d0e46069035d95a923646d276d" + "d9d95b8696fa29ab0de18e53f6f119310f8dd9efca62f0679291166fed8cbd5f18fe1" + "3a5f1ead1d71d8c90f40382818c18c8d069be793dbc094f69", 16), + dmq1=int( + "a8d4a0aaa2212ccc875796a81353da1fdf00d46676c88d2b96a4bfcdd924622d8e607" + "f3ac1c01dda7ebfb0a97dd7875c2a7b2db6728fb827b89c519f5716fb3228f4121647" + "04b30253c17de2289e9cce3343baa82eb404f789e094a094577a9b0c5314f1725fdf5" + "8e87611ad20da331bd30b8aebc7dc97d0e9a9ba8579772c9", 16), + iqmp=int( + "17bd5ef638c49440d1853acb3fa63a5aca28cb7f94ed350db7001c8445da8943866a7" + "0936e1ee2716c98b484e357cc054d82fbbd98d42f880695d38a1dd4eb096f629b9417" + "aca47e6de5da9f34e60e8a0ffd7e35be74deeef67298d94b3e0db73fc4b7a4cb360c8" + "9d2117a0bfd9434d37dc7c027d6b01e5295c875015510917d", 16), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "c17afc7e77474caa5aa83036158a3ffbf7b5216851ba2230e5d6abfcc1c6cfef5" + "9e923ea1330bc593b73802ab608a6e4a3306523a3116ba5aa3966145174e13b6c" + "49e9b78062e449d72efb10fd49e91fa08b96d051e782e9f5abc5b5a6f7984827a" + "db8e73da00f22b2efdcdb76eab46edad98ed65662743fdc6c0e336a5d0cdbaa7d" + "c29e53635e24c87a5b2c4215968063cdeb68a972babbc1e3cff00fb9a80e372a4" + "d0c2c920d1e8cee333ce470dc2e8145adb05bf29aee1d24f141e8cc784989c587" + "fc6fbacd979f3f2163c1d7299b365bc72ffe2848e967aed1e48dcc515b3a50ed4" + "de04fd053846ca10a223b10cc841cc80fdebee44f3114c13e886af583", 16), + ) +) + +RSA_KEY_2048_ALT = RSAPrivateNumbers( + d=int( + "7522768467449591813737881904131688860626637897199391200040629" + "8641018746450502628484395471408986929218353894683769457466923" + "3079369551423094451013669595729568593462009746342148367797495" + "5529909313614750246672441810743580455199636293179539903480635" + "3091286716112931976896334411287175213124504134181121011488550" + "5290054443979198998564749640800633368957384058700741073997703" + "8877364695937023906368630297588990131009278072614118207348356" + "4640244134189285070202534488517371577359510236833464698189075" + "5160693085297816063285814039518178249628112908466649245545732" + "5791532385553960363601827996980725025898649392004494256400884" + "092073" + ), + dmp1=int( + "5847872614112935747739644055317429405973942336206460017493394" + "9737607778799766591021036792892472774720417920838206576785118" + "8889624058962939702950175807073343659386156232294197300491647" + "1029508414050591959344812347424476498076532682798598325230069" + "0925827594762920534235575029199380552228825468180187156871965" + "973" + ), + dmq1=int( + "2949536259161239302081155875068405238857801001054083407704879" + "8210876832264504685327766351157044892283801611558399025326793" + "4131638001934454489864437565651739832511702151461257267169691" + "6611992398459006200708626815153304591390855807749769768978152" + "9854112656599931724820610358669306523835327459478374630794532" + "167" + ), + iqmp=int( + "7331180989818931535458916053540252830484856703208982675535284" + "4613815808798190559315018094080936347757336989616401164752221" + "8101156529898067044923499386460167055405998646366011838018441" + "3678947694258190172377716154009305082091341215866326061721180" + "3836418654472188816187630316821692982783286322262994892003058" + "782" + ), + p=int( + "1460007723851883695617573533155574746587863843382715314919865" + "2434108956187429726002840717317310431378483921058946835896252" + "7109559207437158778332364464259678946305487699031865937075508" + "8616612925453842458055546540240601585731206561647892336916583" + "0023641764106581040198845259766246869529221084602380669333021" + "0819" + ), + q=int( + "1433897765867889178402883410610177836503402597775250087462018" + "4617952933433119527945447840336616357136736935069377619782227" + "2822380830300262175671282877680573202309319960687756231128996" + "9764855320953993690199846269451095044922353809602378616938811" + "7513900906279873343591486841303392490561500301994171338761080" + "4439" + ), + public_numbers=RSAPublicNumbers( + e=65537, + n=int( + "209350181338107812610165420955871971489973659392253291327" + "839812910252466502190690572476688311285621239204212139711" + "207388949164851984253143698667018532039612470954223918242" + "145976986600705122576087630525229796950722166468064721258" + "490916138706756006902066136471049807637157890128560592039" + "941717275079733754782848729566190631725183735944031456237" + "089928120178187552521649483240599003240074352860189285952" + "078970127554801074176375499583703254849309993132931268013" + "715070507278514207864914944621214574162116786377990456375" + "964817771730371110612100247262908550409785456157505694419" + "00451152778245269283276012328748538414051025541" + ) + ) +) diff --git a/tests/hazmat/primitives/test_3des.py b/tests/hazmat/primitives/test_3des.py new file mode 100644 index 0000000..586afb4 --- /dev/null +++ b/tests/hazmat/primitives/test_3des.py @@ -0,0 +1,205 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +""" +Test using the NIST Test Vectors +""" + +from __future__ import absolute_import, division, print_function + +import binascii +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers import algorithms, modes + +from .utils import generate_encrypt_test +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.TripleDES(b"\x00" * 8), modes.CBC(b"\x00" * 8) + ), + skip_message="Does not support TripleDES CBC", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestTripleDESModeCBC(object): + test_KAT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "CBC"), + [ + "TCBCinvperm.rsp", + "TCBCpermop.rsp", + "TCBCsubtab.rsp", + "TCBCvarkey.rsp", + "TCBCvartext.rsp", + ], + lambda keys, **kwargs: algorithms.TripleDES(binascii.unhexlify(keys)), + lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), + ) + + test_MMT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "CBC"), + [ + "TCBCMMT1.rsp", + "TCBCMMT2.rsp", + "TCBCMMT3.rsp", + ], + lambda key1, key2, key3, **kwargs: algorithms.TripleDES( + binascii.unhexlify(key1 + key2 + key3) + ), + lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.TripleDES(b"\x00" * 8), modes.OFB(b"\x00" * 8) + ), + skip_message="Does not support TripleDES OFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestTripleDESModeOFB(object): + test_KAT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "OFB"), + [ + "TOFBpermop.rsp", + "TOFBsubtab.rsp", + "TOFBvarkey.rsp", + "TOFBvartext.rsp", + "TOFBinvperm.rsp", + ], + lambda keys, **kwargs: algorithms.TripleDES(binascii.unhexlify(keys)), + lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), + ) + + test_MMT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "OFB"), + [ + "TOFBMMT1.rsp", + "TOFBMMT2.rsp", + "TOFBMMT3.rsp", + ], + lambda key1, key2, key3, **kwargs: algorithms.TripleDES( + binascii.unhexlify(key1 + key2 + key3) + ), + lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.TripleDES(b"\x00" * 8), modes.CFB(b"\x00" * 8) + ), + skip_message="Does not support TripleDES CFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestTripleDESModeCFB(object): + test_KAT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "CFB"), + [ + "TCFB64invperm.rsp", + "TCFB64permop.rsp", + "TCFB64subtab.rsp", + "TCFB64varkey.rsp", + "TCFB64vartext.rsp", + ], + lambda keys, **kwargs: algorithms.TripleDES(binascii.unhexlify(keys)), + lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), + ) + + test_MMT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "CFB"), + [ + "TCFB64MMT1.rsp", + "TCFB64MMT2.rsp", + "TCFB64MMT3.rsp", + ], + lambda key1, key2, key3, **kwargs: algorithms.TripleDES( + binascii.unhexlify(key1 + key2 + key3) + ), + lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.TripleDES(b"\x00" * 8), modes.CFB8(b"\x00" * 8) + ), + skip_message="Does not support TripleDES CFB8", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestTripleDESModeCFB8(object): + test_KAT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "CFB"), + [ + "TCFB8invperm.rsp", + "TCFB8permop.rsp", + "TCFB8subtab.rsp", + "TCFB8varkey.rsp", + "TCFB8vartext.rsp", + ], + lambda keys, **kwargs: algorithms.TripleDES(binascii.unhexlify(keys)), + lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), + ) + + test_MMT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "CFB"), + [ + "TCFB8MMT1.rsp", + "TCFB8MMT2.rsp", + "TCFB8MMT3.rsp", + ], + lambda key1, key2, key3, **kwargs: algorithms.TripleDES( + binascii.unhexlify(key1 + key2 + key3) + ), + lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.TripleDES("\x00" * 8), modes.ECB() + ), + skip_message="Does not support TripleDES ECB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestTripleDESModeECB(object): + test_KAT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "ECB"), + [ + "TECBinvperm.rsp", + "TECBpermop.rsp", + "TECBsubtab.rsp", + "TECBvarkey.rsp", + "TECBvartext.rsp", + ], + lambda keys, **kwargs: algorithms.TripleDES(binascii.unhexlify(keys)), + lambda **kwargs: modes.ECB(), + ) + + test_MMT = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "3DES", "ECB"), + [ + "TECBMMT1.rsp", + "TECBMMT2.rsp", + "TECBMMT3.rsp", + ], + lambda key1, key2, key3, **kwargs: algorithms.TripleDES( + binascii.unhexlify(key1 + key2 + key3) + ), + lambda **kwargs: modes.ECB(), + ) diff --git a/tests/hazmat/primitives/test_aes.py b/tests/hazmat/primitives/test_aes.py new file mode 100644 index 0000000..8826aae --- /dev/null +++ b/tests/hazmat/primitives/test_aes.py @@ -0,0 +1,305 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers import algorithms, base, modes + +from .utils import generate_aead_test, generate_encrypt_test +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.CBC(b"\x00" * 16) + ), + skip_message="Does not support AES CBC", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESModeCBC(object): + test_CBC = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "AES", "CBC"), + [ + "CBCGFSbox128.rsp", + "CBCGFSbox192.rsp", + "CBCGFSbox256.rsp", + "CBCKeySbox128.rsp", + "CBCKeySbox192.rsp", + "CBCKeySbox256.rsp", + "CBCVarKey128.rsp", + "CBCVarKey192.rsp", + "CBCVarKey256.rsp", + "CBCVarTxt128.rsp", + "CBCVarTxt192.rsp", + "CBCVarTxt256.rsp", + "CBCMMT128.rsp", + "CBCMMT192.rsp", + "CBCMMT256.rsp", + ], + lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES("\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES ECB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESModeECB(object): + test_ECB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "AES", "ECB"), + [ + "ECBGFSbox128.rsp", + "ECBGFSbox192.rsp", + "ECBGFSbox256.rsp", + "ECBKeySbox128.rsp", + "ECBKeySbox192.rsp", + "ECBKeySbox256.rsp", + "ECBVarKey128.rsp", + "ECBVarKey192.rsp", + "ECBVarKey256.rsp", + "ECBVarTxt128.rsp", + "ECBVarTxt192.rsp", + "ECBVarTxt256.rsp", + "ECBMMT128.rsp", + "ECBMMT192.rsp", + "ECBMMT256.rsp", + ], + lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), + lambda **kwargs: modes.ECB(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.OFB(b"\x00" * 16) + ), + skip_message="Does not support AES OFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESModeOFB(object): + test_OFB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "AES", "OFB"), + [ + "OFBGFSbox128.rsp", + "OFBGFSbox192.rsp", + "OFBGFSbox256.rsp", + "OFBKeySbox128.rsp", + "OFBKeySbox192.rsp", + "OFBKeySbox256.rsp", + "OFBVarKey128.rsp", + "OFBVarKey192.rsp", + "OFBVarKey256.rsp", + "OFBVarTxt128.rsp", + "OFBVarTxt192.rsp", + "OFBVarTxt256.rsp", + "OFBMMT128.rsp", + "OFBMMT192.rsp", + "OFBMMT256.rsp", + ], + lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.CFB(b"\x00" * 16) + ), + skip_message="Does not support AES CFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESModeCFB(object): + test_CFB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "AES", "CFB"), + [ + "CFB128GFSbox128.rsp", + "CFB128GFSbox192.rsp", + "CFB128GFSbox256.rsp", + "CFB128KeySbox128.rsp", + "CFB128KeySbox192.rsp", + "CFB128KeySbox256.rsp", + "CFB128VarKey128.rsp", + "CFB128VarKey192.rsp", + "CFB128VarKey256.rsp", + "CFB128VarTxt128.rsp", + "CFB128VarTxt192.rsp", + "CFB128VarTxt256.rsp", + "CFB128MMT128.rsp", + "CFB128MMT192.rsp", + "CFB128MMT256.rsp", + ], + lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.CFB8(b"\x00" * 16) + ), + skip_message="Does not support AES CFB8", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESModeCFB8(object): + test_CFB8 = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "AES", "CFB"), + [ + "CFB8GFSbox128.rsp", + "CFB8GFSbox192.rsp", + "CFB8GFSbox256.rsp", + "CFB8KeySbox128.rsp", + "CFB8KeySbox192.rsp", + "CFB8KeySbox256.rsp", + "CFB8VarKey128.rsp", + "CFB8VarKey192.rsp", + "CFB8VarKey256.rsp", + "CFB8VarTxt128.rsp", + "CFB8VarTxt192.rsp", + "CFB8VarTxt256.rsp", + "CFB8MMT128.rsp", + "CFB8MMT192.rsp", + "CFB8MMT256.rsp", + ], + lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CFB8(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.CTR(b"\x00" * 16) + ), + skip_message="Does not support AES CTR", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESModeCTR(object): + test_CTR = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "AES", "CTR"), + ["aes-128-ctr.txt", "aes-192-ctr.txt", "aes-256-ctr.txt"], + lambda key, **kwargs: algorithms.AES(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CTR(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) + ), + skip_message="Does not support AES GCM", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESModeGCM(object): + test_GCM = generate_aead_test( + load_nist_vectors, + os.path.join("ciphers", "AES", "GCM"), + [ + "gcmDecrypt128.rsp", + "gcmDecrypt192.rsp", + "gcmDecrypt256.rsp", + "gcmEncryptExtIV128.rsp", + "gcmEncryptExtIV192.rsp", + "gcmEncryptExtIV256.rsp", + ], + algorithms.AES, + modes.GCM, + ) + + def test_gcm_tag_with_only_aad(self, backend): + key = binascii.unhexlify(b"5211242698bed4774a090620a6ca56f3") + iv = binascii.unhexlify(b"b1e1349120b6e832ef976f5d") + aad = binascii.unhexlify(b"b6d729aab8e6416d7002b9faa794c410d8d2f193") + tag = binascii.unhexlify(b"0f247e7f9c2505de374006738018493b") + + cipher = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ) + encryptor = cipher.encryptor() + encryptor.authenticate_additional_data(aad) + encryptor.finalize() + assert encryptor.tag == tag + + def test_gcm_ciphertext_with_no_aad(self, backend): + key = binascii.unhexlify(b"e98b72a9881a84ca6b76e0f43e68647a") + iv = binascii.unhexlify(b"8b23299fde174053f3d652ba") + ct = binascii.unhexlify(b"5a3c1cf1985dbb8bed818036fdd5ab42") + tag = binascii.unhexlify(b"23c7ab0f952b7091cd324835043b5eb5") + pt = binascii.unhexlify(b"28286a321293253c3e0aa2704a278032") + + cipher = base.Cipher( + algorithms.AES(key), + modes.GCM(iv), + backend=backend + ) + encryptor = cipher.encryptor() + computed_ct = encryptor.update(pt) + encryptor.finalize() + assert computed_ct == ct + assert encryptor.tag == tag + + def test_gcm_ciphertext_limit(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor._bytes_processed = modes.GCM._MAX_ENCRYPTED_BYTES - 16 + encryptor.update(b"0" * 16) + assert ( + encryptor._bytes_processed == modes.GCM._MAX_ENCRYPTED_BYTES + ) + with pytest.raises(ValueError): + encryptor.update(b"0") + + def test_gcm_aad_limit(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor._aad_bytes_processed = modes.GCM._MAX_AAD_BYTES - 16 + encryptor.authenticate_additional_data(b"0" * 16) + assert encryptor._aad_bytes_processed == modes.GCM._MAX_AAD_BYTES + with pytest.raises(ValueError): + encryptor.authenticate_additional_data(b"0") + + def test_gcm_ciphertext_increments(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor.update(b"0" * 8) + assert encryptor._bytes_processed == 8 + encryptor.update(b"0" * 7) + assert encryptor._bytes_processed == 15 + encryptor.update(b"0" * 18) + assert encryptor._bytes_processed == 33 + + def test_gcm_aad_increments(self, backend): + encryptor = base.Cipher( + algorithms.AES(b"\x00" * 16), + modes.GCM(b"\x01" * 16), + backend=backend + ).encryptor() + encryptor.authenticate_additional_data(b"0" * 8) + assert encryptor._aad_bytes_processed == 8 + encryptor.authenticate_additional_data(b"0" * 18) + assert encryptor._aad_bytes_processed == 26 diff --git a/tests/hazmat/primitives/test_arc4.py b/tests/hazmat/primitives/test_arc4.py new file mode 100644 index 0000000..1a17344 --- /dev/null +++ b/tests/hazmat/primitives/test_arc4.py @@ -0,0 +1,41 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers import algorithms + +from .utils import generate_stream_encryption_test +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.ARC4(b"\x00" * 16), None + ), + skip_message="Does not support ARC4", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestARC4(object): + test_rfc = generate_stream_encryption_test( + load_nist_vectors, + os.path.join("ciphers", "ARC4"), + [ + "rfc-6229-40.txt", + "rfc-6229-56.txt", + "rfc-6229-64.txt", + "rfc-6229-80.txt", + "rfc-6229-128.txt", + "rfc-6229-192.txt", + "rfc-6229-256.txt", + "arc4.txt" + ], + lambda key, **kwargs: algorithms.ARC4(binascii.unhexlify(key)), + ) diff --git a/tests/hazmat/primitives/test_asym_utils.py b/tests/hazmat/primitives/test_asym_utils.py new file mode 100644 index 0000000..bd1fa35 --- /dev/null +++ b/tests/hazmat/primitives/test_asym_utils.py @@ -0,0 +1,83 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography.hazmat.primitives.asymmetric.utils import ( + Prehashed, decode_dss_signature, decode_rfc6979_signature, + encode_dss_signature, encode_rfc6979_signature, +) + + +def test_deprecated_rfc6979_signature(): + sig = pytest.deprecated_call(encode_rfc6979_signature, 1, 1) + assert sig == b"0\x06\x02\x01\x01\x02\x01\x01" + decoded = pytest.deprecated_call(decode_rfc6979_signature, sig) + assert decoded == (1, 1) + + +def test_dss_signature(): + sig = encode_dss_signature(1, 1) + assert sig == b"0\x06\x02\x01\x01\x02\x01\x01" + assert decode_dss_signature(sig) == (1, 1) + + r_s1 = ( + 1037234182290683143945502320610861668562885151617, + 559776156650501990899426031439030258256861634312 + ) + sig2 = encode_dss_signature(*r_s1) + assert sig2 == ( + b'0-\x02\x15\x00\xb5\xaf0xg\xfb\x8bT9\x00\x13\xccg\x02\r\xdf\x1f,\x0b' + b'\x81\x02\x14b\r;"\xabP1D\x0c>5\xea\xb6\xf4\x81)\x8f\x9e\x9f\x08' + ) + assert decode_dss_signature(sig2) == r_s1 + + sig3 = encode_dss_signature(0, 0) + assert sig3 == b"0\x06\x02\x01\x00\x02\x01\x00" + assert decode_dss_signature(sig3) == (0, 0) + + sig4 = encode_dss_signature(-1, 0) + assert sig4 == b"0\x06\x02\x01\xFF\x02\x01\x00" + assert decode_dss_signature(sig4) == (-1, 0) + + +def test_encode_dss_non_integer(): + with pytest.raises(ValueError): + encode_dss_signature("h", 3) + + with pytest.raises(ValueError): + encode_dss_signature("3", "2") + + with pytest.raises(ValueError): + encode_dss_signature(3, "h") + + with pytest.raises(ValueError): + encode_dss_signature(3.3, 1.2) + + with pytest.raises(ValueError): + encode_dss_signature("hello", "world") + + +def test_decode_dss_trailing_bytes(): + with pytest.raises(ValueError): + decode_dss_signature(b"0\x06\x02\x01\x01\x02\x01\x01\x00\x00\x00") + + +def test_decode_dss_invalid_asn1(): + with pytest.raises(ValueError): + # This byte sequence has an invalid ASN.1 sequence length as well as + # an invalid integer length for the second integer. + decode_dss_signature(b"0\x07\x02\x01\x01\x02\x02\x01") + + with pytest.raises(ValueError): + # This is the BER "end-of-contents octets," which older versions of + # pyasn1 are wrongly willing to return from top-level DER decoding. + decode_dss_signature(b"\x00\x00") + + +def test_pass_invalid_prehashed_arg(): + with pytest.raises(TypeError): + Prehashed(object()) diff --git a/tests/hazmat/primitives/test_block.py b/tests/hazmat/primitives/test_block.py new file mode 100644 index 0000000..eb0a2c3 --- /dev/null +++ b/tests/hazmat/primitives/test_block.py @@ -0,0 +1,209 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, _Reasons +) +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers import ( + Cipher, algorithms, base, modes +) + +from .utils import ( + generate_aead_exception_test, generate_aead_tag_exception_test +) +from ...doubles import DummyCipherAlgorithm, DummyMode +from ...utils import raises_unsupported_algorithm + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCipher(object): + def test_creates_encryptor(self, backend): + cipher = Cipher( + algorithms.AES(binascii.unhexlify(b"0" * 32)), + modes.CBC(binascii.unhexlify(b"0" * 32)), + backend + ) + assert isinstance(cipher.encryptor(), base.CipherContext) + + def test_creates_decryptor(self, backend): + cipher = Cipher( + algorithms.AES(binascii.unhexlify(b"0" * 32)), + modes.CBC(binascii.unhexlify(b"0" * 32)), + backend + ) + assert isinstance(cipher.decryptor(), base.CipherContext) + + def test_instantiate_with_non_algorithm(self, backend): + algorithm = object() + with pytest.raises(TypeError): + Cipher(algorithm, mode=None, backend=backend) + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCipherContext(object): + def test_use_after_finalize(self, backend): + cipher = Cipher( + algorithms.AES(binascii.unhexlify(b"0" * 32)), + modes.CBC(binascii.unhexlify(b"0" * 32)), + backend + ) + encryptor = cipher.encryptor() + encryptor.update(b"a" * 16) + encryptor.finalize() + with pytest.raises(AlreadyFinalized): + encryptor.update(b"b" * 16) + with pytest.raises(AlreadyFinalized): + encryptor.finalize() + decryptor = cipher.decryptor() + decryptor.update(b"a" * 16) + decryptor.finalize() + with pytest.raises(AlreadyFinalized): + decryptor.update(b"b" * 16) + with pytest.raises(AlreadyFinalized): + decryptor.finalize() + + def test_unaligned_block_encryption(self, backend): + cipher = Cipher( + algorithms.AES(binascii.unhexlify(b"0" * 32)), + modes.ECB(), + backend + ) + encryptor = cipher.encryptor() + ct = encryptor.update(b"a" * 15) + assert ct == b"" + ct += encryptor.update(b"a" * 65) + assert len(ct) == 80 + ct += encryptor.finalize() + decryptor = cipher.decryptor() + pt = decryptor.update(ct[:3]) + assert pt == b"" + pt += decryptor.update(ct[3:]) + assert len(pt) == 80 + assert pt == b"a" * 80 + decryptor.finalize() + + @pytest.mark.parametrize("mode", [DummyMode(), None]) + def test_nonexistent_cipher(self, backend, mode): + cipher = Cipher( + DummyCipherAlgorithm(), mode, backend + ) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + cipher.encryptor() + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + cipher.decryptor() + + def test_incorrectly_padded(self, backend): + cipher = Cipher( + algorithms.AES(b"\x00" * 16), + modes.CBC(b"\x00" * 16), + backend + ) + encryptor = cipher.encryptor() + encryptor.update(b"1") + with pytest.raises(ValueError): + encryptor.finalize() + + decryptor = cipher.decryptor() + decryptor.update(b"1") + with pytest.raises(ValueError): + decryptor.finalize() + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.GCM(b"\x00" * 12) + ), + skip_message="Does not support AES GCM", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAEADCipherContext(object): + test_aead_exceptions = generate_aead_exception_test( + algorithms.AES, + modes.GCM, + ) + test_aead_tag_exceptions = generate_aead_tag_exception_test( + algorithms.AES, + modes.GCM, + ) + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestModeValidation(object): + def test_cbc(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.CBC(b"abc"), + backend, + ) + + def test_ofb(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.OFB(b"abc"), + backend, + ) + + def test_cfb(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.CFB(b"abc"), + backend, + ) + + def test_cfb8(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.CFB8(b"abc"), + backend, + ) + + def test_ctr(self, backend): + with pytest.raises(ValueError): + Cipher( + algorithms.AES(b"\x00" * 16), + modes.CTR(b"abc"), + backend, + ) + + +class TestModesRequireBytes(object): + def test_cbc(self): + with pytest.raises(TypeError): + modes.CBC([1] * 16) + + def test_cfb(self): + with pytest.raises(TypeError): + modes.CFB([1] * 16) + + def test_cfb8(self): + with pytest.raises(TypeError): + modes.CFB8([1] * 16) + + def test_ofb(self): + with pytest.raises(TypeError): + modes.OFB([1] * 16) + + def test_ctr(self): + with pytest.raises(TypeError): + modes.CTR([1] * 16) + + def test_gcm_iv(self): + with pytest.raises(TypeError): + modes.GCM([1] * 16) + + def test_gcm_tag(self): + with pytest.raises(TypeError): + modes.GCM(b"\x00" * 16, [1] * 16) diff --git a/tests/hazmat/primitives/test_blowfish.py b/tests/hazmat/primitives/test_blowfish.py new file mode 100644 index 0000000..0c38b98 --- /dev/null +++ b/tests/hazmat/primitives/test_blowfish.py @@ -0,0 +1,84 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers import algorithms, modes + +from .utils import generate_encrypt_test +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.Blowfish(b"\x00" * 56), modes.ECB() + ), + skip_message="Does not support Blowfish ECB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestBlowfishModeECB(object): + test_ECB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "Blowfish"), + ["bf-ecb.txt"], + lambda key, **kwargs: algorithms.Blowfish(binascii.unhexlify(key)), + lambda **kwargs: modes.ECB(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.Blowfish(b"\x00" * 56), modes.CBC(b"\x00" * 8) + ), + skip_message="Does not support Blowfish CBC", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestBlowfishModeCBC(object): + test_CBC = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "Blowfish"), + ["bf-cbc.txt"], + lambda key, **kwargs: algorithms.Blowfish(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.Blowfish(b"\x00" * 56), modes.OFB(b"\x00" * 8) + ), + skip_message="Does not support Blowfish OFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestBlowfishModeOFB(object): + test_OFB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "Blowfish"), + ["bf-ofb.txt"], + lambda key, **kwargs: algorithms.Blowfish(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.Blowfish(b"\x00" * 56), modes.CFB(b"\x00" * 8) + ), + skip_message="Does not support Blowfish CFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestBlowfishModeCFB(object): + test_CFB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "Blowfish"), + ["bf-cfb.txt"], + lambda key, **kwargs: algorithms.Blowfish(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), + ) diff --git a/tests/hazmat/primitives/test_camellia.py b/tests/hazmat/primitives/test_camellia.py new file mode 100644 index 0000000..87fcfe3 --- /dev/null +++ b/tests/hazmat/primitives/test_camellia.py @@ -0,0 +1,90 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers import algorithms, modes + +from .utils import generate_encrypt_test +from ...utils import ( + load_cryptrec_vectors, load_nist_vectors +) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.Camellia(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support Camellia ECB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCamelliaModeECB(object): + test_ECB = generate_encrypt_test( + load_cryptrec_vectors, + os.path.join("ciphers", "Camellia"), + [ + "camellia-128-ecb.txt", + "camellia-192-ecb.txt", + "camellia-256-ecb.txt" + ], + lambda key, **kwargs: algorithms.Camellia(binascii.unhexlify(key)), + lambda **kwargs: modes.ECB(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.Camellia(b"\x00" * 16), modes.CBC(b"\x00" * 16) + ), + skip_message="Does not support Camellia CBC", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCamelliaModeCBC(object): + test_CBC = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "Camellia"), + ["camellia-cbc.txt"], + lambda key, **kwargs: algorithms.Camellia(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.Camellia(b"\x00" * 16), modes.OFB(b"\x00" * 16) + ), + skip_message="Does not support Camellia OFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCamelliaModeOFB(object): + test_OFB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "Camellia"), + ["camellia-ofb.txt"], + lambda key, **kwargs: algorithms.Camellia(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.Camellia(b"\x00" * 16), modes.CFB(b"\x00" * 16) + ), + skip_message="Does not support Camellia CFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCamelliaModeCFB(object): + test_CFB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "Camellia"), + ["camellia-cfb.txt"], + lambda key, **kwargs: algorithms.Camellia(binascii.unhexlify(key)), + lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)), + ) diff --git a/tests/hazmat/primitives/test_cast5.py b/tests/hazmat/primitives/test_cast5.py new file mode 100644 index 0000000..59af84f --- /dev/null +++ b/tests/hazmat/primitives/test_cast5.py @@ -0,0 +1,101 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers import algorithms, modes + +from .utils import generate_encrypt_test +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.CAST5(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support CAST5 ECB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCAST5ModeECB(object): + test_ECB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "CAST5"), + ["cast5-ecb.txt"], + lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), + lambda **kwargs: modes.ECB(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.CAST5(b"\x00" * 16), modes.CBC(b"\x00" * 8) + ), + skip_message="Does not support CAST5 CBC", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCAST5ModeCBC(object): + test_CBC = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "CAST5"), + ["cast5-cbc.txt"], + lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), + lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)) + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.CAST5(b"\x00" * 16), modes.OFB(b"\x00" * 8) + ), + skip_message="Does not support CAST5 OFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCAST5ModeOFB(object): + test_OFB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "CAST5"), + ["cast5-ofb.txt"], + lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), + lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)) + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.CAST5(b"\x00" * 16), modes.CFB(b"\x00" * 8) + ), + skip_message="Does not support CAST5 CFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCAST5ModeCFB(object): + test_CFB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "CAST5"), + ["cast5-cfb.txt"], + lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), + lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)) + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.CAST5(b"\x00" * 16), modes.CTR(b"\x00" * 8) + ), + skip_message="Does not support CAST5 CTR", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestCAST5ModeCTR(object): + test_CTR = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "CAST5"), + ["cast5-ctr.txt"], + lambda key, **kwargs: algorithms.CAST5(binascii.unhexlify((key))), + lambda iv, **kwargs: modes.CTR(binascii.unhexlify(iv)) + ) diff --git a/tests/hazmat/primitives/test_ciphers.py b/tests/hazmat/primitives/test_ciphers.py new file mode 100644 index 0000000..d9a07ff --- /dev/null +++ b/tests/hazmat/primitives/test_ciphers.py @@ -0,0 +1,135 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii + +import pytest + +from cryptography.exceptions import _Reasons +from cryptography.hazmat.primitives import ciphers +from cryptography.hazmat.primitives.ciphers.algorithms import ( + AES, ARC4, Blowfish, CAST5, Camellia, IDEA, SEED, TripleDES +) +from cryptography.hazmat.primitives.ciphers.modes import ECB + +from ...utils import raises_unsupported_algorithm + + +class TestAES(object): + @pytest.mark.parametrize(("key", "keysize"), [ + (b"0" * 32, 128), + (b"0" * 48, 192), + (b"0" * 64, 256), + ]) + def test_key_size(self, key, keysize): + cipher = AES(binascii.unhexlify(key)) + assert cipher.key_size == keysize + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + AES(binascii.unhexlify(b"0" * 12)) + + +class TestCamellia(object): + @pytest.mark.parametrize(("key", "keysize"), [ + (b"0" * 32, 128), + (b"0" * 48, 192), + (b"0" * 64, 256), + ]) + def test_key_size(self, key, keysize): + cipher = Camellia(binascii.unhexlify(key)) + assert cipher.key_size == keysize + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + Camellia(binascii.unhexlify(b"0" * 12)) + + +class TestTripleDES(object): + @pytest.mark.parametrize("key", [ + b"0" * 16, + b"0" * 32, + b"0" * 48, + ]) + def test_key_size(self, key): + cipher = TripleDES(binascii.unhexlify(key)) + assert cipher.key_size == 192 + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + TripleDES(binascii.unhexlify(b"0" * 12)) + + +class TestBlowfish(object): + @pytest.mark.parametrize(("key", "keysize"), [ + (b"0" * (keysize // 4), keysize) for keysize in range(32, 449, 8) + ]) + def test_key_size(self, key, keysize): + cipher = Blowfish(binascii.unhexlify(key)) + assert cipher.key_size == keysize + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + Blowfish(binascii.unhexlify(b"0" * 6)) + + +class TestCAST5(object): + @pytest.mark.parametrize(("key", "keysize"), [ + (b"0" * (keysize // 4), keysize) for keysize in range(40, 129, 8) + ]) + def test_key_size(self, key, keysize): + cipher = CAST5(binascii.unhexlify(key)) + assert cipher.key_size == keysize + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + CAST5(binascii.unhexlify(b"0" * 34)) + + +class TestARC4(object): + @pytest.mark.parametrize(("key", "keysize"), [ + (b"0" * 10, 40), + (b"0" * 14, 56), + (b"0" * 16, 64), + (b"0" * 20, 80), + (b"0" * 32, 128), + (b"0" * 48, 192), + (b"0" * 64, 256), + ]) + def test_key_size(self, key, keysize): + cipher = ARC4(binascii.unhexlify(key)) + assert cipher.key_size == keysize + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + ARC4(binascii.unhexlify(b"0" * 34)) + + +class TestIDEA(object): + def test_key_size(self): + cipher = IDEA(b"\x00" * 16) + assert cipher.key_size == 128 + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + IDEA(b"\x00" * 17) + + +class TestSEED(object): + def test_key_size(self): + cipher = SEED(b"\x00" * 16) + assert cipher.key_size == 128 + + def test_invalid_key_size(self): + with pytest.raises(ValueError): + SEED(b"\x00" * 17) + + +def test_invalid_backend(): + pretend_backend = object() + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + ciphers.Cipher(AES(b"AAAAAAAAAAAAAAAA"), ECB, pretend_backend) diff --git a/tests/hazmat/primitives/test_cmac.py b/tests/hazmat/primitives/test_cmac.py new file mode 100644 index 0000000..08b6bf5 --- /dev/null +++ b/tests/hazmat/primitives/test_cmac.py @@ -0,0 +1,205 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii + +import pretend + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, InvalidSignature, _Reasons +) +from cryptography.hazmat.backends.interfaces import CMACBackend +from cryptography.hazmat.primitives.ciphers.algorithms import ( + AES, ARC4, TripleDES +) +from cryptography.hazmat.primitives.cmac import CMAC + +from ..backends.test_multibackend import DummyCMACBackend +from ...utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) + +vectors_aes128 = load_vectors_from_file( + "CMAC/nist-800-38b-aes128.txt", load_nist_vectors) + +vectors_aes192 = load_vectors_from_file( + "CMAC/nist-800-38b-aes192.txt", load_nist_vectors) + +vectors_aes256 = load_vectors_from_file( + "CMAC/nist-800-38b-aes256.txt", load_nist_vectors) + +vectors_aes = vectors_aes128 + vectors_aes192 + vectors_aes256 + +vectors_3des = load_vectors_from_file( + "CMAC/nist-800-38b-3des.txt", load_nist_vectors) + +fake_key = b"\x00" * 16 + + +@pytest.mark.requires_backend_interface(interface=CMACBackend) +class TestCMAC(object): + @pytest.mark.supported( + only_if=lambda backend: backend.cmac_algorithm_supported( + AES(fake_key)), + skip_message="Does not support CMAC." + ) + @pytest.mark.parametrize("params", vectors_aes) + def test_aes_generate(self, backend, params): + key = params["key"] + message = params["message"] + output = params["output"] + + cmac = CMAC(AES(binascii.unhexlify(key)), backend) + cmac.update(binascii.unhexlify(message)) + assert binascii.hexlify(cmac.finalize()) == output + + @pytest.mark.supported( + only_if=lambda backend: backend.cmac_algorithm_supported( + AES(fake_key)), + skip_message="Does not support CMAC." + ) + @pytest.mark.parametrize("params", vectors_aes) + def test_aes_verify(self, backend, params): + key = params["key"] + message = params["message"] + output = params["output"] + + cmac = CMAC(AES(binascii.unhexlify(key)), backend) + cmac.update(binascii.unhexlify(message)) + assert cmac.verify(binascii.unhexlify(output)) is None + + @pytest.mark.supported( + only_if=lambda backend: backend.cmac_algorithm_supported( + TripleDES(fake_key)), + skip_message="Does not support CMAC." + ) + @pytest.mark.parametrize("params", vectors_3des) + def test_3des_generate(self, backend, params): + key1 = params["key1"] + key2 = params["key2"] + key3 = params["key3"] + + key = key1 + key2 + key3 + + message = params["message"] + output = params["output"] + + cmac = CMAC(TripleDES(binascii.unhexlify(key)), backend) + cmac.update(binascii.unhexlify(message)) + assert binascii.hexlify(cmac.finalize()) == output + + @pytest.mark.supported( + only_if=lambda backend: backend.cmac_algorithm_supported( + TripleDES(fake_key)), + skip_message="Does not support CMAC." + ) + @pytest.mark.parametrize("params", vectors_3des) + def test_3des_verify(self, backend, params): + key1 = params["key1"] + key2 = params["key2"] + key3 = params["key3"] + + key = key1 + key2 + key3 + + message = params["message"] + output = params["output"] + + cmac = CMAC(TripleDES(binascii.unhexlify(key)), backend) + cmac.update(binascii.unhexlify(message)) + assert cmac.verify(binascii.unhexlify(output)) is None + + @pytest.mark.supported( + only_if=lambda backend: backend.cmac_algorithm_supported( + AES(fake_key)), + skip_message="Does not support CMAC." + ) + def test_invalid_verify(self, backend): + key = b"2b7e151628aed2a6abf7158809cf4f3c" + cmac = CMAC(AES(key), backend) + cmac.update(b"6bc1bee22e409f96e93d7e117393172a") + + with pytest.raises(InvalidSignature): + cmac.verify(b"foobar") + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + ARC4(fake_key), None), + skip_message="Does not support CMAC." + ) + def test_invalid_algorithm(self, backend): + key = b"0102030405" + with pytest.raises(TypeError): + CMAC(ARC4(key), backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.cmac_algorithm_supported( + AES(fake_key)), + skip_message="Does not support CMAC." + ) + def test_raises_after_finalize(self, backend): + key = b"2b7e151628aed2a6abf7158809cf4f3c" + cmac = CMAC(AES(key), backend) + cmac.finalize() + + with pytest.raises(AlreadyFinalized): + cmac.update(b"foo") + + with pytest.raises(AlreadyFinalized): + cmac.copy() + + with pytest.raises(AlreadyFinalized): + cmac.finalize() + + with pytest.raises(AlreadyFinalized): + cmac.verify(b"") + + @pytest.mark.supported( + only_if=lambda backend: backend.cmac_algorithm_supported( + AES(fake_key)), + skip_message="Does not support CMAC." + ) + def test_verify_reject_unicode(self, backend): + key = b"2b7e151628aed2a6abf7158809cf4f3c" + cmac = CMAC(AES(key), backend) + + with pytest.raises(TypeError): + cmac.update(u'') + + with pytest.raises(TypeError): + cmac.verify(u'') + + @pytest.mark.supported( + only_if=lambda backend: backend.cmac_algorithm_supported( + AES(fake_key)), + skip_message="Does not support CMAC." + ) + def test_copy_with_backend(self, backend): + key = b"2b7e151628aed2a6abf7158809cf4f3c" + cmac = CMAC(AES(key), backend) + cmac.update(b"6bc1bee22e409f96e93d7e117393172a") + copy_cmac = cmac.copy() + assert cmac.finalize() == copy_cmac.finalize() + + +def test_copy(): + backend = DummyCMACBackend([AES]) + copied_ctx = pretend.stub() + pretend_ctx = pretend.stub(copy=lambda: copied_ctx) + key = b"2b7e151628aed2a6abf7158809cf4f3c" + cmac = CMAC(AES(key), backend=backend, ctx=pretend_ctx) + + assert cmac._backend is backend + assert cmac.copy()._backend is backend + + +def test_invalid_backend(): + key = b"2b7e151628aed2a6abf7158809cf4f3c" + pretend_backend = object() + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + CMAC(AES(key), pretend_backend) diff --git a/tests/hazmat/primitives/test_concatkdf.py b/tests/hazmat/primitives/test_concatkdf.py new file mode 100644 index 0000000..aa568c1 --- /dev/null +++ b/tests/hazmat/primitives/test_concatkdf.py @@ -0,0 +1,272 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, _Reasons +) +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHMAC +from cryptography.hazmat.primitives.kdf.concatkdf import ConcatKDFHash + +from ...utils import raises_unsupported_algorithm + + +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestConcatKDFHash(object): + def test_length_limit(self, backend): + big_length = hashes.SHA256().digest_size * (2 ** 32 - 1) + 1 + + with pytest.raises(ValueError): + ConcatKDFHash(hashes.SHA256(), big_length, None, backend) + + def test_already_finalized(self, backend): + ckdf = ConcatKDFHash(hashes.SHA256(), 16, None, backend) + + ckdf.derive(b"\x01" * 16) + + with pytest.raises(AlreadyFinalized): + ckdf.derive(b"\x02" * 16) + + def test_derive(self, backend): + prk = binascii.unhexlify( + b"52169af5c485dcc2321eb8d26d5efa21fb9b93c98e38412ee2484cf14f0d0d23" + ) + + okm = binascii.unhexlify(b"1c3bc9e7c4547c5191c0d478cccaed55") + + oinfo = binascii.unhexlify( + b"a1b2c3d4e53728157e634612c12d6d5223e204aeea4341565369647bd184bcd2" + b"46f72971f292badaa2fe4124612cba" + ) + + ckdf = ConcatKDFHash(hashes.SHA256(), 16, oinfo, backend) + + assert ckdf.derive(prk) == okm + + def test_verify(self, backend): + prk = binascii.unhexlify( + b"52169af5c485dcc2321eb8d26d5efa21fb9b93c98e38412ee2484cf14f0d0d23" + ) + + okm = binascii.unhexlify(b"1c3bc9e7c4547c5191c0d478cccaed55") + + oinfo = binascii.unhexlify( + b"a1b2c3d4e53728157e634612c12d6d5223e204aeea4341565369647bd184bcd2" + b"46f72971f292badaa2fe4124612cba" + ) + + ckdf = ConcatKDFHash(hashes.SHA256(), 16, oinfo, backend) + + assert ckdf.verify(prk, okm) is None + + def test_invalid_verify(self, backend): + prk = binascii.unhexlify( + b"52169af5c485dcc2321eb8d26d5efa21fb9b93c98e38412ee2484cf14f0d0d23" + ) + + oinfo = binascii.unhexlify( + b"a1b2c3d4e53728157e634612c12d6d5223e204aeea4341565369647bd184bcd2" + b"46f72971f292badaa2fe4124612cba" + ) + + ckdf = ConcatKDFHash(hashes.SHA256(), 16, oinfo, backend) + + with pytest.raises(InvalidKey): + ckdf.verify(prk, b"wrong key") + + def test_unicode_typeerror(self, backend): + with pytest.raises(TypeError): + ConcatKDFHash( + hashes.SHA256(), + 16, + otherinfo=u"foo", + backend=backend + ) + + with pytest.raises(TypeError): + ckdf = ConcatKDFHash( + hashes.SHA256(), + 16, + otherinfo=None, + backend=backend + ) + + ckdf.derive(u"foo") + + with pytest.raises(TypeError): + ckdf = ConcatKDFHash( + hashes.SHA256(), + 16, + otherinfo=None, + backend=backend + ) + + ckdf.verify(u"foo", b"bar") + + with pytest.raises(TypeError): + ckdf = ConcatKDFHash( + hashes.SHA256(), + 16, + otherinfo=None, + backend=backend + ) + + ckdf.verify(b"foo", u"bar") + + +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestConcatKDFHMAC(object): + def test_length_limit(self, backend): + big_length = hashes.SHA256().digest_size * (2 ** 32 - 1) + 1 + + with pytest.raises(ValueError): + ConcatKDFHMAC(hashes.SHA256(), big_length, None, None, backend) + + def test_already_finalized(self, backend): + ckdf = ConcatKDFHMAC(hashes.SHA256(), 16, None, None, backend) + + ckdf.derive(b"\x01" * 16) + + with pytest.raises(AlreadyFinalized): + ckdf.derive(b"\x02" * 16) + + def test_derive(self, backend): + prk = binascii.unhexlify( + b"013951627c1dea63ea2d7702dd24e963eef5faac6b4af7e4" + b"b831cde499dff1ce45f6179f741c728aa733583b02409208" + b"8f0af7fce1d045edbc5790931e8d5ca79c73" + ) + + okm = binascii.unhexlify(b"64ce901db10d558661f10b6836a122a7" + b"605323ce2f39bf27eaaac8b34cf89f2f") + + oinfo = binascii.unhexlify( + b"a1b2c3d4e55e600be5f367e0e8a465f4bf2704db00c9325c" + b"9fbd216d12b49160b2ae5157650f43415653696421e68e" + ) + + ckdf = ConcatKDFHMAC(hashes.SHA512(), 32, None, oinfo, backend) + + assert ckdf.derive(prk) == okm + + def test_derive_explicit_salt(self, backend): + prk = binascii.unhexlify( + b"013951627c1dea63ea2d7702dd24e963eef5faac6b4af7e4" + b"b831cde499dff1ce45f6179f741c728aa733583b02409208" + b"8f0af7fce1d045edbc5790931e8d5ca79c73" + ) + + okm = binascii.unhexlify(b"64ce901db10d558661f10b6836a122a7" + b"605323ce2f39bf27eaaac8b34cf89f2f") + + oinfo = binascii.unhexlify( + b"a1b2c3d4e55e600be5f367e0e8a465f4bf2704db00c9325c" + b"9fbd216d12b49160b2ae5157650f43415653696421e68e" + ) + + ckdf = ConcatKDFHMAC( + hashes.SHA512(), 32, b"\x00" * 128, oinfo, backend + ) + + assert ckdf.derive(prk) == okm + + def test_verify(self, backend): + prk = binascii.unhexlify( + b"013951627c1dea63ea2d7702dd24e963eef5faac6b4af7e4" + b"b831cde499dff1ce45f6179f741c728aa733583b02409208" + b"8f0af7fce1d045edbc5790931e8d5ca79c73" + ) + + okm = binascii.unhexlify(b"64ce901db10d558661f10b6836a122a7" + b"605323ce2f39bf27eaaac8b34cf89f2f") + + oinfo = binascii.unhexlify( + b"a1b2c3d4e55e600be5f367e0e8a465f4bf2704db00c9325c" + b"9fbd216d12b49160b2ae5157650f43415653696421e68e" + ) + + ckdf = ConcatKDFHMAC(hashes.SHA512(), 32, None, oinfo, backend) + + assert ckdf.verify(prk, okm) is None + + def test_invalid_verify(self, backend): + prk = binascii.unhexlify( + b"013951627c1dea63ea2d7702dd24e963eef5faac6b4af7e4" + b"b831cde499dff1ce45f6179f741c728aa733583b02409208" + b"8f0af7fce1d045edbc5790931e8d5ca79c73" + ) + + oinfo = binascii.unhexlify( + b"a1b2c3d4e55e600be5f367e0e8a465f4bf2704db00c9325c" + b"9fbd216d12b49160b2ae5157650f43415653696421e68e" + ) + + ckdf = ConcatKDFHMAC(hashes.SHA512(), 32, None, oinfo, backend) + + with pytest.raises(InvalidKey): + ckdf.verify(prk, b"wrong key") + + def test_unicode_typeerror(self, backend): + with pytest.raises(TypeError): + ConcatKDFHMAC( + hashes.SHA256(), + 16, salt=u"foo", + otherinfo=None, + backend=backend + ) + + with pytest.raises(TypeError): + ConcatKDFHMAC( + hashes.SHA256(), + 16, salt=None, + otherinfo=u"foo", + backend=backend + ) + + with pytest.raises(TypeError): + ckdf = ConcatKDFHMAC( + hashes.SHA256(), + 16, salt=None, + otherinfo=None, + backend=backend + ) + + ckdf.derive(u"foo") + + with pytest.raises(TypeError): + ckdf = ConcatKDFHMAC( + hashes.SHA256(), + 16, salt=None, + otherinfo=None, + backend=backend + ) + + ckdf.verify(u"foo", b"bar") + + with pytest.raises(TypeError): + ckdf = ConcatKDFHMAC( + hashes.SHA256(), + 16, salt=None, + otherinfo=None, + backend=backend + ) + + ckdf.verify(b"foo", u"bar") + + +def test_invalid_backend(): + pretend_backend = object() + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + ConcatKDFHash(hashes.SHA256(), 16, None, pretend_backend) + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + ConcatKDFHMAC(hashes.SHA256(), 16, None, None, pretend_backend) diff --git a/tests/hazmat/primitives/test_constant_time.py b/tests/hazmat/primitives/test_constant_time.py new file mode 100644 index 0000000..e8e85a8 --- /dev/null +++ b/tests/hazmat/primitives/test_constant_time.py @@ -0,0 +1,30 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography.hazmat.primitives import constant_time + + +class TestConstantTimeBytesEq(object): + def test_reject_unicode(self): + with pytest.raises(TypeError): + constant_time.bytes_eq(b"foo", u"foo") + + with pytest.raises(TypeError): + constant_time.bytes_eq(u"foo", b"foo") + + with pytest.raises(TypeError): + constant_time.bytes_eq(u"foo", u"foo") + + def test_compares(self): + assert constant_time.bytes_eq(b"foo", b"foo") is True + + assert constant_time.bytes_eq(b"foo", b"bar") is False + + assert constant_time.bytes_eq(b"foobar", b"foo") is False + + assert constant_time.bytes_eq(b"foo", b"foobar") is False diff --git a/tests/hazmat/primitives/test_dh.py b/tests/hazmat/primitives/test_dh.py new file mode 100644 index 0000000..ba724bf --- /dev/null +++ b/tests/hazmat/primitives/test_dh.py @@ -0,0 +1,287 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import DHBackend +from cryptography.hazmat.primitives.asymmetric import dh +from cryptography.utils import bit_length, int_from_bytes +from ...utils import load_nist_vectors, load_vectors_from_file + + +def test_dh_parameternumbers(): + params = dh.DHParameterNumbers( + 65537, 2 + ) + + assert params.p == 65537 + assert params.g == 2 + + with pytest.raises(TypeError): + dh.DHParameterNumbers( + None, 2 + ) + + with pytest.raises(TypeError): + dh.DHParameterNumbers( + 65537, None + ) + + with pytest.raises(TypeError): + dh.DHParameterNumbers( + None, None + ) + + with pytest.raises(ValueError): + dh.DHParameterNumbers( + 65537, 7 + ) + + +def test_dh_numbers(): + params = dh.DHParameterNumbers( + 65537, 2 + ) + + public = dh.DHPublicNumbers( + 1, params + ) + + assert public.parameter_numbers is params + assert public.y == 1 + + with pytest.raises(TypeError): + dh.DHPublicNumbers( + 1, None + ) + + with pytest.raises(TypeError): + dh.DHPublicNumbers( + None, params + ) + + private = dh.DHPrivateNumbers( + 1, public + ) + + assert private.public_numbers is public + assert private.x == 1 + + with pytest.raises(TypeError): + dh.DHPrivateNumbers( + 1, None + ) + + with pytest.raises(TypeError): + dh.DHPrivateNumbers( + None, public + ) + + +def test_dh_parameter_numbers_equality(): + assert dh.DHParameterNumbers(65537, 2) == dh.DHParameterNumbers(65537, 2) + assert dh.DHParameterNumbers(6, 2) != dh.DHParameterNumbers(65537, 2) + assert dh.DHParameterNumbers(65537, 5) != dh.DHParameterNumbers(65537, 2) + assert dh.DHParameterNumbers(65537, 2) != object() + + +def test_dh_private_numbers_equality(): + params = dh.DHParameterNumbers(65537, 2) + public = dh.DHPublicNumbers(1, params) + private = dh.DHPrivateNumbers(2, public) + + assert private == dh.DHPrivateNumbers(2, public) + assert private != dh.DHPrivateNumbers(0, public) + assert private != dh.DHPrivateNumbers(2, dh.DHPublicNumbers(0, params)) + assert private != dh.DHPrivateNumbers( + 2, dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 5)) + ) + assert private != object() + + +def test_dh_public_numbers_equality(): + params = dh.DHParameterNumbers(65537, 2) + public = dh.DHPublicNumbers(1, params) + + assert public == dh.DHPublicNumbers(1, params) + assert public != dh.DHPublicNumbers(0, params) + assert public != dh.DHPublicNumbers(1, dh.DHParameterNumbers(65537, 5)) + assert public != object() + + +@pytest.mark.requires_backend_interface(interface=DHBackend) +class TestDH(object): + def test_small_key_generate_dh(self, backend): + with pytest.raises(ValueError): + dh.generate_parameters(2, 511, backend) + + def test_unsupported_generator_generate_dh(self, backend): + with pytest.raises(ValueError): + dh.generate_parameters(7, 512, backend) + + def test_dh_parameters_supported(self, backend): + assert backend.dh_parameters_supported(23, 5) + assert not backend.dh_parameters_supported(23, 18) + + def test_convert_to_numbers(self, backend): + parameters = backend.generate_dh_private_key_and_parameters(2, 512) + + private = parameters.private_numbers() + + p = private.public_numbers.parameter_numbers.p + g = private.public_numbers.parameter_numbers.g + + params = dh.DHParameterNumbers(p, g) + public = dh.DHPublicNumbers(1, params) + private = dh.DHPrivateNumbers(2, public) + + deserialized_params = params.parameters(backend) + deserialized_public = public.public_key(backend) + deserialized_private = private.private_key(backend) + + assert isinstance(deserialized_params, + dh.DHParametersWithSerialization) + assert isinstance(deserialized_public, + dh.DHPublicKeyWithSerialization) + assert isinstance(deserialized_private, + dh.DHPrivateKeyWithSerialization) + + def test_numbers_unsupported_parameters(self, backend): + params = dh.DHParameterNumbers(23, 2) + public = dh.DHPublicNumbers(1, params) + private = dh.DHPrivateNumbers(2, public) + + with pytest.raises(ValueError): + private.private_key(backend) + + def test_generate_dh(self, backend): + generator = 2 + key_size = 512 + + parameters = dh.generate_parameters(generator, key_size, backend) + assert isinstance(parameters, dh.DHParameters) + + key = parameters.generate_private_key() + assert isinstance(key, dh.DHPrivateKey) + assert key.key_size == key_size + + public = key.public_key() + assert isinstance(public, dh.DHPublicKey) + assert public.key_size == key_size + + assert isinstance(parameters, dh.DHParametersWithSerialization) + parameter_numbers = parameters.parameter_numbers() + assert isinstance(parameter_numbers, dh.DHParameterNumbers) + assert bit_length(parameter_numbers.p) == key_size + + assert isinstance(public, dh.DHPublicKeyWithSerialization) + assert isinstance(public.public_numbers(), dh.DHPublicNumbers) + assert isinstance(public.parameters(), dh.DHParameters) + + assert isinstance(key, dh.DHPrivateKeyWithSerialization) + assert isinstance(key.private_numbers(), dh.DHPrivateNumbers) + assert isinstance(key.parameters(), dh.DHParameters) + + def test_exchange(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + assert isinstance(parameters, dh.DHParameters) + + key1 = parameters.generate_private_key() + key2 = parameters.generate_private_key() + + symkey1 = key1.exchange(key2.public_key()) + assert symkey1 + assert len(symkey1) == 512 // 8 + + symkey2 = key2.exchange(key1.public_key()) + assert symkey1 == symkey2 + + def test_exchange_algorithm(self, backend): + parameters = dh.generate_parameters(2, 512, backend) + + key1 = parameters.generate_private_key() + key2 = parameters.generate_private_key() + + shared_key_bytes = key2.exchange(key1.public_key()) + symkey = int_from_bytes(shared_key_bytes, 'big') + + symkey_manual = pow(key1.public_key().public_numbers().y, + key2.private_numbers().x, + parameters.parameter_numbers().p) + + assert symkey == symkey_manual + + def test_symmetric_key_padding(self, backend): + """ + This test has specific parameters that produce a symmetric key + In length 63 bytes instead 64. We make sure here that we add + padding to the key. + """ + p = int("11859949538425015739337467917303613431031019140213666" + "129025407300654026585086345323066284800963463204246390" + "256567934582260424238844463330887962689642467123") + g = 2 + y = int("32155788395534640648739966373159697798396966919821525" + "72238852825117261342483718574508213761865276905503199" + "969908098203345481366464874759377454476688391248") + x = int("409364065449673443397833358558926598469347813468816037" + "268451847116982490733450463194921405069999008617231539" + "7147035896687401350877308899732826446337707128") + parameters = dh.DHParameterNumbers(p, g) + public = dh.DHPublicNumbers(y, parameters) + private = dh.DHPrivateNumbers(x, public) + key = private.private_key(backend) + symkey = key.exchange(public.public_key(backend)) + assert len(symkey) == 512 // 8 + assert symkey[:1] == b'\x00' + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "bad_exchange.txt"), + load_nist_vectors)) + def test_bad_exchange(self, backend, vector): + parameters1 = dh.DHParameterNumbers(int(vector["p1"]), + int(vector["g"])) + public1 = dh.DHPublicNumbers(int(vector["y1"]), parameters1) + private1 = dh.DHPrivateNumbers(int(vector["x1"]), public1) + key1 = private1.private_key(backend) + pub_key1 = key1.public_key() + + parameters2 = dh.DHParameterNumbers(int(vector["p2"]), + int(vector["g"])) + public2 = dh.DHPublicNumbers(int(vector["y2"]), parameters2) + private2 = dh.DHPrivateNumbers(int(vector["x2"]), public2) + key2 = private2.private_key(backend) + pub_key2 = key2.public_key() + + if pub_key2.public_numbers().y >= parameters1.p: + with pytest.raises(ValueError): + key1.exchange(pub_key2) + else: + symkey1 = key1.exchange(pub_key2) + assert symkey1 + + symkey2 = key2.exchange(pub_key1) + + assert symkey1 != symkey2 + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join("asymmetric", "DH", "vec.txt"), + load_nist_vectors)) + def test_dh_vectors(self, backend, vector): + parameters = dh.DHParameterNumbers(int(vector["p"]), + int(vector["g"])) + public = dh.DHPublicNumbers(int(vector["y"]), parameters) + private = dh.DHPrivateNumbers(int(vector["x"]), public) + key = private.private_key(backend) + symkey = key.exchange(public.public_key(backend)) + + assert int_from_bytes(symkey, 'big') == int(vector["k"], 16) diff --git a/tests/hazmat/primitives/test_dsa.py b/tests/hazmat/primitives/test_dsa.py new file mode 100644 index 0000000..8160ad9 --- /dev/null +++ b/tests/hazmat/primitives/test_dsa.py @@ -0,0 +1,1142 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import itertools +import os + +import pytest + +from cryptography.exceptions import AlreadyFinalized, InvalidSignature +from cryptography.hazmat.backends.interfaces import ( + DSABackend, PEMSerializationBackend +) +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import dsa +from cryptography.hazmat.primitives.asymmetric.utils import ( + Prehashed, encode_dss_signature +) +from cryptography.utils import bit_length + +from .fixtures_dsa import ( + DSA_KEY_1024, DSA_KEY_2048, DSA_KEY_3072 +) +from ...doubles import DummyHashAlgorithm, DummyKeySerializationEncryption +from ...utils import ( + load_fips_dsa_key_pair_vectors, load_fips_dsa_sig_vectors, + load_vectors_from_file, +) + + +def _skip_if_dsa_not_supported(backend, algorithm, p, q, g): + if ( + not backend.dsa_parameters_supported(p, q, g) or + not backend.dsa_hash_supported(algorithm) + ): + pytest.skip( + "{0} does not support the provided parameters".format(backend) + ) + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +def test_skip_if_dsa_not_supported(backend): + with pytest.raises(pytest.skip.Exception): + _skip_if_dsa_not_supported(backend, DummyHashAlgorithm(), 1, 1, 1) + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +class TestDSA(object): + def test_generate_dsa_parameters(self, backend): + parameters = dsa.generate_parameters(1024, backend) + assert isinstance(parameters, dsa.DSAParameters) + + def test_generate_invalid_dsa_parameters(self, backend): + with pytest.raises(ValueError): + dsa.generate_parameters(1, backend) + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join( + "asymmetric", "DSA", "FIPS_186-3", "KeyPair.rsp"), + load_fips_dsa_key_pair_vectors + ) + ) + def test_generate_dsa_keys(self, vector, backend): + parameters = dsa.DSAParameterNumbers( + p=vector['p'], + q=vector['q'], + g=vector['g'] + ).parameters(backend) + skey = parameters.generate_private_key() + numbers = skey.private_numbers() + skey_parameters = numbers.public_numbers.parameter_numbers + pkey = skey.public_key() + parameters = pkey.parameters() + parameter_numbers = parameters.parameter_numbers() + assert parameter_numbers.p == skey_parameters.p + assert parameter_numbers.q == skey_parameters.q + assert parameter_numbers.g == skey_parameters.g + assert skey_parameters.p == vector['p'] + assert skey_parameters.q == vector['q'] + assert skey_parameters.g == vector['g'] + assert skey.key_size == bit_length(vector['p']) + assert pkey.key_size == skey.key_size + public_numbers = pkey.public_numbers() + assert numbers.public_numbers.y == public_numbers.y + assert numbers.public_numbers.y == pow( + skey_parameters.g, numbers.x, skey_parameters.p + ) + + def test_generate_dsa_private_key_and_parameters(self, backend): + skey = dsa.generate_private_key(1024, backend) + assert skey + numbers = skey.private_numbers() + skey_parameters = numbers.public_numbers.parameter_numbers + assert numbers.public_numbers.y == pow( + skey_parameters.g, numbers.x, skey_parameters.p + ) + + def test_invalid_parameters_values(self, backend): + # Test a p < 1024 bits in length + with pytest.raises(ValueError): + dsa.DSAParameterNumbers( + p=2 ** 1000, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ).parameters(backend) + + # Test a p < 2048 bits in length + with pytest.raises(ValueError): + dsa.DSAParameterNumbers( + p=2 ** 2000, + q=DSA_KEY_2048.public_numbers.parameter_numbers.q, + g=DSA_KEY_2048.public_numbers.parameter_numbers.g, + ).parameters(backend) + + # Test a p < 3072 bits in length + with pytest.raises(ValueError): + dsa.DSAParameterNumbers( + p=2 ** 3000, + q=DSA_KEY_3072.public_numbers.parameter_numbers.q, + g=DSA_KEY_3072.public_numbers.parameter_numbers.g, + ).parameters(backend) + + # Test a p > 3072 bits in length + with pytest.raises(ValueError): + dsa.DSAParameterNumbers( + p=2 ** 3100, + q=DSA_KEY_3072.public_numbers.parameter_numbers.q, + g=DSA_KEY_3072.public_numbers.parameter_numbers.g, + ).parameters(backend) + + # Test a q < 160 bits in length + with pytest.raises(ValueError): + dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=2 ** 150, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ).parameters(backend) + + # Test a q < 256 bits in length + with pytest.raises(ValueError): + dsa.DSAParameterNumbers( + p=DSA_KEY_2048.public_numbers.parameter_numbers.p, + q=2 ** 250, + g=DSA_KEY_2048.public_numbers.parameter_numbers.g + ).parameters(backend) + + # Test a q > 256 bits in length + with pytest.raises(ValueError): + dsa.DSAParameterNumbers( + p=DSA_KEY_3072.public_numbers.parameter_numbers.p, + q=2 ** 260, + g=DSA_KEY_3072.public_numbers.parameter_numbers.g, + ).parameters(backend) + + # Test a g < 1 + with pytest.raises(ValueError): + dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=0 + ).parameters(backend) + + # Test a g = 1 + with pytest.raises(ValueError): + dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=1 + ).parameters(backend) + + # Test a g > p + with pytest.raises(ValueError): + dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=2 ** 1200 + ).parameters(backend) + + def test_invalid_dsa_private_key_arguments(self, backend): + # Test a p < 1024 bits in length + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=2 ** 1000, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_1024.public_numbers.y + ), + x=DSA_KEY_1024.x + ).private_key(backend) + + # Test a p < 2048 bits in length + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=2 ** 2000, + q=DSA_KEY_2048.public_numbers.parameter_numbers.q, + g=DSA_KEY_2048.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_2048.public_numbers.y + ), + x=DSA_KEY_2048.x, + ).private_key(backend) + + # Test a p < 3072 bits in length + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=2 ** 3000, + q=DSA_KEY_3072.public_numbers.parameter_numbers.q, + g=DSA_KEY_3072.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_3072.public_numbers.y + ), + x=DSA_KEY_3072.x, + ).private_key(backend) + + # Test a p > 3072 bits in length + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=2 ** 3100, + q=DSA_KEY_3072.public_numbers.parameter_numbers.q, + g=DSA_KEY_3072.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_3072.public_numbers.y + ), + x=DSA_KEY_3072.x, + ).private_key(backend) + + # Test a q < 160 bits in length + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=2 ** 150, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_1024.public_numbers.y + ), + x=DSA_KEY_1024.x, + ).private_key(backend) + + # Test a q < 256 bits in length + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_2048.public_numbers.parameter_numbers.p, + q=2 ** 250, + g=DSA_KEY_2048.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_2048.public_numbers.y + ), + x=DSA_KEY_2048.x, + ).private_key(backend) + + # Test a q > 256 bits in length + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_3072.public_numbers.parameter_numbers.p, + q=2 ** 260, + g=DSA_KEY_3072.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_3072.public_numbers.y + ), + x=DSA_KEY_3072.x, + ).private_key(backend) + + # Test a g < 1 + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=0, + ), + y=DSA_KEY_1024.public_numbers.y + ), + x=DSA_KEY_1024.x, + ).private_key(backend) + + # Test a g = 1 + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=1, + ), + y=DSA_KEY_1024.public_numbers.y + ), + x=DSA_KEY_1024.x, + ).private_key(backend) + + # Test a g > p + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=2 ** 1200, + ), + y=DSA_KEY_1024.public_numbers.y + ), + x=DSA_KEY_1024.x, + ).private_key(backend) + + # Test x = 0 + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_1024.public_numbers.y + ), + x=0, + ).private_key(backend) + + # Test x < 0 + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_1024.public_numbers.y + ), + x=-2, + ).private_key(backend) + + # Test x = q + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_1024.public_numbers.y + ), + x=2 ** 159, + ).private_key(backend) + + # Test x > q + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_1024.public_numbers.y + ), + x=2 ** 200, + ).private_key(backend) + + # Test y != (g ** x) % p + with pytest.raises(ValueError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + y=2 ** 100 + ), + x=DSA_KEY_1024.x, + ).private_key(backend) + + # Test a non-integer y value + with pytest.raises(TypeError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + y=None + ), + x=DSA_KEY_1024.x, + ).private_key(backend) + + # Test a non-integer x value + with pytest.raises(TypeError): + dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_1024.public_numbers.y + ), + x=None, + ).private_key(backend) + + def test_invalid_dsa_public_key_arguments(self, backend): + # Test a p < 1024 bits in length + with pytest.raises(ValueError): + dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=2 ** 1000, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_1024.public_numbers.y + ).public_key(backend) + + # Test a p < 2048 bits in length + with pytest.raises(ValueError): + dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=2 ** 2000, + q=DSA_KEY_2048.public_numbers.parameter_numbers.q, + g=DSA_KEY_2048.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_2048.public_numbers.y + ).public_key(backend) + + # Test a p < 3072 bits in length + with pytest.raises(ValueError): + dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=2 ** 3000, + q=DSA_KEY_3072.public_numbers.parameter_numbers.q, + g=DSA_KEY_3072.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_3072.public_numbers.y + ).public_key(backend) + + # Test a p > 3072 bits in length + with pytest.raises(ValueError): + dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=2 ** 3100, + q=DSA_KEY_3072.public_numbers.parameter_numbers.q, + g=DSA_KEY_3072.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_3072.public_numbers.y + ).public_key(backend) + + # Test a q < 160 bits in length + with pytest.raises(ValueError): + dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=2 ** 150, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_1024.public_numbers.y + ).public_key(backend) + + # Test a q < 256 bits in length + with pytest.raises(ValueError): + dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_2048.public_numbers.parameter_numbers.p, + q=2 ** 250, + g=DSA_KEY_2048.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_2048.public_numbers.y + ).public_key(backend) + + # Test a q > 256 bits in length + with pytest.raises(ValueError): + dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_3072.public_numbers.parameter_numbers.p, + q=2 ** 260, + g=DSA_KEY_3072.public_numbers.parameter_numbers.g, + ), + y=DSA_KEY_3072.public_numbers.y + ).public_key(backend) + + # Test a g < 1 + with pytest.raises(ValueError): + dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=0, + ), + y=DSA_KEY_1024.public_numbers.y + ).public_key(backend) + + # Test a g = 1 + with pytest.raises(ValueError): + dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=1, + ), + y=DSA_KEY_1024.public_numbers.y + ).public_key(backend) + + # Test a g > p + with pytest.raises(ValueError): + dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=2 ** 1200, + ), + y=DSA_KEY_1024.public_numbers.y + ).public_key(backend) + + # Test a non-integer y value + with pytest.raises(TypeError): + dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + p=DSA_KEY_1024.public_numbers.parameter_numbers.p, + q=DSA_KEY_1024.public_numbers.parameter_numbers.q, + g=DSA_KEY_1024.public_numbers.parameter_numbers.g, + ), + y=None + ).public_key(backend) + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +class TestDSAVerification(object): + _algorithms_dict = { + 'SHA1': hashes.SHA1, + 'SHA224': hashes.SHA224, + 'SHA256': hashes.SHA256, + 'SHA384': hashes.SHA384, + 'SHA512': hashes.SHA512 + } + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join( + "asymmetric", "DSA", "FIPS_186-3", "SigVer.rsp"), + load_fips_dsa_sig_vectors + ) + ) + def test_dsa_verification(self, vector, backend): + digest_algorithm = vector['digest_algorithm'].replace("-", "") + algorithm = self._algorithms_dict[digest_algorithm] + + _skip_if_dsa_not_supported( + backend, algorithm, vector['p'], vector['q'], vector['g'] + ) + + public_key = dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + vector['p'], vector['q'], vector['g'] + ), + y=vector['y'] + ).public_key(backend) + sig = encode_dss_signature(vector['r'], vector['s']) + verifier = public_key.verifier(sig, algorithm()) + verifier.update(vector['msg']) + if vector['result'] == "F": + with pytest.raises(InvalidSignature): + verifier.verify() + else: + verifier.verify() + + def test_dsa_verify_invalid_asn1(self, backend): + public_key = DSA_KEY_1024.public_numbers.public_key(backend) + verifier = public_key.verifier(b'fakesig', hashes.SHA1()) + verifier.update(b'fakesig') + with pytest.raises(InvalidSignature): + verifier.verify() + + def test_signature_not_bytes(self, backend): + public_key = DSA_KEY_1024.public_numbers.public_key(backend) + with pytest.raises(TypeError): + public_key.verifier(1234, hashes.SHA1()) + + def test_use_after_finalize(self, backend): + public_key = DSA_KEY_1024.public_numbers.public_key(backend) + verifier = public_key.verifier(b'fakesig', hashes.SHA1()) + verifier.update(b'irrelevant') + with pytest.raises(InvalidSignature): + verifier.verify() + with pytest.raises(AlreadyFinalized): + verifier.verify() + with pytest.raises(AlreadyFinalized): + verifier.update(b"more data") + + def test_verify(self, backend): + message = b"one little message" + algorithm = hashes.SHA1() + private_key = DSA_KEY_1024.private_key(backend) + signer = private_key.signer(algorithm) + signer.update(message) + signature = signer.finalize() + public_key = private_key.public_key() + public_key.verify(signature, message, algorithm) + + def test_prehashed_verify(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = Prehashed(hashes.SHA1()) + signature = private_key.sign(message, hashes.SHA1()) + public_key = private_key.public_key() + public_key.verify(signature, digest, prehashed_alg) + + def test_prehashed_digest_mismatch(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + public_key = private_key.public_key() + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = Prehashed(hashes.SHA224()) + with pytest.raises(ValueError): + public_key.verify(b"\x00" * 128, digest, prehashed_alg) + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +class TestDSASignature(object): + _algorithms_dict = { + 'SHA1': hashes.SHA1, + 'SHA224': hashes.SHA224, + 'SHA256': hashes.SHA256, + 'SHA384': hashes.SHA384, + 'SHA512': hashes.SHA512} + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join( + "asymmetric", "DSA", "FIPS_186-3", "SigGen.txt"), + load_fips_dsa_sig_vectors + ) + ) + def test_dsa_signing(self, vector, backend): + digest_algorithm = vector['digest_algorithm'].replace("-", "") + algorithm = self._algorithms_dict[digest_algorithm] + + _skip_if_dsa_not_supported( + backend, algorithm, vector['p'], vector['q'], vector['g'] + ) + + private_key = dsa.DSAPrivateNumbers( + public_numbers=dsa.DSAPublicNumbers( + parameter_numbers=dsa.DSAParameterNumbers( + vector['p'], vector['q'], vector['g'] + ), + y=vector['y'] + ), + x=vector['x'] + ).private_key(backend) + signer = private_key.signer(algorithm()) + signer.update(vector['msg']) + signature = signer.finalize() + assert signature + + public_key = private_key.public_key() + verifier = public_key.verifier(signature, algorithm()) + verifier.update(vector['msg']) + verifier.verify() + + def test_use_after_finalize(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + signer = private_key.signer(hashes.SHA1()) + signer.update(b"data") + signer.finalize() + with pytest.raises(AlreadyFinalized): + signer.finalize() + with pytest.raises(AlreadyFinalized): + signer.update(b"more data") + + def test_sign(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + message = b"one little message" + algorithm = hashes.SHA1() + signature = private_key.sign(message, algorithm) + public_key = private_key.public_key() + verifier = public_key.verifier(signature, algorithm) + verifier.update(message) + verifier.verify() + + def test_prehashed_sign(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = Prehashed(hashes.SHA1()) + signature = private_key.sign(digest, prehashed_alg) + public_key = private_key.public_key() + verifier = public_key.verifier(signature, hashes.SHA1()) + verifier.update(message) + verifier.verify() + + def test_prehashed_digest_mismatch(self, backend): + private_key = DSA_KEY_1024.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = Prehashed(hashes.SHA224()) + with pytest.raises(ValueError): + private_key.sign(digest, prehashed_alg) + + +class TestDSANumbers(object): + def test_dsa_parameter_numbers(self): + parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) + assert parameter_numbers.p == 1 + assert parameter_numbers.q == 2 + assert parameter_numbers.g == 3 + + def test_dsa_parameter_numbers_invalid_types(self): + with pytest.raises(TypeError): + dsa.DSAParameterNumbers(p=None, q=2, g=3) + + with pytest.raises(TypeError): + dsa.DSAParameterNumbers(p=1, q=None, g=3) + + with pytest.raises(TypeError): + dsa.DSAParameterNumbers(p=1, q=2, g=None) + + def test_dsa_public_numbers(self): + parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) + public_numbers = dsa.DSAPublicNumbers( + y=4, + parameter_numbers=parameter_numbers + ) + assert public_numbers.y == 4 + assert public_numbers.parameter_numbers == parameter_numbers + + def test_dsa_public_numbers_invalid_types(self): + with pytest.raises(TypeError): + dsa.DSAPublicNumbers(y=4, parameter_numbers=None) + + with pytest.raises(TypeError): + parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) + dsa.DSAPublicNumbers(y=None, parameter_numbers=parameter_numbers) + + def test_dsa_private_numbers(self): + parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) + public_numbers = dsa.DSAPublicNumbers( + y=4, + parameter_numbers=parameter_numbers + ) + private_numbers = dsa.DSAPrivateNumbers( + x=5, + public_numbers=public_numbers + ) + assert private_numbers.x == 5 + assert private_numbers.public_numbers == public_numbers + + def test_dsa_private_numbers_invalid_types(self): + parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) + public_numbers = dsa.DSAPublicNumbers( + y=4, + parameter_numbers=parameter_numbers + ) + with pytest.raises(TypeError): + dsa.DSAPrivateNumbers(x=4, public_numbers=None) + + with pytest.raises(TypeError): + dsa.DSAPrivateNumbers(x=None, public_numbers=public_numbers) + + def test_repr(self): + parameter_numbers = dsa.DSAParameterNumbers(p=1, q=2, g=3) + assert ( + repr(parameter_numbers) == "" + ) + + public_numbers = dsa.DSAPublicNumbers( + y=4, + parameter_numbers=parameter_numbers + ) + assert repr(public_numbers) == ( + ")>" + ) + + +class TestDSANumberEquality(object): + def test_parameter_numbers_eq(self): + param = dsa.DSAParameterNumbers(1, 2, 3) + assert param == dsa.DSAParameterNumbers(1, 2, 3) + + def test_parameter_numbers_ne(self): + param = dsa.DSAParameterNumbers(1, 2, 3) + assert param != dsa.DSAParameterNumbers(1, 2, 4) + assert param != dsa.DSAParameterNumbers(1, 1, 3) + assert param != dsa.DSAParameterNumbers(2, 2, 3) + assert param != object() + + def test_public_numbers_eq(self): + pub = dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3)) + assert pub == dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3)) + + def test_public_numbers_ne(self): + pub = dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3)) + assert pub != dsa.DSAPublicNumbers(2, dsa.DSAParameterNumbers(1, 2, 3)) + assert pub != dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(2, 2, 3)) + assert pub != dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 3, 3)) + assert pub != dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 4)) + assert pub != object() + + def test_private_numbers_eq(self): + pub = dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3)) + priv = dsa.DSAPrivateNumbers(1, pub) + assert priv == dsa.DSAPrivateNumbers( + 1, dsa.DSAPublicNumbers( + 1, dsa.DSAParameterNumbers(1, 2, 3) + ) + ) + + def test_private_numbers_ne(self): + pub = dsa.DSAPublicNumbers(1, dsa.DSAParameterNumbers(1, 2, 3)) + priv = dsa.DSAPrivateNumbers(1, pub) + assert priv != dsa.DSAPrivateNumbers( + 2, dsa.DSAPublicNumbers( + 1, dsa.DSAParameterNumbers(1, 2, 3) + ) + ) + assert priv != dsa.DSAPrivateNumbers( + 1, dsa.DSAPublicNumbers( + 2, dsa.DSAParameterNumbers(1, 2, 3) + ) + ) + assert priv != dsa.DSAPrivateNumbers( + 1, dsa.DSAPublicNumbers( + 1, dsa.DSAParameterNumbers(2, 2, 3) + ) + ) + assert priv != dsa.DSAPrivateNumbers( + 1, dsa.DSAPublicNumbers( + 1, dsa.DSAParameterNumbers(1, 3, 3) + ) + ) + assert priv != dsa.DSAPrivateNumbers( + 1, dsa.DSAPublicNumbers( + 1, dsa.DSAParameterNumbers(1, 2, 4) + ) + ) + assert priv != object() + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +class TestDSASerialization(object): + @pytest.mark.parametrize( + ("fmt", "password"), + itertools.product( + [ + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.PrivateFormat.PKCS8 + ], + [ + b"s", + b"longerpassword", + b"!*$&(@#$*&($T@%_somesymbols", + b"\x01" * 1000, + ] + ) + ) + def test_private_bytes_encrypted_pem(self, backend, fmt, password): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_private_key(key_bytes, None, backend) + serialized = key.private_bytes( + serialization.Encoding.PEM, + fmt, + serialization.BestAvailableEncryption(password) + ) + loaded_key = serialization.load_pem_private_key( + serialized, password, backend + ) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + ("fmt", "password"), + [ + [serialization.PrivateFormat.PKCS8, b"s"], + [serialization.PrivateFormat.PKCS8, b"longerpassword"], + [serialization.PrivateFormat.PKCS8, b"!*$&(@#$*&($T@%_somesymbol"], + [serialization.PrivateFormat.PKCS8, b"\x01" * 1000] + ] + ) + def test_private_bytes_encrypted_der(self, backend, fmt, password): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_private_key(key_bytes, None, backend) + serialized = key.private_bytes( + serialization.Encoding.DER, + fmt, + serialization.BestAvailableEncryption(password) + ) + loaded_key = serialization.load_der_private_key( + serialized, password, backend + ) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + ("encoding", "fmt", "loader_func"), + [ + [ + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.load_pem_private_key + ], + [ + serialization.Encoding.DER, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.load_der_private_key + ], + [ + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.load_pem_private_key + ], + [ + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.load_der_private_key + ], + ] + ) + def test_private_bytes_unencrypted(self, backend, encoding, fmt, + loader_func): + key = DSA_KEY_1024.private_key(backend) + serialized = key.private_bytes( + encoding, fmt, serialization.NoEncryption() + ) + loaded_key = loader_func(serialized, None, backend) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + ("key_path", "encoding", "loader_func"), + [ + [ + os.path.join( + "asymmetric", + "Traditional_OpenSSL_Serialization", + "dsa.1024.pem" + ), + serialization.Encoding.PEM, + serialization.load_pem_private_key + ], + [ + os.path.join( + "asymmetric", "DER_Serialization", "dsa.1024.der" + ), + serialization.Encoding.DER, + serialization.load_der_private_key + ], + ] + ) + def test_private_bytes_traditional_openssl_unencrypted( + self, backend, key_path, encoding, loader_func + ): + key_bytes = load_vectors_from_file( + key_path, lambda pemfile: pemfile.read(), mode="rb" + ) + key = loader_func(key_bytes, None, backend) + serialized = key.private_bytes( + encoding, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption() + ) + assert serialized == key_bytes + + def test_private_bytes_traditional_der_encrypted_invalid(self, backend): + key = DSA_KEY_1024.private_key(backend) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.DER, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.BestAvailableEncryption(b"password") + ) + + def test_private_bytes_invalid_encoding(self, backend): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + with pytest.raises(TypeError): + key.private_bytes( + "notencoding", + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_format(self, backend): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + "invalidformat", + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_encryption_algorithm(self, backend): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + "notanencalg" + ) + + def test_private_bytes_unsupported_encryption_type(self, backend): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + DummyKeySerializationEncryption() + ) + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +class TestDSAPEMPublicKeySerialization(object): + @pytest.mark.parametrize( + ("key_path", "loader_func", "encoding"), + [ + ( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pub.pem"), + serialization.load_pem_public_key, + serialization.Encoding.PEM, + ), ( + os.path.join( + "asymmetric", + "DER_Serialization", + "unenc-dsa-pkcs8.pub.der" + ), + serialization.load_der_public_key, + serialization.Encoding.DER, + ) + ] + ) + def test_public_bytes_match(self, key_path, loader_func, encoding, + backend): + key_bytes = load_vectors_from_file( + key_path, lambda pemfile: pemfile.read(), mode="rb" + ) + key = loader_func(key_bytes, backend) + serialized = key.public_bytes( + encoding, serialization.PublicFormat.SubjectPublicKeyInfo, + ) + assert serialized == key_bytes + + def test_public_bytes_openssh(self, backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pub.pem"), + lambda pemfile: pemfile.read(), mode="rb" + ) + key = serialization.load_pem_public_key(key_bytes, backend) + + ssh_bytes = key.public_bytes( + serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH + ) + assert ssh_bytes == ( + b"ssh-dss AAAAB3NzaC1kc3MAAACBAKoJMMwUWCUiHK/6KKwolBlqJ4M95ewhJweR" + b"aJQgd3Si57I4sNNvGySZosJYUIPrAUMpJEGNhn+qIS3RBx1NzrJ4J5StOTzAik1K" + b"2n9o1ug5pfzTS05ALYLLioy0D+wxkRv5vTYLA0yqy0xelHmSVzyekAmcGw8FlAyr" + b"5dLeSaFnAAAAFQCtwOhps28KwBOmgf301ImdaYIEUQAAAIEAjGtFia+lOk0QSL/D" + b"RtHzhsp1UhzPct2qJRKGiA7hMgH/SIkLv8M9ebrK7HHnp3hQe9XxpmQi45QVvgPn" + b"EUG6Mk9bkxMZKRgsiKn6QGKDYGbOvnS1xmkMfRARBsJAq369VOTjMB/Qhs5q2ski" + b"+ycTorCIfLoTubxozlz/8kHNMkYAAACAKyYOqX3GoSrpMsZA5989j/BKigWgMk+N" + b"Xxsj8V+hcP8/QgYRJO/yWGyxG0moLc3BuQ/GqE+xAQnLZ9tdLalxrq8Xvl43KEVj" + b"5MZNnl/ISAJYsxnw3inVTYNQcNnih5FNd9+BSR9EI7YtqYTrP0XrKin86l2uUlrG" + b"q2vM4Ev99bY=" + ) + + def test_public_bytes_invalid_encoding(self, backend): + key = DSA_KEY_2048.private_key(backend).public_key() + with pytest.raises(TypeError): + key.public_bytes( + "notencoding", + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + def test_public_bytes_invalid_format(self, backend): + key = DSA_KEY_2048.private_key(backend).public_key() + with pytest.raises(TypeError): + key.public_bytes(serialization.Encoding.PEM, "invalidformat") + + def test_public_bytes_pkcs1_unsupported(self, backend): + key = DSA_KEY_2048.private_key(backend).public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 + ) diff --git a/tests/hazmat/primitives/test_ec.py b/tests/hazmat/primitives/test_ec.py new file mode 100644 index 0000000..7127071 --- /dev/null +++ b/tests/hazmat/primitives/test_ec.py @@ -0,0 +1,1106 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import itertools +import os + +from binascii import hexlify + +import pytest + +from cryptography import exceptions, utils +from cryptography.hazmat.backends.interfaces import ( + EllipticCurveBackend, PEMSerializationBackend +) +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.asymmetric.utils import ( + Prehashed, encode_dss_signature +) + +from .fixtures_ec import EC_KEY_SECP384R1 +from ...doubles import DummyKeySerializationEncryption +from ...utils import ( + load_fips_ecdsa_key_pair_vectors, load_fips_ecdsa_signing_vectors, + load_kasvs_ecdh_vectors, load_vectors_from_file, + raises_unsupported_algorithm +) + +_HASH_TYPES = { + "SHA-1": hashes.SHA1, + "SHA-224": hashes.SHA224, + "SHA-256": hashes.SHA256, + "SHA-384": hashes.SHA384, + "SHA-512": hashes.SHA512, +} + + +def _skip_ecdsa_vector(backend, curve_type, hash_type): + if not backend.elliptic_curve_signature_algorithm_supported( + ec.ECDSA(hash_type()), + curve_type() + ): + pytest.skip( + "ECDSA not supported with this hash {0} and curve {1}".format( + hash_type().name, curve_type().name + ) + ) + + +def _skip_curve_unsupported(backend, curve): + if not backend.elliptic_curve_supported(curve): + pytest.skip( + "Curve {0} is not supported by this backend {1}".format( + curve.name, backend + ) + ) + + +def _skip_exchange_algorithm_unsupported(backend, algorithm, curve): + if not backend.elliptic_curve_exchange_algorithm_supported( + algorithm, curve + ): + pytest.skip( + "Exchange algorithm is not supported by this backend {0}".format( + backend + ) + ) + + +@utils.register_interface(ec.EllipticCurve) +class DummyCurve(object): + name = "dummy-curve" + key_size = 1 + + +@utils.register_interface(ec.EllipticCurveSignatureAlgorithm) +class DummySignatureAlgorithm(object): + algorithm = None + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_skip_curve_unsupported(backend): + with pytest.raises(pytest.skip.Exception): + _skip_curve_unsupported(backend, DummyCurve()) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_skip_exchange_algorithm_unsupported(backend): + with pytest.raises(pytest.skip.Exception): + _skip_exchange_algorithm_unsupported(backend, ec.ECDH(), DummyCurve()) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_skip_ecdsa_vector(backend): + with pytest.raises(pytest.skip.Exception): + _skip_ecdsa_vector(backend, DummyCurve, hashes.SHA256) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_derive_private_key_success(backend): + curve = ec.SECP256K1() + _skip_curve_unsupported(backend, curve) + + private_numbers = ec.generate_private_key(curve, backend).private_numbers() + + derived_key = ec.derive_private_key( + private_numbers.private_value, curve, backend + ) + + assert private_numbers == derived_key.private_numbers() + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +def test_derive_private_key_errors(backend): + curve = ec.SECP256K1() + _skip_curve_unsupported(backend, curve) + + with pytest.raises(TypeError): + ec.derive_private_key('one', curve, backend) + + with pytest.raises(TypeError): + ec.derive_private_key(10, 'five', backend) + + with pytest.raises(ValueError): + ec.derive_private_key(-7, curve, backend) + + +def test_ec_numbers(): + numbers = ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + 2, 3, DummyCurve() + ) + ) + + assert numbers.private_value == 1 + assert numbers.public_numbers.x == 2 + assert numbers.public_numbers.y == 3 + assert isinstance(numbers.public_numbers.curve, DummyCurve) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + None, + ec.EllipticCurvePublicNumbers( + 2, 3, DummyCurve() + ) + ) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + None, 3, DummyCurve() + ) + ) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + 2, None, DummyCurve() + ) + ) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + 1, + ec.EllipticCurvePublicNumbers( + 2, 3, None + ) + ) + + with pytest.raises(TypeError): + ec.EllipticCurvePrivateNumbers( + 1, + None + ) + + +def test_encode_point(): + # secp256r1 point + x = int( + '233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec', + 16 + ) + y = int( + '3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e', + 16 + ) + pn = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()) + data = pn.encode_point() + assert data == binascii.unhexlify( + "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" + "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e" + ) + + +def test_from_encoded_point(): + # secp256r1 point + data = binascii.unhexlify( + "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" + "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e" + ) + pn = ec.EllipticCurvePublicNumbers.from_encoded_point( + ec.SECP256R1(), data + ) + assert pn.x == int( + '233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22aec', + 16 + ) + assert pn.y == int( + '3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460b35f442e', + 16 + ) + + +def test_from_encoded_point_invalid_length(): + bad_data = binascii.unhexlify( + "04233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22ae" + "c3ea2c10a84153862be4ec82940f0543f9ba866af9751a6ee79d38460" + ) + with pytest.raises(ValueError): + ec.EllipticCurvePublicNumbers.from_encoded_point( + ec.SECP384R1(), bad_data + ) + + +def test_from_encoded_point_unsupported_point_type(): + # set to point type 2. + unsupported_type = binascii.unhexlify( + "02233ea3b0027127084cd2cd336a13aeef69c598d8af61369a36454a17c6c22a" + ) + with pytest.raises(ValueError): + ec.EllipticCurvePublicNumbers.from_encoded_point( + ec.SECP256R1(), unsupported_type + ) + + +def test_from_encoded_point_not_a_curve(): + with pytest.raises(TypeError): + ec.EllipticCurvePublicNumbers.from_encoded_point( + "notacurve", b"\x04data" + ) + + +def test_ec_public_numbers_repr(): + pn = ec.EllipticCurvePublicNumbers(2, 3, ec.SECP256R1()) + assert repr(pn) == "" + + +def test_ec_public_numbers_hash(): + pn1 = ec.EllipticCurvePublicNumbers(2, 3, ec.SECP256R1()) + pn2 = ec.EllipticCurvePublicNumbers(2, 3, ec.SECP256R1()) + pn3 = ec.EllipticCurvePublicNumbers(1, 3, ec.SECP256R1()) + + assert hash(pn1) == hash(pn2) + assert hash(pn1) != hash(pn3) + + +def test_ec_private_numbers_hash(): + numbers1 = ec.EllipticCurvePrivateNumbers( + 1, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) + ) + numbers2 = ec.EllipticCurvePrivateNumbers( + 1, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) + ) + numbers3 = ec.EllipticCurvePrivateNumbers( + 2, ec.EllipticCurvePublicNumbers(2, 3, DummyCurve()) + ) + + assert hash(numbers1) == hash(numbers2) + assert hash(numbers1) != hash(numbers3) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +class TestECWithNumbers(object): + @pytest.mark.parametrize( + ("vector", "hash_type"), + list(itertools.product( + load_vectors_from_file( + os.path.join( + "asymmetric", "ECDSA", "FIPS_186-3", "KeyPair.rsp"), + load_fips_ecdsa_key_pair_vectors + ), + _HASH_TYPES.values() + )) + ) + def test_with_numbers(self, backend, vector, hash_type): + curve_type = ec._CURVE_TYPES[vector['curve']] + + _skip_ecdsa_vector(backend, curve_type, hash_type) + + key = ec.EllipticCurvePrivateNumbers( + vector['d'], + ec.EllipticCurvePublicNumbers( + vector['x'], + vector['y'], + curve_type() + ) + ).private_key(backend) + assert key + + priv_num = key.private_numbers() + assert priv_num.private_value == vector['d'] + assert priv_num.public_numbers.x == vector['x'] + assert priv_num.public_numbers.y == vector['y'] + assert curve_type().name == priv_num.public_numbers.curve.name + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +class TestECDSAVectors(object): + @pytest.mark.parametrize( + ("vector", "hash_type"), + list(itertools.product( + load_vectors_from_file( + os.path.join( + "asymmetric", "ECDSA", "FIPS_186-3", "KeyPair.rsp"), + load_fips_ecdsa_key_pair_vectors + ), + _HASH_TYPES.values() + )) + ) + def test_signing_with_example_keys(self, backend, vector, hash_type): + curve_type = ec._CURVE_TYPES[vector['curve']] + + _skip_ecdsa_vector(backend, curve_type, hash_type) + + key = ec.EllipticCurvePrivateNumbers( + vector['d'], + ec.EllipticCurvePublicNumbers( + vector['x'], + vector['y'], + curve_type() + ) + ).private_key(backend) + assert key + + pkey = key.public_key() + assert pkey + + signer = key.signer(ec.ECDSA(hash_type())) + signer.update(b"YELLOW SUBMARINE") + signature = signer.finalize() + + verifier = pkey.verifier(signature, ec.ECDSA(hash_type())) + verifier.update(b"YELLOW SUBMARINE") + verifier.verify() + + @pytest.mark.parametrize( + "curve", ec._CURVE_TYPES.values() + ) + def test_generate_vector_curves(self, backend, curve): + _skip_curve_unsupported(backend, curve()) + + key = ec.generate_private_key(curve(), backend) + assert key + assert isinstance(key.curve, curve) + assert key.curve.key_size + + pkey = key.public_key() + assert pkey + assert isinstance(pkey.curve, curve) + assert key.curve.key_size == pkey.curve.key_size + + def test_generate_unknown_curve(self, backend): + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_ELLIPTIC_CURVE + ): + ec.generate_private_key(DummyCurve(), backend) + + assert backend.elliptic_curve_signature_algorithm_supported( + ec.ECDSA(hashes.SHA256()), + DummyCurve() + ) is False + + def test_unknown_signature_algoritm(self, backend): + _skip_curve_unsupported(backend, ec.SECP192R1()) + + key = ec.generate_private_key(ec.SECP192R1(), backend) + + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + key.signer(DummySignatureAlgorithm()) + + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + key.sign(b"somedata", DummySignatureAlgorithm()) + + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + key.public_key().verifier(b"", DummySignatureAlgorithm()) + + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + key.public_key().verify( + b"signature", b"data", DummySignatureAlgorithm() + ) + + assert backend.elliptic_curve_signature_algorithm_supported( + DummySignatureAlgorithm(), + ec.SECP192R1() + ) is False + + def test_load_invalid_ec_key_from_numbers(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + + numbers = ec.EllipticCurvePrivateNumbers( + 357646505660320080863666618182642070958081774038609089496899025506, + ec.EllipticCurvePublicNumbers( + 47250808410327023131573602008345894927686381772325561185532964, + 1120253292479243545483756778742719537373113335231773536789915, + ec.SECP256R1(), + ) + ) + with pytest.raises(ValueError): + numbers.private_key(backend) + + numbers = ec.EllipticCurvePrivateNumbers( + 357646505660320080863666618182642070958081774038609089496899025506, + ec.EllipticCurvePublicNumbers( + -4725080841032702313157360200834589492768638177232556118553296, + 1120253292479243545483756778742719537373113335231773536789915, + ec.SECP256R1(), + ) + ) + with pytest.raises(ValueError): + numbers.private_key(backend) + + numbers = ec.EllipticCurvePrivateNumbers( + 357646505660320080863666618182642070958081774038609089496899025506, + ec.EllipticCurvePublicNumbers( + 47250808410327023131573602008345894927686381772325561185532964, + -1120253292479243545483756778742719537373113335231773536789915, + ec.SECP256R1(), + ) + ) + with pytest.raises(ValueError): + numbers.private_key(backend) + + def test_load_invalid_public_ec_key_from_numbers(self, backend): + _skip_curve_unsupported(backend, ec.SECP521R1()) + + # Bad X coordinate + numbers = ec.EllipticCurvePublicNumbers( + int("000003647356b91f8ace114c7247ecf4f4a622553fc025e04a178f179ef27" + "9090c184af678a4c78f635483bdd8aa544851c6ef291c1f0d6a241ebfd145" + "77d1d30d9903ce", 16), + int("000001499bc7e079322ea0fcfbd6b40103fa6a1536c2257b182db0df4b369" + "6ec643adf100eb4f2025d1b873f82e5a475d6e4400ba777090eeb4563a115" + "09e4c87319dc26", 16), + ec.SECP521R1() + ) + with pytest.raises(ValueError): + numbers.public_key(backend) + + # Bad Y coordinate + numbers = ec.EllipticCurvePublicNumbers( + int("0000019aadc221cc0525118ab6d5aa1f64720603de0be128cbfea0b381ad8" + "02a2facc6370bb58cf88b3f0c692bc654ee19d6cad198f10d4b681b396f20" + "d2e40603fa945b", 16), + int("0000025da392803a320717a08d4cb3dea932039badff363b71bdb8064e726" + "6c7f4f4b748d4d425347fc33e3885d34b750fa7fcd5691f4d90c89522ce33" + "feff5db10088a5", 16), + ec.SECP521R1() + ) + with pytest.raises(ValueError): + numbers.public_key(backend) + + @pytest.mark.parametrize( + "vector", + itertools.chain( + load_vectors_from_file( + os.path.join( + "asymmetric", "ECDSA", "FIPS_186-3", "SigGen.txt"), + load_fips_ecdsa_signing_vectors + ), + load_vectors_from_file( + os.path.join( + "asymmetric", "ECDSA", "SECP256K1", "SigGen.txt"), + load_fips_ecdsa_signing_vectors + ), + ) + ) + def test_signatures(self, backend, vector): + hash_type = _HASH_TYPES[vector['digest_algorithm']] + curve_type = ec._CURVE_TYPES[vector['curve']] + + _skip_ecdsa_vector(backend, curve_type, hash_type) + + key = ec.EllipticCurvePublicNumbers( + vector['x'], + vector['y'], + curve_type() + ).public_key(backend) + + signature = encode_dss_signature(vector['r'], vector['s']) + + verifier = key.verifier( + signature, + ec.ECDSA(hash_type()) + ) + verifier.update(vector['message']) + assert verifier.verify() + + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join( + "asymmetric", "ECDSA", "FIPS_186-3", "SigVer.rsp"), + load_fips_ecdsa_signing_vectors + ) + ) + def test_signature_failures(self, backend, vector): + hash_type = _HASH_TYPES[vector['digest_algorithm']] + curve_type = ec._CURVE_TYPES[vector['curve']] + + _skip_ecdsa_vector(backend, curve_type, hash_type) + + key = ec.EllipticCurvePublicNumbers( + vector['x'], + vector['y'], + curve_type() + ).public_key(backend) + + signature = encode_dss_signature(vector['r'], vector['s']) + + verifier = key.verifier( + signature, + ec.ECDSA(hash_type()) + ) + verifier.update(vector['message']) + + if vector["fail"] is True: + with pytest.raises(exceptions.InvalidSignature): + verifier.verify() + else: + verifier.verify() + + def test_sign(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + algorithm = ec.ECDSA(hashes.SHA1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + signature = private_key.sign(message, algorithm) + public_key = private_key.public_key() + verifier = public_key.verifier(signature, algorithm) + verifier.update(message) + verifier.verify() + + def test_sign_prehashed(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + algorithm = ec.ECDSA(Prehashed(hashes.SHA1())) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + signature = private_key.sign(data, algorithm) + public_key = private_key.public_key() + verifier = public_key.verifier(signature, ec.ECDSA(hashes.SHA1())) + verifier.update(message) + verifier.verify() + + def test_sign_prehashed_digest_mismatch(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + algorithm = ec.ECDSA(Prehashed(hashes.SHA256())) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + with pytest.raises(ValueError): + private_key.sign(data, algorithm) + + def test_verify(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + algorithm = ec.ECDSA(hashes.SHA1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + signer = private_key.signer(algorithm) + signer.update(message) + signature = signer.finalize() + public_key = private_key.public_key() + public_key.verify(signature, message, algorithm) + + def test_verify_prehashed(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + algorithm = ec.ECDSA(hashes.SHA1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + signer = private_key.signer(algorithm) + signer.update(message) + signature = signer.finalize() + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + public_key = private_key.public_key() + public_key.verify( + signature, data, ec.ECDSA(Prehashed(hashes.SHA1())) + ) + + def test_verify_prehashed_digest_mismatch(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + message = b"one little message" + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + public_key = private_key.public_key() + with pytest.raises(ValueError): + public_key.verify( + b"\x00" * 32, data, ec.ECDSA(Prehashed(hashes.SHA256())) + ) + + +class TestECNumbersEquality(object): + def test_public_numbers_eq(self): + pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) + assert pub == ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) + + def test_public_numbers_ne(self): + pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) + assert pub != ec.EllipticCurvePublicNumbers(1, 2, ec.SECP384R1()) + assert pub != ec.EllipticCurvePublicNumbers(1, 3, ec.SECP192R1()) + assert pub != ec.EllipticCurvePublicNumbers(2, 2, ec.SECP192R1()) + assert pub != object() + + def test_private_numbers_eq(self): + pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) + priv = ec.EllipticCurvePrivateNumbers(1, pub) + assert priv == ec.EllipticCurvePrivateNumbers( + 1, ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) + ) + + def test_private_numbers_ne(self): + pub = ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) + priv = ec.EllipticCurvePrivateNumbers(1, pub) + assert priv != ec.EllipticCurvePrivateNumbers( + 2, ec.EllipticCurvePublicNumbers(1, 2, ec.SECP192R1()) + ) + assert priv != ec.EllipticCurvePrivateNumbers( + 1, ec.EllipticCurvePublicNumbers(2, 2, ec.SECP192R1()) + ) + assert priv != ec.EllipticCurvePrivateNumbers( + 1, ec.EllipticCurvePublicNumbers(1, 3, ec.SECP192R1()) + ) + assert priv != ec.EllipticCurvePrivateNumbers( + 1, ec.EllipticCurvePublicNumbers(1, 2, ec.SECP521R1()) + ) + assert priv != object() + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +class TestECSerialization(object): + @pytest.mark.parametrize( + ("fmt", "password"), + itertools.product( + [ + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.PrivateFormat.PKCS8 + ], + [ + b"s", + b"longerpassword", + b"!*$&(@#$*&($T@%_somesymbols", + b"\x01" * 1000, + ] + ) + ) + def test_private_bytes_encrypted_pem(self, backend, fmt, password): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key_bytes = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_private_key(key_bytes, None, backend) + serialized = key.private_bytes( + serialization.Encoding.PEM, + fmt, + serialization.BestAvailableEncryption(password) + ) + loaded_key = serialization.load_pem_private_key( + serialized, password, backend + ) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + ("fmt", "password"), + [ + [serialization.PrivateFormat.PKCS8, b"s"], + [serialization.PrivateFormat.PKCS8, b"longerpassword"], + [serialization.PrivateFormat.PKCS8, b"!*$&(@#$*&($T@%_somesymbol"], + [serialization.PrivateFormat.PKCS8, b"\x01" * 1000] + ] + ) + def test_private_bytes_encrypted_der(self, backend, fmt, password): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key_bytes = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_private_key(key_bytes, None, backend) + serialized = key.private_bytes( + serialization.Encoding.DER, + fmt, + serialization.BestAvailableEncryption(password) + ) + loaded_key = serialization.load_der_private_key( + serialized, password, backend + ) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + ("encoding", "fmt", "loader_func"), + [ + [ + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.load_pem_private_key + ], + [ + serialization.Encoding.DER, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.load_der_private_key + ], + [ + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.load_pem_private_key + ], + [ + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.load_der_private_key + ], + ] + ) + def test_private_bytes_unencrypted(self, backend, encoding, fmt, + loader_func): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key_bytes = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: pemfile.read().encode() + ) + key = serialization.load_pem_private_key(key_bytes, None, backend) + serialized = key.private_bytes( + encoding, fmt, serialization.NoEncryption() + ) + loaded_key = loader_func(serialized, None, backend) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + ("key_path", "encoding", "loader_func"), + [ + [ + os.path.join( + "asymmetric", "PEM_Serialization", "ec_private_key.pem" + ), + serialization.Encoding.PEM, + serialization.load_pem_private_key + ], + [ + os.path.join( + "asymmetric", "DER_Serialization", "ec_private_key.der" + ), + serialization.Encoding.DER, + serialization.load_der_private_key + ], + ] + ) + def test_private_bytes_traditional_openssl_unencrypted( + self, backend, key_path, encoding, loader_func + ): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key_bytes = load_vectors_from_file( + key_path, lambda pemfile: pemfile.read(), mode="rb" + ) + key = loader_func(key_bytes, None, backend) + serialized = key.private_bytes( + encoding, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption() + ) + assert serialized == key_bytes + + def test_private_bytes_traditional_der_encrypted_invalid(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.DER, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.BestAvailableEncryption(b"password") + ) + + def test_private_bytes_invalid_encoding(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + with pytest.raises(TypeError): + key.private_bytes( + "notencoding", + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_format(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + "invalidformat", + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_encryption_algorithm(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + "notanencalg" + ) + + def test_private_bytes_unsupported_encryption_type(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + DummyKeySerializationEncryption() + ) + + def test_public_bytes_from_derived_public_key(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + public = key.public_key() + pem = public.public_bytes( + serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo + ) + parsed_public = serialization.load_pem_public_key(pem, backend) + assert parsed_public + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +class TestEllipticCurvePEMPublicKeySerialization(object): + @pytest.mark.parametrize( + ("key_path", "loader_func", "encoding"), + [ + ( + os.path.join( + "asymmetric", "PEM_Serialization", "ec_public_key.pem" + ), + serialization.load_pem_public_key, + serialization.Encoding.PEM, + ), ( + os.path.join( + "asymmetric", "DER_Serialization", "ec_public_key.der" + ), + serialization.load_der_public_key, + serialization.Encoding.DER, + ) + ] + ) + def test_public_bytes_match(self, key_path, loader_func, encoding, + backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key_bytes = load_vectors_from_file( + key_path, lambda pemfile: pemfile.read(), mode="rb" + ) + key = loader_func(key_bytes, backend) + serialized = key.public_bytes( + encoding, serialization.PublicFormat.SubjectPublicKeyInfo, + ) + assert serialized == key_bytes + + def test_public_bytes_openssh(self, backend): + _skip_curve_unsupported(backend, ec.SECP192R1()) + _skip_curve_unsupported(backend, ec.SECP256R1()) + + key_bytes = load_vectors_from_file( + os.path.join( + "asymmetric", "PEM_Serialization", "ec_public_key.pem" + ), + lambda pemfile: pemfile.read(), mode="rb" + ) + key = serialization.load_pem_public_key(key_bytes, backend) + + ssh_bytes = key.public_bytes( + serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH + ) + assert ssh_bytes == ( + b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" + b"NTYAAABBBCS8827s9rUZyxZTi/um01+oIlWrwLHOjQxRU9CDAndom00zVAw5BRrI" + b"KtHB+SWD4P+sVJTARSq1mHt8kOIWrPc=" + ) + + key = ec.generate_private_key(ec.SECP192R1(), backend).public_key() + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.OpenSSH, + serialization.PublicFormat.OpenSSH + ) + + def test_public_bytes_invalid_encoding(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PEM_Serialization", "ec_public_key.pem" + ), + lambda pemfile: serialization.load_pem_public_key( + pemfile.read().encode(), backend + ) + ) + with pytest.raises(TypeError): + key.public_bytes( + "notencoding", + serialization.PublicFormat.SubjectPublicKeyInfo + ) + + def test_public_bytes_invalid_format(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PEM_Serialization", "ec_public_key.pem" + ), + lambda pemfile: serialization.load_pem_public_key( + pemfile.read().encode(), backend + ) + ) + with pytest.raises(TypeError): + key.public_bytes(serialization.Encoding.PEM, "invalidformat") + + def test_public_bytes_pkcs1_unsupported(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PEM_Serialization", "ec_public_key.pem" + ), + lambda pemfile: serialization.load_pem_public_key( + pemfile.read().encode(), backend + ) + ) + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, serialization.PublicFormat.PKCS1 + ) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +class TestECDSAVerification(object): + def test_signature_not_bytes(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = ec.generate_private_key(ec.SECP256R1(), backend) + public_key = key.public_key() + with pytest.raises(TypeError): + public_key.verifier(1234, ec.ECDSA(hashes.SHA256())) + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +class TestECDH(object): + @pytest.mark.parametrize( + "vector", + load_vectors_from_file( + os.path.join( + "asymmetric", "ECDH", + "KASValidityTest_ECCStaticUnified_NOKC_ZZOnly_init.fax"), + load_kasvs_ecdh_vectors + ) + ) + def test_key_exchange_with_vectors(self, backend, vector): + _skip_exchange_algorithm_unsupported( + backend, ec.ECDH(), ec._CURVE_TYPES[vector['curve']] + ) + + key_numbers = vector['IUT'] + private_numbers = ec.EllipticCurvePrivateNumbers( + key_numbers['d'], + ec.EllipticCurvePublicNumbers( + key_numbers['x'], + key_numbers['y'], + ec._CURVE_TYPES[vector['curve']]() + ) + ) + # Errno 5 and 6 indicates a bad public key, this doesn't test the ECDH + # code at all + if vector['fail'] and vector['errno'] in [5, 6]: + with pytest.raises(ValueError): + private_numbers.private_key(backend) + return + else: + private_key = private_numbers.private_key(backend) + + peer_numbers = vector['CAVS'] + public_numbers = ec.EllipticCurvePublicNumbers( + peer_numbers['x'], + peer_numbers['y'], + ec._CURVE_TYPES[vector['curve']]() + ) + # Errno 1 and 2 indicates a bad public key, this doesn't test the ECDH + # code at all + if vector['fail'] and vector['errno'] in [1, 2]: + with pytest.raises(ValueError): + public_numbers.public_key(backend) + return + else: + peer_pubkey = public_numbers.public_key(backend) + + z = private_key.exchange(ec.ECDH(), peer_pubkey) + z = int(hexlify(z).decode('ascii'), 16) + # At this point fail indicates that one of the underlying keys was + # changed. This results in a non-matching derived key. + if vector['fail']: + assert z != vector['Z'] + else: + assert z == vector['Z'] + + def test_exchange_unsupported_algorithm(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + + with raises_unsupported_algorithm( + exceptions._Reasons.UNSUPPORTED_EXCHANGE_ALGORITHM + ): + key.exchange(None, key.public_key()) + + def test_exchange_non_matching_curve(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + _skip_curve_unsupported(backend, ec.SECP384R1()) + + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "ec_private_key.pem"), + lambda pemfile: serialization.load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + public_key = EC_KEY_SECP384R1.public_numbers.public_key(backend) + + with pytest.raises(ValueError): + key.exchange(ec.ECDH(), public_key) diff --git a/tests/hazmat/primitives/test_hash_vectors.py b/tests/hazmat/primitives/test_hash_vectors.py new file mode 100644 index 0000000..8757df2 --- /dev/null +++ b/tests/hazmat/primitives/test_hash_vectors.py @@ -0,0 +1,194 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.hazmat.primitives import hashes + +from .utils import generate_hash_test, generate_long_string_hash_test +from ...utils import load_hash_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA1()), + skip_message="Does not support SHA1", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA1(object): + test_SHA1 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHA1"), + [ + "SHA1LongMsg.rsp", + "SHA1ShortMsg.rsp", + ], + hashes.SHA1(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA224()), + skip_message="Does not support SHA224", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA224(object): + test_SHA224 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHA2"), + [ + "SHA224LongMsg.rsp", + "SHA224ShortMsg.rsp", + ], + hashes.SHA224(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA256()), + skip_message="Does not support SHA256", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA256(object): + test_SHA256 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHA2"), + [ + "SHA256LongMsg.rsp", + "SHA256ShortMsg.rsp", + ], + hashes.SHA256(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA384()), + skip_message="Does not support SHA384", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA384(object): + test_SHA384 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHA2"), + [ + "SHA384LongMsg.rsp", + "SHA384ShortMsg.rsp", + ], + hashes.SHA384(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA512()), + skip_message="Does not support SHA512", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA512(object): + test_SHA512 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "SHA2"), + [ + "SHA512LongMsg.rsp", + "SHA512ShortMsg.rsp", + ], + hashes.SHA512(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160()), + skip_message="Does not support RIPEMD160", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestRIPEMD160(object): + test_RIPEMD160 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "ripemd160"), + [ + "ripevectors.txt", + ], + hashes.RIPEMD160(), + ) + + test_RIPEMD160_long_string = generate_long_string_hash_test( + hashes.RIPEMD160(), + "52783243c1697bdbe16d37f97f68f08325dc1528", + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.Whirlpool()), + skip_message="Does not support Whirlpool", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestWhirlpool(object): + test_whirlpool = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "whirlpool"), + [ + "iso-test-vectors.txt", + ], + hashes.Whirlpool(), + ) + + test_whirlpool_long_string = generate_long_string_hash_test( + hashes.Whirlpool(), + ("0c99005beb57eff50a7cf005560ddf5d29057fd86b2" + "0bfd62deca0f1ccea4af51fc15490eddc47af32bb2b" + "66c34ff9ad8c6008ad677f77126953b226e4ed8b01"), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.MD5()), + skip_message="Does not support MD5", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestMD5(object): + test_md5 = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "MD5"), + [ + "rfc-1321.txt", + ], + hashes.MD5(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2b(digest_size=64)), + skip_message="Does not support BLAKE2b", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestBLAKE2b(object): + test_b2b = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "blake2"), + [ + "blake2b.txt", + ], + hashes.BLAKE2b(digest_size=64), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2s(digest_size=32)), + skip_message="Does not support BLAKE2s", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestBLAKE2s256(object): + test_b2s = generate_hash_test( + load_hash_vectors, + os.path.join("hashes", "blake2"), + [ + "blake2s.txt", + ], + hashes.BLAKE2s(digest_size=32), + ) diff --git a/tests/hazmat/primitives/test_hashes.py b/tests/hazmat/primitives/test_hashes.py new file mode 100644 index 0000000..fe96b32 --- /dev/null +++ b/tests/hazmat/primitives/test_hashes.py @@ -0,0 +1,214 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pretend + +import pytest + +from cryptography.exceptions import AlreadyFinalized, _Reasons +from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.hazmat.primitives import hashes + +from .utils import generate_base_hash_test +from ..backends.test_multibackend import DummyHashBackend +from ...doubles import DummyHashAlgorithm +from ...utils import raises_unsupported_algorithm + + +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestHashContext(object): + def test_hash_reject_unicode(self, backend): + m = hashes.Hash(hashes.SHA1(), backend=backend) + with pytest.raises(TypeError): + m.update(u"\u00FC") + + def test_copy_backend_object(self): + backend = DummyHashBackend([hashes.SHA1]) + copied_ctx = pretend.stub() + pretend_ctx = pretend.stub(copy=lambda: copied_ctx) + h = hashes.Hash(hashes.SHA1(), backend=backend, ctx=pretend_ctx) + assert h._backend is backend + assert h.copy()._backend is h._backend + + def test_hash_algorithm_instance(self, backend): + with pytest.raises(TypeError): + hashes.Hash(hashes.SHA1, backend=backend) + + def test_raises_after_finalize(self, backend): + h = hashes.Hash(hashes.SHA1(), backend=backend) + h.finalize() + + with pytest.raises(AlreadyFinalized): + h.update(b"foo") + + with pytest.raises(AlreadyFinalized): + h.copy() + + with pytest.raises(AlreadyFinalized): + h.finalize() + + def test_unsupported_hash(self, backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + hashes.Hash(DummyHashAlgorithm(), backend) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA1()), + skip_message="Does not support SHA1", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA1(object): + test_SHA1 = generate_base_hash_test( + hashes.SHA1(), + digest_size=20, + block_size=64, + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA224()), + skip_message="Does not support SHA224", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA224(object): + test_SHA224 = generate_base_hash_test( + hashes.SHA224(), + digest_size=28, + block_size=64, + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA256()), + skip_message="Does not support SHA256", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA256(object): + test_SHA256 = generate_base_hash_test( + hashes.SHA256(), + digest_size=32, + block_size=64, + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA384()), + skip_message="Does not support SHA384", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA384(object): + test_SHA384 = generate_base_hash_test( + hashes.SHA384(), + digest_size=48, + block_size=128, + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA512()), + skip_message="Does not support SHA512", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestSHA512(object): + test_SHA512 = generate_base_hash_test( + hashes.SHA512(), + digest_size=64, + block_size=128, + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.RIPEMD160()), + skip_message="Does not support RIPEMD160", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestRIPEMD160(object): + test_RIPEMD160 = generate_base_hash_test( + hashes.RIPEMD160(), + digest_size=20, + block_size=64, + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.Whirlpool()), + skip_message="Does not support Whirlpool", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestWhirlpool(object): + test_Whirlpool = generate_base_hash_test( + hashes.Whirlpool(), + digest_size=64, + block_size=64, + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.MD5()), + skip_message="Does not support MD5", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestMD5(object): + test_MD5 = generate_base_hash_test( + hashes.MD5(), + digest_size=16, + block_size=64, + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2b(digest_size=64)), + skip_message="Does not support BLAKE2b", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestBLAKE2b(object): + test_BLAKE2b = generate_base_hash_test( + hashes.BLAKE2b(digest_size=64), + digest_size=64, + block_size=128, + ) + + def test_invalid_digest_size(self, backend): + with pytest.raises(ValueError): + hashes.BLAKE2b(digest_size=65) + + with pytest.raises(ValueError): + hashes.BLAKE2b(digest_size=0) + + with pytest.raises(ValueError): + hashes.BLAKE2b(digest_size=-1) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hash_supported( + hashes.BLAKE2s(digest_size=32)), + skip_message="Does not support BLAKE2s", +) +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestBLAKE2s(object): + test_BLAKE2s = generate_base_hash_test( + hashes.BLAKE2s(digest_size=32), + digest_size=32, + block_size=64, + ) + + def test_invalid_digest_size(self, backend): + with pytest.raises(ValueError): + hashes.BLAKE2s(digest_size=33) + + with pytest.raises(ValueError): + hashes.BLAKE2s(digest_size=0) + + with pytest.raises(ValueError): + hashes.BLAKE2s(digest_size=-1) + + +def test_invalid_backend(): + pretend_backend = object() + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + hashes.Hash(hashes.SHA1(), pretend_backend) diff --git a/tests/hazmat/primitives/test_hkdf.py b/tests/hazmat/primitives/test_hkdf.py new file mode 100644 index 0000000..a05fd75 --- /dev/null +++ b/tests/hazmat/primitives/test_hkdf.py @@ -0,0 +1,220 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, _Reasons +) +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand + +from ...utils import raises_unsupported_algorithm + + +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHKDF(object): + def test_length_limit(self, backend): + big_length = 255 * (hashes.SHA256().digest_size // 8) + 1 + + with pytest.raises(ValueError): + HKDF( + hashes.SHA256(), + big_length, + salt=None, + info=None, + backend=backend + ) + + def test_already_finalized(self, backend): + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.derive(b"\x01" * 16) + + with pytest.raises(AlreadyFinalized): + hkdf.derive(b"\x02" * 16) + + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.verify(b"\x01" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") + + with pytest.raises(AlreadyFinalized): + hkdf.verify(b"\x02" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") + + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + def test_verify(self, backend): + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.verify(b"\x01" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") + + def test_verify_invalid(self, backend): + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + with pytest.raises(InvalidKey): + hkdf.verify(b"\x02" * 16, b"gJ\xfb{\xb1Oi\xc5sMC\xb7\xe4@\xf7u") + + def test_unicode_typeerror(self, backend): + with pytest.raises(TypeError): + HKDF( + hashes.SHA256(), + 16, + salt=u"foo", + info=None, + backend=backend + ) + + with pytest.raises(TypeError): + HKDF( + hashes.SHA256(), + 16, + salt=None, + info=u"foo", + backend=backend + ) + + with pytest.raises(TypeError): + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.derive(u"foo") + + with pytest.raises(TypeError): + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.verify(u"foo", b"bar") + + with pytest.raises(TypeError): + hkdf = HKDF( + hashes.SHA256(), + 16, + salt=None, + info=None, + backend=backend + ) + + hkdf.verify(b"foo", u"bar") + + def test_derive_short_output(self, backend): + hkdf = HKDF( + hashes.SHA256(), + 4, + salt=None, + info=None, + backend=backend + ) + + assert hkdf.derive(b"\x01" * 16) == b"gJ\xfb{" + + +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHKDFExpand(object): + def test_derive(self, backend): + prk = binascii.unhexlify( + b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" + ) + + okm = (b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c" + b"5bf34007208d5b887185865") + + info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") + hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) + + assert binascii.hexlify(hkdf.derive(prk)) == okm + + def test_verify(self, backend): + prk = binascii.unhexlify( + b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" + ) + + okm = (b"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c" + b"5bf34007208d5b887185865") + + info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") + hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) + + assert hkdf.verify(prk, binascii.unhexlify(okm)) is None + + def test_invalid_verify(self, backend): + prk = binascii.unhexlify( + b"077709362c2e32df0ddc3f0dc47bba6390b6c73bb50f9c3122ec844ad7c2b3e5" + ) + + info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") + hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) + + with pytest.raises(InvalidKey): + hkdf.verify(prk, b"wrong key") + + def test_already_finalized(self, backend): + info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") + hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) + + hkdf.derive(b"first") + + with pytest.raises(AlreadyFinalized): + hkdf.derive(b"second") + + def test_unicode_error(self, backend): + info = binascii.unhexlify(b"f0f1f2f3f4f5f6f7f8f9") + hkdf = HKDFExpand(hashes.SHA256(), 42, info, backend) + + with pytest.raises(TypeError): + hkdf.derive(u"first") + + +def test_invalid_backend(): + pretend_backend = object() + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + HKDF(hashes.SHA256(), 16, None, None, pretend_backend) + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + HKDFExpand(hashes.SHA256(), 16, None, pretend_backend) diff --git a/tests/hazmat/primitives/test_hkdf_vectors.py b/tests/hazmat/primitives/test_hkdf_vectors.py new file mode 100644 index 0000000..74bf929 --- /dev/null +++ b/tests/hazmat/primitives/test_hkdf_vectors.py @@ -0,0 +1,43 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.primitives import hashes + +from .utils import generate_hkdf_test +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), + skip_message="Does not support SHA1." +) +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHKDFSHA1(object): + test_HKDFSHA1 = generate_hkdf_test( + load_nist_vectors, + os.path.join("KDF"), + ["rfc-5869-HKDF-SHA1.txt"], + hashes.SHA1() + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA256()), + skip_message="Does not support SHA256." +) +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHKDFSHA256(object): + test_HKDFSHA1 = generate_hkdf_test( + load_nist_vectors, + os.path.join("KDF"), + ["rfc-5869-HKDF-SHA256.txt"], + hashes.SHA256() + ) diff --git a/tests/hazmat/primitives/test_hmac.py b/tests/hazmat/primitives/test_hmac.py new file mode 100644 index 0000000..82082a2 --- /dev/null +++ b/tests/hazmat/primitives/test_hmac.py @@ -0,0 +1,98 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pretend + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, InvalidSignature, _Reasons +) +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.primitives import hashes, hmac + +from .utils import generate_base_hmac_test +from ..backends.test_multibackend import DummyHMACBackend +from ...doubles import DummyHashAlgorithm +from ...utils import raises_unsupported_algorithm + + +@pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.MD5()), + skip_message="Does not support MD5", +) +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHMACCopy(object): + test_copy = generate_base_hmac_test( + hashes.MD5(), + ) + + +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHMAC(object): + def test_hmac_reject_unicode(self, backend): + h = hmac.HMAC(b"mykey", hashes.SHA1(), backend=backend) + with pytest.raises(TypeError): + h.update(u"\u00FC") + + def test_copy_backend_object(self): + backend = DummyHMACBackend([hashes.SHA1]) + copied_ctx = pretend.stub() + pretend_ctx = pretend.stub(copy=lambda: copied_ctx) + h = hmac.HMAC(b"key", hashes.SHA1(), backend=backend, ctx=pretend_ctx) + assert h._backend is backend + assert h.copy()._backend is backend + + def test_hmac_algorithm_instance(self, backend): + with pytest.raises(TypeError): + hmac.HMAC(b"key", hashes.SHA1, backend=backend) + + def test_raises_after_finalize(self, backend): + h = hmac.HMAC(b"key", hashes.SHA1(), backend=backend) + h.finalize() + + with pytest.raises(AlreadyFinalized): + h.update(b"foo") + + with pytest.raises(AlreadyFinalized): + h.copy() + + with pytest.raises(AlreadyFinalized): + h.finalize() + + def test_verify(self, backend): + h = hmac.HMAC(b'', hashes.SHA1(), backend=backend) + digest = h.finalize() + + h = hmac.HMAC(b'', hashes.SHA1(), backend=backend) + h.verify(digest) + + with pytest.raises(AlreadyFinalized): + h.verify(b'') + + def test_invalid_verify(self, backend): + h = hmac.HMAC(b'', hashes.SHA1(), backend=backend) + with pytest.raises(InvalidSignature): + h.verify(b'') + + with pytest.raises(AlreadyFinalized): + h.verify(b'') + + def test_verify_reject_unicode(self, backend): + h = hmac.HMAC(b'', hashes.SHA1(), backend=backend) + with pytest.raises(TypeError): + h.verify(u'') + + def test_unsupported_hash(self, backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + hmac.HMAC(b"key", DummyHashAlgorithm(), backend) + + +def test_invalid_backend(): + pretend_backend = object() + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + hmac.HMAC(b"key", hashes.SHA1(), pretend_backend) diff --git a/tests/hazmat/primitives/test_hmac_vectors.py b/tests/hazmat/primitives/test_hmac_vectors.py new file mode 100644 index 0000000..8704e72 --- /dev/null +++ b/tests/hazmat/primitives/test_hmac_vectors.py @@ -0,0 +1,125 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.primitives import hashes + +from .utils import generate_hmac_test +from ...utils import load_hash_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.MD5()), + skip_message="Does not support MD5", +) +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHMACMD5(object): + test_hmac_md5 = generate_hmac_test( + load_hash_vectors, + "HMAC", + [ + "rfc-2202-md5.txt", + ], + hashes.MD5(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), + skip_message="Does not support SHA1", +) +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHMACSHA1(object): + test_hmac_sha1 = generate_hmac_test( + load_hash_vectors, + "HMAC", + [ + "rfc-2202-sha1.txt", + ], + hashes.SHA1(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA224()), + skip_message="Does not support SHA224", +) +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHMACSHA224(object): + test_hmac_sha224 = generate_hmac_test( + load_hash_vectors, + "HMAC", + [ + "rfc-4231-sha224.txt", + ], + hashes.SHA224(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA256()), + skip_message="Does not support SHA256", +) +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHMACSHA256(object): + test_hmac_sha256 = generate_hmac_test( + load_hash_vectors, + "HMAC", + [ + "rfc-4231-sha256.txt", + ], + hashes.SHA256(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA384()), + skip_message="Does not support SHA384", +) +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHMACSHA384(object): + test_hmac_sha384 = generate_hmac_test( + load_hash_vectors, + "HMAC", + [ + "rfc-4231-sha384.txt", + ], + hashes.SHA384(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA512()), + skip_message="Does not support SHA512", +) +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHMACSHA512(object): + test_hmac_sha512 = generate_hmac_test( + load_hash_vectors, + "HMAC", + [ + "rfc-4231-sha512.txt", + ], + hashes.SHA512(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.RIPEMD160()), + skip_message="Does not support RIPEMD160", +) +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHMACRIPEMD160(object): + test_hmac_ripemd160 = generate_hmac_test( + load_hash_vectors, + "HMAC", + [ + "rfc-2286-ripemd160.txt", + ], + hashes.RIPEMD160(), + ) diff --git a/tests/hazmat/primitives/test_idea.py b/tests/hazmat/primitives/test_idea.py new file mode 100644 index 0000000..75116dc --- /dev/null +++ b/tests/hazmat/primitives/test_idea.py @@ -0,0 +1,84 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers import algorithms, modes + +from .utils import generate_encrypt_test +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.IDEA(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support IDEA ECB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestIDEAModeECB(object): + test_ECB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "IDEA"), + ["idea-ecb.txt"], + lambda key, **kwargs: algorithms.IDEA(binascii.unhexlify((key))), + lambda **kwargs: modes.ECB(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.IDEA(b"\x00" * 16), modes.CBC(b"\x00" * 8) + ), + skip_message="Does not support IDEA CBC", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestIDEAModeCBC(object): + test_CBC = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "IDEA"), + ["idea-cbc.txt"], + lambda key, **kwargs: algorithms.IDEA(binascii.unhexlify((key))), + lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)) + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.IDEA(b"\x00" * 16), modes.OFB(b"\x00" * 8) + ), + skip_message="Does not support IDEA OFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestIDEAModeOFB(object): + test_OFB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "IDEA"), + ["idea-ofb.txt"], + lambda key, **kwargs: algorithms.IDEA(binascii.unhexlify((key))), + lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)) + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.IDEA(b"\x00" * 16), modes.CFB(b"\x00" * 8) + ), + skip_message="Does not support IDEA CFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestIDEAModeCFB(object): + test_CFB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "IDEA"), + ["idea-cfb.txt"], + lambda key, **kwargs: algorithms.IDEA(binascii.unhexlify((key))), + lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)) + ) diff --git a/tests/hazmat/primitives/test_kbkdf.py b/tests/hazmat/primitives/test_kbkdf.py new file mode 100644 index 0000000..45a53ac --- /dev/null +++ b/tests/hazmat/primitives/test_kbkdf.py @@ -0,0 +1,151 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, _Reasons +) +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.kbkdf import ( + CounterLocation, KBKDFHMAC, Mode +) + +from ...doubles import DummyHashAlgorithm +from ...utils import raises_unsupported_algorithm + + +class TestKBKDFHMAC(object): + def test_invalid_key(self): + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=default_backend()) + + key = kdf.derive(b"material") + + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=default_backend()) + + with pytest.raises(InvalidKey): + kdf.verify(b"material2", key) + + def test_already_finalized(self): + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=default_backend()) + + kdf.derive(b'material') + + with pytest.raises(AlreadyFinalized): + kdf.derive(b'material2') + + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=default_backend()) + + key = kdf.derive(b'material') + + with pytest.raises(AlreadyFinalized): + kdf.verify(b'material', key) + + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=default_backend()) + kdf.verify(b'material', key) + + with pytest.raises(AlreadyFinalized): + kdf.verify(b"material", key) + + def test_key_length(self): + kdf = KBKDFHMAC(hashes.SHA1(), Mode.CounterMode, 85899345920, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=default_backend()) + + with pytest.raises(ValueError): + kdf.derive(b'material') + + def test_rlen(self): + with pytest.raises(ValueError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 5, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=default_backend()) + + def test_r_type(self): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA1(), Mode.CounterMode, 32, b'r', 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=default_backend()) + + def test_l_type(self): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA1(), Mode.CounterMode, 32, 4, b'l', + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=default_backend()) + + def test_l(self): + with pytest.raises(ValueError): + KBKDFHMAC(hashes.SHA1(), Mode.CounterMode, 32, 4, None, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=default_backend()) + + def test_unsupported_mode(self): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA256(), None, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=default_backend()) + + def test_unsupported_location(self): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + None, b'label', b'context', None, + backend=default_backend()) + + def test_unsupported_parameters(self): + with pytest.raises(ValueError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + b'fixed', backend=default_backend()) + + def test_unsupported_hash(self): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + KBKDFHMAC(object(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=default_backend()) + + def test_unsupported_algorithm(self): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + KBKDFHMAC(DummyHashAlgorithm(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=default_backend()) + + def test_invalid_backend(self): + mock_backend = object + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', b'context', + None, backend=mock_backend()) + + def test_unicode_error_label(self): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, u'label', b'context', + backend=default_backend()) + + def test_unicode_error_context(self): + with pytest.raises(TypeError): + KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', u'context', + None, backend=default_backend()) + + def test_unicode_error_key_material(self): + with pytest.raises(TypeError): + kdf = KBKDFHMAC(hashes.SHA256(), Mode.CounterMode, 32, 4, 4, + CounterLocation.BeforeFixed, b'label', + b'context', None, backend=default_backend()) + kdf.derive(u'material') diff --git a/tests/hazmat/primitives/test_kbkdf_vectors.py b/tests/hazmat/primitives/test_kbkdf_vectors.py new file mode 100644 index 0000000..c8263e2 --- /dev/null +++ b/tests/hazmat/primitives/test_kbkdf_vectors.py @@ -0,0 +1,23 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import HMACBackend + +from .utils import generate_kbkdf_counter_mode_test +from ...utils import load_nist_kbkdf_vectors + + +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestCounterKDFCounterMode(object): + test_HKDFSHA1 = generate_kbkdf_counter_mode_test( + load_nist_kbkdf_vectors, + os.path.join("KDF"), + ["nist-800-108-KBKDF-CTR.txt"] + ) diff --git a/tests/hazmat/primitives/test_keywrap.py b/tests/hazmat/primitives/test_keywrap.py new file mode 100644 index 0000000..f41baed --- /dev/null +++ b/tests/hazmat/primitives/test_keywrap.py @@ -0,0 +1,116 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives import keywrap +from cryptography.hazmat.primitives.ciphers import algorithms, modes + +from .utils import _load_all_params +from ...utils import load_nist_vectors + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestAESKeyWrap(object): + @pytest.mark.parametrize( + "params", + _load_all_params( + os.path.join("keywrap", "kwtestvectors"), + ["KW_AE_128.txt", "KW_AE_192.txt", "KW_AE_256.txt"], + load_nist_vectors + ) + ) + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_wrap(self, backend, params): + wrapping_key = binascii.unhexlify(params["k"]) + key_to_wrap = binascii.unhexlify(params["p"]) + wrapped_key = keywrap.aes_key_wrap(wrapping_key, key_to_wrap, backend) + assert params["c"] == binascii.hexlify(wrapped_key) + + @pytest.mark.parametrize( + "params", + _load_all_params( + os.path.join("keywrap", "kwtestvectors"), + ["KW_AD_128.txt", "KW_AD_192.txt", "KW_AD_256.txt"], + load_nist_vectors + ) + ) + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_unwrap(self, backend, params): + wrapping_key = binascii.unhexlify(params["k"]) + wrapped_key = binascii.unhexlify(params["c"]) + if params.get("fail") is True: + with pytest.raises(keywrap.InvalidUnwrap): + keywrap.aes_key_unwrap(wrapping_key, wrapped_key, backend) + else: + unwrapped_key = keywrap.aes_key_unwrap( + wrapping_key, wrapped_key, backend + ) + assert params["p"] == binascii.hexlify(unwrapped_key) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_wrap_invalid_key_length(self, backend): + # The wrapping key must be of length [16, 24, 32] + with pytest.raises(ValueError): + keywrap.aes_key_wrap(b"badkey", b"sixteen_byte_key", backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_unwrap_invalid_key_length(self, backend): + with pytest.raises(ValueError): + keywrap.aes_key_unwrap(b"badkey", b"\x00" * 24, backend) + + @pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support AES key wrap (RFC 3394) because AES-ECB" + " is unsupported", + ) + def test_wrap_invalid_key_to_wrap_length(self, backend): + # Keys to wrap must be at least 16 bytes long + with pytest.raises(ValueError): + keywrap.aes_key_wrap(b"sixteen_byte_key", b"\x00" * 15, backend) + + # Keys to wrap must be a multiple of 8 bytes + with pytest.raises(ValueError): + keywrap.aes_key_wrap(b"sixteen_byte_key", b"\x00" * 23, backend) + + def test_unwrap_invalid_wrapped_key_length(self, backend): + # Keys to unwrap must be at least 24 bytes + with pytest.raises(ValueError): + keywrap.aes_key_unwrap(b"sixteen_byte_key", b"\x00" * 16, backend) + + # Keys to unwrap must be a multiple of 8 bytes + with pytest.raises(ValueError): + keywrap.aes_key_unwrap(b"sixteen_byte_key", b"\x00" * 27, backend) diff --git a/tests/hazmat/primitives/test_padding.py b/tests/hazmat/primitives/test_padding.py new file mode 100644 index 0000000..fb72a79 --- /dev/null +++ b/tests/hazmat/primitives/test_padding.py @@ -0,0 +1,209 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest + +import six + +from cryptography.exceptions import AlreadyFinalized +from cryptography.hazmat.primitives import padding + + +class TestPKCS7(object): + @pytest.mark.parametrize("size", [127, 4096, -2]) + def test_invalid_block_size(self, size): + with pytest.raises(ValueError): + padding.PKCS7(size) + + @pytest.mark.parametrize(("size", "padded"), [ + (128, b"1111"), + (128, b"1111111111111111"), + (128, b"111111111111111\x06"), + (128, b""), + (128, b"\x06" * 6), + (128, b"\x00" * 16), + ]) + def test_invalid_padding(self, size, padded): + unpadder = padding.PKCS7(size).unpadder() + with pytest.raises(ValueError): + unpadder.update(padded) + unpadder.finalize() + + def test_non_bytes(self): + padder = padding.PKCS7(128).padder() + with pytest.raises(TypeError): + padder.update(u"abc") + unpadder = padding.PKCS7(128).unpadder() + with pytest.raises(TypeError): + unpadder.update(u"abc") + + @pytest.mark.parametrize(("size", "unpadded", "padded"), [ + ( + 128, + b"1111111111", + b"1111111111\x06\x06\x06\x06\x06\x06", + ), + ( + 128, + b"111111111111111122222222222222", + b"111111111111111122222222222222\x02\x02", + ), + ( + 128, + b"1" * 16, + b"1" * 16 + b"\x10" * 16, + ), + ( + 128, + b"1" * 17, + b"1" * 17 + b"\x0F" * 15, + ) + ]) + def test_pad(self, size, unpadded, padded): + padder = padding.PKCS7(size).padder() + result = padder.update(unpadded) + result += padder.finalize() + assert result == padded + + @pytest.mark.parametrize(("size", "unpadded", "padded"), [ + ( + 128, + b"1111111111", + b"1111111111\x06\x06\x06\x06\x06\x06", + ), + ( + 128, + b"111111111111111122222222222222", + b"111111111111111122222222222222\x02\x02", + ), + ]) + def test_unpad(self, size, unpadded, padded): + unpadder = padding.PKCS7(size).unpadder() + result = unpadder.update(padded) + result += unpadder.finalize() + assert result == unpadded + + def test_use_after_finalize(self): + padder = padding.PKCS7(128).padder() + b = padder.finalize() + with pytest.raises(AlreadyFinalized): + padder.update(b"") + with pytest.raises(AlreadyFinalized): + padder.finalize() + + unpadder = padding.PKCS7(128).unpadder() + unpadder.update(b) + unpadder.finalize() + with pytest.raises(AlreadyFinalized): + unpadder.update(b"") + with pytest.raises(AlreadyFinalized): + unpadder.finalize() + + def test_large_padding(self): + padder = padding.PKCS7(2040).padder() + padded_data = padder.update(b"") + padded_data += padder.finalize() + + for i in six.iterbytes(padded_data): + assert i == 255 + + unpadder = padding.PKCS7(2040).unpadder() + data = unpadder.update(padded_data) + data += unpadder.finalize() + + assert data == b"" + + +class TestANSIX923(object): + @pytest.mark.parametrize("size", [127, 4096, -2]) + def test_invalid_block_size(self, size): + with pytest.raises(ValueError): + padding.ANSIX923(size) + + @pytest.mark.parametrize(("size", "padded"), [ + (128, b"1111"), + (128, b"1111111111111111"), + (128, b"111111111111111\x06"), + (128, b"1111111111\x06\x06\x06\x06\x06\x06"), + (128, b""), + (128, b"\x06" * 6), + (128, b"\x00" * 16), + ]) + def test_invalid_padding(self, size, padded): + unpadder = padding.ANSIX923(size).unpadder() + with pytest.raises(ValueError): + unpadder.update(padded) + unpadder.finalize() + + def test_non_bytes(self): + padder = padding.ANSIX923(128).padder() + with pytest.raises(TypeError): + padder.update(u"abc") + unpadder = padding.ANSIX923(128).unpadder() + with pytest.raises(TypeError): + unpadder.update(u"abc") + + @pytest.mark.parametrize(("size", "unpadded", "padded"), [ + ( + 128, + b"1111111111", + b"1111111111\x00\x00\x00\x00\x00\x06", + ), + ( + 128, + b"111111111111111122222222222222", + b"111111111111111122222222222222\x00\x02", + ), + ( + 128, + b"1" * 16, + b"1" * 16 + b"\x00" * 15 + b"\x10", + ), + ( + 128, + b"1" * 17, + b"1" * 17 + b"\x00" * 14 + b"\x0F", + ) + ]) + def test_pad(self, size, unpadded, padded): + padder = padding.ANSIX923(size).padder() + result = padder.update(unpadded) + result += padder.finalize() + assert result == padded + + @pytest.mark.parametrize(("size", "unpadded", "padded"), [ + ( + 128, + b"1111111111", + b"1111111111\x00\x00\x00\x00\x00\x06", + ), + ( + 128, + b"111111111111111122222222222222", + b"111111111111111122222222222222\x00\x02", + ), + ]) + def test_unpad(self, size, unpadded, padded): + unpadder = padding.ANSIX923(size).unpadder() + result = unpadder.update(padded) + result += unpadder.finalize() + assert result == unpadded + + def test_use_after_finalize(self): + padder = padding.ANSIX923(128).padder() + b = padder.finalize() + with pytest.raises(AlreadyFinalized): + padder.update(b"") + with pytest.raises(AlreadyFinalized): + padder.finalize() + + unpadder = padding.ANSIX923(128).unpadder() + unpadder.update(b) + unpadder.finalize() + with pytest.raises(AlreadyFinalized): + unpadder.update(b"") + with pytest.raises(AlreadyFinalized): + unpadder.finalize() diff --git a/tests/hazmat/primitives/test_pbkdf2hmac.py b/tests/hazmat/primitives/test_pbkdf2hmac.py new file mode 100644 index 0000000..d971ebd --- /dev/null +++ b/tests/hazmat/primitives/test_pbkdf2hmac.py @@ -0,0 +1,65 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, _Reasons +) +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + +from ...doubles import DummyHashAlgorithm +from ...utils import raises_unsupported_algorithm + + +class TestPBKDF2HMAC(object): + def test_already_finalized(self): + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf.derive(b"password") + with pytest.raises(AlreadyFinalized): + kdf.derive(b"password2") + + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + key = kdf.derive(b"password") + with pytest.raises(AlreadyFinalized): + kdf.verify(b"password", key) + + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + kdf.verify(b"password", key) + with pytest.raises(AlreadyFinalized): + kdf.verify(b"password", key) + + def test_unsupported_algorithm(self): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_HASH): + PBKDF2HMAC( + DummyHashAlgorithm(), 20, b"salt", 10, default_backend() + ) + + def test_invalid_key(self): + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + key = kdf.derive(b"password") + + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + with pytest.raises(InvalidKey): + kdf.verify(b"password2", key) + + def test_unicode_error_with_salt(self): + with pytest.raises(TypeError): + PBKDF2HMAC(hashes.SHA1(), 20, u"salt", 10, default_backend()) + + def test_unicode_error_with_key_material(self): + kdf = PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, default_backend()) + with pytest.raises(TypeError): + kdf.derive(u"unicode here") + + +def test_invalid_backend(): + pretend_backend = object() + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + PBKDF2HMAC(hashes.SHA1(), 20, b"salt", 10, pretend_backend) diff --git a/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py b/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py new file mode 100644 index 0000000..fe51f54 --- /dev/null +++ b/tests/hazmat/primitives/test_pbkdf2hmac_vectors.py @@ -0,0 +1,29 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography.hazmat.backends.interfaces import PBKDF2HMACBackend +from cryptography.hazmat.primitives import hashes + +from .utils import generate_pbkdf2_test +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.pbkdf2_hmac_supported(hashes.SHA1()), + skip_message="Does not support SHA1 for PBKDF2HMAC", +) +@pytest.mark.requires_backend_interface(interface=PBKDF2HMACBackend) +class TestPBKDF2HMACSHA1(object): + test_pbkdf2_sha1 = generate_pbkdf2_test( + load_nist_vectors, + "KDF", + [ + "rfc-6070-PBKDF2-SHA1.txt", + ], + hashes.SHA1(), + ) diff --git a/tests/hazmat/primitives/test_rsa.py b/tests/hazmat/primitives/test_rsa.py new file mode 100644 index 0000000..cbb9be6 --- /dev/null +++ b/tests/hazmat/primitives/test_rsa.py @@ -0,0 +1,2317 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import itertools +import math +import os + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, InvalidSignature, _Reasons +) +from cryptography.hazmat.backends.interfaces import ( + PEMSerializationBackend, RSABackend +) +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import ( + padding, rsa, utils as asym_utils +) +from cryptography.hazmat.primitives.asymmetric.rsa import ( + RSAPrivateNumbers, RSAPublicNumbers +) + +from .fixtures_rsa import ( + RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, RSA_KEY_1028, + RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, RSA_KEY_1536, RSA_KEY_2048, + RSA_KEY_2048_ALT, RSA_KEY_512, RSA_KEY_512_ALT, RSA_KEY_522, RSA_KEY_599, + RSA_KEY_745, RSA_KEY_768, +) +from .utils import ( + _check_rsa_private_numbers, generate_rsa_verification_test +) +from ...doubles import ( + DummyAsymmetricPadding, DummyHashAlgorithm, DummyKeySerializationEncryption +) +from ...utils import ( + load_pkcs1_vectors, load_rsa_nist_vectors, load_vectors_from_file, + raises_unsupported_algorithm +) + + +class DummyMGF(object): + _salt_length = 0 + + +def _check_rsa_private_numbers_if_serializable(key): + if isinstance(key, rsa.RSAPrivateKeyWithSerialization): + _check_rsa_private_numbers(key.private_numbers()) + + +def test_check_rsa_private_numbers_if_serializable(): + _check_rsa_private_numbers_if_serializable("notserializable") + + +def _flatten_pkcs1_examples(vectors): + flattened_vectors = [] + for vector in vectors: + examples = vector[0].pop("examples") + for example in examples: + merged_vector = (vector[0], vector[1], example) + flattened_vectors.append(merged_vector) + + return flattened_vectors + + +def _build_oaep_sha2_vectors(): + base_path = os.path.join("asymmetric", "RSA", "oaep-custom") + vectors = [] + hashalgs = [ + hashes.SHA1(), + hashes.SHA224(), + hashes.SHA256(), + hashes.SHA384(), + hashes.SHA512(), + ] + for mgf1alg, oaepalg in itertools.product(hashalgs, hashalgs): + if mgf1alg.name == "sha1" and oaepalg.name == "sha1": + # We need to generate the cartesian product of the permutations + # of all the SHAs above, but SHA1/SHA1 is something we already + # tested previously and thus did not generate custom vectors for. + continue + + examples = _flatten_pkcs1_examples( + load_vectors_from_file( + os.path.join( + base_path, + "oaep-{0}-{1}.txt".format( + mgf1alg.name, oaepalg.name + ) + ), + load_pkcs1_vectors + ) + ) + # We've loaded the files, but the loaders don't give us any information + # about the mgf1 or oaep hash algorithms. We know this info so we'll + # just add that to the end of the tuple + for private, public, vector in examples: + vectors.append((private, public, vector, mgf1alg, oaepalg)) + return vectors + + +def _skip_pss_hash_algorithm_unsupported(backend, hash_alg): + if not backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hash_alg), + salt_length=padding.PSS.MAX_LENGTH + ) + ): + pytest.skip( + "Does not support {0} in MGF1 using PSS.".format(hash_alg.name) + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +def test_skip_pss_hash_algorithm_unsupported(backend): + with pytest.raises(pytest.skip.Exception): + _skip_pss_hash_algorithm_unsupported(backend, DummyHashAlgorithm()) + + +def test_modular_inverse(): + p = int( + "d1f9f6c09fd3d38987f7970247b85a6da84907753d42ec52bc23b745093f4fff5cff3" + "617ce43d00121a9accc0051f519c76e08cf02fc18acfe4c9e6aea18da470a2b611d2e" + "56a7b35caa2c0239bc041a53cc5875ca0b668ae6377d4b23e932d8c995fd1e58ecfd8" + "c4b73259c0d8a54d691cca3f6fb85c8a5c1baf588e898d481", 16 + ) + q = int( + "d1519255eb8f678c86cfd06802d1fbef8b664441ac46b73d33d13a8404580a33a8e74" + "cb2ea2e2963125b3d454d7a922cef24dd13e55f989cbabf64255a736671f4629a47b5" + "b2347cfcd669133088d1c159518531025297c2d67c9da856a12e80222cd03b4c6ec0f" + "86c957cb7bb8de7a127b645ec9e820aa94581e4762e209f01", 16 + ) + assert rsa._modinv(q, p) == int( + "0275e06afa722999315f8f322275483e15e2fb46d827b17800f99110b269a6732748f" + "624a382fa2ed1ec68c99f7fc56fb60e76eea51614881f497ba7034c17dde955f92f15" + "772f8b2b41f3e56d88b1e096cdd293eba4eae1e82db815e0fadea0c4ec971bc6fd875" + "c20e67e48c31a611e98d32c6213ae4c4d7b53023b2f80c538", 16 + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +class TestRSA(object): + @pytest.mark.parametrize( + ("public_exponent", "key_size"), + itertools.product( + (3, 5, 65537), + (1024, 1025, 1026, 1027, 1028, 1029, 1030, 1031, 1536, 2048) + ) + ) + def test_generate_rsa_keys(self, backend, public_exponent, key_size): + skey = rsa.generate_private_key(public_exponent, key_size, backend) + assert skey.key_size == key_size + + _check_rsa_private_numbers_if_serializable(skey) + pkey = skey.public_key() + assert isinstance(pkey.public_numbers(), rsa.RSAPublicNumbers) + + def test_generate_bad_public_exponent(self, backend): + with pytest.raises(ValueError): + rsa.generate_private_key(public_exponent=1, + key_size=2048, + backend=backend) + + with pytest.raises(ValueError): + rsa.generate_private_key(public_exponent=4, + key_size=2048, + backend=backend) + + def test_cant_generate_insecure_tiny_key(self, backend): + with pytest.raises(ValueError): + rsa.generate_private_key(public_exponent=65537, + key_size=511, + backend=backend) + + with pytest.raises(ValueError): + rsa.generate_private_key(public_exponent=65537, + key_size=256, + backend=backend) + + @pytest.mark.parametrize( + "pkcs1_example", + load_vectors_from_file( + os.path.join( + "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "pss-vect.txt"), + load_pkcs1_vectors + ) + ) + def test_load_pss_vect_example_keys(self, pkcs1_example): + secret, public = pkcs1_example + + private_num = rsa.RSAPrivateNumbers( + p=secret["p"], + q=secret["q"], + d=secret["private_exponent"], + dmp1=secret["dmp1"], + dmq1=secret["dmq1"], + iqmp=secret["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=secret["public_exponent"], + n=secret["modulus"] + ) + ) + _check_rsa_private_numbers(private_num) + + public_num = rsa.RSAPublicNumbers( + e=public["public_exponent"], + n=public["modulus"] + ) + assert public_num + + public_num2 = private_num.public_numbers + assert public_num2 + + assert public_num.n == public_num2.n + assert public_num.e == public_num2.e + + +def test_rsa_generate_invalid_backend(): + pretend_backend = object() + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + rsa.generate_private_key(65537, 2048, pretend_backend) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +class TestRSASignature(object): + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + @pytest.mark.parametrize( + "pkcs1_example", + _flatten_pkcs1_examples(load_vectors_from_file( + os.path.join( + "asymmetric", "RSA", "pkcs1v15sign-vectors.txt"), + load_pkcs1_vectors + )) + ) + def test_pkcs1v15_signing(self, pkcs1_example, backend): + private, public, example = pkcs1_example + private_key = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], + n=private["modulus"] + ) + ).private_key(backend) + signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) + signer.update(binascii.unhexlify(example["message"])) + signature = signer.finalize() + assert binascii.hexlify(signature) == example["signature"] + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) + @pytest.mark.parametrize( + "pkcs1_example", + _flatten_pkcs1_examples(load_vectors_from_file( + os.path.join( + "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "pss-vect.txt"), + load_pkcs1_vectors + )) + ) + def test_pss_signing(self, pkcs1_example, backend): + private, public, example = pkcs1_example + private_key = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], + n=private["modulus"] + ) + ).private_key(backend) + public_key = rsa.RSAPublicNumbers( + e=public["public_exponent"], + n=public["modulus"] + ).public_key(backend) + signer = private_key.signer( + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) + signer.update(binascii.unhexlify(example["message"])) + signature = signer.finalize() + assert len(signature) == math.ceil(private_key.key_size / 8.0) + # PSS signatures contain randomness so we can't do an exact + # signature check. Instead we'll verify that the signature created + # successfully verifies. + verifier = public_key.verifier( + signature, + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1(), + ) + verifier.update(binascii.unhexlify(example["message"])) + verifier.verify() + + @pytest.mark.parametrize( + "hash_alg", + [hashes.SHA224(), hashes.SHA256(), hashes.SHA384(), hashes.SHA512()] + ) + def test_pss_signing_sha2(self, hash_alg, backend): + _skip_pss_hash_algorithm_unsupported(backend, hash_alg) + private_key = RSA_KEY_768.private_key(backend) + public_key = private_key.public_key() + pss = padding.PSS( + mgf=padding.MGF1(hash_alg), + salt_length=padding.PSS.MAX_LENGTH + ) + signer = private_key.signer(pss, hash_alg) + signer.update(b"testing signature") + signature = signer.finalize() + verifier = public_key.verifier(signature, pss, hash_alg) + verifier.update(b"testing signature") + verifier.verify() + + @pytest.mark.supported( + only_if=lambda backend: ( + backend.hash_supported(hashes.SHA512()) and + backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ) + ), + skip_message="Does not support SHA512." + ) + def test_pss_minimum_key_size_for_digest(self, backend): + private_key = RSA_KEY_522.private_key(backend) + signer = private_key.signer( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA512() + ) + signer.update(b"no failure") + signer.finalize() + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA512()), + skip_message="Does not support SHA512." + ) + def test_pss_signing_digest_too_large_for_key_size(self, backend): + private_key = RSA_KEY_512.private_key(backend) + with pytest.raises(ValueError): + private_key.signer( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA512() + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) + def test_pss_signing_salt_length_too_long(self, backend): + private_key = RSA_KEY_512.private_key(backend) + signer = private_key.signer( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=1000000 + ), + hashes.SHA1() + ) + signer.update(b"failure coming") + with pytest.raises(ValueError): + signer.finalize() + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_use_after_finalize(self, backend): + private_key = RSA_KEY_512.private_key(backend) + signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) + signer.update(b"sign me") + signer.finalize() + with pytest.raises(AlreadyFinalized): + signer.finalize() + with pytest.raises(AlreadyFinalized): + signer.update(b"more data") + + def test_unsupported_padding(self, backend): + private_key = RSA_KEY_512.private_key(backend) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + private_key.signer(DummyAsymmetricPadding(), hashes.SHA1()) + + def test_padding_incorrect_type(self, backend): + private_key = RSA_KEY_512.private_key(backend) + with pytest.raises(TypeError): + private_key.signer("notpadding", hashes.SHA1()) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) + def test_unsupported_pss_mgf(self, backend): + private_key = RSA_KEY_512.private_key(backend) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): + private_key.signer( + padding.PSS( + mgf=DummyMGF(), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_pkcs1_digest_too_large_for_key_size(self, backend): + private_key = RSA_KEY_599.private_key(backend) + signer = private_key.signer( + padding.PKCS1v15(), + hashes.SHA512() + ) + signer.update(b"failure coming") + with pytest.raises(ValueError): + signer.finalize() + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_pkcs1_minimum_key_size(self, backend): + private_key = RSA_KEY_745.private_key(backend) + signer = private_key.signer( + padding.PKCS1v15(), + hashes.SHA512() + ) + signer.update(b"no failure") + signer.finalize() + + def test_sign(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + pkcs = padding.PKCS1v15() + algorithm = hashes.SHA1() + signature = private_key.sign(message, pkcs, algorithm) + public_key = private_key.public_key() + verifier = public_key.verifier(signature, pkcs, algorithm) + verifier.update(message) + verifier.verify() + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) + def test_prehashed_sign(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + signature = private_key.sign(digest, pss, prehashed_alg) + public_key = private_key.public_key() + verifier = public_key.verifier(signature, pss, hashes.SHA1()) + verifier.update(message) + verifier.verify() + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) + def test_prehashed_digest_mismatch(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA512(), backend) + h.update(message) + digest = h.finalize() + pss = padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + with pytest.raises(ValueError): + private_key.sign(digest, pss, prehashed_alg) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +class TestRSAVerification(object): + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + @pytest.mark.parametrize( + "pkcs1_example", + _flatten_pkcs1_examples(load_vectors_from_file( + os.path.join( + "asymmetric", "RSA", "pkcs1v15sign-vectors.txt"), + load_pkcs1_vectors + )) + ) + def test_pkcs1v15_verification(self, pkcs1_example, backend): + private, public, example = pkcs1_example + public_key = rsa.RSAPublicNumbers( + e=public["public_exponent"], + n=public["modulus"] + ).public_key(backend) + verifier = public_key.verifier( + binascii.unhexlify(example["signature"]), + padding.PKCS1v15(), + hashes.SHA1() + ) + verifier.update(binascii.unhexlify(example["message"])) + verifier.verify() + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_invalid_pkcs1v15_signature_wrong_data(self, backend): + private_key = RSA_KEY_512.private_key(backend) + public_key = private_key.public_key() + signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) + signer.update(b"sign me") + signature = signer.finalize() + verifier = public_key.verifier( + signature, + padding.PKCS1v15(), + hashes.SHA1() + ) + verifier.update(b"incorrect data") + with pytest.raises(InvalidSignature): + verifier.verify() + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_invalid_pkcs1v15_signature_wrong_key(self, backend): + private_key = RSA_KEY_512.private_key(backend) + private_key2 = RSA_KEY_512_ALT.private_key(backend) + public_key = private_key2.public_key() + signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) + signer.update(b"sign me") + signature = signer.finalize() + verifier = public_key.verifier( + signature, + padding.PKCS1v15(), + hashes.SHA1() + ) + verifier.update(b"sign me") + with pytest.raises(InvalidSignature): + verifier.verify() + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=20 + ) + ), + skip_message="Does not support PSS." + ) + @pytest.mark.parametrize( + "pkcs1_example", + _flatten_pkcs1_examples(load_vectors_from_file( + os.path.join( + "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "pss-vect.txt"), + load_pkcs1_vectors + )) + ) + def test_pss_verification(self, pkcs1_example, backend): + private, public, example = pkcs1_example + public_key = rsa.RSAPublicNumbers( + e=public["public_exponent"], + n=public["modulus"] + ).public_key(backend) + verifier = public_key.verifier( + binascii.unhexlify(example["signature"]), + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=20 + ), + hashes.SHA1() + ) + verifier.update(binascii.unhexlify(example["message"])) + verifier.verify() + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) + def test_invalid_pss_signature_wrong_data(self, backend): + public_key = rsa.RSAPublicNumbers( + n=int( + b"dffc2137d5e810cde9e4b4612f5796447218bab913b3fa98bdf7982e4fa6" + b"ec4d6653ef2b29fb1642b095befcbea6decc178fb4bed243d3c3592c6854" + b"6af2d3f3", 16 + ), + e=65537 + ).public_key(backend) + signature = binascii.unhexlify( + b"0e68c3649df91c5bc3665f96e157efa75b71934aaa514d91e94ca8418d100f45" + b"6f05288e58525f99666bab052adcffdf7186eb40f583bd38d98c97d3d524808b" + ) + verifier = public_key.verifier( + signature, + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) + verifier.update(b"incorrect data") + with pytest.raises(InvalidSignature): + verifier.verify() + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) + def test_invalid_pss_signature_wrong_key(self, backend): + signature = binascii.unhexlify( + b"3a1880165014ba6eb53cc1449d13e5132ebcc0cfd9ade6d7a2494a0503bd0826" + b"f8a46c431e0d7be0ca3e453f8b2b009e2733764da7927cc6dbe7a021437a242e" + ) + public_key = rsa.RSAPublicNumbers( + n=int( + b"381201f4905d67dfeb3dec131a0fbea773489227ec7a1448c3109189ac68" + b"5a95441be90866a14c4d2e139cd16db540ec6c7abab13ffff91443fd46a8" + b"960cbb7658ded26a5c95c86f6e40384e1c1239c63e541ba221191c4dd303" + b"231b42e33c6dbddf5ec9a746f09bf0c25d0f8d27f93ee0ae5c0d723348f4" + b"030d3581e13522e1", 16 + ), + e=65537 + ).public_key(backend) + verifier = public_key.verifier( + signature, + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) + verifier.update(b"sign me") + with pytest.raises(InvalidSignature): + verifier.verify() + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) + def test_invalid_pss_signature_data_too_large_for_modulus(self, backend): + signature = binascii.unhexlify( + b"cb43bde4f7ab89eb4a79c6e8dd67e0d1af60715da64429d90c716a490b799c29" + b"194cf8046509c6ed851052367a74e2e92d9b38947ed74332acb115a03fcc0222" + ) + public_key = rsa.RSAPublicNumbers( + n=int( + b"381201f4905d67dfeb3dec131a0fbea773489227ec7a1448c3109189ac68" + b"5a95441be90866a14c4d2e139cd16db540ec6c7abab13ffff91443fd46a8" + b"960cbb7658ded26a5c95c86f6e40384e1c1239c63e541ba221191c4dd303" + b"231b42e33c6dbddf5ec9a746f09bf0c25d0f8d27f93ee0ae5c0d723348f4" + b"030d3581e13522", 16 + ), + e=65537 + ).public_key(backend) + verifier = public_key.verifier( + signature, + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) + verifier.update(b"sign me") + with pytest.raises(InvalidSignature): + verifier.verify() + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_use_after_finalize(self, backend): + private_key = RSA_KEY_512.private_key(backend) + public_key = private_key.public_key() + signer = private_key.signer(padding.PKCS1v15(), hashes.SHA1()) + signer.update(b"sign me") + signature = signer.finalize() + + verifier = public_key.verifier( + signature, + padding.PKCS1v15(), + hashes.SHA1() + ) + verifier.update(b"sign me") + verifier.verify() + with pytest.raises(AlreadyFinalized): + verifier.verify() + with pytest.raises(AlreadyFinalized): + verifier.update(b"more data") + + def test_unsupported_padding(self, backend): + private_key = RSA_KEY_512.private_key(backend) + public_key = private_key.public_key() + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + public_key.verifier( + b"sig", DummyAsymmetricPadding(), hashes.SHA1() + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_signature_not_bytes(self, backend): + public_key = RSA_KEY_512.public_numbers.public_key(backend) + signature = 1234 + + with pytest.raises(TypeError): + public_key.verifier( + signature, + padding.PKCS1v15(), + hashes.SHA1() + ) + + def test_padding_incorrect_type(self, backend): + private_key = RSA_KEY_512.private_key(backend) + public_key = private_key.public_key() + with pytest.raises(TypeError): + public_key.verifier(b"sig", "notpadding", hashes.SHA1()) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS(mgf=padding.MGF1(hashes.SHA1()), salt_length=0) + ), + skip_message="Does not support PSS." + ) + def test_unsupported_pss_mgf(self, backend): + private_key = RSA_KEY_512.private_key(backend) + public_key = private_key.public_key() + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): + public_key.verifier( + b"sig", + padding.PSS( + mgf=DummyMGF(), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA1() + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) + @pytest.mark.supported( + only_if=lambda backend: backend.hash_supported(hashes.SHA512()), + skip_message="Does not support SHA512." + ) + def test_pss_verify_digest_too_large_for_key_size(self, backend): + private_key = RSA_KEY_512.private_key(backend) + signature = binascii.unhexlify( + b"8b9a3ae9fb3b64158f3476dd8d8a1f1425444e98940e0926378baa9944d219d8" + b"534c050ef6b19b1bdc6eb4da422e89161106a6f5b5cc16135b11eb6439b646bd" + ) + public_key = private_key.public_key() + with pytest.raises(ValueError): + public_key.verifier( + signature, + padding.PSS( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ), + hashes.SHA512() + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS." + ) + def test_pss_verify_salt_length_too_long(self, backend): + signature = binascii.unhexlify( + b"8b9a3ae9fb3b64158f3476dd8d8a1f1425444e98940e0926378baa9944d219d8" + b"534c050ef6b19b1bdc6eb4da422e89161106a6f5b5cc16135b11eb6439b646bd" + ) + public_key = rsa.RSAPublicNumbers( + n=int( + b"d309e4612809437548b747d7f9eb9cd3340f54fe42bb3f84a36933b0839c" + b"11b0c8b7f67e11f7252370161e31159c49c784d4bc41c42a78ce0f0b40a3" + b"ca8ffb91", 16 + ), + e=65537 + ).public_key(backend) + verifier = public_key.verifier( + signature, + padding.PSS( + mgf=padding.MGF1( + algorithm=hashes.SHA1(), + ), + salt_length=1000000 + ), + hashes.SHA1() + ) + verifier.update(b"sign me") + with pytest.raises(InvalidSignature): + verifier.verify() + + def test_verify(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + pkcs = padding.PKCS1v15() + algorithm = hashes.SHA1() + signer = private_key.signer(pkcs, algorithm) + signer.update(message) + signature = signer.finalize() + public_key = private_key.public_key() + public_key.verify(signature, message, pkcs, algorithm) + + def test_prehashed_verify(self, backend): + private_key = RSA_KEY_512.private_key(backend) + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + digest = h.finalize() + prehashed_alg = asym_utils.Prehashed(hashes.SHA1()) + pkcs = padding.PKCS1v15() + signature = private_key.sign(message, pkcs, hashes.SHA1()) + public_key = private_key.public_key() + public_key.verify(signature, digest, pkcs, prehashed_alg) + + def test_prehashed_digest_mismatch(self, backend): + public_key = RSA_KEY_512.private_key(backend).public_key() + message = b"one little message" + h = hashes.Hash(hashes.SHA1(), backend) + h.update(message) + data = h.finalize() + prehashed_alg = asym_utils.Prehashed(hashes.SHA512()) + pkcs = padding.PKCS1v15() + with pytest.raises(ValueError): + public_key.verify(b"\x00" * 64, data, pkcs, prehashed_alg) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +class TestRSAPSSMGF1Verification(object): + test_rsa_pss_mgf1_sha1 = pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA1()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS using MGF1 with SHA1." + )(generate_rsa_verification_test( + load_rsa_nist_vectors, + os.path.join("asymmetric", "RSA", "FIPS_186-2"), + [ + "SigGenPSS_186-2.rsp", + "SigGenPSS_186-3.rsp", + "SigVerPSS_186-3.rsp", + ], + hashes.SHA1(), + lambda params, hash_alg: padding.PSS( + mgf=padding.MGF1( + algorithm=hash_alg, + ), + salt_length=params["salt_length"] + ) + )) + + test_rsa_pss_mgf1_sha224 = pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA224()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS using MGF1 with SHA224." + )(generate_rsa_verification_test( + load_rsa_nist_vectors, + os.path.join("asymmetric", "RSA", "FIPS_186-2"), + [ + "SigGenPSS_186-2.rsp", + "SigGenPSS_186-3.rsp", + "SigVerPSS_186-3.rsp", + ], + hashes.SHA224(), + lambda params, hash_alg: padding.PSS( + mgf=padding.MGF1( + algorithm=hash_alg, + ), + salt_length=params["salt_length"] + ) + )) + + test_rsa_pss_mgf1_sha256 = pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA256()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS using MGF1 with SHA256." + )(generate_rsa_verification_test( + load_rsa_nist_vectors, + os.path.join("asymmetric", "RSA", "FIPS_186-2"), + [ + "SigGenPSS_186-2.rsp", + "SigGenPSS_186-3.rsp", + "SigVerPSS_186-3.rsp", + ], + hashes.SHA256(), + lambda params, hash_alg: padding.PSS( + mgf=padding.MGF1( + algorithm=hash_alg, + ), + salt_length=params["salt_length"] + ) + )) + + test_rsa_pss_mgf1_sha384 = pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA384()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS using MGF1 with SHA384." + )(generate_rsa_verification_test( + load_rsa_nist_vectors, + os.path.join("asymmetric", "RSA", "FIPS_186-2"), + [ + "SigGenPSS_186-2.rsp", + "SigGenPSS_186-3.rsp", + "SigVerPSS_186-3.rsp", + ], + hashes.SHA384(), + lambda params, hash_alg: padding.PSS( + mgf=padding.MGF1( + algorithm=hash_alg, + ), + salt_length=params["salt_length"] + ) + )) + + test_rsa_pss_mgf1_sha512 = pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PSS( + mgf=padding.MGF1(hashes.SHA512()), + salt_length=padding.PSS.MAX_LENGTH + ) + ), + skip_message="Does not support PSS using MGF1 with SHA512." + )(generate_rsa_verification_test( + load_rsa_nist_vectors, + os.path.join("asymmetric", "RSA", "FIPS_186-2"), + [ + "SigGenPSS_186-2.rsp", + "SigGenPSS_186-3.rsp", + "SigVerPSS_186-3.rsp", + ], + hashes.SHA512(), + lambda params, hash_alg: padding.PSS( + mgf=padding.MGF1( + algorithm=hash_alg, + ), + salt_length=params["salt_length"] + ) + )) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +class TestRSAPKCS1Verification(object): + test_rsa_pkcs1v15_verify_sha1 = pytest.mark.supported( + only_if=lambda backend: ( + backend.hash_supported(hashes.SHA1()) and + backend.rsa_padding_supported(padding.PKCS1v15()) + ), + skip_message="Does not support SHA1 and PKCS1v1.5." + )(generate_rsa_verification_test( + load_rsa_nist_vectors, + os.path.join("asymmetric", "RSA", "FIPS_186-2"), + [ + "SigGen15_186-2.rsp", + "SigGen15_186-3.rsp", + "SigVer15_186-3.rsp", + ], + hashes.SHA1(), + lambda params, hash_alg: padding.PKCS1v15() + )) + + test_rsa_pkcs1v15_verify_sha224 = pytest.mark.supported( + only_if=lambda backend: ( + backend.hash_supported(hashes.SHA224()) and + backend.rsa_padding_supported(padding.PKCS1v15()) + ), + skip_message="Does not support SHA224 and PKCS1v1.5." + )(generate_rsa_verification_test( + load_rsa_nist_vectors, + os.path.join("asymmetric", "RSA", "FIPS_186-2"), + [ + "SigGen15_186-2.rsp", + "SigGen15_186-3.rsp", + "SigVer15_186-3.rsp", + ], + hashes.SHA224(), + lambda params, hash_alg: padding.PKCS1v15() + )) + + test_rsa_pkcs1v15_verify_sha256 = pytest.mark.supported( + only_if=lambda backend: ( + backend.hash_supported(hashes.SHA256()) and + backend.rsa_padding_supported(padding.PKCS1v15()) + ), + skip_message="Does not support SHA256 and PKCS1v1.5." + )(generate_rsa_verification_test( + load_rsa_nist_vectors, + os.path.join("asymmetric", "RSA", "FIPS_186-2"), + [ + "SigGen15_186-2.rsp", + "SigGen15_186-3.rsp", + "SigVer15_186-3.rsp", + ], + hashes.SHA256(), + lambda params, hash_alg: padding.PKCS1v15() + )) + + test_rsa_pkcs1v15_verify_sha384 = pytest.mark.supported( + only_if=lambda backend: ( + backend.hash_supported(hashes.SHA384()) and + backend.rsa_padding_supported(padding.PKCS1v15()) + ), + skip_message="Does not support SHA384 and PKCS1v1.5." + )(generate_rsa_verification_test( + load_rsa_nist_vectors, + os.path.join("asymmetric", "RSA", "FIPS_186-2"), + [ + "SigGen15_186-2.rsp", + "SigGen15_186-3.rsp", + "SigVer15_186-3.rsp", + ], + hashes.SHA384(), + lambda params, hash_alg: padding.PKCS1v15() + )) + + test_rsa_pkcs1v15_verify_sha512 = pytest.mark.supported( + only_if=lambda backend: ( + backend.hash_supported(hashes.SHA512()) and + backend.rsa_padding_supported(padding.PKCS1v15()) + ), + skip_message="Does not support SHA512 and PKCS1v1.5." + )(generate_rsa_verification_test( + load_rsa_nist_vectors, + os.path.join("asymmetric", "RSA", "FIPS_186-2"), + [ + "SigGen15_186-2.rsp", + "SigGen15_186-3.rsp", + "SigVer15_186-3.rsp", + ], + hashes.SHA512(), + lambda params, hash_alg: padding.PKCS1v15() + )) + + +class TestPSS(object): + def test_calculate_max_pss_salt_length(self): + with pytest.raises(TypeError): + padding.calculate_max_pss_salt_length(object(), hashes.SHA256()) + + def test_invalid_salt_length_not_integer(self): + with pytest.raises(TypeError): + padding.PSS( + mgf=padding.MGF1( + hashes.SHA1() + ), + salt_length=b"not_a_length" + ) + + def test_invalid_salt_length_negative_integer(self): + with pytest.raises(ValueError): + padding.PSS( + mgf=padding.MGF1( + hashes.SHA1() + ), + salt_length=-1 + ) + + def test_valid_pss_parameters(self): + algorithm = hashes.SHA1() + salt_length = algorithm.digest_size + mgf = padding.MGF1(algorithm) + pss = padding.PSS(mgf=mgf, salt_length=salt_length) + assert pss._mgf == mgf + assert pss._salt_length == salt_length + + def test_valid_pss_parameters_maximum(self): + algorithm = hashes.SHA1() + mgf = padding.MGF1(algorithm) + pss = padding.PSS(mgf=mgf, salt_length=padding.PSS.MAX_LENGTH) + assert pss._mgf == mgf + assert pss._salt_length == padding.PSS.MAX_LENGTH + + +class TestMGF1(object): + def test_invalid_hash_algorithm(self): + with pytest.raises(TypeError): + padding.MGF1(b"not_a_hash") + + def test_valid_mgf1_parameters(self): + algorithm = hashes.SHA1() + mgf = padding.MGF1(algorithm) + assert mgf._algorithm == algorithm + + +class TestOAEP(object): + def test_invalid_algorithm(self): + mgf = padding.MGF1(hashes.SHA1()) + with pytest.raises(TypeError): + padding.OAEP( + mgf=mgf, + algorithm=b"", + label=None + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +class TestRSADecryption(object): + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + @pytest.mark.parametrize( + "vector", + _flatten_pkcs1_examples(load_vectors_from_file( + os.path.join( + "asymmetric", "RSA", "pkcs1v15crypt-vectors.txt"), + load_pkcs1_vectors + )) + ) + def test_decrypt_pkcs1v15_vectors(self, vector, backend): + private, public, example = vector + skey = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], + n=private["modulus"] + ) + ).private_key(backend) + ciphertext = binascii.unhexlify(example["encryption"]) + assert len(ciphertext) == math.ceil(skey.key_size / 8.0) + message = skey.decrypt(ciphertext, padding.PKCS1v15()) + assert message == binascii.unhexlify(example["message"]) + + def test_unsupported_padding(self, backend): + private_key = RSA_KEY_512.private_key(backend) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + private_key.decrypt(b"0" * 64, DummyAsymmetricPadding()) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_decrypt_invalid_decrypt(self, backend): + private_key = RSA_KEY_512.private_key(backend) + with pytest.raises(ValueError): + private_key.decrypt( + b"\x00" * 64, + padding.PKCS1v15() + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_decrypt_ciphertext_too_large(self, backend): + private_key = RSA_KEY_512.private_key(backend) + with pytest.raises(ValueError): + private_key.decrypt( + b"\x00" * 65, + padding.PKCS1v15() + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + def test_decrypt_ciphertext_too_small(self, backend): + private_key = RSA_KEY_512.private_key(backend) + ct = binascii.unhexlify( + b"50b4c14136bd198c2f3c3ed243fce036e168d56517984a263cd66492b80804f1" + b"69d210f2b9bdfb48b12f9ea05009c77da257cc600ccefe3a6283789d8ea0" + ) + with pytest.raises(ValueError): + private_key.decrypt( + ct, + padding.PKCS1v15() + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ), + skip_message="Does not support OAEP." + ) + @pytest.mark.parametrize( + "vector", + _flatten_pkcs1_examples(load_vectors_from_file( + os.path.join( + "asymmetric", "RSA", "pkcs-1v2-1d2-vec", "oaep-vect.txt"), + load_pkcs1_vectors + )) + ) + def test_decrypt_oaep_vectors(self, vector, backend): + private, public, example = vector + skey = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], + n=private["modulus"] + ) + ).private_key(backend) + message = skey.decrypt( + binascii.unhexlify(example["encryption"]), + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ) + assert message == binascii.unhexlify(example["message"]) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA224()), + algorithm=hashes.SHA224(), + label=None + ) + ), + skip_message="Does not support OAEP using SHA224 MGF1 and SHA224 hash." + ) + @pytest.mark.parametrize( + "vector", + _build_oaep_sha2_vectors() + ) + def test_decrypt_oaep_sha2_vectors(self, vector, backend): + private, public, example, mgf1_alg, hash_alg = vector + skey = rsa.RSAPrivateNumbers( + p=private["p"], + q=private["q"], + d=private["private_exponent"], + dmp1=private["dmp1"], + dmq1=private["dmq1"], + iqmp=private["iqmp"], + public_numbers=rsa.RSAPublicNumbers( + e=private["public_exponent"], + n=private["modulus"] + ) + ).private_key(backend) + message = skey.decrypt( + binascii.unhexlify(example["encryption"]), + padding.OAEP( + mgf=padding.MGF1(algorithm=mgf1_alg), + algorithm=hash_alg, + label=None + ) + ) + assert message == binascii.unhexlify(example["message"]) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ), + skip_message="Does not support OAEP." + ) + def test_invalid_oaep_decryption(self, backend): + # More recent versions of OpenSSL may raise RSA_R_OAEP_DECODING_ERROR + # This test triggers it and confirms that we properly handle it. Other + # backends should also return the proper ValueError. + private_key = RSA_KEY_512.private_key(backend) + + ciphertext = private_key.public_key().encrypt( + b'secure data', + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ) + + private_key_alt = RSA_KEY_512_ALT.private_key(backend) + + with pytest.raises(ValueError): + private_key_alt.decrypt( + ciphertext, + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ) + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ), + skip_message="Does not support OAEP." + ) + def test_invalid_oaep_decryption_data_to_large_for_modulus(self, backend): + key = RSA_KEY_2048_ALT.private_key(backend) + + ciphertext = ( + b'\xb1ph\xc0\x0b\x1a|\xe6\xda\xea\xb5\xd7%\x94\x07\xf96\xfb\x96' + b'\x11\x9b\xdc4\xea.-\x91\x80\x13S\x94\x04m\xe9\xc5/F\x1b\x9b:\\' + b'\x1d\x04\x16ML\xae\xb32J\x01yuA\xbb\x83\x1c\x8f\xf6\xa5\xdbp\xcd' + b'\nx\xc7\xf6\x15\xb2/\xdcH\xae\xe7\x13\x13by\r4t\x99\x0fc\x1f\xc1' + b'\x1c\xb1\xdd\xc5\x08\xd1\xee\xa1XQ\xb8H@L5v\xc3\xaf\xf2\r\x97' + b'\xed\xaa\xe7\xf1\xd4xai\xd3\x83\xd9\xaa9\xbfx\xe1\x87F \x01\xff' + b'L\xccv}ae\xb3\xfa\xf2B\xb8\xf9\x04H\x94\x85\xcb\x86\xbb\\ghx!W31' + b'\xc7;t\na_E\xc2\x16\xb0;\xa1\x18\t\x1b\xe1\xdb\x80>)\x15\xc6\x12' + b'\xcb\xeeg`\x8b\x9b\x1b\x05y4\xb0\x84M6\xcd\xa1\x827o\xfd\x96\xba' + b'Z#\x8d\xae\x01\xc9\xf2\xb6\xde\x89{8&eQ\x1e8\x03\x01#?\xb66\\' + b'\xad.\xe9\xfa!\x95 c{\xcaz\xe0*\tP\r\x91\x9a)B\xb5\xadN\xf4$\x83' + b'\t\xb5u\xab\x19\x99' + ) + + with pytest.raises(ValueError): + key.decrypt( + ciphertext, + padding.OAEP( + algorithm=hashes.SHA1(), + mgf=padding.MGF1(hashes.SHA1()), + label=None + ) + ) + + def test_unsupported_oaep_mgf(self, backend): + private_key = RSA_KEY_512.private_key(backend) + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): + private_key.decrypt( + b"0" * 64, + padding.OAEP( + mgf=DummyMGF(), + algorithm=hashes.SHA1(), + label=None + ) + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +class TestRSAEncryption(object): + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ), + skip_message="Does not support OAEP." + ) + @pytest.mark.parametrize( + ("key_data", "pad"), + itertools.product( + (RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, + RSA_KEY_1028, RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, + RSA_KEY_1536, RSA_KEY_2048), + [ + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ) + ] + ) + ) + def test_rsa_encrypt_oaep(self, key_data, pad, backend): + private_key = key_data.private_key(backend) + pt = b"encrypt me!" + public_key = private_key.public_key() + ct = public_key.encrypt(pt, pad) + assert ct != pt + assert len(ct) == math.ceil(public_key.key_size / 8.0) + recovered_pt = private_key.decrypt(ct, pad) + assert recovered_pt == pt + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA256()), + algorithm=hashes.SHA512(), + label=None + ) + ), + skip_message="Does not support OAEP using SHA256 MGF1 and SHA512 hash." + ) + @pytest.mark.parametrize( + ("mgf1hash", "oaephash"), + itertools.product([ + hashes.SHA1(), + hashes.SHA224(), + hashes.SHA256(), + hashes.SHA384(), + hashes.SHA512(), + ], [ + hashes.SHA1(), + hashes.SHA224(), + hashes.SHA256(), + hashes.SHA384(), + hashes.SHA512(), + ]) + ) + def test_rsa_encrypt_oaep_sha2(self, mgf1hash, oaephash, backend): + pad = padding.OAEP( + mgf=padding.MGF1(algorithm=mgf1hash), + algorithm=oaephash, + label=None + ) + private_key = RSA_KEY_2048.private_key(backend) + pt = b"encrypt me using sha2 hashes!" + public_key = private_key.public_key() + ct = public_key.encrypt(pt, pad) + assert ct != pt + assert len(ct) == math.ceil(public_key.key_size / 8.0) + recovered_pt = private_key.decrypt(ct, pad) + assert recovered_pt == pt + + @pytest.mark.supported( + only_if=lambda backend: backend.rsa_padding_supported( + padding.PKCS1v15() + ), + skip_message="Does not support PKCS1v1.5." + ) + @pytest.mark.parametrize( + ("key_data", "pad"), + itertools.product( + (RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, + RSA_KEY_1028, RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, + RSA_KEY_1536, RSA_KEY_2048), + [padding.PKCS1v15()] + ) + ) + def test_rsa_encrypt_pkcs1v15(self, key_data, pad, backend): + private_key = key_data.private_key(backend) + pt = b"encrypt me!" + public_key = private_key.public_key() + ct = public_key.encrypt(pt, pad) + assert ct != pt + assert len(ct) == math.ceil(public_key.key_size / 8.0) + recovered_pt = private_key.decrypt(ct, pad) + assert recovered_pt == pt + + @pytest.mark.parametrize( + ("key_data", "pad"), + itertools.product( + (RSA_KEY_1024, RSA_KEY_1025, RSA_KEY_1026, RSA_KEY_1027, + RSA_KEY_1028, RSA_KEY_1029, RSA_KEY_1030, RSA_KEY_1031, + RSA_KEY_1536, RSA_KEY_2048), + ( + padding.OAEP( + mgf=padding.MGF1(algorithm=hashes.SHA1()), + algorithm=hashes.SHA1(), + label=None + ), + padding.PKCS1v15() + ) + ) + ) + def test_rsa_encrypt_key_too_small(self, key_data, pad, backend): + private_key = key_data.private_key(backend) + public_key = private_key.public_key() + # Slightly smaller than the key size but not enough for padding. + with pytest.raises(ValueError): + public_key.encrypt( + b"\x00" * (private_key.key_size // 8 - 1), + pad + ) + + # Larger than the key size. + with pytest.raises(ValueError): + public_key.encrypt( + b"\x00" * (private_key.key_size // 8 + 5), + pad + ) + + def test_unsupported_padding(self, backend): + private_key = RSA_KEY_512.private_key(backend) + public_key = private_key.public_key() + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_PADDING): + public_key.encrypt(b"somedata", DummyAsymmetricPadding()) + with pytest.raises(TypeError): + public_key.encrypt(b"somedata", padding=object()) + + def test_unsupported_oaep_mgf(self, backend): + private_key = RSA_KEY_512.private_key(backend) + public_key = private_key.public_key() + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_MGF): + public_key.encrypt( + b"ciphertext", + padding.OAEP( + mgf=DummyMGF(), + algorithm=hashes.SHA1(), + label=None + ) + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +class TestRSANumbers(object): + def test_rsa_public_numbers(self): + public_numbers = rsa.RSAPublicNumbers(e=1, n=15) + assert public_numbers.e == 1 + assert public_numbers.n == 15 + + def test_rsa_private_numbers(self): + public_numbers = rsa.RSAPublicNumbers(e=1, n=15) + private_numbers = rsa.RSAPrivateNumbers( + p=3, + q=5, + d=1, + dmp1=1, + dmq1=1, + iqmp=2, + public_numbers=public_numbers + ) + + assert private_numbers.p == 3 + assert private_numbers.q == 5 + assert private_numbers.d == 1 + assert private_numbers.dmp1 == 1 + assert private_numbers.dmq1 == 1 + assert private_numbers.iqmp == 2 + assert private_numbers.public_numbers == public_numbers + + def test_rsa_private_numbers_create_key(self, backend): + private_key = RSA_KEY_1024.private_key(backend) + assert private_key + + def test_rsa_public_numbers_create_key(self, backend): + public_key = RSA_KEY_1024.public_numbers.public_key(backend) + assert public_key + + def test_public_numbers_invalid_types(self): + with pytest.raises(TypeError): + rsa.RSAPublicNumbers(e=None, n=15) + + with pytest.raises(TypeError): + rsa.RSAPublicNumbers(e=1, n=None) + + def test_private_numbers_invalid_types(self): + public_numbers = rsa.RSAPublicNumbers(e=1, n=15) + + with pytest.raises(TypeError): + rsa.RSAPrivateNumbers( + p=None, + q=5, + d=1, + dmp1=1, + dmq1=1, + iqmp=2, + public_numbers=public_numbers + ) + + with pytest.raises(TypeError): + rsa.RSAPrivateNumbers( + p=3, + q=None, + d=1, + dmp1=1, + dmq1=1, + iqmp=2, + public_numbers=public_numbers + ) + + with pytest.raises(TypeError): + rsa.RSAPrivateNumbers( + p=3, + q=5, + d=None, + dmp1=1, + dmq1=1, + iqmp=2, + public_numbers=public_numbers + ) + + with pytest.raises(TypeError): + rsa.RSAPrivateNumbers( + p=3, + q=5, + d=1, + dmp1=None, + dmq1=1, + iqmp=2, + public_numbers=public_numbers + ) + + with pytest.raises(TypeError): + rsa.RSAPrivateNumbers( + p=3, + q=5, + d=1, + dmp1=1, + dmq1=None, + iqmp=2, + public_numbers=public_numbers + ) + + with pytest.raises(TypeError): + rsa.RSAPrivateNumbers( + p=3, + q=5, + d=1, + dmp1=1, + dmq1=1, + iqmp=None, + public_numbers=public_numbers + ) + + with pytest.raises(TypeError): + rsa.RSAPrivateNumbers( + p=3, + q=5, + d=1, + dmp1=1, + dmq1=1, + iqmp=2, + public_numbers=None + ) + + def test_invalid_public_numbers_argument_values(self, backend): + # Start with public_exponent=7, modulus=15. Then change one value at a + # time to test the bounds. + + # Test a modulus < 3. + + with pytest.raises(ValueError): + rsa.RSAPublicNumbers(e=7, n=2).public_key(backend) + + # Test a public_exponent < 3 + with pytest.raises(ValueError): + rsa.RSAPublicNumbers(e=1, n=15).public_key(backend) + + # Test a public_exponent > modulus + with pytest.raises(ValueError): + rsa.RSAPublicNumbers(e=17, n=15).public_key(backend) + + # Test a public_exponent that is not odd. + with pytest.raises(ValueError): + rsa.RSAPublicNumbers(e=14, n=15).public_key(backend) + + def test_invalid_private_numbers_argument_values(self, backend): + # Start with p=3, q=11, private_exponent=3, public_exponent=7, + # modulus=33, dmp1=1, dmq1=3, iqmp=2. Then change one value at + # a time to test the bounds. + + # Test a modulus < 3. + with pytest.raises(ValueError): + rsa.RSAPrivateNumbers( + p=3, + q=11, + d=3, + dmp1=1, + dmq1=3, + iqmp=2, + public_numbers=rsa.RSAPublicNumbers( + e=7, + n=2 + ) + ).private_key(backend) + + # Test a modulus != p * q. + with pytest.raises(ValueError): + rsa.RSAPrivateNumbers( + p=3, + q=11, + d=3, + dmp1=1, + dmq1=3, + iqmp=2, + public_numbers=rsa.RSAPublicNumbers( + e=7, + n=35 + ) + ).private_key(backend) + + # Test a p > modulus. + with pytest.raises(ValueError): + rsa.RSAPrivateNumbers( + p=37, + q=11, + d=3, + dmp1=1, + dmq1=3, + iqmp=2, + public_numbers=rsa.RSAPublicNumbers( + e=7, + n=33 + ) + ).private_key(backend) + + # Test a q > modulus. + with pytest.raises(ValueError): + rsa.RSAPrivateNumbers( + p=3, + q=37, + d=3, + dmp1=1, + dmq1=3, + iqmp=2, + public_numbers=rsa.RSAPublicNumbers( + e=7, + n=33 + ) + ).private_key(backend) + + # Test a dmp1 > modulus. + with pytest.raises(ValueError): + rsa.RSAPrivateNumbers( + p=3, + q=11, + d=3, + dmp1=35, + dmq1=3, + iqmp=2, + public_numbers=rsa.RSAPublicNumbers( + e=7, + n=33 + ) + ).private_key(backend) + + # Test a dmq1 > modulus. + with pytest.raises(ValueError): + rsa.RSAPrivateNumbers( + p=3, + q=11, + d=3, + dmp1=1, + dmq1=35, + iqmp=2, + public_numbers=rsa.RSAPublicNumbers( + e=7, + n=33 + ) + ).private_key(backend) + + # Test an iqmp > modulus. + with pytest.raises(ValueError): + rsa.RSAPrivateNumbers( + p=3, + q=11, + d=3, + dmp1=1, + dmq1=3, + iqmp=35, + public_numbers=rsa.RSAPublicNumbers( + e=7, + n=33 + ) + ).private_key(backend) + + # Test a private_exponent > modulus + with pytest.raises(ValueError): + rsa.RSAPrivateNumbers( + p=3, + q=11, + d=37, + dmp1=1, + dmq1=3, + iqmp=2, + public_numbers=rsa.RSAPublicNumbers( + e=7, + n=33 + ) + ).private_key(backend) + + # Test a public_exponent < 3 + with pytest.raises(ValueError): + rsa.RSAPrivateNumbers( + p=3, + q=11, + d=3, + dmp1=1, + dmq1=3, + iqmp=2, + public_numbers=rsa.RSAPublicNumbers( + e=1, + n=33 + ) + ).private_key(backend) + + # Test a public_exponent > modulus + with pytest.raises(ValueError): + rsa.RSAPrivateNumbers( + p=3, + q=11, + d=3, + dmp1=1, + dmq1=3, + iqmp=35, + public_numbers=rsa.RSAPublicNumbers( + e=65537, + n=33 + ) + ).private_key(backend) + + # Test a public_exponent that is not odd. + with pytest.raises(ValueError): + rsa.RSAPrivateNumbers( + p=3, + q=11, + d=3, + dmp1=1, + dmq1=3, + iqmp=2, + public_numbers=rsa.RSAPublicNumbers( + e=6, + n=33 + ) + ).private_key(backend) + + # Test a dmp1 that is not odd. + with pytest.raises(ValueError): + rsa.RSAPrivateNumbers( + p=3, + q=11, + d=3, + dmp1=2, + dmq1=3, + iqmp=2, + public_numbers=rsa.RSAPublicNumbers( + e=7, + n=33 + ) + ).private_key(backend) + + # Test a dmq1 that is not odd. + with pytest.raises(ValueError): + rsa.RSAPrivateNumbers( + p=3, + q=11, + d=3, + dmp1=1, + dmq1=4, + iqmp=2, + public_numbers=rsa.RSAPublicNumbers( + e=7, + n=33 + ) + ).private_key(backend) + + def test_public_number_repr(self): + num = RSAPublicNumbers(1, 1) + assert repr(num) == "" + + +class TestRSANumbersEquality(object): + def test_public_numbers_eq(self): + num = RSAPublicNumbers(1, 2) + num2 = RSAPublicNumbers(1, 2) + assert num == num2 + + def test_public_numbers_ne(self): + num = RSAPublicNumbers(1, 2) + assert num != RSAPublicNumbers(2, 2) + assert num != RSAPublicNumbers(1, 3) + assert num != object() + + def test_private_numbers_eq(self): + pub = RSAPublicNumbers(1, 2) + num = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, pub) + pub2 = RSAPublicNumbers(1, 2) + num2 = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, pub2) + assert num == num2 + + def test_private_numbers_ne(self): + pub = RSAPublicNumbers(1, 2) + num = RSAPrivateNumbers(1, 2, 3, 4, 5, 6, pub) + assert num != RSAPrivateNumbers( + 1, 2, 3, 4, 5, 7, RSAPublicNumbers(1, 2) + ) + assert num != RSAPrivateNumbers( + 1, 2, 3, 4, 4, 6, RSAPublicNumbers(1, 2) + ) + assert num != RSAPrivateNumbers( + 1, 2, 3, 5, 5, 6, RSAPublicNumbers(1, 2) + ) + assert num != RSAPrivateNumbers( + 1, 2, 4, 4, 5, 6, RSAPublicNumbers(1, 2) + ) + assert num != RSAPrivateNumbers( + 1, 3, 3, 4, 5, 6, RSAPublicNumbers(1, 2) + ) + assert num != RSAPrivateNumbers( + 2, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 2) + ) + assert num != RSAPrivateNumbers( + 1, 2, 3, 4, 5, 6, RSAPublicNumbers(2, 2) + ) + assert num != RSAPrivateNumbers( + 1, 2, 3, 4, 5, 6, RSAPublicNumbers(1, 3) + ) + 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( + "vector", + _flatten_pkcs1_examples(load_vectors_from_file( + os.path.join( + "asymmetric", "RSA", "pkcs1v15crypt-vectors.txt"), + load_pkcs1_vectors + )) + ) + def test_recover_prime_factors(self, vector): + private, public, example = vector + p, q = rsa.rsa_recover_prime_factors( + private["modulus"], + private["public_exponent"], + private["private_exponent"] + ) + # Unfortunately there is no convention on which prime should be p + # and which one q. The function we use always makes p > q, but the + # NIST vectors are not so consistent. Accordingly, we verify we've + # recovered the proper (p, q) by sorting them and asserting on that. + assert sorted([p, q]) == sorted([private["p"], private["q"]]) + assert p > q + + def test_invalid_recover_prime_factors(self): + with pytest.raises(ValueError): + rsa.rsa_recover_prime_factors(34, 3, 7) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +class TestRSAPrivateKeySerialization(object): + @pytest.mark.parametrize( + ("fmt", "password"), + itertools.product( + [ + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.PrivateFormat.PKCS8 + ], + [ + b"s", + b"longerpassword", + b"!*$&(@#$*&($T@%_somesymbols", + b"\x01" * 1000, + ] + ) + ) + def test_private_bytes_encrypted_pem(self, backend, fmt, password): + key = RSA_KEY_2048.private_key(backend) + serialized = key.private_bytes( + serialization.Encoding.PEM, + fmt, + serialization.BestAvailableEncryption(password) + ) + loaded_key = serialization.load_pem_private_key( + serialized, password, backend + ) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + ("fmt", "password"), + [ + [serialization.PrivateFormat.PKCS8, b"s"], + [serialization.PrivateFormat.PKCS8, b"longerpassword"], + [serialization.PrivateFormat.PKCS8, b"!*$&(@#$*&($T@%_somesymbol"], + [serialization.PrivateFormat.PKCS8, b"\x01" * 1000] + ] + ) + def test_private_bytes_encrypted_der(self, backend, fmt, password): + key = RSA_KEY_2048.private_key(backend) + serialized = key.private_bytes( + serialization.Encoding.DER, + fmt, + serialization.BestAvailableEncryption(password) + ) + loaded_key = serialization.load_der_private_key( + serialized, password, backend + ) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + ("encoding", "fmt", "loader_func"), + [ + [ + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.load_pem_private_key + ], + [ + serialization.Encoding.DER, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.load_der_private_key + ], + [ + serialization.Encoding.PEM, + serialization.PrivateFormat.PKCS8, + serialization.load_pem_private_key + ], + [ + serialization.Encoding.DER, + serialization.PrivateFormat.PKCS8, + serialization.load_der_private_key + ], + ] + ) + def test_private_bytes_unencrypted(self, backend, encoding, fmt, + loader_func): + key = RSA_KEY_2048.private_key(backend) + serialized = key.private_bytes( + encoding, fmt, serialization.NoEncryption() + ) + loaded_key = loader_func(serialized, None, backend) + loaded_priv_num = loaded_key.private_numbers() + priv_num = key.private_numbers() + assert loaded_priv_num == priv_num + + @pytest.mark.parametrize( + ("key_path", "encoding", "loader_func"), + [ + [ + os.path.join( + "asymmetric", + "Traditional_OpenSSL_Serialization", + "testrsa.pem" + ), + serialization.Encoding.PEM, + serialization.load_pem_private_key + ], + [ + os.path.join("asymmetric", "DER_Serialization", "testrsa.der"), + serialization.Encoding.DER, + serialization.load_der_private_key + ], + ] + ) + def test_private_bytes_traditional_openssl_unencrypted( + self, backend, key_path, encoding, loader_func + ): + key_bytes = load_vectors_from_file( + key_path, lambda pemfile: pemfile.read(), mode="rb" + ) + key = loader_func(key_bytes, None, backend) + serialized = key.private_bytes( + encoding, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.NoEncryption() + ) + assert serialized == key_bytes + + def test_private_bytes_traditional_der_encrypted_invalid(self, backend): + key = RSA_KEY_2048.private_key(backend) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.DER, + serialization.PrivateFormat.TraditionalOpenSSL, + serialization.BestAvailableEncryption(b"password") + ) + + def test_private_bytes_invalid_encoding(self, backend): + key = RSA_KEY_2048.private_key(backend) + with pytest.raises(TypeError): + key.private_bytes( + "notencoding", + serialization.PrivateFormat.PKCS8, + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_format(self, backend): + key = RSA_KEY_2048.private_key(backend) + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + "invalidformat", + serialization.NoEncryption() + ) + + def test_private_bytes_invalid_encryption_algorithm(self, backend): + key = RSA_KEY_2048.private_key(backend) + with pytest.raises(TypeError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + "notanencalg" + ) + + def test_private_bytes_unsupported_encryption_type(self, backend): + key = RSA_KEY_2048.private_key(backend) + with pytest.raises(ValueError): + key.private_bytes( + serialization.Encoding.PEM, + serialization.PrivateFormat.TraditionalOpenSSL, + DummyKeySerializationEncryption() + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +class TestRSAPEMPublicKeySerialization(object): + @pytest.mark.parametrize( + ("key_path", "loader_func", "encoding", "format"), + [ + ( + os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.pem"), + serialization.load_pem_public_key, + serialization.Encoding.PEM, + serialization.PublicFormat.PKCS1, + ), ( + os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.der"), + serialization.load_der_public_key, + serialization.Encoding.DER, + serialization.PublicFormat.PKCS1, + ), ( + os.path.join("asymmetric", "PKCS8", "unenc-rsa-pkcs8.pub.pem"), + serialization.load_pem_public_key, + serialization.Encoding.PEM, + serialization.PublicFormat.SubjectPublicKeyInfo, + ), ( + os.path.join( + "asymmetric", + "DER_Serialization", + "unenc-rsa-pkcs8.pub.der" + ), + serialization.load_der_public_key, + serialization.Encoding.DER, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + ] + ) + def test_public_bytes_match(self, key_path, loader_func, encoding, format, + backend): + key_bytes = load_vectors_from_file( + key_path, lambda pemfile: pemfile.read(), mode="rb" + ) + key = loader_func(key_bytes, backend) + serialized = key.public_bytes(encoding, format) + assert serialized == key_bytes + + def test_public_bytes_openssh(self, backend): + key_bytes = load_vectors_from_file( + os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.pem"), + lambda pemfile: pemfile.read(), mode="rb" + ) + key = serialization.load_pem_public_key(key_bytes, backend) + + ssh_bytes = key.public_bytes( + serialization.Encoding.OpenSSH, serialization.PublicFormat.OpenSSH + ) + assert ssh_bytes == ( + b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAAgQC7JHoJfg6yNzLMOWet8Z49a4KD" + b"0dCspMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkkFPZk/7x0" + b"xmdsTPECSWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAvSKAFKEvy" + b"D43si00DQnXWrYHAEQ==" + ) + + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.PEM, serialization.PublicFormat.OpenSSH + ) + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.DER, serialization.PublicFormat.OpenSSH + ) + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.OpenSSH, + serialization.PublicFormat.PKCS1, + ) + with pytest.raises(ValueError): + key.public_bytes( + serialization.Encoding.OpenSSH, + serialization.PublicFormat.SubjectPublicKeyInfo, + ) + + def test_public_bytes_invalid_encoding(self, backend): + key = RSA_KEY_2048.private_key(backend).public_key() + with pytest.raises(TypeError): + key.public_bytes("notencoding", serialization.PublicFormat.PKCS1) + + def test_public_bytes_invalid_format(self, backend): + key = RSA_KEY_2048.private_key(backend).public_key() + with pytest.raises(TypeError): + key.public_bytes(serialization.Encoding.PEM, "invalidformat") diff --git a/tests/hazmat/primitives/test_scrypt.py b/tests/hazmat/primitives/test_scrypt.py new file mode 100644 index 0000000..49b304e --- /dev/null +++ b/tests/hazmat/primitives/test_scrypt.py @@ -0,0 +1,136 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii + +import os + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, UnsupportedAlgorithm +) +from cryptography.hazmat.backends.interfaces import ScryptBackend +from cryptography.hazmat.primitives.kdf.scrypt import Scrypt + +from tests.utils import load_nist_vectors, load_vectors_from_file + +vectors = load_vectors_from_file( + os.path.join("KDF", "scrypt.txt"), load_nist_vectors) + + +@pytest.mark.requires_backend_interface(interface=ScryptBackend) +class TestScrypt(object): + @pytest.mark.parametrize("params", vectors) + def test_derive(self, backend, params): + password = params["password"] + work_factor = int(params["n"]) + block_size = int(params["r"]) + parallelization_factor = int(params["p"]) + length = int(params["length"]) + salt = params["salt"] + derived_key = params["derived_key"] + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + assert binascii.hexlify(scrypt.derive(password)) == derived_key + + def test_unsupported_backend(self): + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + backend = object() + + with pytest.raises(UnsupportedAlgorithm): + Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + def test_salt_not_bytes(self, backend): + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = 1 + + with pytest.raises(TypeError): + Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + def test_password_not_bytes(self, backend): + password = 1 + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + with pytest.raises(TypeError): + scrypt.derive(password) + + @pytest.mark.parametrize("params", vectors) + def test_verify(self, backend, params): + password = params["password"] + work_factor = int(params["n"]) + block_size = int(params["r"]) + parallelization_factor = int(params["p"]) + length = int(params["length"]) + salt = params["salt"] + derived_key = params["derived_key"] + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + assert scrypt.verify(password, binascii.unhexlify(derived_key)) is None + + def test_invalid_verify(self, backend): + password = b"password" + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + derived_key = b"fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e773" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + + with pytest.raises(InvalidKey): + scrypt.verify(password, binascii.unhexlify(derived_key)) + + def test_already_finalized(self, backend): + password = b"password" + work_factor = 1024 + block_size = 8 + parallelization_factor = 16 + length = 64 + salt = b"NaCl" + + scrypt = Scrypt(salt, length, work_factor, block_size, + parallelization_factor, backend) + scrypt.derive(password) + with pytest.raises(AlreadyFinalized): + scrypt.derive(password) + + def test_invalid_n(self, backend): + # n is less than 2 + with pytest.raises(ValueError): + Scrypt(b"NaCl", 64, 1, 8, 16, backend) + + # n is not a power of 2 + with pytest.raises(ValueError): + Scrypt(b"NaCl", 64, 3, 8, 16, backend) + + def test_invalid_r(self, backend): + with pytest.raises(ValueError): + Scrypt(b"NaCl", 64, 2, 0, 16, backend) + + def test_invalid_p(self, backend): + with pytest.raises(ValueError): + Scrypt(b"NaCl", 64, 2, 8, 0, backend) diff --git a/tests/hazmat/primitives/test_seed.py b/tests/hazmat/primitives/test_seed.py new file mode 100644 index 0000000..29cae4f --- /dev/null +++ b/tests/hazmat/primitives/test_seed.py @@ -0,0 +1,84 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import os + +import pytest + +from cryptography.hazmat.backends.interfaces import CipherBackend +from cryptography.hazmat.primitives.ciphers import algorithms, modes + +from .utils import generate_encrypt_test +from ...utils import load_nist_vectors + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.SEED(b"\x00" * 16), modes.ECB() + ), + skip_message="Does not support SEED ECB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestSEEDModeECB(object): + test_ECB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "SEED"), + ["rfc-4269.txt"], + lambda key, **kwargs: algorithms.SEED(binascii.unhexlify((key))), + lambda **kwargs: modes.ECB(), + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.SEED(b"\x00" * 16), modes.CBC(b"\x00" * 16) + ), + skip_message="Does not support SEED CBC", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestSEEDModeCBC(object): + test_CBC = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "SEED"), + ["rfc-4196.txt"], + lambda key, **kwargs: algorithms.SEED(binascii.unhexlify((key))), + lambda iv, **kwargs: modes.CBC(binascii.unhexlify(iv)) + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.SEED(b"\x00" * 16), modes.OFB(b"\x00" * 16) + ), + skip_message="Does not support SEED OFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestSEEDModeOFB(object): + test_OFB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "SEED"), + ["seed-ofb.txt"], + lambda key, **kwargs: algorithms.SEED(binascii.unhexlify((key))), + lambda iv, **kwargs: modes.OFB(binascii.unhexlify(iv)) + ) + + +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.SEED(b"\x00" * 16), modes.CFB(b"\x00" * 16) + ), + skip_message="Does not support SEED CFB", +) +@pytest.mark.requires_backend_interface(interface=CipherBackend) +class TestSEEDModeCFB(object): + test_CFB = generate_encrypt_test( + load_nist_vectors, + os.path.join("ciphers", "SEED"), + ["seed-cfb.txt"], + lambda key, **kwargs: algorithms.SEED(binascii.unhexlify((key))), + lambda iv, **kwargs: modes.CFB(binascii.unhexlify(iv)) + ) diff --git a/tests/hazmat/primitives/test_serialization.py b/tests/hazmat/primitives/test_serialization.py new file mode 100644 index 0000000..1ba8a3b --- /dev/null +++ b/tests/hazmat/primitives/test_serialization.py @@ -0,0 +1,1181 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import base64 +import itertools +import os +import textwrap + +import pytest + +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons +from cryptography.hazmat.backends.interfaces import ( + DERSerializationBackend, DSABackend, EllipticCurveBackend, + PEMSerializationBackend, RSABackend +) +from cryptography.hazmat.primitives.asymmetric import dsa, ec, rsa +from cryptography.hazmat.primitives.serialization import ( + BestAvailableEncryption, load_der_private_key, load_der_public_key, + load_pem_private_key, load_pem_public_key, load_ssh_public_key +) + + +from .test_ec import _skip_curve_unsupported +from .utils import ( + _check_dsa_private_numbers, _check_rsa_private_numbers, + load_vectors_from_file +) +from ...utils import raises_unsupported_algorithm + + +@pytest.mark.requires_backend_interface(interface=DERSerializationBackend) +class TestDERSerialization(object): + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.parametrize( + ("key_path", "password"), + [ + (["DER_Serialization", "enc-rsa-pkcs8.der"], b"foobar"), + (["DER_Serialization", "enc2-rsa-pkcs8.der"], b"baz"), + (["DER_Serialization", "unenc-rsa-pkcs8.der"], None), + (["DER_Serialization", "testrsa.der"], None), + ] + ) + def test_load_der_rsa_private_key(self, key_path, password, backend): + key = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda derfile: load_der_private_key( + derfile.read(), password, backend + ), + mode="rb" + ) + assert key + assert isinstance(key, rsa.RSAPrivateKey) + _check_rsa_private_numbers(key.private_numbers()) + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.parametrize( + ("key_path", "password"), + [ + (["DER_Serialization", "unenc-dsa-pkcs8.der"], None), + (["DER_Serialization", "dsa.1024.der"], None), + (["DER_Serialization", "dsa.2048.der"], None), + (["DER_Serialization", "dsa.3072.der"], None), + ] + ) + def test_load_der_dsa_private_key(self, key_path, password, backend): + key = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda derfile: load_der_private_key( + derfile.read(), password, backend + ), + mode="rb" + ) + assert key + assert isinstance(key, dsa.DSAPrivateKey) + _check_dsa_private_numbers(key.private_numbers()) + + @pytest.mark.parametrize( + ("key_path", "password"), + [ + (["DER_Serialization", "ec_private_key.der"], None), + (["DER_Serialization", "ec_private_key_encrypted.der"], b"123456"), + ] + ) + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + def test_load_der_ec_private_key(self, key_path, password, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda derfile: load_der_private_key( + derfile.read(), password, backend + ), + mode="rb" + ) + + assert key + assert isinstance(key, ec.EllipticCurvePrivateKey) + assert key.curve.name == "secp256r1" + assert key.curve.key_size == 256 + + @pytest.mark.parametrize( + "key_path", + [ + ["DER_Serialization", "enc-rsa-pkcs8.der"], + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_wrong_password(self, key_path, backend): + key_file = os.path.join("asymmetric", *key_path) + password = b"this password is wrong" + + with pytest.raises(ValueError): + load_vectors_from_file( + key_file, + lambda derfile: load_der_private_key( + derfile.read(), password, backend + ), + mode="rb" + ) + + @pytest.mark.parametrize( + "key_path", + [ + ["DER_Serialization", "unenc-rsa-pkcs8.der"] + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_unused_password(self, key_path, backend): + key_file = os.path.join("asymmetric", *key_path) + password = b"this password will not be used" + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda derfile: load_der_private_key( + derfile.read(), password, backend + ), + mode="rb" + ) + + @pytest.mark.parametrize( + ("key_path", "password"), + itertools.product( + [ + ["DER_Serialization", "enc-rsa-pkcs8.der"], + ], + [b"", None] + ) + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_missing_password(self, key_path, password, backend): + key_file = os.path.join("asymmetric", *key_path) + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda derfile: load_der_private_key( + derfile.read(), password, backend + ), + mode="rb" + ) + + def test_wrong_format(self, backend): + key_data = b"---- NOT A KEY ----\n" + + with pytest.raises(ValueError): + load_der_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_der_private_key( + key_data, b"this password will not be used", backend + ) + + def test_corrupt_der_pkcs8(self, backend): + # unenc-rsa-pkcs8 with a bunch of data missing. + key_data = textwrap.dedent("""\ + MIICdQIBADALBgkqhkiG9w0BAQEEggJhMIICXQIBAAKBgQC7JHoJfg6yNzLMOWet + 8Z49a4KD0dCspMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkk + FPZk/7x0xmdsTPECSWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAv + FiRC0Cgz+frQPFQEBsAV9RuasyQxqzxrR0Ow0qncBeGBWbYE6WZhqtcLAI895b+i + +F4lbB4iD7T9QeIDMU/aIMXA81UO4cns1z4qDAHKeyLLrPQrJ/B4X7XC+egUWm5+ + hr1qmyAMusyXIBECQQDJWZ8piluf4yrYfsJAn6hF5T4RjTztbqvO0GVG2McHY7Uj + NPSffhzHx/ll0fQEQji+OgydCCX8o3HZrgw5YfSJAkEA7e+rqdU5nO5ZG//PSEQb + tjLnRiTzBH/elQhtdZ5nF7pcpNTi4k13zutmKcWW4GK75azcRGJUhu1kDM7QYAOd + SQJAVNkYcifkvna7GmooL5VYEsQsqLbM4v0NF2TIGNfG3z1MGp75KrC5LhL97MNR + we2p/bd2k0HYyCKUGnf2nMPDiQJBAI75pwittSoE240EobUGIDTSz8CJsXIxuDmL + z+KOpdpPRR5TQmbEMEspjsFpFymMiuYPgmihQbO2cJl1qScY5OkCQQCJ6m5tcN8l + Xxg/SNpjEIv+qAyUD96XVlOJlOIeLHQ8kYE0C6ZA+MsqYIzgAreJk88Yn0lU/X0/ + mu/UpE/BRZmR + """).encode() + bad_der = base64.b64decode(b"".join(key_data.splitlines())) + + with pytest.raises(ValueError): + load_der_private_key( + bad_der, None, backend + ) + + with pytest.raises(ValueError): + load_der_private_key( + bad_der, b"this password will not be used", backend + ) + + def test_corrupt_traditional_format_der(self, backend): + # privkey with a bunch of data missing. + key_data = textwrap.dedent("""\ + MIIBPAIBAAJBAKrbeqkuRk8VcRmWFmtP+LviMB3+6dizWW3DwaffznyHGAFwUJ/I + Tv0XtbsCyl3QoyKGhrOAy3RvPK5M38iuXT0CAwEAAQJAZ3cnzaHXM/bxGaR5CR1R + rD1qFBAVfoQFiOH9uPJgMaoAuoQEisPHVcZDKcOv4wEg6/TInAIXBnEigtqvRzuy + mvcpHZwQJdmdHHkGKAs37Dfxi67HbkUCIQCeZGliHXFa071Fp06ZeWlR2ADonTZz + rJBhdTe0v5pCeQIhAIZfkiGgGBX4cIuuckzEm43g9WMUjxP/0GlK39vIyihxAiEA + mymehFRT0MvqW5xAKAx7Pgkt8HVKwVhc2LwGKHE0DZM= + """).encode() + bad_der = base64.b64decode(b"".join(key_data.splitlines())) + + with pytest.raises(ValueError): + load_pem_private_key(bad_der, None, backend) + + with pytest.raises(ValueError): + load_pem_private_key( + bad_der, b"this password will not be used", backend + ) + + @pytest.mark.parametrize( + "key_file", + [ + os.path.join( + "asymmetric", "DER_Serialization", "unenc-rsa-pkcs8.pub.der"), + os.path.join( + "asymmetric", "DER_Serialization", "rsa_public_key.der"), + os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.der"), + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_load_der_rsa_public_key(self, key_file, backend): + key = load_vectors_from_file( + key_file, + lambda derfile: load_der_public_key( + derfile.read(), backend + ), + mode="rb" + ) + assert key + assert isinstance(key, rsa.RSAPublicKey) + numbers = key.public_numbers() + assert numbers.e == 65537 + + def test_load_der_invalid_public_key(self, backend): + with pytest.raises(ValueError): + load_der_public_key(b"invalid data", backend) + + @pytest.mark.parametrize( + "key_file", + [ + os.path.join( + "asymmetric", "DER_Serialization", "unenc-dsa-pkcs8.pub.der"), + os.path.join( + "asymmetric", "DER_Serialization", "dsa_public_key.der"), + ] + ) + @pytest.mark.requires_backend_interface(interface=DSABackend) + def test_load_der_dsa_public_key(self, key_file, backend): + key = load_vectors_from_file( + key_file, + lambda derfile: load_der_public_key( + derfile.read(), backend + ), + mode="rb" + ) + assert key + assert isinstance(key, dsa.DSAPublicKey) + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + def test_load_ec_public_key(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "DER_Serialization", + "ec_public_key.der"), + lambda derfile: load_der_public_key( + derfile.read(), backend + ), + mode="rb" + ) + assert key + assert isinstance(key, ec.EllipticCurvePublicKey) + assert key.curve.name == "secp256r1" + assert key.curve.key_size == 256 + + +@pytest.mark.requires_backend_interface(interface=PEMSerializationBackend) +class TestPEMSerialization(object): + @pytest.mark.parametrize( + ("key_file", "password"), + [ + (["PEM_Serialization", "rsa_private_key.pem"], b"123456"), + (["PKCS8", "unenc-rsa-pkcs8.pem"], None), + (["PKCS8", "enc-rsa-pkcs8.pem"], b"foobar"), + (["PKCS8", "enc2-rsa-pkcs8.pem"], b"baz"), + (["PKCS8", "pkcs12_s2k_pem-X_9607.pem"], b"123456"), + (["PKCS8", "pkcs12_s2k_pem-X_9671.pem"], b"123456"), + (["PKCS8", "pkcs12_s2k_pem-X_9925.pem"], b"123456"), + (["PKCS8", "pkcs12_s2k_pem-X_9926.pem"], b"123456"), + (["PKCS8", "pkcs12_s2k_pem-X_9927.pem"], b"123456"), + (["PKCS8", "pkcs12_s2k_pem-X_9928.pem"], b"123456"), + (["PKCS8", "pkcs12_s2k_pem-X_9929.pem"], b"123456"), + (["PKCS8", "pkcs12_s2k_pem-X_9930.pem"], b"123456"), + (["PKCS8", "pkcs12_s2k_pem-X_9931.pem"], b"123456"), + (["PKCS8", "pkcs12_s2k_pem-X_9932.pem"], b"123456"), + (["Traditional_OpenSSL_Serialization", "key1.pem"], b"123456"), + (["Traditional_OpenSSL_Serialization", "key2.pem"], b"a123456"), + (["Traditional_OpenSSL_Serialization", "testrsa.pem"], None), + (["Traditional_OpenSSL_Serialization", "testrsa-encrypted.pem"], + b"password"), + ] + ) + def test_load_pem_rsa_private_key(self, key_file, password, backend): + key = load_vectors_from_file( + os.path.join("asymmetric", *key_file), + lambda pemfile: load_pem_private_key( + pemfile.read().encode(), password, backend + ) + ) + + assert key + assert isinstance(key, rsa.RSAPrivateKey) + _check_rsa_private_numbers(key.private_numbers()) + + @pytest.mark.parametrize( + ("key_path", "password"), + [ + (["Traditional_OpenSSL_Serialization", "dsa.1024.pem"], None), + (["Traditional_OpenSSL_Serialization", "dsa.2048.pem"], None), + (["Traditional_OpenSSL_Serialization", "dsa.3072.pem"], None), + (["PKCS8", "unenc-dsa-pkcs8.pem"], None), + (["PEM_Serialization", "dsa_private_key.pem"], b"123456"), + ] + ) + def test_load_dsa_private_key(self, key_path, password, backend): + key = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: load_pem_private_key( + pemfile.read().encode(), password, backend + ) + ) + assert key + assert isinstance(key, dsa.DSAPrivateKey) + _check_dsa_private_numbers(key.private_numbers()) + + @pytest.mark.parametrize( + ("key_path", "password"), + [ + (["PKCS8", "ec_private_key.pem"], None), + (["PKCS8", "ec_private_key_encrypted.pem"], b"123456"), + (["PEM_Serialization", "ec_private_key.pem"], None), + (["PEM_Serialization", "ec_private_key_encrypted.pem"], b"123456"), + ] + ) + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + def test_load_pem_ec_private_key(self, key_path, password, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join("asymmetric", *key_path), + lambda pemfile: load_pem_private_key( + pemfile.read().encode(), password, backend + ) + ) + + assert key + assert isinstance(key, ec.EllipticCurvePrivateKey) + assert key.curve.name == "secp256r1" + assert key.curve.key_size == 256 + + @pytest.mark.parametrize( + ("key_file"), + [ + os.path.join("asymmetric", "PKCS8", "unenc-rsa-pkcs8.pub.pem"), + os.path.join( + "asymmetric", "PEM_Serialization", "rsa_public_key.pem"), + os.path.join("asymmetric", "public", "PKCS1", "rsa.pub.pem"), + ] + ) + def test_load_pem_rsa_public_key(self, key_file, backend): + key = load_vectors_from_file( + key_file, + lambda pemfile: load_pem_public_key( + pemfile.read().encode(), backend + ) + ) + assert key + assert isinstance(key, rsa.RSAPublicKey) + numbers = key.public_numbers() + assert numbers.e == 65537 + + @pytest.mark.parametrize( + ("key_file"), + [ + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pub.pem"), + os.path.join( + "asymmetric", "PEM_Serialization", + "dsa_public_key.pem"), + ] + ) + def test_load_pem_dsa_public_key(self, key_file, backend): + key = load_vectors_from_file( + key_file, + lambda pemfile: load_pem_public_key( + pemfile.read().encode(), backend + ) + ) + assert key + assert isinstance(key, dsa.DSAPublicKey) + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + def test_load_ec_public_key(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + key = load_vectors_from_file( + os.path.join( + "asymmetric", "PEM_Serialization", + "ec_public_key.pem"), + lambda pemfile: load_pem_public_key( + pemfile.read().encode(), backend + ) + ) + assert key + assert isinstance(key, ec.EllipticCurvePublicKey) + assert key.curve.name == "secp256r1" + assert key.curve.key_size == 256 + + def test_rsa_traditional_encrypted_values(self, backend): + pkey = load_vectors_from_file( + os.path.join( + "asymmetric", "Traditional_OpenSSL_Serialization", "key1.pem"), + lambda pemfile: load_pem_private_key( + pemfile.read().encode(), b"123456", backend + ) + ) + assert pkey + + numbers = pkey.private_numbers() + assert numbers.p == int( + "fb7d316fc51531b36d93adaefaf52db6ad5beb793d37c4cf9dfc1ddd17cfbafb", + 16 + ) + assert numbers.q == int( + "df98264e646de9a0fbeab094e31caad5bc7adceaaae3c800ca0275dd4bb307f5", + 16 + ) + assert numbers.d == int( + "db4848c36f478dd5d38f35ae519643b6b810d404bcb76c00e44015e56ca1cab0" + "7bb7ae91f6b4b43fcfc82a47d7ed55b8c575152116994c2ce5325ec24313b911", + 16 + ) + assert numbers.dmp1 == int( + "ce997f967192c2bcc3853186f1559fd355c190c58ddc15cbf5de9b6df954c727", + 16 + ) + assert numbers.dmq1 == int( + "b018a57ab20ffaa3862435445d863369b852cf70a67c55058213e3fe10e3848d", + 16 + ) + assert numbers.iqmp == int( + "6a8d830616924f5cf2d1bc1973f97fde6b63e052222ac7be06aa2532d10bac76", + 16 + ) + assert numbers.public_numbers.e == 65537 + assert numbers.public_numbers.n == int( + "dba786074f2f0350ce1d99f5aed5b520cfe0deb5429ec8f2a88563763f566e77" + "9814b7c310e5326edae31198eed439b845dd2db99eaa60f5c16a43f4be6bcf37", + 16 + ) + + @pytest.mark.parametrize( + "key_path", + [ + ["Traditional_OpenSSL_Serialization", "testrsa.pem"], + ["PKCS8", "unenc-rsa-pkcs8.pem"] + ] + ) + def test_unused_password(self, key_path, backend): + key_file = os.path.join("asymmetric", *key_path) + password = b"this password will not be used" + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_private_key( + pemfile.read().encode(), password, backend + ) + ) + + @pytest.mark.parametrize( + "key_path", + [ + ["Traditional_OpenSSL_Serialization", "testrsa-encrypted.pem"], + ["PKCS8", "enc-rsa-pkcs8.pem"] + ] + ) + def test_wrong_password(self, key_path, backend): + key_file = os.path.join("asymmetric", *key_path) + password = b"this password is wrong" + + with pytest.raises(ValueError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_private_key( + pemfile.read().encode(), password, backend + ) + ) + + @pytest.mark.parametrize( + ("key_path", "password"), + itertools.product( + [ + ["Traditional_OpenSSL_Serialization", + "testrsa-encrypted.pem"], + ["PKCS8", "enc-rsa-pkcs8.pem"], + ], + [b"", None] + ) + ) + def test_missing_password(self, key_path, password, backend): + key_file = os.path.join("asymmetric", *key_path) + + with pytest.raises(TypeError): + load_vectors_from_file( + key_file, + lambda pemfile: load_pem_private_key( + pemfile.read().encode(), password, backend + ) + ) + + def test_wrong_private_format(self, backend): + key_data = b"---- NOT A KEY ----\n" + + with pytest.raises(ValueError): + load_pem_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_private_key( + key_data, b"this password will not be used", backend + ) + + def test_wrong_public_format(self, backend): + key_data = b"---- NOT A KEY ----\n" + + with pytest.raises(ValueError): + load_pem_public_key(key_data, backend) + + def test_corrupt_traditional_format(self, backend): + # privkey.pem with a bunch of data missing. + key_data = textwrap.dedent("""\ + -----BEGIN RSA PRIVATE KEY----- + MIIBPAIBAAJBAKrbeqkuRk8VcRmWFmtP+LviMB3+6dizWW3DwaffznyHGAFwUJ/I + Tv0XtbsCyl3QoyKGhrOAy3RvPK5M38iuXT0CAwEAAQJAZ3cnzaHXM/bxGaR5CR1R + rD1qFBAVfoQFiOH9uPJgMaoAuoQEisPHVcZDKcOv4wEg6/TInAIXBnEigtqvRzuy + mvcpHZwQJdmdHHkGKAs37Dfxi67HbkUCIQCeZGliHXFa071Fp06ZeWlR2ADonTZz + rJBhdTe0v5pCeQIhAIZfkiGgGBX4cIuuckzEm43g9WMUjxP/0GlK39vIyihxAiEA + mymehFRT0MvqW5xAKAx7Pgkt8HVKwVhc2LwGKHE0DZM= + -----END RSA PRIVATE KEY----- + """).encode() + + with pytest.raises(ValueError): + load_pem_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_private_key( + key_data, b"this password will not be used", backend + ) + + def test_traditional_encrypted_corrupt_format(self, backend): + # privkey.pem with a single bit flipped + key_data = textwrap.dedent("""\ + -----BEGIN RSA PRIVATE KEY----- + Proc-Type: <,ENCRYPTED + DEK-Info: AES-128-CBC,5E22A2BD85A653FB7A3ED20DE84F54CD + + hAqtb5ZkTMGcs4BBDQ1SKZzdQThWRDzEDxM3qBfjvYa35KxZ54aic013mW/lwj2I + v5bbpOjrHYHNAiZYZ7RNb+ztbF6F/g5PA5g7mFwEq+LFBY0InIplYBSv9QtE+lot + Dy4AlZa/+NzJwgdKDb+JVfk5SddyD4ywnyeORnMPy4xXKvjXwmW+iLibZVKsjIgw + H8hSxcD+FhWyJm9h9uLtmpuqhQo0jTUYpnTezZx2xeVPB53Ev7YCxR9Nsgj5GsVf + 9Z/hqLB7IFgM3pa0z3PQeUIZF/cEf72fISWIOBwwkzVrPUkXWfbuWeJXQXSs3amE + 5A295jD9BQp9CY0nNFSsy+qiXWToq2xT3y5zVNEStmN0SCGNaIlUnJzL9IHW+oMI + kPmXZMnAYBWeeCF1gf3J3aE5lZInegHNfEI0+J0LazC2aNU5Dg/BNqrmRqKWEIo/ + -----END RSA PRIVATE KEY----- + """).encode() + + password = b"this password is wrong" + + with pytest.raises(ValueError): + load_pem_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_private_key( + key_data, password, backend + ) + + def test_unsupported_key_encryption(self, backend): + key_data = textwrap.dedent("""\ + -----BEGIN RSA PRIVATE KEY----- + Proc-Type: 4,ENCRYPTED + DEK-Info: FAKE-123,5E22A2BD85A653FB7A3ED20DE84F54CD + + hAqtb5ZkTMGcs4BBDQ1SKZzdQThWRDzEDxM3qBfjvYa35KxZ54aic013mW/lwj2I + v5bbpOjrHYHNAiZYZ7RNb+ztbF6F/g5PA5g7mFwEq+LFBY0InIplYBSv9QtE+lot + Dy4AlZa/+NzJwgdKDb+JVfk5SddyD4ywnyeORnMPy4xXKvjXwmW+iLibZVKsjIgw + H8hSxcD+FhWyJm9h9uLtmpuqhQo0jTUYpnTezZx2xeVPB53Ev7YCxR9Nsgj5GsVf + 9Z/hqLB7IFgM3pa0z3PQeUIZF/cEf72fISWIOBwwkzVrPUkXWfbuWeJXQXSs3amE + 5A295jD9BQp9CY0nNFSsy+qiXWToq2xT3y5zVNEStmN0SCGNaIlUnJzL9IHW+oMI + kPmXZMnAYBWeeCF1gf3J3aE5lZInegHNfEI0+J0LazC2aNU5Dg/BNqrmRqKWEIo/ + -----END RSA PRIVATE KEY----- + """).encode() + + password = b"password" + + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + load_pem_private_key( + key_data, password, backend + ) + + def test_corrupt_pkcs8_format(self, backend): + # unenc-rsa-pkcs8.pem with a bunch of data missing. + key_data = textwrap.dedent("""\ + -----BEGIN PRIVATE KEY----- + MIICdQIBADALBgkqhkiG9w0BAQEEggJhMIICXQIBAAKBgQC7JHoJfg6yNzLMOWet + 8Z49a4KD0dCspMAYvo2YAMB7/wdEycocujbhJ2n/seONi+5XqTqqFkM5VBl8rmkk + FPZk/7x0xmdsTPECSWnHK+HhoaNDFPR3j8jQhVo1laxiqcEhAHegi5cwtFosuJAv + FiRC0Cgz+frQPFQEBsAV9RuasyQxqzxrR0Ow0qncBeGBWbYE6WZhqtcLAI895b+i + +F4lbB4iD7T9QeIDMU/aIMXA81UO4cns1z4qDAHKeyLLrPQrJ/B4X7XC+egUWm5+ + hr1qmyAMusyXIBECQQDJWZ8piluf4yrYfsJAn6hF5T4RjTztbqvO0GVG2McHY7Uj + NPSffhzHx/ll0fQEQji+OgydCCX8o3HZrgw5YfSJAkEA7e+rqdU5nO5ZG//PSEQb + tjLnRiTzBH/elQhtdZ5nF7pcpNTi4k13zutmKcWW4GK75azcRGJUhu1kDM7QYAOd + SQJAVNkYcifkvna7GmooL5VYEsQsqLbM4v0NF2TIGNfG3z1MGp75KrC5LhL97MNR + we2p/bd2k0HYyCKUGnf2nMPDiQJBAI75pwittSoE240EobUGIDTSz8CJsXIxuDmL + z+KOpdpPRR5TQmbEMEspjsFpFymMiuYPgmihQbO2cJl1qScY5OkCQQCJ6m5tcN8l + Xxg/SNpjEIv+qAyUD96XVlOJlOIeLHQ8kYE0C6ZA+MsqYIzgAreJk88Yn0lU/X0/ + mu/UpE/BRZmR + -----END PRIVATE KEY----- + """).encode() + + with pytest.raises(ValueError): + load_pem_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_private_key( + key_data, b"this password will not be used", backend + ) + + def test_pks8_encrypted_corrupt_format(self, backend): + # enc-rsa-pkcs8.pem with some bits flipped. + key_data = textwrap.dedent("""\ + -----BEGIN ENCRYPTED PRIVATE KEY----- + MIICojAcBgoqhkiG9w0BDAEDMA4ECHK0M0+QuEL9AgIBIcSCAoDRq+KRY+0XP0tO + lwBTzViiXSXoyNnKAZKt5r5K/fGNntv22g/1s/ZNCetrqsJDC5eMUPPacz06jFq/ + Ipsep4/OgjQ9UAOzXNrWEoNyrHnWDo7usgD3CW0mKyqER4+wG0adVMbt3N+CJHGB + 85jzRmQTfkdx1rSWeSx+XyswHn8ER4+hQ+omKWMVm7AFkjjmP/KnhUnLT98J8rhU + ArQoFPHz/6HVkypFccNaPPNg6IA4aS2A+TU9vJYOaXSVfFB2yf99hfYYzC+ukmuU + 5Lun0cysK5s/5uSwDueUmDQKspnaNyiaMGDxvw8hilJc7vg0fGObfnbIpizhxJwq + gKBfR7Zt0Hv8OYi1He4MehfMGdbHskztF+yQ40LplBGXQrvAqpU4zShga1BoQ98T + 0ekbBmqj7hg47VFsppXR7DKhx7G7rpMmdKbFhAZVCjae7rRGpUtD52cpFdPhMyAX + huhMkoczwUW8B/rM4272lkHo6Br0yk/TQfTEGkvryflNVu6lniPTV151WV5U1M3o + 3G3a44eDyt7Ln+WSOpWtbPQMTrpKhur6WXgJvrpa/m02oOGdvOlDsoOCgavgQMWg + 7xKKL7620pHl7p7f/8tlE8q6vLXVvyNtAOgt/JAr2rgvrHaZSzDE0DwgCjBXEm+7 + cVMVNkHod7bLQefVanVtWqPzbmr8f7gKeuGwWSG9oew/lN2hxcLEPJHAQlnLgx3P + 0GdGjK9NvwA0EP2gYIeE4+UtSder7xQ7bVh25VB20R4TTIIs4aXXCVOoQPagnzaT + 6JLgl8FrvdfjHwIvmSOO1YMNmILBq000Q8WDqyErBDs4hsvtO6VQ4LeqJj6gClX3 + qeJNaJFu + -----END ENCRYPTED PRIVATE KEY----- + """).encode() + + password = b"this password is wrong" + + with pytest.raises(ValueError): + load_pem_private_key( + key_data, None, backend + ) + + with pytest.raises(ValueError): + load_pem_private_key( + key_data, password, backend + ) + + def test_rsa_pkcs8_encrypted_values(self, backend): + pkey = load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", "enc-rsa-pkcs8.pem"), + lambda pemfile: load_pem_private_key( + pemfile.read().encode(), b"foobar", backend + ) + ) + assert pkey + + numbers = pkey.private_numbers() + + assert numbers.public_numbers.n == int( + "00beec64d6db5760ac2fd4c971145641b9bd7f5c56558ece608795c79807" + "376a7fe5b19f95b35ca358ea5c8abd7ae051d49cd2f1e45969a1ae945460" + "3c14b278664a0e414ebc8913acb6203626985525e17a600611b028542dd0" + "562aad787fb4f1650aa318cdcff751e1b187cbf6785fbe164e9809491b95" + "dd68480567c99b1a57", 16 + ) + + assert numbers.public_numbers.e == 65537 + + assert numbers.d == int( + "0cfe316e9dc6b8817f4fcfd5ae38a0886f68f773b8a6db4c9e6d8703c599" + "f3d9785c3a2c09e4c8090909fb3721e19a3009ec21221523a729265707a5" + "8f13063671c42a4096cad378ef2510cb59e23071489d8893ac4934dd149f" + "34f2d094bea57f1c8027c3a77248ac9b91218737d0c3c3dfa7d7829e6977" + "cf7d995688c86c81", 16 + ) + + assert numbers.p == int( + "00db122ac857b2c0437d7616daa98e597bb75ca9ad3a47a70bec10c10036" + "03328794b225c8e3eee6ffd3fd6d2253d28e071fe27d629ab072faa14377" + "ce6118cb67", 16 + ) + + assert numbers.q == int( + "00df1b8aa8506fcbbbb9d00257f2975e38b33d2698fd0f37e82d7ef38c56" + "f21b6ced63c825383782a7115cfcc093300987dbd2853b518d1c8f26382a" + "2d2586d391", 16 + ) + + assert numbers.dmp1 == int( + "00be18aca13e60712fdf5daa85421eb10d86d654b269e1255656194fb0c4" + "2dd01a1070ea12c19f5c39e09587af02f7b1a1030d016a9ffabf3b36d699" + "ceaf38d9bf", 16 + ) + + assert numbers.dmq1 == int( + "71aa8978f90a0c050744b77cf1263725b203ac9f730606d8ae1d289dce4a" + "28b8d534e9ea347aeb808c73107e583eb80c546d2bddadcdb3c82693a4c1" + "3d863451", 16 + ) + + assert numbers.iqmp == int( + "136b7b1afac6e6279f71b24217b7083485a5e827d156024609dae39d48a6" + "bdb55af2f062cc4a3b077434e6fffad5faa29a2b5dba2bed3e4621e478c0" + "97ccfe7f", 16 + ) + + def test_load_pem_dsa_private_key(self, backend): + key = load_vectors_from_file( + os.path.join("asymmetric", "PKCS8", "unenc-dsa-pkcs8.pem"), + lambda pemfile: load_pem_private_key( + pemfile.read().encode(), None, backend + ) + ) + assert key + assert isinstance(key, dsa.DSAPrivateKey) + + params = key.parameters() + assert isinstance(params, dsa.DSAParameters) + + num = key.private_numbers() + pub = num.public_numbers + parameter_numbers = pub.parameter_numbers + assert num.x == int("00a535a8e1d0d91beafc8bee1d9b2a3a8de3311203", + 16) + assert pub.y == int( + "2b260ea97dc6a12ae932c640e7df3d8ff04a8a05a0324f8d5f1b23f15fa1" + "70ff3f42061124eff2586cb11b49a82dcdc1b90fc6a84fb10109cb67db5d" + "2da971aeaf17be5e37284563e4c64d9e5fc8480258b319f0de29d54d8350" + "70d9e287914d77df81491f4423b62da984eb3f45eb2a29fcea5dae525ac6" + "ab6bcce04bfdf5b6", + 16 + ) + + assert parameter_numbers.p == int( + "00aa0930cc145825221caffa28ac2894196a27833de5ec21270791689420" + "7774a2e7b238b0d36f1b2499a2c2585083eb01432924418d867faa212dd1" + "071d4dceb2782794ad393cc08a4d4ada7f68d6e839a5fcd34b4e402d82cb" + "8a8cb40fec31911bf9bd360b034caacb4c5e947992573c9e90099c1b0f05" + "940cabe5d2de49a167", + 16 + ) + + assert parameter_numbers.q == int( + "00adc0e869b36f0ac013a681fdf4d4899d69820451", 16) + + assert parameter_numbers.g == int( + "008c6b4589afa53a4d1048bfc346d1f386ca75521ccf72ddaa251286880e" + "e13201ff48890bbfc33d79bacaec71e7a778507bd5f1a66422e39415be03" + "e71141ba324f5b93131929182c88a9fa4062836066cebe74b5c6690c7d10" + "1106c240ab7ebd54e4e3301fd086ce6adac922fb2713a2b0887cba13b9bc" + "68ce5cfff241cd3246", + 16 + ) + + @pytest.mark.parametrize( + ("key_file", "password"), + [ + ("bad-oid-dsa-key.pem", None), + ] + ) + def test_load_bad_oid_key(self, key_file, password, backend): + with raises_unsupported_algorithm( + _Reasons.UNSUPPORTED_PUBLIC_KEY_ALGORITHM + ): + load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", key_file), + lambda pemfile: load_pem_private_key( + pemfile.read().encode(), password, backend + ) + ) + + @pytest.mark.parametrize( + ("key_file", "password"), + [ + ("bad-encryption-oid.pem", b"password"), + ] + ) + def test_load_bad_encryption_oid_key(self, key_file, password, backend): + with raises_unsupported_algorithm(_Reasons.UNSUPPORTED_CIPHER): + load_vectors_from_file( + os.path.join( + "asymmetric", "PKCS8", key_file), + lambda pemfile: load_pem_private_key( + pemfile.read().encode(), password, backend + ) + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +class TestRSASSHSerialization(object): + def test_load_ssh_public_key_unsupported(self, backend): + ssh_key = b'ecdsa-sha2-junk AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY=' + + with pytest.raises(UnsupportedAlgorithm): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_bad_format(self, backend): + ssh_key = b'ssh-rsa not-a-real-key' + + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_rsa_too_short(self, backend): + ssh_key = b'ssh-rsa' + + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_truncated_int(self, backend): + ssh_key = b'ssh-rsa AAAAB3NzaC1yc2EAAAA=' + + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + ssh_key = b'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAACKr+IHXo' + + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_rsa_comment_with_spaces(self, backend): + ssh_key = ( + b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" + b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" + b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK" + b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f" + b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy" + b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX" + # Extra section appended + b"2MzHvnbv testkey@localhost extra" + ) + + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_rsa_extra_data_after_modulo(self, backend): + ssh_key = ( + b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" + b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" + b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK" + b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f" + b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy" + b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX" + b"2MzHvnbvAQ== testkey@localhost" + ) + + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_rsa_different_string(self, backend): + ssh_key = ( + # "AAAAB3NzA" the final A is capitalized here to cause the string + # ssh-rsa inside the base64 encoded blob to be incorrect. It should + # be a lower case 'a'. + b"ssh-rsa AAAAB3NzAC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" + b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" + b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK" + b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f" + b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy" + b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX" + b"2MzHvnbvAQ== testkey@localhost" + ) + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_rsa(self, backend): + ssh_key = ( + b"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDDu/XRP1kyK6Cgt36gts9XAk" + b"FiiuJLW6RU0j3KKVZSs1I7Z3UmU9/9aVh/rZV43WQG8jaR6kkcP4stOR0DEtll" + b"PDA7ZRBnrfiHpSQYQ874AZaAoIjgkv7DBfsE6gcDQLub0PFjWyrYQUJhtOLQEK" + b"vY/G0vt2iRL3juawWmCFdTK3W3XvwAdgGk71i6lHt+deOPNEPN2H58E4odrZ2f" + b"sxn/adpDqfb2sM0kPwQs0aWvrrKGvUaustkivQE4XWiSFnB0oJB/lKK/CKVKuy" + b"///ImSCGHQRvhwariN2tvZ6CBNSLh3iQgeB0AkyJlng7MXB2qYq/Ci2FUOryCX" + b"2MzHvnbv testkey@localhost" + ) + + key = load_ssh_public_key(ssh_key, backend) + + assert key is not None + assert isinstance(key, rsa.RSAPublicKey) + + numbers = key.public_numbers() + + expected_e = 0x10001 + expected_n = int( + '00C3BBF5D13F59322BA0A0B77EA0B6CF570241628AE24B5BA454D' + '23DCA295652B3523B67752653DFFD69587FAD9578DD6406F23691' + 'EA491C3F8B2D391D0312D9653C303B651067ADF887A5241843CEF' + '8019680A088E092FEC305FB04EA070340BB9BD0F1635B2AD84142' + '61B4E2D010ABD8FC6D2FB768912F78EE6B05A60857532B75B75EF' + 'C007601A4EF58BA947B7E75E38F3443CDD87E7C138A1DAD9D9FB3' + '19FF69DA43A9F6F6B0CD243F042CD1A5AFAEB286BD46AEB2D922B' + 'D01385D6892167074A0907F94A2BF08A54ABB2FFFFC89920861D0' + '46F8706AB88DDADBD9E8204D48B87789081E074024C8996783B31' + '7076A98ABF0A2D8550EAF2097D8CCC7BE76EF', 16) + + expected = rsa.RSAPublicNumbers(expected_e, expected_n) + + assert numbers == expected + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +class TestDSSSSHSerialization(object): + def test_load_ssh_public_key_dss_too_short(self, backend): + ssh_key = b'ssh-dss' + + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_dss_comment_with_spaces(self, backend): + ssh_key = ( + b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" + b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" + b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ" + b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2" + b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf" + b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J" + b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi" + b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa" + b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr" + b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost extra" + ) + + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_dss_extra_data_after_modulo(self, backend): + ssh_key = ( + b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" + b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" + b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ" + b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2" + b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf" + b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J" + b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi" + b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa" + b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr" + b"z53N7tPF/IhHTjBHb1Ol7IFu9p9AAwMD== testkey@localhost" + ) + + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_dss_different_string(self, backend): + ssh_key = ( + # "AAAAB3NzA" the final A is capitalized here to cause the string + # ssh-dss inside the base64 encoded blob to be incorrect. It should + # be a lower case 'a'. + b"ssh-dss AAAAB3NzAC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" + b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" + b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ" + b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2" + b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf" + b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J" + b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi" + b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa" + b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr" + b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost" + ) + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_dss(self, backend): + ssh_key = ( + b"ssh-dss AAAAB3NzaC1kc3MAAACBALmwUtfwdjAUjU2Dixd5DvT0NDcjjr69UD" + b"LqSD/Xt5Al7D3GXr1WOrWGpjO0NE9qzRCvMTU7zykRH6XjuNXB6Hvv48Zfm4vm" + b"nHQHFmmMg2bI75JbnOwdzWnnPZJrVU4rS23dFFPqs5ug+EbhVVrcwzxahjcSjJ" + b"7WEQSkVQWnSPbbAAAAFQDXmpD3DIkGvLSBf1GdUF4PHKtUrQAAAIB/bJFwss+2" + b"fngmfG/Li5OyL7A9iVoGdkUaFaxEUROTp7wkm2z49fXFAir+/U31v50Tu98YLf" + b"WvKlxdHcdgQYV9Ww5LIrhWwwD4UKOwC6w5S3KHVbi3pWUi7vxJFXOWfeu1mC/J" + b"TWqMKR91j+rmOtdppWIZRyIVIqLcMdGO3m+2VgAAAIANFDz5KQH5NvoljpoRQi" + b"RgyPjxWXiE7vjLElKj4v8KrpanAywBzdhIW1y/tzpGuwRwj5ihi8iNTHgSsoTa" + b"j5AG5HPomJf5vJElxpu/2O9pHA52wcNObIQ7j+JA5uWusxNIbl+pF6sSiP8abr" + b"z53N7tPF/IhHTjBHb1Ol7IFu9p9A== testkey@localhost" + ) + + key = load_ssh_public_key(ssh_key, backend) + + assert key is not None + assert isinstance(key, dsa.DSAPublicKey) + + numbers = key.public_numbers() + + expected_y = int( + "d143cf92901f936fa258e9a11422460c8f8f1597884eef8cb1252a3e2ff0aae" + "96a7032c01cdd8485b5cbfb73a46bb04708f98a18bc88d4c7812b284da8f900" + "6e473e89897f9bc9125c69bbfd8ef691c0e76c1c34e6c843b8fe240e6e5aeb3" + "13486e5fa917ab1288ff1a6ebcf9dcdeed3c5fc88474e30476f53a5ec816ef6" + "9f4", 16 + ) + expected_p = int( + "b9b052d7f07630148d4d838b17790ef4f43437238ebebd5032ea483fd7b7902" + "5ec3dc65ebd563ab586a633b4344f6acd10af31353bcf29111fa5e3b8d5c1e8" + "7befe3c65f9b8be69c740716698c8366c8ef925b9cec1dcd69e73d926b554e2" + "b4b6ddd1453eab39ba0f846e1555adcc33c5a8637128c9ed61104a45505a748" + "f6db", 16 + ) + expected_q = 1230879958723280233885494314531920096931919647917 + expected_g = int( + "7f6c9170b2cfb67e78267c6fcb8b93b22fb03d895a0676451a15ac44511393a" + "7bc249b6cf8f5f5c5022afefd4df5bf9d13bbdf182df5af2a5c5d1dc7604185" + "7d5b0e4b22b856c300f850a3b00bac394b728755b8b7a56522eefc491573967" + "debb5982fc94d6a8c291f758feae63ad769a5621947221522a2dc31d18ede6f" + "b656", 16 + ) + expected = dsa.DSAPublicNumbers( + expected_y, + dsa.DSAParameterNumbers(expected_p, expected_q, expected_g) + ) + + assert numbers == expected + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +class TestECDSASSHSerialization(object): + def test_load_ssh_public_key_ecdsa_nist_p256(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + + ssh_key = ( + b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" + b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" + b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01" + ) + key = load_ssh_public_key(ssh_key, backend) + assert isinstance(key, ec.EllipticCurvePublicKey) + + expected_x = int( + "44196257377740326295529888716212621920056478823906609851236662550" + "785814128027", 10 + ) + expected_y = int( + "12257763433170736656417248739355923610241609728032203358057767672" + "925775019611", 10 + ) + + assert key.public_numbers() == ec.EllipticCurvePublicNumbers( + expected_x, expected_y, ec.SECP256R1() + ) + + def test_load_ssh_public_key_ecdsa_nist_p384(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + ssh_key = ( + b"ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAz" + b"ODQAAABhBMzucOm9wbwg4iMr5QL0ya0XNQGXpw4wM5f12E3tWhdcrzyGHyel71t1" + b"4bvF9JZ2/WIuSxUr33XDl8jYo+lMQ5N7Vanc7f7i3AR1YydatL3wQfZStQ1I3rBa" + b"qQtRSEU8Tg== root@cloud-server-01" + ) + key = load_ssh_public_key(ssh_key, backend) + + expected_x = int( + "31541830871345183397582554827482786756220448716666815789487537666" + "592636882822352575507883817901562613492450642523901", 10 + ) + expected_y = int( + "15111413269431823234030344298767984698884955023183354737123929430" + "995703524272335782455051101616329050844273733614670", 10 + ) + + assert key.public_numbers() == ec.EllipticCurvePublicNumbers( + expected_x, expected_y, ec.SECP384R1() + ) + + def test_load_ssh_public_key_ecdsa_nist_p521(self, backend): + _skip_curve_unsupported(backend, ec.SECP521R1()) + ssh_key = ( + b"ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1" + b"MjEAAACFBAGTrRhMSEgF6Ni+PXNz+5fjS4lw3ypUILVVQ0Av+0hQxOx+MyozELon" + b"I8NKbrbBjijEs1GuImsmkTmWsMXS1j2A7wB4Kseh7W9KA9IZJ1+TMrzWUEwvOOXi" + b"wT23pbaWWXG4NaM7vssWfZBnvz3S174TCXnJ+DSccvWBFnKP0KchzLKxbg== " + b"root@cloud-server-01" + ) + key = load_ssh_public_key(ssh_key, backend) + + expected_x = int( + "54124123120178189598842622575230904027376313369742467279346415219" + "77809037378785192537810367028427387173980786968395921877911964629" + "142163122798974160187785455", 10 + ) + expected_y = int( + "16111775122845033200938694062381820957441843014849125660011303579" + "15284560361402515564433711416776946492019498546572162801954089916" + "006665939539407104638103918", 10 + ) + + assert key.public_numbers() == ec.EllipticCurvePublicNumbers( + expected_x, expected_y, ec.SECP521R1() + ) + + def test_load_ssh_public_key_ecdsa_nist_p256_trailing_data(self, backend): + ssh_key = ( + b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" + b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" + b"teIg1TO03/FD9hbpBFgBeix3NrCFPltB= root@cloud-server-01" + ) + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_ecdsa_nist_p256_missing_data(self, backend): + ssh_key = ( + b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" + b"NTYAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" + b"teIg1TO03/FD9hbpBFgBeix3NrCF= root@cloud-server-01" + ) + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_ecdsa_nist_p256_compressed(self, backend): + # If we ever implement compressed points, note that this is not a valid + # one, it just has the compressed marker in the right place. + ssh_key = ( + b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" + b"NTYAAABBAWG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" + b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01" + ) + with pytest.raises(NotImplementedError): + load_ssh_public_key(ssh_key, backend) + + def test_load_ssh_public_key_ecdsa_nist_p256_bad_curve_name(self, backend): + ssh_key = ( + # The curve name in here is changed to be "nistp255". + b"ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy" + b"NTUAAABBBGG2MfkHXp0UkxUyllDzWNBAImsvt5t7pFtTXegZK2WbGxml8zMrgWi5" + b"teIg1TO03/FD9hbpBFgBeix3NrCFPls= root@cloud-server-01" + ) + with pytest.raises(ValueError): + load_ssh_public_key(ssh_key, backend) + + +class TestKeySerializationEncryptionTypes(object): + def test_non_bytes_password(self): + with pytest.raises(ValueError): + BestAvailableEncryption(object()) + + def test_encryption_with_zero_length_password(self): + with pytest.raises(ValueError): + BestAvailableEncryption(b"") diff --git a/tests/hazmat/primitives/test_x963_vectors.py b/tests/hazmat/primitives/test_x963_vectors.py new file mode 100644 index 0000000..4a945d4 --- /dev/null +++ b/tests/hazmat/primitives/test_x963_vectors.py @@ -0,0 +1,66 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import os + +import pytest + +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF + +from ...doubles import DummyHashAlgorithm +from ...utils import load_vectors_from_file, load_x963_vectors + + +def _skip_hashfn_unsupported(backend, hashfn): + if not backend.hash_supported(hashfn): + pytest.skip( + "Hash {0} is not supported by this backend {1}".format( + hashfn.name, backend + ) + ) + + +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestX963(object): + _algorithms_dict = { + 'SHA-1': hashes.SHA1, + 'SHA-224': hashes.SHA224, + 'SHA-256': hashes.SHA256, + 'SHA-384': hashes.SHA384, + 'SHA-512': hashes.SHA512 + } + + @pytest.mark.parametrize( + ("vector"), + load_vectors_from_file( + os.path.join("KDF", "ansx963_2001.txt"), + load_x963_vectors + ) + ) + def test_x963(self, backend, vector): + hashfn = self._algorithms_dict[vector["hash"]] + _skip_hashfn_unsupported(backend, hashfn()) + + key = binascii.unhexlify(vector["Z"]) + sharedinfo = None + if vector["sharedinfo_length"] != 0: + sharedinfo = binascii.unhexlify(vector["sharedinfo"]) + key_data_len = vector["key_data_length"] // 8 + key_data = binascii.unhexlify(vector["key_data"]) + + xkdf = X963KDF(algorithm=hashfn(), + length=key_data_len, + sharedinfo=sharedinfo, + backend=default_backend()) + xkdf.verify(key, key_data) + + def test_unsupported_hash(self, backend): + with pytest.raises(pytest.skip.Exception): + _skip_hashfn_unsupported(backend, DummyHashAlgorithm()) diff --git a/tests/hazmat/primitives/test_x963kdf.py b/tests/hazmat/primitives/test_x963kdf.py new file mode 100644 index 0000000..d87a46b --- /dev/null +++ b/tests/hazmat/primitives/test_x963kdf.py @@ -0,0 +1,120 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, InvalidKey, _Reasons +) +from cryptography.hazmat.backends.interfaces import HashBackend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.x963kdf import X963KDF + +from ...utils import raises_unsupported_algorithm + + +@pytest.mark.requires_backend_interface(interface=HashBackend) +class TestX963KDF(object): + def test_length_limit(self, backend): + big_length = hashes.SHA256().digest_size * (2 ** 32 - 1) + 1 + + with pytest.raises(ValueError): + X963KDF(hashes.SHA256(), big_length, None, backend) + + def test_already_finalized(self, backend): + xkdf = X963KDF(hashes.SHA256(), 16, None, backend) + + xkdf.derive(b"\x01" * 16) + + with pytest.raises(AlreadyFinalized): + xkdf.derive(b"\x02" * 16) + + def test_derive(self, backend): + key = binascii.unhexlify( + b"96c05619d56c328ab95fe84b18264b08725b85e33fd34f08" + ) + + derivedkey = binascii.unhexlify(b"443024c3dae66b95e6f5670601558f71") + + xkdf = X963KDF(hashes.SHA256(), 16, None, backend) + + assert xkdf.derive(key) == derivedkey + + def test_verify(self, backend): + key = binascii.unhexlify( + b"22518b10e70f2a3f243810ae3254139efbee04aa57c7af7d" + ) + + sharedinfo = binascii.unhexlify(b"75eef81aa3041e33b80971203d2c0c52") + + derivedkey = binascii.unhexlify( + b"c498af77161cc59f2962b9a713e2b215152d139766ce34a776df11866a69bf2e" + b"52a13d9c7c6fc878c50c5ea0bc7b00e0da2447cfd874f6cf92f30d0097111485" + b"500c90c3af8b487872d04685d14c8d1dc8d7fa08beb0ce0ababc11f0bd496269" + b"142d43525a78e5bc79a17f59676a5706dc54d54d4d1f0bd7e386128ec26afc21" + ) + + xkdf = X963KDF(hashes.SHA256(), 128, sharedinfo, backend) + + assert xkdf.verify(key, derivedkey) is None + + def test_invalid_verify(self, backend): + key = binascii.unhexlify( + b"96c05619d56c328ab95fe84b18264b08725b85e33fd34f08" + ) + + xkdf = X963KDF(hashes.SHA256(), 16, None, backend) + + with pytest.raises(InvalidKey): + xkdf.verify(key, b"wrong derived key") + + def test_unicode_typeerror(self, backend): + with pytest.raises(TypeError): + X963KDF( + hashes.SHA256(), + 16, + sharedinfo=u"foo", + backend=backend + ) + + with pytest.raises(TypeError): + xkdf = X963KDF( + hashes.SHA256(), + 16, + sharedinfo=None, + backend=backend + ) + + xkdf.derive(u"foo") + + with pytest.raises(TypeError): + xkdf = X963KDF( + hashes.SHA256(), + 16, + sharedinfo=None, + backend=backend + ) + + xkdf.verify(u"foo", b"bar") + + with pytest.raises(TypeError): + xkdf = X963KDF( + hashes.SHA256(), + 16, + sharedinfo=None, + backend=backend + ) + + xkdf.verify(b"foo", u"bar") + + +def test_invalid_backend(): + pretend_backend = object() + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + X963KDF(hashes.SHA256(), 16, None, pretend_backend) diff --git a/tests/hazmat/primitives/twofactor/__init__.py b/tests/hazmat/primitives/twofactor/__init__.py new file mode 100644 index 0000000..4b54088 --- /dev/null +++ b/tests/hazmat/primitives/twofactor/__init__.py @@ -0,0 +1,5 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function diff --git a/tests/hazmat/primitives/twofactor/test_hotp.py b/tests/hazmat/primitives/twofactor/test_hotp.py new file mode 100644 index 0000000..4c561f7 --- /dev/null +++ b/tests/hazmat/primitives/twofactor/test_hotp.py @@ -0,0 +1,119 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import os + +import pytest + +from cryptography.exceptions import _Reasons +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.hashes import MD5, SHA1 +from cryptography.hazmat.primitives.twofactor import InvalidToken +from cryptography.hazmat.primitives.twofactor.hotp import HOTP + +from ....utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) + +vectors = load_vectors_from_file( + "twofactor/rfc-4226.txt", load_nist_vectors) + + +@pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), + skip_message="Does not support HMAC-SHA1." +) +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestHOTP(object): + def test_invalid_key_length(self, backend): + secret = os.urandom(10) + + with pytest.raises(ValueError): + HOTP(secret, 6, SHA1(), backend) + + def test_unenforced_invalid_kwy_length(self, backend): + secret = os.urandom(10) + HOTP(secret, 6, SHA1(), backend, enforce_key_length=False) + + def test_invalid_hotp_length(self, backend): + secret = os.urandom(16) + + with pytest.raises(ValueError): + HOTP(secret, 4, SHA1(), backend) + + def test_invalid_algorithm(self, backend): + secret = os.urandom(16) + + with pytest.raises(TypeError): + HOTP(secret, 6, MD5(), backend) + + @pytest.mark.parametrize("params", vectors) + def test_truncate(self, backend, params): + secret = params["secret"] + counter = int(params["counter"]) + truncated = params["truncated"] + + hotp = HOTP(secret, 6, SHA1(), backend) + + assert hotp._dynamic_truncate(counter) == int(truncated.decode(), 16) + + @pytest.mark.parametrize("params", vectors) + def test_generate(self, backend, params): + secret = params["secret"] + counter = int(params["counter"]) + hotp_value = params["hotp"] + + hotp = HOTP(secret, 6, SHA1(), backend) + + assert hotp.generate(counter) == hotp_value + + @pytest.mark.parametrize("params", vectors) + def test_verify(self, backend, params): + secret = params["secret"] + counter = int(params["counter"]) + hotp_value = params["hotp"] + + hotp = HOTP(secret, 6, SHA1(), backend) + + assert hotp.verify(hotp_value, counter) is None + + def test_invalid_verify(self, backend): + secret = b"12345678901234567890" + counter = 0 + + hotp = HOTP(secret, 6, SHA1(), backend) + + with pytest.raises(InvalidToken): + hotp.verify(b"123456", counter) + + def test_length_not_int(self, backend): + secret = b"12345678901234567890" + + with pytest.raises(TypeError): + HOTP(secret, b"foo", SHA1(), backend) + + def test_get_provisioning_uri(self, backend): + secret = b"12345678901234567890" + hotp = HOTP(secret, 6, SHA1(), backend) + + assert hotp.get_provisioning_uri("Alice Smith", 1, None) == ( + "otpauth://hotp/Alice%20Smith?digits=6&secret=GEZDGNBV" + "GY3TQOJQGEZDGNBVGY3TQOJQ&algorithm=SHA1&counter=1") + + assert hotp.get_provisioning_uri("Alice Smith", 1, 'Foo') == ( + "otpauth://hotp/Foo:Alice%20Smith?digits=6&secret=GEZD" + "GNBVGY3TQOJQGEZDGNBVGY3TQOJQ&algorithm=SHA1&issuer=Foo" + "&counter=1") + + +def test_invalid_backend(): + secret = b"12345678901234567890" + + pretend_backend = object() + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + HOTP(secret, 8, hashes.SHA1(), pretend_backend) diff --git a/tests/hazmat/primitives/twofactor/test_totp.py b/tests/hazmat/primitives/twofactor/test_totp.py new file mode 100644 index 0000000..9582971 --- /dev/null +++ b/tests/hazmat/primitives/twofactor/test_totp.py @@ -0,0 +1,149 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest + +from cryptography.exceptions import _Reasons +from cryptography.hazmat.backends.interfaces import HMACBackend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.twofactor import InvalidToken +from cryptography.hazmat.primitives.twofactor.totp import TOTP + +from ....utils import ( + load_nist_vectors, load_vectors_from_file, raises_unsupported_algorithm +) + +vectors = load_vectors_from_file( + "twofactor/rfc-6238.txt", load_nist_vectors) + + +@pytest.mark.requires_backend_interface(interface=HMACBackend) +class TestTOTP(object): + @pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), + skip_message="Does not support HMAC-SHA1." + ) + @pytest.mark.parametrize( + "params", [i for i in vectors if i["mode"] == b"SHA1"]) + def test_generate_sha1(self, backend, params): + secret = params["secret"] + time = int(params["time"]) + totp_value = params["totp"] + + totp = TOTP(secret, 8, hashes.SHA1(), 30, backend) + assert totp.generate(time) == totp_value + + @pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA256()), + skip_message="Does not support HMAC-SHA256." + ) + @pytest.mark.parametrize( + "params", [i for i in vectors if i["mode"] == b"SHA256"]) + def test_generate_sha256(self, backend, params): + secret = params["secret"] + time = int(params["time"]) + totp_value = params["totp"] + + totp = TOTP(secret, 8, hashes.SHA256(), 30, backend) + assert totp.generate(time) == totp_value + + @pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA512()), + skip_message="Does not support HMAC-SHA512." + ) + @pytest.mark.parametrize( + "params", [i for i in vectors if i["mode"] == b"SHA512"]) + def test_generate_sha512(self, backend, params): + secret = params["secret"] + time = int(params["time"]) + totp_value = params["totp"] + + totp = TOTP(secret, 8, hashes.SHA512(), 30, backend) + assert totp.generate(time) == totp_value + + @pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA1()), + skip_message="Does not support HMAC-SHA1." + ) + @pytest.mark.parametrize( + "params", [i for i in vectors if i["mode"] == b"SHA1"]) + def test_verify_sha1(self, backend, params): + secret = params["secret"] + time = int(params["time"]) + totp_value = params["totp"] + + totp = TOTP(secret, 8, hashes.SHA1(), 30, backend) + + assert totp.verify(totp_value, time) is None + + @pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA256()), + skip_message="Does not support HMAC-SHA256." + ) + @pytest.mark.parametrize( + "params", [i for i in vectors if i["mode"] == b"SHA256"]) + def test_verify_sha256(self, backend, params): + secret = params["secret"] + time = int(params["time"]) + totp_value = params["totp"] + + totp = TOTP(secret, 8, hashes.SHA256(), 30, backend) + + assert totp.verify(totp_value, time) is None + + @pytest.mark.supported( + only_if=lambda backend: backend.hmac_supported(hashes.SHA512()), + skip_message="Does not support HMAC-SHA512." + ) + @pytest.mark.parametrize( + "params", [i for i in vectors if i["mode"] == b"SHA512"]) + def test_verify_sha512(self, backend, params): + secret = params["secret"] + time = int(params["time"]) + totp_value = params["totp"] + + totp = TOTP(secret, 8, hashes.SHA512(), 30, backend) + + assert totp.verify(totp_value, time) is None + + def test_invalid_verify(self, backend): + secret = b"12345678901234567890" + time = 59 + + totp = TOTP(secret, 8, hashes.SHA1(), 30, backend) + + with pytest.raises(InvalidToken): + totp.verify(b"12345678", time) + + def test_floating_point_time_generate(self, backend): + secret = b"12345678901234567890" + time = 59.1 + + totp = TOTP(secret, 8, hashes.SHA1(), 30, backend) + + assert totp.generate(time) == b"94287082" + + def test_get_provisioning_uri(self, backend): + secret = b"12345678901234567890" + totp = TOTP(secret, 6, hashes.SHA1(), 30, backend=backend) + + assert totp.get_provisioning_uri("Alice Smith", None) == ( + "otpauth://totp/Alice%20Smith?digits=6&secret=GEZDGNBVG" + "Y3TQOJQGEZDGNBVGY3TQOJQ&algorithm=SHA1&period=30") + + assert totp.get_provisioning_uri("Alice Smith", 'World') == ( + "otpauth://totp/World:Alice%20Smith?digits=6&secret=GEZ" + "DGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&algorithm=SHA1&issuer=World" + "&period=30") + + +def test_invalid_backend(): + secret = b"12345678901234567890" + + pretend_backend = object() + + with raises_unsupported_algorithm(_Reasons.BACKEND_MISSING_INTERFACE): + TOTP(secret, 8, hashes.SHA1(), 30, pretend_backend) diff --git a/tests/hazmat/primitives/utils.py b/tests/hazmat/primitives/utils.py new file mode 100644 index 0000000..d0e87a7 --- /dev/null +++ b/tests/hazmat/primitives/utils.py @@ -0,0 +1,482 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import itertools +import os + +import pytest + +from cryptography.exceptions import ( + AlreadyFinalized, AlreadyUpdated, InvalidSignature, InvalidTag, + NotYetFinalized +) +from cryptography.hazmat.primitives import hashes, hmac +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.ciphers import Cipher +from cryptography.hazmat.primitives.kdf.hkdf import HKDF, HKDFExpand +from cryptography.hazmat.primitives.kdf.kbkdf import ( + CounterLocation, KBKDFHMAC, Mode +) +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + +from ...utils import load_vectors_from_file + + +def _load_all_params(path, file_names, param_loader): + all_params = [] + for file_name in file_names: + all_params.extend( + load_vectors_from_file(os.path.join(path, file_name), param_loader) + ) + return all_params + + +def generate_encrypt_test(param_loader, path, file_names, cipher_factory, + mode_factory): + all_params = _load_all_params(path, file_names, param_loader) + + @pytest.mark.parametrize("params", all_params) + def test_encryption(self, backend, params): + encrypt_test(backend, cipher_factory, mode_factory, params) + + return test_encryption + + +def encrypt_test(backend, cipher_factory, mode_factory, params): + if not backend.cipher_supported( + cipher_factory(**params), mode_factory(**params) + ): + pytest.skip("cipher/mode combo is unsupported by this backend") + + plaintext = params["plaintext"] + ciphertext = params["ciphertext"] + cipher = Cipher( + cipher_factory(**params), + mode_factory(**params), + backend=backend + ) + encryptor = cipher.encryptor() + actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext)) + actual_ciphertext += encryptor.finalize() + assert actual_ciphertext == binascii.unhexlify(ciphertext) + decryptor = cipher.decryptor() + actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) + actual_plaintext += decryptor.finalize() + assert actual_plaintext == binascii.unhexlify(plaintext) + + +def generate_aead_test(param_loader, path, file_names, cipher_factory, + mode_factory): + all_params = _load_all_params(path, file_names, param_loader) + + @pytest.mark.parametrize("params", all_params) + def test_aead(self, backend, params): + aead_test(backend, cipher_factory, mode_factory, params) + + return test_aead + + +def aead_test(backend, cipher_factory, mode_factory, params): + if params.get("pt") is not None: + plaintext = params["pt"] + ciphertext = params["ct"] + aad = params["aad"] + if params.get("fail") is True: + cipher = Cipher( + cipher_factory(binascii.unhexlify(params["key"])), + mode_factory(binascii.unhexlify(params["iv"]), + binascii.unhexlify(params["tag"]), + len(binascii.unhexlify(params["tag"]))), + backend + ) + decryptor = cipher.decryptor() + decryptor.authenticate_additional_data(binascii.unhexlify(aad)) + actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) + with pytest.raises(InvalidTag): + decryptor.finalize() + else: + cipher = Cipher( + cipher_factory(binascii.unhexlify(params["key"])), + mode_factory(binascii.unhexlify(params["iv"]), None), + backend + ) + encryptor = cipher.encryptor() + encryptor.authenticate_additional_data(binascii.unhexlify(aad)) + actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext)) + actual_ciphertext += encryptor.finalize() + tag_len = len(binascii.unhexlify(params["tag"])) + assert binascii.hexlify(encryptor.tag[:tag_len]) == params["tag"] + cipher = Cipher( + cipher_factory(binascii.unhexlify(params["key"])), + mode_factory(binascii.unhexlify(params["iv"]), + binascii.unhexlify(params["tag"]), + min_tag_length=tag_len), + backend + ) + decryptor = cipher.decryptor() + decryptor.authenticate_additional_data(binascii.unhexlify(aad)) + actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) + actual_plaintext += decryptor.finalize() + assert actual_plaintext == binascii.unhexlify(plaintext) + + +def generate_stream_encryption_test(param_loader, path, file_names, + cipher_factory): + all_params = _load_all_params(path, file_names, param_loader) + + @pytest.mark.parametrize("params", all_params) + def test_stream_encryption(self, backend, params): + stream_encryption_test(backend, cipher_factory, params) + return test_stream_encryption + + +def stream_encryption_test(backend, cipher_factory, params): + plaintext = params["plaintext"] + ciphertext = params["ciphertext"] + offset = params["offset"] + cipher = Cipher(cipher_factory(**params), None, backend=backend) + encryptor = cipher.encryptor() + # throw away offset bytes + encryptor.update(b"\x00" * int(offset)) + actual_ciphertext = encryptor.update(binascii.unhexlify(plaintext)) + actual_ciphertext += encryptor.finalize() + assert actual_ciphertext == binascii.unhexlify(ciphertext) + decryptor = cipher.decryptor() + decryptor.update(b"\x00" * int(offset)) + actual_plaintext = decryptor.update(binascii.unhexlify(ciphertext)) + actual_plaintext += decryptor.finalize() + assert actual_plaintext == binascii.unhexlify(plaintext) + + +def generate_hash_test(param_loader, path, file_names, hash_cls): + all_params = _load_all_params(path, file_names, param_loader) + + @pytest.mark.parametrize("params", all_params) + def test_hash(self, backend, params): + hash_test(backend, hash_cls, params) + return test_hash + + +def hash_test(backend, algorithm, params): + msg, md = params + m = hashes.Hash(algorithm, backend=backend) + m.update(binascii.unhexlify(msg)) + expected_md = md.replace(" ", "").lower().encode("ascii") + assert m.finalize() == binascii.unhexlify(expected_md) + + +def generate_base_hash_test(algorithm, digest_size, block_size): + def test_base_hash(self, backend): + base_hash_test(backend, algorithm, digest_size, block_size) + return test_base_hash + + +def base_hash_test(backend, algorithm, digest_size, block_size): + m = hashes.Hash(algorithm, backend=backend) + assert m.algorithm.digest_size == digest_size + assert m.algorithm.block_size == block_size + m_copy = m.copy() + assert m != m_copy + assert m._ctx != m_copy._ctx + + m.update(b"abc") + copy = m.copy() + copy.update(b"123") + m.update(b"123") + assert copy.finalize() == m.finalize() + + +def generate_long_string_hash_test(hash_factory, md): + def test_long_string_hash(self, backend): + long_string_hash_test(backend, hash_factory, md) + return test_long_string_hash + + +def long_string_hash_test(backend, algorithm, md): + m = hashes.Hash(algorithm, backend=backend) + m.update(b"a" * 1000000) + assert m.finalize() == binascii.unhexlify(md.lower().encode("ascii")) + + +def generate_base_hmac_test(hash_cls): + def test_base_hmac(self, backend): + base_hmac_test(backend, hash_cls) + return test_base_hmac + + +def base_hmac_test(backend, algorithm): + key = b"ab" + h = hmac.HMAC(binascii.unhexlify(key), algorithm, backend=backend) + h_copy = h.copy() + assert h != h_copy + assert h._ctx != h_copy._ctx + + +def generate_hmac_test(param_loader, path, file_names, algorithm): + all_params = _load_all_params(path, file_names, param_loader) + + @pytest.mark.parametrize("params", all_params) + def test_hmac(self, backend, params): + hmac_test(backend, algorithm, params) + return test_hmac + + +def hmac_test(backend, algorithm, params): + msg, md, key = params + h = hmac.HMAC(binascii.unhexlify(key), algorithm, backend=backend) + h.update(binascii.unhexlify(msg)) + assert h.finalize() == binascii.unhexlify(md.encode("ascii")) + + +def generate_pbkdf2_test(param_loader, path, file_names, algorithm): + all_params = _load_all_params(path, file_names, param_loader) + + @pytest.mark.parametrize("params", all_params) + def test_pbkdf2(self, backend, params): + pbkdf2_test(backend, algorithm, params) + return test_pbkdf2 + + +def pbkdf2_test(backend, algorithm, params): + # Password and salt can contain \0, which should be loaded as a null char. + # The NIST loader loads them as literal strings so we replace with the + # proper value. + kdf = PBKDF2HMAC( + algorithm, + int(params["length"]), + params["salt"], + int(params["iterations"]), + backend + ) + derived_key = kdf.derive(params["password"]) + assert binascii.hexlify(derived_key) == params["derived_key"] + + +def generate_aead_exception_test(cipher_factory, mode_factory): + def test_aead_exception(self, backend): + aead_exception_test(backend, cipher_factory, mode_factory) + return test_aead_exception + + +def aead_exception_test(backend, cipher_factory, mode_factory): + cipher = Cipher( + cipher_factory(binascii.unhexlify(b"0" * 32)), + mode_factory(binascii.unhexlify(b"0" * 24)), + backend + ) + encryptor = cipher.encryptor() + encryptor.update(b"a" * 16) + with pytest.raises(NotYetFinalized): + encryptor.tag + with pytest.raises(AlreadyUpdated): + encryptor.authenticate_additional_data(b"b" * 16) + encryptor.finalize() + with pytest.raises(AlreadyFinalized): + encryptor.authenticate_additional_data(b"b" * 16) + with pytest.raises(AlreadyFinalized): + encryptor.update(b"b" * 16) + with pytest.raises(AlreadyFinalized): + encryptor.finalize() + cipher = Cipher( + cipher_factory(binascii.unhexlify(b"0" * 32)), + mode_factory(binascii.unhexlify(b"0" * 24), b"0" * 16), + backend + ) + decryptor = cipher.decryptor() + decryptor.update(b"a" * 16) + with pytest.raises(AttributeError): + decryptor.tag + + +def generate_aead_tag_exception_test(cipher_factory, mode_factory): + def test_aead_tag_exception(self, backend): + aead_tag_exception_test(backend, cipher_factory, mode_factory) + return test_aead_tag_exception + + +def aead_tag_exception_test(backend, cipher_factory, mode_factory): + cipher = Cipher( + cipher_factory(binascii.unhexlify(b"0" * 32)), + mode_factory(binascii.unhexlify(b"0" * 24)), + backend + ) + with pytest.raises(ValueError): + cipher.decryptor() + + with pytest.raises(ValueError): + mode_factory(binascii.unhexlify(b"0" * 24), b"000") + + with pytest.raises(ValueError): + mode_factory(binascii.unhexlify(b"0" * 24), b"000000", 2) + + cipher = Cipher( + cipher_factory(binascii.unhexlify(b"0" * 32)), + mode_factory(binascii.unhexlify(b"0" * 24), b"0" * 16), + backend + ) + with pytest.raises(ValueError): + cipher.encryptor() + + +def hkdf_derive_test(backend, algorithm, params): + hkdf = HKDF( + algorithm, + int(params["l"]), + salt=binascii.unhexlify(params["salt"]) or None, + info=binascii.unhexlify(params["info"]) or None, + backend=backend + ) + + okm = hkdf.derive(binascii.unhexlify(params["ikm"])) + + assert okm == binascii.unhexlify(params["okm"]) + + +def hkdf_extract_test(backend, algorithm, params): + hkdf = HKDF( + algorithm, + int(params["l"]), + salt=binascii.unhexlify(params["salt"]) or None, + info=binascii.unhexlify(params["info"]) or None, + backend=backend + ) + + prk = hkdf._extract(binascii.unhexlify(params["ikm"])) + + assert prk == binascii.unhexlify(params["prk"]) + + +def hkdf_expand_test(backend, algorithm, params): + hkdf = HKDFExpand( + algorithm, + int(params["l"]), + info=binascii.unhexlify(params["info"]) or None, + backend=backend + ) + + okm = hkdf.derive(binascii.unhexlify(params["prk"])) + + assert okm == binascii.unhexlify(params["okm"]) + + +def generate_hkdf_test(param_loader, path, file_names, algorithm): + all_params = _load_all_params(path, file_names, param_loader) + + all_tests = [hkdf_extract_test, hkdf_expand_test, hkdf_derive_test] + + @pytest.mark.parametrize( + ("params", "hkdf_test"), + itertools.product(all_params, all_tests) + ) + def test_hkdf(self, backend, params, hkdf_test): + hkdf_test(backend, algorithm, params) + + return test_hkdf + + +def generate_kbkdf_counter_mode_test(param_loader, path, file_names): + all_params = _load_all_params(path, file_names, param_loader) + + @pytest.mark.parametrize("params", all_params) + def test_kbkdf(self, backend, params): + kbkdf_counter_mode_test(backend, params) + return test_kbkdf + + +def kbkdf_counter_mode_test(backend, params): + supported_algorithms = { + 'hmac_sha1': hashes.SHA1, + 'hmac_sha224': hashes.SHA224, + 'hmac_sha256': hashes.SHA256, + 'hmac_sha384': hashes.SHA384, + 'hmac_sha512': hashes.SHA512, + } + + supported_counter_locations = { + "before_fixed": CounterLocation.BeforeFixed, + "after_fixed": CounterLocation.AfterFixed, + } + + algorithm = supported_algorithms.get(params.get('prf')) + if algorithm is None or not backend.hmac_supported(algorithm()): + pytest.skip("KBKDF does not support algorithm: {0}".format( + params.get('prf') + )) + + ctr_loc = supported_counter_locations.get(params.get("ctrlocation")) + if ctr_loc is None or not isinstance(ctr_loc, CounterLocation): + pytest.skip("Does not support counter location: {0}".format( + params.get('ctrlocation') + )) + + ctrkdf = KBKDFHMAC( + algorithm(), + Mode.CounterMode, + params['l'] // 8, + params['rlen'] // 8, + None, + ctr_loc, + None, + None, + binascii.unhexlify(params['fixedinputdata']), + backend=backend) + + ko = ctrkdf.derive(binascii.unhexlify(params['ki'])) + assert binascii.hexlify(ko) == params["ko"] + + +def generate_rsa_verification_test(param_loader, path, file_names, hash_alg, + pad_factory): + all_params = _load_all_params(path, file_names, param_loader) + all_params = [i for i in all_params + if i["algorithm"] == hash_alg.name.upper()] + + @pytest.mark.parametrize("params", all_params) + def test_rsa_verification(self, backend, params): + rsa_verification_test(backend, params, hash_alg, pad_factory) + + return test_rsa_verification + + +def rsa_verification_test(backend, params, hash_alg, pad_factory): + public_numbers = rsa.RSAPublicNumbers( + e=params["public_exponent"], + n=params["modulus"] + ) + public_key = public_numbers.public_key(backend) + pad = pad_factory(params, hash_alg) + verifier = public_key.verifier( + binascii.unhexlify(params["s"]), + pad, + hash_alg + ) + verifier.update(binascii.unhexlify(params["msg"])) + if params["fail"]: + with pytest.raises(InvalidSignature): + verifier.verify() + else: + verifier.verify() + + +def _check_rsa_private_numbers(skey): + assert skey + pkey = skey.public_numbers + assert pkey + assert pkey.e + assert pkey.n + assert skey.d + assert skey.p * skey.q == pkey.n + assert skey.dmp1 == rsa.rsa_crt_dmp1(skey.d, skey.p) + assert skey.dmq1 == rsa.rsa_crt_dmq1(skey.d, skey.q) + assert skey.iqmp == rsa.rsa_crt_iqmp(skey.p, skey.q) + + +def _check_dsa_private_numbers(skey): + assert skey + pkey = skey.public_numbers + params = pkey.parameter_numbers + assert pow(params.g, skey.x, params.p) == pkey.y diff --git a/tests/hypothesis/__init__.py b/tests/hypothesis/__init__.py new file mode 100644 index 0000000..0b34406 --- /dev/null +++ b/tests/hypothesis/__init__.py @@ -0,0 +1,9 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import pytest +# hypothesis no longer supports Python 2.6 so we simply skip it there +pytest.importorskip("hypothesis") diff --git a/tests/hypothesis/test_fernet.py b/tests/hypothesis/test_fernet.py new file mode 100644 index 0000000..c652869 --- /dev/null +++ b/tests/hypothesis/test_fernet.py @@ -0,0 +1,15 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from hypothesis import given +from hypothesis.strategies import binary + +from cryptography.fernet import Fernet + + +@given(binary()) +def test_fernet(data): + f = Fernet(Fernet.generate_key()) + ct = f.encrypt(data) + assert f.decrypt(ct) == data diff --git a/tests/hypothesis/test_padding.py b/tests/hypothesis/test_padding.py new file mode 100644 index 0000000..a433391 --- /dev/null +++ b/tests/hypothesis/test_padding.py @@ -0,0 +1,32 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from hypothesis import given +from hypothesis.strategies import binary, integers + +from cryptography.hazmat.primitives.padding import ANSIX923, PKCS7 + + +@given(integers(min_value=1, max_value=255), binary()) +def test_pkcs7(block_size, data): + # Generate in [1, 31] so we can easily get block_size in bits by + # multiplying by 8. + p = PKCS7(block_size=block_size * 8) + padder = p.padder() + unpadder = p.unpadder() + + padded = padder.update(data) + padder.finalize() + + assert unpadder.update(padded) + unpadder.finalize() == data + + +@given(integers(min_value=1, max_value=255), binary()) +def test_ansix923(block_size, data): + a = ANSIX923(block_size=block_size * 8) + padder = a.padder() + unpadder = a.unpadder() + + padded = padder.update(data) + padder.finalize() + + assert unpadder.update(padded) + unpadder.finalize() == data diff --git a/tests/test_cryptography_utils.py b/tests/test_cryptography_utils.py new file mode 100644 index 0000000..037d11c --- /dev/null +++ b/tests/test_cryptography_utils.py @@ -0,0 +1,11 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +from cryptography import utils + + +def test_int_from_bytes_bytearray(): + assert utils.int_from_bytes(bytearray(b"\x02\x10"), "big") == 528 diff --git a/tests/test_fernet.py b/tests/test_fernet.py new file mode 100644 index 0000000..dbce44f --- /dev/null +++ b/tests/test_fernet.py @@ -0,0 +1,158 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import base64 +import calendar +import json +import os +import time + +import iso8601 + +import pytest + +import six + +from cryptography.fernet import Fernet, InvalidToken, MultiFernet +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.backends.interfaces import CipherBackend, HMACBackend +from cryptography.hazmat.primitives.ciphers import algorithms, modes + +import cryptography_vectors + + +def json_parametrize(keys, filename): + vector_file = cryptography_vectors.open_vector_file( + os.path.join('fernet', filename), "r" + ) + with vector_file: + data = json.load(vector_file) + return pytest.mark.parametrize(keys, [ + tuple([entry[k] for k in keys]) + for entry in data + ]) + + +def test_default_backend(): + f = Fernet(Fernet.generate_key()) + assert f._backend is default_backend() + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +@pytest.mark.requires_backend_interface(interface=HMACBackend) +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 32), modes.CBC(b"\x00" * 16) + ), + skip_message="Does not support AES CBC", +) +class TestFernet(object): + @json_parametrize( + ("secret", "now", "iv", "src", "token"), "generate.json", + ) + def test_generate(self, secret, now, iv, src, token, backend): + f = Fernet(secret.encode("ascii"), backend=backend) + actual_token = f._encrypt_from_parts( + src.encode("ascii"), + calendar.timegm(iso8601.parse_date(now).utctimetuple()), + b"".join(map(six.int2byte, iv)) + ) + assert actual_token == token.encode("ascii") + + @json_parametrize( + ("secret", "now", "src", "ttl_sec", "token"), "verify.json", + ) + def test_verify(self, secret, now, src, ttl_sec, token, backend, + monkeypatch): + f = Fernet(secret.encode("ascii"), backend=backend) + current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) + monkeypatch.setattr(time, "time", lambda: current_time) + payload = f.decrypt(token.encode("ascii"), ttl=ttl_sec) + assert payload == src.encode("ascii") + + @json_parametrize(("secret", "token", "now", "ttl_sec"), "invalid.json") + def test_invalid(self, secret, token, now, ttl_sec, backend, monkeypatch): + f = Fernet(secret.encode("ascii"), backend=backend) + current_time = calendar.timegm(iso8601.parse_date(now).utctimetuple()) + monkeypatch.setattr(time, "time", lambda: current_time) + with pytest.raises(InvalidToken): + f.decrypt(token.encode("ascii"), ttl=ttl_sec) + + def test_invalid_start_byte(self, backend): + f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + with pytest.raises(InvalidToken): + f.decrypt(base64.urlsafe_b64encode(b"\x81")) + + def test_timestamp_too_short(self, backend): + f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + with pytest.raises(InvalidToken): + f.decrypt(base64.urlsafe_b64encode(b"\x80abc")) + + def test_non_base64_token(self, backend): + f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + with pytest.raises(InvalidToken): + f.decrypt(b"\x00") + + def test_unicode(self, backend): + f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + with pytest.raises(TypeError): + f.encrypt(u"") + with pytest.raises(TypeError): + f.decrypt(u"") + + def test_timestamp_ignored_no_ttl(self, monkeypatch, backend): + f = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + pt = b"encrypt me" + token = f.encrypt(pt) + ts = "1985-10-26T01:20:01-07:00" + current_time = calendar.timegm(iso8601.parse_date(ts).utctimetuple()) + monkeypatch.setattr(time, "time", lambda: current_time) + assert f.decrypt(token, ttl=None) == pt + + @pytest.mark.parametrize("message", [b"", b"Abc!", b"\x00\xFF\x00\x80"]) + def test_roundtrips(self, message, backend): + f = Fernet(Fernet.generate_key(), backend=backend) + assert f.decrypt(f.encrypt(message)) == message + + def test_bad_key(self, backend): + with pytest.raises(ValueError): + Fernet(base64.urlsafe_b64encode(b"abc"), backend=backend) + + +@pytest.mark.requires_backend_interface(interface=CipherBackend) +@pytest.mark.requires_backend_interface(interface=HMACBackend) +@pytest.mark.supported( + only_if=lambda backend: backend.cipher_supported( + algorithms.AES(b"\x00" * 32), modes.CBC(b"\x00" * 16) + ), + skip_message="Does not support AES CBC", +) +class TestMultiFernet(object): + def test_encrypt(self, backend): + f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) + f = MultiFernet([f1, f2]) + + assert f1.decrypt(f.encrypt(b"abc")) == b"abc" + + def test_decrypt(self, backend): + f1 = Fernet(base64.urlsafe_b64encode(b"\x00" * 32), backend=backend) + f2 = Fernet(base64.urlsafe_b64encode(b"\x01" * 32), backend=backend) + f = MultiFernet([f1, f2]) + + assert f.decrypt(f1.encrypt(b"abc")) == b"abc" + assert f.decrypt(f2.encrypt(b"abc")) == b"abc" + + with pytest.raises(InvalidToken): + f.decrypt(b"\x00" * 16) + + def test_no_fernets(self, backend): + with pytest.raises(ValueError): + MultiFernet([]) + + def test_non_iterable_argument(self, backend): + with pytest.raises(TypeError): + MultiFernet(None) diff --git a/tests/test_interfaces.py b/tests/test_interfaces.py new file mode 100644 index 0000000..97df45a --- /dev/null +++ b/tests/test_interfaces.py @@ -0,0 +1,84 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +import abc + +import pytest + +import six + +from cryptography.utils import ( + InterfaceNotImplemented, register_interface_if, verify_interface +) + + +def test_register_interface_if_true(): + @six.add_metaclass(abc.ABCMeta) + class SimpleInterface(object): + pass + + @register_interface_if(1 == 1, SimpleInterface) + class SimpleClass(object): + pass + + assert issubclass(SimpleClass, SimpleInterface) is True + + +def test_register_interface_if_false(): + @six.add_metaclass(abc.ABCMeta) + class SimpleInterface(object): + pass + + @register_interface_if(1 == 2, SimpleInterface) + class SimpleClass(object): + pass + + assert issubclass(SimpleClass, SimpleInterface) is False + + +class TestVerifyInterface(object): + def test_verify_missing_method(self): + @six.add_metaclass(abc.ABCMeta) + class SimpleInterface(object): + @abc.abstractmethod + def method(self): + """A simple method""" + + class NonImplementer(object): + pass + + with pytest.raises(InterfaceNotImplemented): + verify_interface(SimpleInterface, NonImplementer) + + def test_different_arguments(self): + @six.add_metaclass(abc.ABCMeta) + class SimpleInterface(object): + @abc.abstractmethod + def method(self, a): + """Method with one argument""" + + class NonImplementer(object): + def method(self): + """Method with no arguments""" + + # Invoke this to ensure the line is covered + NonImplementer().method() + with pytest.raises(InterfaceNotImplemented): + verify_interface(SimpleInterface, NonImplementer) + + def test_handles_abstract_property(self): + @six.add_metaclass(abc.ABCMeta) + class SimpleInterface(object): + @abc.abstractproperty + def property(self): + """An abstract property""" + + class NonImplementer(object): + @property + def property(self): + """A concrete property""" + + # Invoke this to ensure the line is covered + NonImplementer().property + verify_interface(SimpleInterface, NonImplementer) diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..0f69523 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,3625 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import os +import textwrap + +import pretend + +import pytest + +import cryptography +from cryptography.exceptions import UnsupportedAlgorithm, _Reasons + +import cryptography_vectors + +from .utils import ( + check_backend_support, load_cryptrec_vectors, + load_fips_dsa_key_pair_vectors, load_fips_dsa_sig_vectors, + load_fips_ecdsa_key_pair_vectors, load_fips_ecdsa_signing_vectors, + load_hash_vectors, load_kasvs_dh_vectors, + load_kasvs_ecdh_vectors, load_nist_kbkdf_vectors, load_nist_vectors, + load_pkcs1_vectors, load_rsa_nist_vectors, load_vectors_from_file, + load_x963_vectors, raises_unsupported_algorithm, select_backends, + skip_if_empty +) + + +class FakeInterface(object): + pass + + +def test_select_one_backend(): + b1 = pretend.stub(name="b1") + b2 = pretend.stub(name="b2") + b3 = pretend.stub(name="b3") + backends = [b1, b2, b3] + name = "b2" + selected_backends = select_backends(name, backends) + assert len(selected_backends) == 1 + assert selected_backends[0] == b2 + + +def test_select_no_backend(): + b1 = pretend.stub(name="b1") + b2 = pretend.stub(name="b2") + b3 = pretend.stub(name="b3") + backends = [b1, b2, b3] + name = "back!" + with pytest.raises(ValueError): + select_backends(name, backends) + + +def test_select_backends_none(): + b1 = pretend.stub(name="b1") + b2 = pretend.stub(name="b2") + b3 = pretend.stub(name="b3") + backends = [b1, b2, b3] + name = None + selected_backends = select_backends(name, backends) + assert len(selected_backends) == 3 + + +def test_select_two_backends(): + b1 = pretend.stub(name="b1") + b2 = pretend.stub(name="b2") + b3 = pretend.stub(name="b3") + backends = [b1, b2, b3] + name = "b2 ,b1 " + selected_backends = select_backends(name, backends) + assert len(selected_backends) == 2 + assert selected_backends == [b1, b2] + + +def test_skip_if_empty(): + with pytest.raises(pytest.skip.Exception): + skip_if_empty([], [FakeInterface]) + + skip_if_empty(["notempty"], [FakeInterface]) + + +def test_check_backend_support_skip(): + supported = pretend.stub( + kwargs={"only_if": lambda backend: False, "skip_message": "Nope"} + ) + item = pretend.stub(keywords={"supported": [supported]}, + funcargs={"backend": True}) + with pytest.raises(pytest.skip.Exception) as exc_info: + check_backend_support(item) + assert exc_info.value.args[0] == "Nope (True)" + + +def test_check_backend_support_no_skip(): + supported = pretend.stub( + kwargs={"only_if": lambda backend: True, "skip_message": "Nope"} + ) + item = pretend.stub(keywords={"supported": [supported]}, + funcargs={"backend": True}) + assert check_backend_support(item) is None + + +def test_check_backend_support_no_backend(): + supported = pretend.stub( + kwargs={"only_if": "notalambda", "skip_message": "Nope"} + ) + item = pretend.stub(keywords={"supported": supported}, + funcargs={}) + with pytest.raises(ValueError): + check_backend_support(item) + + +def test_load_nist_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 11.1 + # Config info for aes_values + # AESVS GFSbox test data for CBC + # State : Encrypt and Decrypt + # Key Length : 128 + # Generated on Fri Apr 22 15:11:33 2011 + + [ENCRYPT] + + COUNT = 0 + KEY = 00000000000000000000000000000000 + IV = 00000000000000000000000000000000 + PLAINTEXT = f34481ec3cc627bacd5dc3fb08f273e6 + CIPHERTEXT = 0336763e966d92595a567cc9ce537f5e + + COUNT = 1 + KEY = 00000000000000000000000000000000 + IV = 00000000000000000000000000000000 + PLAINTEXT = 9798c4640bad75c7c3227db910174e72 + CIPHERTEXT = a9a1631bf4996954ebc093957b234589 + + [DECRYPT] + + COUNT = 0 + KEY = 00000000000000000000000000000000 + IV = 00000000000000000000000000000000 + CIPHERTEXT = 0336763e966d92595a567cc9ce537f5e + PLAINTEXT = f34481ec3cc627bacd5dc3fb08f273e6 + + COUNT = 1 + KEY = 00000000000000000000000000000000 + IV = 00000000000000000000000000000000 + CIPHERTEXT = a9a1631bf4996954ebc093957b234589 + PLAINTEXT = 9798c4640bad75c7c3227db910174e72 + """).splitlines() + + assert load_nist_vectors(vector_data) == [ + { + "key": b"00000000000000000000000000000000", + "iv": b"00000000000000000000000000000000", + "plaintext": b"f34481ec3cc627bacd5dc3fb08f273e6", + "ciphertext": b"0336763e966d92595a567cc9ce537f5e", + }, + { + "key": b"00000000000000000000000000000000", + "iv": b"00000000000000000000000000000000", + "plaintext": b"9798c4640bad75c7c3227db910174e72", + "ciphertext": b"a9a1631bf4996954ebc093957b234589", + }, + { + "key": b"00000000000000000000000000000000", + "iv": b"00000000000000000000000000000000", + "plaintext": b"f34481ec3cc627bacd5dc3fb08f273e6", + "ciphertext": b"0336763e966d92595a567cc9ce537f5e", + }, + { + "key": b"00000000000000000000000000000000", + "iv": b"00000000000000000000000000000000", + "plaintext": b"9798c4640bad75c7c3227db910174e72", + "ciphertext": b"a9a1631bf4996954ebc093957b234589", + }, + ] + + +def test_load_nist_vectors_with_null_chars(): + vector_data = textwrap.dedent(""" + COUNT = 0 + KEY = thing\\0withnulls + + COUNT = 1 + KEY = 00000000000000000000000000000000 + """).splitlines() + + assert load_nist_vectors(vector_data) == [ + { + "key": b"thing\x00withnulls", + }, + { + "key": b"00000000000000000000000000000000", + }, + ] + + +def test_load_cryptrec_vectors(): + vector_data = textwrap.dedent(""" + # Vectors taken from http://info.isl.ntt.co.jp/crypt/eng/camellia/ + # Download is t_camelia.txt + + # Camellia with 128-bit key + + K No.001 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + P No.001 : 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + C No.001 : 07 92 3A 39 EB 0A 81 7D 1C 4D 87 BD B8 2D 1F 1C + + P No.002 : 40 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + C No.002 : 48 CD 64 19 80 96 72 D2 34 92 60 D8 9A 08 D3 D3 + + K No.002 : 10 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + + P No.001 : 80 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + C No.001 : 07 92 3A 39 EB 0A 81 7D 1C 4D 87 BD B8 2D 1F 1C + """).splitlines() + + assert load_cryptrec_vectors(vector_data) == [ + { + "key": b"00000000000000000000000000000000", + "plaintext": b"80000000000000000000000000000000", + "ciphertext": b"07923A39EB0A817D1C4D87BDB82D1F1C", + }, + { + "key": b"00000000000000000000000000000000", + "plaintext": b"40000000000000000000000000000000", + "ciphertext": b"48CD6419809672D2349260D89A08D3D3", + }, + { + "key": b"10000000000000000000000000000000", + "plaintext": b"80000000000000000000000000000000", + "ciphertext": b"07923A39EB0A817D1C4D87BDB82D1F1C", + }, + ] + + +def test_load_cryptrec_vectors_invalid(): + vector_data = textwrap.dedent(""" + # Vectors taken from http://info.isl.ntt.co.jp/crypt/eng/camellia/ + # Download is t_camelia.txt + + # Camellia with 128-bit key + + E No.001 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + """).splitlines() + + with pytest.raises(ValueError): + load_cryptrec_vectors(vector_data) + + +def test_load_hash_vectors(): + vector_data = textwrap.dedent(""" + + # http://tools.ietf.org/html/rfc1321 + [irrelevant] + + Len = 0 + Msg = 00 + MD = d41d8cd98f00b204e9800998ecf8427e + + Len = 8 + Msg = 61 + MD = 0cc175b9c0f1b6a831c399e269772661 + + Len = 24 + Msg = 616263 + MD = 900150983cd24fb0d6963f7d28e17f72 + + Len = 112 + Msg = 6d65737361676520646967657374 + MD = f96b697d7cb7938d525a2f31aaf161d0 + """).splitlines() + assert load_hash_vectors(vector_data) == [ + (b"", "d41d8cd98f00b204e9800998ecf8427e"), + (b"61", "0cc175b9c0f1b6a831c399e269772661"), + (b"616263", "900150983cd24fb0d6963f7d28e17f72"), + (b"6d65737361676520646967657374", "f96b697d7cb7938d525a2f31aaf161d0"), + ] + + +def test_load_hmac_vectors(): + vector_data = textwrap.dedent(""" +Len = 224 +# "Jefe" +Key = 4a656665 +# "what do ya want for nothing?" +Msg = 7768617420646f2079612077616e7420666f72206e6f7468696e673f +MD = 750c783e6ab0b503eaa86e310a5db738 + """).splitlines() + assert load_hash_vectors(vector_data) == [ + (b"7768617420646f2079612077616e7420666f72206e6f7468696e673f", + "750c783e6ab0b503eaa86e310a5db738", + b"4a656665"), + ] + + +def test_load_hash_vectors_bad_data(): + vector_data = textwrap.dedent(""" + # http://tools.ietf.org/html/rfc1321 + + Len = 0 + Msg = 00 + UNKNOWN=Hello World + """).splitlines() + with pytest.raises(ValueError): + load_hash_vectors(vector_data) + + +def test_load_vectors_from_file(): + vectors = load_vectors_from_file( + os.path.join("ciphers", "Blowfish", "bf-cfb.txt"), + load_nist_vectors, + ) + assert vectors == [ + { + "key": b"0123456789ABCDEFF0E1D2C3B4A59687", + "iv": b"FEDCBA9876543210", + "plaintext": ( + b"37363534333231204E6F77206973207468652074696D6520666F722000" + ), + "ciphertext": ( + b"E73214A2822139CAF26ECF6D2EB9E76E3DA3DE04D1517200519D57A6C3" + ), + } + ] + + +def test_load_nist_gcm_vectors(): + vector_data = textwrap.dedent(""" + [Keylen = 128] + [IVlen = 96] + [PTlen = 0] + [AADlen = 0] + [Taglen = 128] + + Count = 0 + Key = 11754cd72aec309bf52f7687212e8957 + IV = 3c819d9a9bed087615030b65 + PT = + AAD = + CT = + Tag = 250327c674aaf477aef2675748cf6971 + + Count = 1 + Key = 272f16edb81a7abbea887357a58c1917 + IV = 794ec588176c703d3d2a7a07 + PT = + AAD = + CT = + Tag = b6e6f197168f5049aeda32dafbdaeb + + Count = 2 + Key = a49a5e26a2f8cb63d05546c2a62f5343 + IV = 907763b19b9b4ab6bd4f0281 + CT = + AAD = + Tag = a2be08210d8c470a8df6e8fbd79ec5cf + FAIL + + Count = 3 + Key = 5c1155084cc0ede76b3bc22e9f7574ef + IV = 9549e4ba69a61cad7856efc1 + PT = d1448fa852b84408e2dad8381f363de7 + AAD = e98e9d9c618e46fef32660976f854ee3 + CT = f78b60ca125218493bea1c50a2e12ef4 + Tag = d72da7f5c6cf0bca7242c71835809449 + + [Keylen = 128] + [IVlen = 96] + [PTlen = 0] + [AADlen = 0] + [Taglen = 120] + + Count = 0 + Key = eac258e99c55e6ae8ef1da26640613d7 + IV = 4e8df20faaf2c8eebe922902 + CT = + AAD = + Tag = e39aeaebe86aa309a4d062d6274339 + PT = + + Count = 1 + Key = 3726cf02fcc6b8639a5497652c94350d + IV = 55fef82cde693ce76efcc193 + CT = + AAD = + Tag = 3d68111a81ed22d2ef5bccac4fc27f + FAIL + + Count = 2 + Key = f202299d5fd74f03b12d2119a6c4c038 + IV = eec51e7958c3f20a1bb71815 + CT = + AAD = + Tag = a81886b3fb26e51fca87b267e1e157 + FAIL + + Count = 3 + Key = fd52925f39546b4c55ffb6b20c59898c + IV = f5cf3227444afd905a5f6dba + CT = + AAD = + Tag = 1665b0f1a0b456e1664cfd3de08ccd + PT = + + [Keylen = 128] + [IVlen = 8] + [PTlen = 104] + [AADlen = 0] + [Taglen = 128] + + Count = 0 + Key = 58fab7632bcf10d2bcee58520bf37414 + IV = 3c + CT = 15c4db4cbb451211179d57017f + AAD = + Tag = eae841d4355feeb3f786bc86625f1e5b + FAIL + """).splitlines() + assert load_nist_vectors(vector_data) == [ + {'aad': b'', + 'pt': b'', + 'iv': b'3c819d9a9bed087615030b65', + 'tag': b'250327c674aaf477aef2675748cf6971', + 'key': b'11754cd72aec309bf52f7687212e8957', + 'ct': b''}, + {'aad': b'', + 'pt': b'', + 'iv': b'794ec588176c703d3d2a7a07', + 'tag': b'b6e6f197168f5049aeda32dafbdaeb', + 'key': b'272f16edb81a7abbea887357a58c1917', + 'ct': b''}, + {'aad': b'', + 'iv': b'907763b19b9b4ab6bd4f0281', + 'tag': b'a2be08210d8c470a8df6e8fbd79ec5cf', + 'key': b'a49a5e26a2f8cb63d05546c2a62f5343', + 'ct': b'', + 'fail': True}, + {'aad': b'e98e9d9c618e46fef32660976f854ee3', + 'pt': b'd1448fa852b84408e2dad8381f363de7', + 'iv': b'9549e4ba69a61cad7856efc1', + 'tag': b'd72da7f5c6cf0bca7242c71835809449', + 'key': b'5c1155084cc0ede76b3bc22e9f7574ef', + 'ct': b'f78b60ca125218493bea1c50a2e12ef4'}, + {'aad': b'', + 'pt': b'', + 'iv': b'4e8df20faaf2c8eebe922902', + 'tag': b'e39aeaebe86aa309a4d062d6274339', + 'key': b'eac258e99c55e6ae8ef1da26640613d7', + 'ct': b''}, + {'aad': b'', + 'iv': b'55fef82cde693ce76efcc193', + 'tag': b'3d68111a81ed22d2ef5bccac4fc27f', + 'key': b'3726cf02fcc6b8639a5497652c94350d', + 'ct': b'', + 'fail': True}, + {'aad': b'', + 'iv': b'eec51e7958c3f20a1bb71815', + 'tag': b'a81886b3fb26e51fca87b267e1e157', + 'key': b'f202299d5fd74f03b12d2119a6c4c038', + 'ct': b'', + 'fail': True}, + {'aad': b'', + 'pt': b'', + 'iv': b'f5cf3227444afd905a5f6dba', + 'tag': b'1665b0f1a0b456e1664cfd3de08ccd', + 'key': b'fd52925f39546b4c55ffb6b20c59898c', + 'ct': b''}, + {'aad': b'', + 'iv': b'3c', + 'tag': b'eae841d4355feeb3f786bc86625f1e5b', + 'key': b'58fab7632bcf10d2bcee58520bf37414', + 'ct': b'15c4db4cbb451211179d57017f', + 'fail': True}, + ] + + +def test_load_pkcs1_vectors(): + vector_data = textwrap.dedent(""" + Test vectors for RSA-PSS + ======================== + + This file contains an extract of the original pss-vect.txt + + Key lengths: + + Key 8: 1031 bits + Key 9: 1536 bits + =========================================================================== + + + + # Example 8: A 1031-bit RSA key pair + # ----------------------------------- + + + # Public key + # ---------- + + # Modulus: + 49 53 70 a1 fb 18 54 3c 16 d3 63 1e 31 63 25 5d + f6 2b e6 ee e8 90 d5 f2 55 09 e4 f7 78 a8 ea 6f + bb bc df 85 df f6 4e 0d 97 20 03 ab 36 81 fb ba + 6d d4 1f d5 41 82 9b 2e 58 2d e9 f2 a4 a4 e0 a2 + d0 90 0b ef 47 53 db 3c ee 0e e0 6c 7d fa e8 b1 + d5 3b 59 53 21 8f 9c ce ea 69 5b 08 66 8e de aa + dc ed 94 63 b1 d7 90 d5 eb f2 7e 91 15 b4 6c ad + 4d 9a 2b 8e fa b0 56 1b 08 10 34 47 39 ad a0 73 + 3f + + # Exponent: + 01 00 01 + + # Private key + # ----------- + + # Modulus: + 49 53 70 a1 fb 18 54 3c 16 d3 63 1e 31 63 25 5d + f6 2b e6 ee e8 90 d5 f2 55 09 e4 f7 78 a8 ea 6f + bb bc df 85 df f6 4e 0d 97 20 03 ab 36 81 fb ba + 6d d4 1f d5 41 82 9b 2e 58 2d e9 f2 a4 a4 e0 a2 + d0 90 0b ef 47 53 db 3c ee 0e e0 6c 7d fa e8 b1 + d5 3b 59 53 21 8f 9c ce ea 69 5b 08 66 8e de aa + dc ed 94 63 b1 d7 90 d5 eb f2 7e 91 15 b4 6c ad + 4d 9a 2b 8e fa b0 56 1b 08 10 34 47 39 ad a0 73 + 3f + + # Public exponent: + 01 00 01 + + # Exponent: + 6c 66 ff e9 89 80 c3 8f cd ea b5 15 98 98 83 61 + 65 f4 b4 b8 17 c4 f6 a8 d4 86 ee 4e a9 13 0f e9 + b9 09 2b d1 36 d1 84 f9 5f 50 4a 60 7e ac 56 58 + 46 d2 fd d6 59 7a 89 67 c7 39 6e f9 5a 6e ee bb + 45 78 a6 43 96 6d ca 4d 8e e3 de 84 2d e6 32 79 + c6 18 15 9c 1a b5 4a 89 43 7b 6a 61 20 e4 93 0a + fb 52 a4 ba 6c ed 8a 49 47 ac 64 b3 0a 34 97 cb + e7 01 c2 d6 26 6d 51 72 19 ad 0e c6 d3 47 db e9 + + # Prime 1: + 08 da d7 f1 13 63 fa a6 23 d5 d6 d5 e8 a3 19 32 + 8d 82 19 0d 71 27 d2 84 6c 43 9b 0a b7 26 19 b0 + a4 3a 95 32 0e 4e c3 4f c3 a9 ce a8 76 42 23 05 + bd 76 c5 ba 7b e9 e2 f4 10 c8 06 06 45 a1 d2 9e + db + + # Prime 2: + 08 47 e7 32 37 6f c7 90 0f 89 8e a8 2e b2 b0 fc + 41 85 65 fd ae 62 f7 d9 ec 4c e2 21 7b 97 99 0d + d2 72 db 15 7f 99 f6 3c 0d cb b9 fb ac db d4 c4 + da db 6d f6 77 56 35 8c a4 17 48 25 b4 8f 49 70 + 6d + + # Prime exponent 1: + 05 c2 a8 3c 12 4b 36 21 a2 aa 57 ea 2c 3e fe 03 + 5e ff 45 60 f3 3d de bb 7a da b8 1f ce 69 a0 c8 + c2 ed c1 65 20 dd a8 3d 59 a2 3b e8 67 96 3a c6 + 5f 2c c7 10 bb cf b9 6e e1 03 de b7 71 d1 05 fd + 85 + + # Prime exponent 2: + 04 ca e8 aa 0d 9f aa 16 5c 87 b6 82 ec 14 0b 8e + d3 b5 0b 24 59 4b 7a 3b 2c 22 0b 36 69 bb 81 9f + 98 4f 55 31 0a 1a e7 82 36 51 d4 a0 2e 99 44 79 + 72 59 51 39 36 34 34 e5 e3 0a 7e 7d 24 15 51 e1 + b9 + + # Coefficient: + 07 d3 e4 7b f6 86 60 0b 11 ac 28 3c e8 8d bb 3f + 60 51 e8 ef d0 46 80 e4 4c 17 1e f5 31 b8 0b 2b + 7c 39 fc 76 63 20 e2 cf 15 d8 d9 98 20 e9 6f f3 + 0d c6 96 91 83 9c 4b 40 d7 b0 6e 45 30 7d c9 1f + 3f + + # RSA-PSS signing of 6 random messages with random salts + # ------------------------------------------------------- + # PSS Example 8.1 + + # ----------------- + + # Message to be signed: + 81 33 2f 4b e6 29 48 41 5e a1 d8 99 79 2e ea cf + 6c 6e 1d b1 da 8b e1 3b 5c ea 41 db 2f ed 46 70 + 92 e1 ff 39 89 14 c7 14 25 97 75 f5 95 f8 54 7f + 73 56 92 a5 75 e6 92 3a f7 8f 22 c6 99 7d db 90 + fb 6f 72 d7 bb 0d d5 74 4a 31 de cd 3d c3 68 58 + 49 83 6e d3 4a ec 59 63 04 ad 11 84 3c 4f 88 48 + 9f 20 97 35 f5 fb 7f da f7 ce c8 ad dc 58 18 16 + 8f 88 0a cb f4 90 d5 10 05 b7 a8 e8 4e 43 e5 42 + 87 97 75 71 dd 99 ee a4 b1 61 eb 2d f1 f5 10 8f + 12 a4 14 2a 83 32 2e db 05 a7 54 87 a3 43 5c 9a + 78 ce 53 ed 93 bc 55 08 57 d7 a9 fb + + # Salt: + 1d 65 49 1d 79 c8 64 b3 73 00 9b e6 f6 f2 46 7b + ac 4c 78 fa + + # Signature: + 02 62 ac 25 4b fa 77 f3 c1 ac a2 2c 51 79 f8 f0 + 40 42 2b 3c 5b af d4 0a 8f 21 cf 0f a5 a6 67 cc + d5 99 3d 42 db af b4 09 c5 20 e2 5f ce 2b 1e e1 + e7 16 57 7f 1e fa 17 f3 da 28 05 2f 40 f0 41 9b + 23 10 6d 78 45 aa f0 11 25 b6 98 e7 a4 df e9 2d + 39 67 bb 00 c4 d0 d3 5b a3 55 2a b9 a8 b3 ee f0 + 7c 7f ec db c5 42 4a c4 db 1e 20 cb 37 d0 b2 74 + 47 69 94 0e a9 07 e1 7f bb ca 67 3b 20 52 23 80 + c5 + + # PSS Example 8.2 + + # ----------------- + + # Message to be signed: + e2 f9 6e af 0e 05 e7 ba 32 6e cc a0 ba 7f d2 f7 + c0 23 56 f3 ce de 9d 0f aa bf 4f cc 8e 60 a9 73 + e5 59 5f d9 ea 08 + + # Salt: + 43 5c 09 8a a9 90 9e b2 37 7f 12 48 b0 91 b6 89 + 87 ff 18 38 + + # Signature: + 27 07 b9 ad 51 15 c5 8c 94 e9 32 e8 ec 0a 28 0f + 56 33 9e 44 a1 b5 8d 4d dc ff 2f 31 2e 5f 34 dc + fe 39 e8 9c 6a 94 dc ee 86 db bd ae 5b 79 ba 4e + 08 19 a9 e7 bf d9 d9 82 e7 ee 6c 86 ee 68 39 6e + 8b 3a 14 c9 c8 f3 4b 17 8e b7 41 f9 d3 f1 21 10 + 9b f5 c8 17 2f ad a2 e7 68 f9 ea 14 33 03 2c 00 + 4a 8a a0 7e b9 90 00 0a 48 dc 94 c8 ba c8 aa be + 2b 09 b1 aa 46 c0 a2 aa 0e 12 f6 3f bb a7 75 ba + 7e + + # + + # ============================================= + + # Example 9: A 1536-bit RSA key pair + # ----------------------------------- + + + # Public key + # ---------- + + # Modulus: + e6 bd 69 2a c9 66 45 79 04 03 fd d0 f5 be b8 b9 + bf 92 ed 10 00 7f c3 65 04 64 19 dd 06 c0 5c 5b + 5b 2f 48 ec f9 89 e4 ce 26 91 09 97 9c bb 40 b4 + a0 ad 24 d2 24 83 d1 ee 31 5a d4 cc b1 53 42 68 + 35 26 91 c5 24 f6 dd 8e 6c 29 d2 24 cf 24 69 73 + ae c8 6c 5b f6 b1 40 1a 85 0d 1b 9a d1 bb 8c bc + ec 47 b0 6f 0f 8c 7f 45 d3 fc 8f 31 92 99 c5 43 + 3d db c2 b3 05 3b 47 de d2 ec d4 a4 ca ef d6 14 + 83 3d c8 bb 62 2f 31 7e d0 76 b8 05 7f e8 de 3f + 84 48 0a d5 e8 3e 4a 61 90 4a 4f 24 8f b3 97 02 + 73 57 e1 d3 0e 46 31 39 81 5c 6f d4 fd 5a c5 b8 + 17 2a 45 23 0e cb 63 18 a0 4f 14 55 d8 4e 5a 8b + + # Exponent: + 01 00 01 + + # Private key + # ----------- + + # Modulus: + e6 bd 69 2a c9 66 45 79 04 03 fd d0 f5 be b8 b9 + bf 92 ed 10 00 7f c3 65 04 64 19 dd 06 c0 5c 5b + 5b 2f 48 ec f9 89 e4 ce 26 91 09 97 9c bb 40 b4 + a0 ad 24 d2 24 83 d1 ee 31 5a d4 cc b1 53 42 68 + 35 26 91 c5 24 f6 dd 8e 6c 29 d2 24 cf 24 69 73 + ae c8 6c 5b f6 b1 40 1a 85 0d 1b 9a d1 bb 8c bc + ec 47 b0 6f 0f 8c 7f 45 d3 fc 8f 31 92 99 c5 43 + 3d db c2 b3 05 3b 47 de d2 ec d4 a4 ca ef d6 14 + 83 3d c8 bb 62 2f 31 7e d0 76 b8 05 7f e8 de 3f + 84 48 0a d5 e8 3e 4a 61 90 4a 4f 24 8f b3 97 02 + 73 57 e1 d3 0e 46 31 39 81 5c 6f d4 fd 5a c5 b8 + 17 2a 45 23 0e cb 63 18 a0 4f 14 55 d8 4e 5a 8b + + # Public exponent: + 01 00 01 + + # Exponent: + 6a 7f d8 4f b8 5f ad 07 3b 34 40 6d b7 4f 8d 61 + a6 ab c1 21 96 a9 61 dd 79 56 5e 9d a6 e5 18 7b + ce 2d 98 02 50 f7 35 95 75 35 92 70 d9 15 90 bb + 0e 42 7c 71 46 0b 55 d5 14 10 b1 91 bc f3 09 fe + a1 31 a9 2c 8e 70 27 38 fa 71 9f 1e 00 41 f5 2e + 40 e9 1f 22 9f 4d 96 a1 e6 f1 72 e1 55 96 b4 51 + 0a 6d ae c2 61 05 f2 be bc 53 31 6b 87 bd f2 13 + 11 66 60 70 e8 df ee 69 d5 2c 71 a9 76 ca ae 79 + c7 2b 68 d2 85 80 dc 68 6d 9f 51 29 d2 25 f8 2b + 3d 61 55 13 a8 82 b3 db 91 41 6b 48 ce 08 88 82 + 13 e3 7e eb 9a f8 00 d8 1c ab 32 8c e4 20 68 99 + 03 c0 0c 7b 5f d3 1b 75 50 3a 6d 41 96 84 d6 29 + + # Prime 1: + f8 eb 97 e9 8d f1 26 64 ee fd b7 61 59 6a 69 dd + cd 0e 76 da ec e6 ed 4b f5 a1 b5 0a c0 86 f7 92 + 8a 4d 2f 87 26 a7 7e 51 5b 74 da 41 98 8f 22 0b + 1c c8 7a a1 fc 81 0c e9 9a 82 f2 d1 ce 82 1e dc + ed 79 4c 69 41 f4 2c 7a 1a 0b 8c 4d 28 c7 5e c6 + 0b 65 22 79 f6 15 4a 76 2a ed 16 5d 47 de e3 67 + + # Prime 2: + ed 4d 71 d0 a6 e2 4b 93 c2 e5 f6 b4 bb e0 5f 5f + b0 af a0 42 d2 04 fe 33 78 d3 65 c2 f2 88 b6 a8 + da d7 ef e4 5d 15 3e ef 40 ca cc 7b 81 ff 93 40 + 02 d1 08 99 4b 94 a5 e4 72 8c d9 c9 63 37 5a e4 + 99 65 bd a5 5c bf 0e fe d8 d6 55 3b 40 27 f2 d8 + 62 08 a6 e6 b4 89 c1 76 12 80 92 d6 29 e4 9d 3d + + # Prime exponent 1: + 2b b6 8b dd fb 0c 4f 56 c8 55 8b ff af 89 2d 80 + 43 03 78 41 e7 fa 81 cf a6 1a 38 c5 e3 9b 90 1c + 8e e7 11 22 a5 da 22 27 bd 6c de eb 48 14 52 c1 + 2a d3 d6 1d 5e 4f 77 6a 0a b5 56 59 1b ef e3 e5 + 9e 5a 7f dd b8 34 5e 1f 2f 35 b9 f4 ce e5 7c 32 + 41 4c 08 6a ec 99 3e 93 53 e4 80 d9 ee c6 28 9f + + # Prime exponent 2: + 4f f8 97 70 9f ad 07 97 46 49 45 78 e7 0f d8 54 + 61 30 ee ab 56 27 c4 9b 08 0f 05 ee 4a d9 f3 e4 + b7 cb a9 d6 a5 df f1 13 a4 1c 34 09 33 68 33 f1 + 90 81 6d 8a 6b c4 2e 9b ec 56 b7 56 7d 0f 3c 9c + 69 6d b6 19 b2 45 d9 01 dd 85 6d b7 c8 09 2e 77 + e9 a1 cc cd 56 ee 4d ba 42 c5 fd b6 1a ec 26 69 + + # Coefficient: + 77 b9 d1 13 7b 50 40 4a 98 27 29 31 6e fa fc 7d + fe 66 d3 4e 5a 18 26 00 d5 f3 0a 0a 85 12 05 1c + 56 0d 08 1d 4d 0a 18 35 ec 3d 25 a6 0f 4e 4d 6a + a9 48 b2 bf 3d bb 5b 12 4c bb c3 48 92 55 a3 a9 + 48 37 2f 69 78 49 67 45 f9 43 e1 db 4f 18 38 2c + ea a5 05 df c6 57 57 bb 3f 85 7a 58 dc e5 21 56 + + # PKCS#1 v1.5 Signature Example 2.17 + + # ----------------- + + # Message to be signed: + 06 ad d7 5a b6 89 de 06 77 44 e6 9a 2e bd 4b 90 + fa 93 83 00 3c d0 5f f5 36 cb f2 94 cd 21 5f 09 + 23 b7 fc 90 04 f0 aa 18 52 71 a1 d0 06 1f d0 e9 + 77 7a d1 ec 0c 71 59 1f 57 8b f7 b8 e5 a1 + + # Signature: + 45 14 21 0e 54 1d 5b ad 7d d6 0a e5 49 b9 43 ac + c4 4f 21 39 0d f5 b6 13 18 45 5a 17 61 0d f5 b7 + 4d 84 ae d2 32 f1 7e 59 d9 1d d2 65 99 22 f8 12 + db d4 96 81 69 03 84 b9 54 e9 ad fb 9b 1a 96 8c + 0c bf f7 63 ec ee d6 27 50 c5 91 64 b5 e0 80 a8 + fe f3 d5 5b fe 2a cf ad 27 52 a6 a8 45 9f a1 fa + b4 9a d3 78 c6 96 4b 23 ee 97 fd 10 34 61 0c 5c + c1 4c 61 e0 eb fb 17 11 f8 ad e9 6f e6 55 7b 38 + + # + + # ============================================= + + # + """).splitlines() + + vectors = tuple(load_pkcs1_vectors(vector_data)) + expected = ( + ( + { + 'modulus': int( + '495370a1fb18543c16d3631e3163255df62be6eee890d5f25509e4f77' + '8a8ea6fbbbcdf85dff64e0d972003ab3681fbba6dd41fd541829b2e58' + '2de9f2a4a4e0a2d0900bef4753db3cee0ee06c7dfae8b1d53b5953218' + 'f9cceea695b08668edeaadced9463b1d790d5ebf27e9115b46cad4d9a' + '2b8efab0561b0810344739ada0733f', 16), + 'public_exponent': int('10001', 16), + 'private_exponent': int( + '6c66ffe98980c38fcdeab5159898836165f4b4b817c4f6a8d486ee4ea' + '9130fe9b9092bd136d184f95f504a607eac565846d2fdd6597a8967c7' + '396ef95a6eeebb4578a643966dca4d8ee3de842de63279c618159c1ab' + '54a89437b6a6120e4930afb52a4ba6ced8a4947ac64b30a3497cbe701' + 'c2d6266d517219ad0ec6d347dbe9', 16), + 'p': int( + '8dad7f11363faa623d5d6d5e8a319328d82190d7127d2846c439b0ab7' + '2619b0a43a95320e4ec34fc3a9cea876422305bd76c5ba7be9e2f410c' + '8060645a1d29edb', 16), + 'q': int( + '847e732376fc7900f898ea82eb2b0fc418565fdae62f7d9ec4ce2217b' + '97990dd272db157f99f63c0dcbb9fbacdbd4c4dadb6df67756358ca41' + '74825b48f49706d', 16), + 'dmp1': int( + '05c2a83c124b3621a2aa57ea2c3efe035eff4560f33ddebb7adab81fc' + 'e69a0c8c2edc16520dda83d59a23be867963ac65f2cc710bbcfb96ee1' + '03deb771d105fd85', 16), + 'dmq1': int( + '04cae8aa0d9faa165c87b682ec140b8ed3b50b24594b7a3b2c220b366' + '9bb819f984f55310a1ae7823651d4a02e99447972595139363434e5e3' + '0a7e7d241551e1b9', 16), + 'iqmp': int( + '07d3e47bf686600b11ac283ce88dbb3f6051e8efd04680e44c171ef53' + '1b80b2b7c39fc766320e2cf15d8d99820e96ff30dc69691839c4b40d7' + 'b06e45307dc91f3f', 16), + 'examples': [ + { + 'message': b'81332f4be62948415ea1d899792eeacf6c6e1db1d' + b'a8be13b5cea41db2fed467092e1ff398914c71425' + b'9775f595f8547f735692a575e6923af78f22c6997' + b'ddb90fb6f72d7bb0dd5744a31decd3dc368584983' + b'6ed34aec596304ad11843c4f88489f209735f5fb7' + b'fdaf7cec8addc5818168f880acbf490d51005b7a8' + b'e84e43e54287977571dd99eea4b161eb2df1f5108' + b'f12a4142a83322edb05a75487a3435c9a78ce53ed' + b'93bc550857d7a9fb', + 'salt': b'1d65491d79c864b373009be6f6f2467bac4c78fa', + 'signature': b'0262ac254bfa77f3c1aca22c5179f8f040422b3' + b'c5bafd40a8f21cf0fa5a667ccd5993d42dbafb4' + b'09c520e25fce2b1ee1e716577f1efa17f3da280' + b'52f40f0419b23106d7845aaf01125b698e7a4df' + b'e92d3967bb00c4d0d35ba3552ab9a8b3eef07c7' + b'fecdbc5424ac4db1e20cb37d0b2744769940ea9' + b'07e17fbbca673b20522380c5' + }, { + 'message': b'e2f96eaf0e05e7ba326ecca0ba7fd2f7c02356f3c' + b'ede9d0faabf4fcc8e60a973e5595fd9ea08', + 'salt': b'435c098aa9909eb2377f1248b091b68987ff1838', + 'signature': b'2707b9ad5115c58c94e932e8ec0a280f56339e4' + b'4a1b58d4ddcff2f312e5f34dcfe39e89c6a94dc' + b'ee86dbbdae5b79ba4e0819a9e7bfd9d982e7ee6' + b'c86ee68396e8b3a14c9c8f34b178eb741f9d3f1' + b'21109bf5c8172fada2e768f9ea1433032c004a8' + b'aa07eb990000a48dc94c8bac8aabe2b09b1aa46' + b'c0a2aa0e12f63fbba775ba7e' + } + ] + }, + + { + 'modulus': int( + '495370a1fb18543c16d3631e3163255df62be6eee890d5f25509e4f77' + '8a8ea6fbbbcdf85dff64e0d972003ab3681fbba6dd41fd541829b2e58' + '2de9f2a4a4e0a2d0900bef4753db3cee0ee06c7dfae8b1d53b5953218' + 'f9cceea695b08668edeaadced9463b1d790d5ebf27e9115b46cad4d9a' + '2b8efab0561b0810344739ada0733f', 16), + 'public_exponent': int('10001', 16) + } + ), + ( + { + 'modulus': int( + 'e6bd692ac96645790403fdd0f5beb8b9bf92ed10007fc365046419dd0' + '6c05c5b5b2f48ecf989e4ce269109979cbb40b4a0ad24d22483d1ee31' + '5ad4ccb1534268352691c524f6dd8e6c29d224cf246973aec86c5bf6b' + '1401a850d1b9ad1bb8cbcec47b06f0f8c7f45d3fc8f319299c5433ddb' + 'c2b3053b47ded2ecd4a4caefd614833dc8bb622f317ed076b8057fe8d' + 'e3f84480ad5e83e4a61904a4f248fb397027357e1d30e463139815c6f' + 'd4fd5ac5b8172a45230ecb6318a04f1455d84e5a8b', 16), + 'public_exponent': int('10001', 16), + 'private_exponent': int( + '6a7fd84fb85fad073b34406db74f8d61a6abc12196a961dd79565e9da' + '6e5187bce2d980250f7359575359270d91590bb0e427c71460b55d514' + '10b191bcf309fea131a92c8e702738fa719f1e0041f52e40e91f229f4' + 'd96a1e6f172e15596b4510a6daec26105f2bebc53316b87bdf2131166' + '6070e8dfee69d52c71a976caae79c72b68d28580dc686d9f5129d225f' + '82b3d615513a882b3db91416b48ce08888213e37eeb9af800d81cab32' + '8ce420689903c00c7b5fd31b75503a6d419684d629', 16), + 'p': int( + 'f8eb97e98df12664eefdb761596a69ddcd0e76daece6ed4bf5a1b50ac' + '086f7928a4d2f8726a77e515b74da41988f220b1cc87aa1fc810ce99a' + '82f2d1ce821edced794c6941f42c7a1a0b8c4d28c75ec60b652279f61' + '54a762aed165d47dee367', 16), + 'q': int( + 'ed4d71d0a6e24b93c2e5f6b4bbe05f5fb0afa042d204fe3378d365c2f' + '288b6a8dad7efe45d153eef40cacc7b81ff934002d108994b94a5e472' + '8cd9c963375ae49965bda55cbf0efed8d6553b4027f2d86208a6e6b48' + '9c176128092d629e49d3d', 16), + 'dmp1': int( + '2bb68bddfb0c4f56c8558bffaf892d8043037841e7fa81cfa61a38c5e' + '39b901c8ee71122a5da2227bd6cdeeb481452c12ad3d61d5e4f776a0a' + 'b556591befe3e59e5a7fddb8345e1f2f35b9f4cee57c32414c086aec9' + '93e9353e480d9eec6289f', 16), + 'dmq1': int( + '4ff897709fad079746494578e70fd8546130eeab5627c49b080f05ee4' + 'ad9f3e4b7cba9d6a5dff113a41c3409336833f190816d8a6bc42e9bec' + '56b7567d0f3c9c696db619b245d901dd856db7c8092e77e9a1cccd56e' + 'e4dba42c5fdb61aec2669', 16), + 'iqmp': int( + '77b9d1137b50404a982729316efafc7dfe66d34e5a182600d5f30a0a8' + '512051c560d081d4d0a1835ec3d25a60f4e4d6aa948b2bf3dbb5b124c' + 'bbc3489255a3a948372f6978496745f943e1db4f18382ceaa505dfc65' + '757bb3f857a58dce52156', 16), + 'examples': [ + { + 'message': b'06add75ab689de067744e69a2ebd4b90fa9383003' + b'cd05ff536cbf294cd215f0923b7fc9004f0aa1852' + b'71a1d0061fd0e9777ad1ec0c71591f578bf7b8e5a' + b'1', + 'signature': b'4514210e541d5bad7dd60ae549b943acc44f213' + b'90df5b61318455a17610df5b74d84aed232f17e' + b'59d91dd2659922f812dbd49681690384b954e9a' + b'dfb9b1a968c0cbff763eceed62750c59164b5e0' + b'80a8fef3d55bfe2acfad2752a6a8459fa1fab49' + b'ad378c6964b23ee97fd1034610c5cc14c61e0eb' + b'fb1711f8ade96fe6557b38' + } + ] + }, + + { + 'modulus': int( + 'e6bd692ac96645790403fdd0f5beb8b9bf92ed10007fc365046419dd0' + '6c05c5b5b2f48ecf989e4ce269109979cbb40b4a0ad24d22483d1ee31' + '5ad4ccb1534268352691c524f6dd8e6c29d224cf246973aec86c5bf6b' + '1401a850d1b9ad1bb8cbcec47b06f0f8c7f45d3fc8f319299c5433ddb' + 'c2b3053b47ded2ecd4a4caefd614833dc8bb622f317ed076b8057fe8d' + 'e3f84480ad5e83e4a61904a4f248fb397027357e1d30e463139815c6f' + 'd4fd5ac5b8172a45230ecb6318a04f1455d84e5a8b', 16), + 'public_exponent': int('10001', 16) + } + ) + ) + assert vectors == expected + + +def test_load_pkcs1_oaep_vectors(): + vector_data = textwrap.dedent(""" + Test vectors for RSA-OAEP + ========================= + + This file contains test vectors for the RSA-OAEP encryption + + Key lengths: + + Key 1: 1024 bits + # + =========================================================================== + # Example 1: A 1024-bit RSA key pair + # ----------------------------------- + + + # Public key + # ---------- + + # Modulus: + a8 b3 b2 84 af 8e b5 0b 38 70 34 a8 60 f1 46 c4 + 91 9f 31 87 63 cd 6c 55 98 c8 ae 48 11 a1 e0 ab + c4 c7 e0 b0 82 d6 93 a5 e7 fc ed 67 5c f4 66 85 + 12 77 2c 0c bc 64 a7 42 c6 c6 30 f5 33 c8 cc 72 + f6 2a e8 33 c4 0b f2 58 42 e9 84 bb 78 bd bf 97 + c0 10 7d 55 bd b6 62 f5 c4 e0 fa b9 84 5c b5 14 + 8e f7 39 2d d3 aa ff 93 ae 1e 6b 66 7b b3 d4 24 + 76 16 d4 f5 ba 10 d4 cf d2 26 de 88 d3 9f 16 fb + + # Exponent: + 01 00 01 + + # Private key + # ----------- + + # Modulus: + a8 b3 b2 84 af 8e b5 0b 38 70 34 a8 60 f1 46 c4 + 91 9f 31 87 63 cd 6c 55 98 c8 ae 48 11 a1 e0 ab + c4 c7 e0 b0 82 d6 93 a5 e7 fc ed 67 5c f4 66 85 + 12 77 2c 0c bc 64 a7 42 c6 c6 30 f5 33 c8 cc 72 + f6 2a e8 33 c4 0b f2 58 42 e9 84 bb 78 bd bf 97 + c0 10 7d 55 bd b6 62 f5 c4 e0 fa b9 84 5c b5 14 + 8e f7 39 2d d3 aa ff 93 ae 1e 6b 66 7b b3 d4 24 + 76 16 d4 f5 ba 10 d4 cf d2 26 de 88 d3 9f 16 fb + + # Public exponent: + 01 00 01 + + # Exponent: + 53 33 9c fd b7 9f c8 46 6a 65 5c 73 16 ac a8 5c + 55 fd 8f 6d d8 98 fd af 11 95 17 ef 4f 52 e8 fd + 8e 25 8d f9 3f ee 18 0f a0 e4 ab 29 69 3c d8 3b + 15 2a 55 3d 4a c4 d1 81 2b 8b 9f a5 af 0e 7f 55 + fe 73 04 df 41 57 09 26 f3 31 1f 15 c4 d6 5a 73 + 2c 48 31 16 ee 3d 3d 2d 0a f3 54 9a d9 bf 7c bf + b7 8a d8 84 f8 4d 5b eb 04 72 4d c7 36 9b 31 de + f3 7d 0c f5 39 e9 cf cd d3 de 65 37 29 ea d5 d1 + + # Prime 1: + d3 27 37 e7 26 7f fe 13 41 b2 d5 c0 d1 50 a8 1b + 58 6f b3 13 2b ed 2f 8d 52 62 86 4a 9c b9 f3 0a + f3 8b e4 48 59 8d 41 3a 17 2e fb 80 2c 21 ac f1 + c1 1c 52 0c 2f 26 a4 71 dc ad 21 2e ac 7c a3 9d + + # Prime 2: + cc 88 53 d1 d5 4d a6 30 fa c0 04 f4 71 f2 81 c7 + b8 98 2d 82 24 a4 90 ed be b3 3d 3e 3d 5c c9 3c + 47 65 70 3d 1d d7 91 64 2f 1f 11 6a 0d d8 52 be + 24 19 b2 af 72 bf e9 a0 30 e8 60 b0 28 8b 5d 77 + + # Prime exponent 1: + 0e 12 bf 17 18 e9 ce f5 59 9b a1 c3 88 2f e8 04 + 6a 90 87 4e ef ce 8f 2c cc 20 e4 f2 74 1f b0 a3 + 3a 38 48 ae c9 c9 30 5f be cb d2 d7 68 19 96 7d + 46 71 ac c6 43 1e 40 37 96 8d b3 78 78 e6 95 c1 + + # Prime exponent 2: + 95 29 7b 0f 95 a2 fa 67 d0 07 07 d6 09 df d4 fc + 05 c8 9d af c2 ef 6d 6e a5 5b ec 77 1e a3 33 73 + 4d 92 51 e7 90 82 ec da 86 6e fe f1 3c 45 9e 1a + 63 13 86 b7 e3 54 c8 99 f5 f1 12 ca 85 d7 15 83 + + # Coefficient: + 4f 45 6c 50 24 93 bd c0 ed 2a b7 56 a3 a6 ed 4d + 67 35 2a 69 7d 42 16 e9 32 12 b1 27 a6 3d 54 11 + ce 6f a9 8d 5d be fd 73 26 3e 37 28 14 27 43 81 + 81 66 ed 7d d6 36 87 dd 2a 8c a1 d2 f4 fb d8 e1 + + # RSA-OAEP encryption of 6 random messages with random seeds + # ----------------------------------------------------------- + + # OAEP Example 1.1 + # ------------------ + + # Message: + 66 28 19 4e 12 07 3d b0 3b a9 4c da 9e f9 53 23 + 97 d5 0d ba 79 b9 87 00 4a fe fe 34 + + # Seed: + 18 b7 76 ea 21 06 9d 69 77 6a 33 e9 6b ad 48 e1 + dd a0 a5 ef + + # Encryption: + 35 4f e6 7b 4a 12 6d 5d 35 fe 36 c7 77 79 1a 3f + 7b a1 3d ef 48 4e 2d 39 08 af f7 22 fa d4 68 fb + 21 69 6d e9 5d 0b e9 11 c2 d3 17 4f 8a fc c2 01 + 03 5f 7b 6d 8e 69 40 2d e5 45 16 18 c2 1a 53 5f + a9 d7 bf c5 b8 dd 9f c2 43 f8 cf 92 7d b3 13 22 + d6 e8 81 ea a9 1a 99 61 70 e6 57 a0 5a 26 64 26 + d9 8c 88 00 3f 84 77 c1 22 70 94 a0 d9 fa 1e 8c + 40 24 30 9c e1 ec cc b5 21 00 35 d4 7a c7 2e 8a + + # OAEP Example 1.2 + # ------------------ + + # Message: + 75 0c 40 47 f5 47 e8 e4 14 11 85 65 23 29 8a c9 + ba e2 45 ef af 13 97 fb e5 6f 9d d5 + + # Seed: + 0c c7 42 ce 4a 9b 7f 32 f9 51 bc b2 51 ef d9 25 + fe 4f e3 5f + + # Encryption: + 64 0d b1 ac c5 8e 05 68 fe 54 07 e5 f9 b7 01 df + f8 c3 c9 1e 71 6c 53 6f c7 fc ec 6c b5 b7 1c 11 + 65 98 8d 4a 27 9e 15 77 d7 30 fc 7a 29 93 2e 3f + 00 c8 15 15 23 6d 8d 8e 31 01 7a 7a 09 df 43 52 + d9 04 cd eb 79 aa 58 3a dc c3 1e a6 98 a4 c0 52 + 83 da ba 90 89 be 54 91 f6 7c 1a 4e e4 8d c7 4b + bb e6 64 3a ef 84 66 79 b4 cb 39 5a 35 2d 5e d1 + 15 91 2d f6 96 ff e0 70 29 32 94 6d 71 49 2b 44 + + # ============================================= + """).splitlines() + + vectors = load_pkcs1_vectors(vector_data) + expected = [ + ( + { + 'modulus': int( + 'a8b3b284af8eb50b387034a860f146c4919f318763cd6c5598c8ae481' + '1a1e0abc4c7e0b082d693a5e7fced675cf4668512772c0cbc64a742c6' + 'c630f533c8cc72f62ae833c40bf25842e984bb78bdbf97c0107d55bdb' + '662f5c4e0fab9845cb5148ef7392dd3aaff93ae1e6b667bb3d4247616' + 'd4f5ba10d4cfd226de88d39f16fb', 16), + 'public_exponent': int('10001', 16), + 'private_exponent': int( + '53339cfdb79fc8466a655c7316aca85c55fd8f6dd898fdaf119517ef4' + 'f52e8fd8e258df93fee180fa0e4ab29693cd83b152a553d4ac4d1812b' + '8b9fa5af0e7f55fe7304df41570926f3311f15c4d65a732c483116ee3' + 'd3d2d0af3549ad9bf7cbfb78ad884f84d5beb04724dc7369b31def37d' + '0cf539e9cfcdd3de653729ead5d1', 16), + 'p': int( + 'd32737e7267ffe1341b2d5c0d150a81b586fb3132bed2f8d5262864a9' + 'cb9f30af38be448598d413a172efb802c21acf1c11c520c2f26a471dc' + 'ad212eac7ca39d', 16), + 'q': int( + 'cc8853d1d54da630fac004f471f281c7b8982d8224a490edbeb33d3e3' + 'd5cc93c4765703d1dd791642f1f116a0dd852be2419b2af72bfe9a030' + 'e860b0288b5d77', 16), + 'dmp1': int( + '0e12bf1718e9cef5599ba1c3882fe8046a90874eefce8f2ccc20e4f27' + '41fb0a33a3848aec9c9305fbecbd2d76819967d4671acc6431e403796' + '8db37878e695c1', 16), + 'dmq1': int( + '95297b0f95a2fa67d00707d609dfd4fc05c89dafc2ef6d6ea55bec771' + 'ea333734d9251e79082ecda866efef13c459e1a631386b7e354c899f5' + 'f112ca85d71583', 16), + 'iqmp': int( + '4f456c502493bdc0ed2ab756a3a6ed4d67352a697d4216e93212b127a' + '63d5411ce6fa98d5dbefd73263e3728142743818166ed7dd63687dd2a' + '8ca1d2f4fbd8e1', 16), + 'examples': [ + { + 'message': b'6628194e12073db03ba94cda9ef9532397d50dba7' + b'9b987004afefe34', + 'seed': b'18b776ea21069d69776a33e96bad48e1dda0a5ef', + 'encryption': b'354fe67b4a126d5d35fe36c777791a3f7ba13d' + b'ef484e2d3908aff722fad468fb21696de95d0b' + b'e911c2d3174f8afcc201035f7b6d8e69402de5' + b'451618c21a535fa9d7bfc5b8dd9fc243f8cf92' + b'7db31322d6e881eaa91a996170e657a05a2664' + b'26d98c88003f8477c1227094a0d9fa1e8c4024' + b'309ce1ecccb5210035d47ac72e8a' + }, { + 'message': b'750c4047f547e8e41411856523298ac9bae245efa' + b'f1397fbe56f9dd5', + 'seed': b'0cc742ce4a9b7f32f951bcb251efd925fe4fe35f', + 'encryption': b'640db1acc58e0568fe5407e5f9b701dff8c3c9' + b'1e716c536fc7fcec6cb5b71c1165988d4a279e' + b'1577d730fc7a29932e3f00c81515236d8d8e31' + b'017a7a09df4352d904cdeb79aa583adcc31ea6' + b'98a4c05283daba9089be5491f67c1a4ee48dc7' + b'4bbbe6643aef846679b4cb395a352d5ed11591' + b'2df696ffe0702932946d71492b44' + } + ] + }, + + { + 'modulus': int( + 'a8b3b284af8eb50b387034a860f146c4919f318763cd6c5598c8ae481' + '1a1e0abc4c7e0b082d693a5e7fced675cf4668512772c0cbc64a742c6' + 'c630f533c8cc72f62ae833c40bf25842e984bb78bdbf97c0107d55bdb' + '662f5c4e0fab9845cb5148ef7392dd3aaff93ae1e6b667bb3d4247616' + 'd4f5ba10d4cfd226de88d39f16fb', 16), + 'public_exponent': int('10001', 16), + } + ) + ] + assert vectors == expected + + +def test_load_hotp_vectors(): + vector_data = textwrap.dedent(""" + # HOTP Test Vectors + # RFC 4226 Appendix D + + COUNT = 0 + COUNTER = 0 + INTERMEDIATE = cc93cf18508d94934c64b65d8ba7667fb7cde4b0 + TRUNCATED = 4c93cf18 + HOTP = 755224 + SECRET = 12345678901234567890 + + COUNT = 1 + COUNTER = 1 + INTERMEDIATE = 75a48a19d4cbe100644e8ac1397eea747a2d33ab + TRUNCATED = 41397eea + HOTP = 287082 + SECRET = 12345678901234567890 + + + COUNT = 2 + COUNTER = 2 + INTERMEDIATE = 0bacb7fa082fef30782211938bc1c5e70416ff44 + TRUNCATED = 82fef30 + HOTP = 359152 + SECRET = 12345678901234567890 + + + COUNT = 3 + COUNTER = 3 + INTERMEDIATE = 66c28227d03a2d5529262ff016a1e6ef76557ece + TRUNCATED = 66ef7655 + HOTP = 969429 + SECRET = 12345678901234567890 + """).splitlines() + + assert load_nist_vectors(vector_data) == [ + { + "counter": b"0", + "intermediate": b"cc93cf18508d94934c64b65d8ba7667fb7cde4b0", + "truncated": b"4c93cf18", + "hotp": b"755224", + "secret": b"12345678901234567890", + }, + { + "counter": b"1", + "intermediate": b"75a48a19d4cbe100644e8ac1397eea747a2d33ab", + "truncated": b"41397eea", + "hotp": b"287082", + "secret": b"12345678901234567890", + }, + { + "counter": b"2", + "intermediate": b"0bacb7fa082fef30782211938bc1c5e70416ff44", + "truncated": b"82fef30", + "hotp": b"359152", + "secret": b"12345678901234567890", + }, + { + "counter": b"3", + "intermediate": b"66c28227d03a2d5529262ff016a1e6ef76557ece", + "truncated": b"66ef7655", + "hotp": b"969429", + "secret": b"12345678901234567890", + }, + ] + + +def test_load_totp_vectors(): + vector_data = textwrap.dedent(""" + # TOTP Test Vectors + # RFC 6238 Appendix B + + COUNT = 0 + TIME = 59 + TOTP = 94287082 + MODE = SHA1 + SECRET = 12345678901234567890 + + COUNT = 1 + TIME = 59 + TOTP = 46119246 + MODE = SHA256 + SECRET = 12345678901234567890 + + COUNT = 2 + TIME = 59 + TOTP = 90693936 + MODE = SHA512 + SECRET = 12345678901234567890 + """).splitlines() + + assert load_nist_vectors(vector_data) == [ + { + "time": b"59", + "totp": b"94287082", + "mode": b"SHA1", + "secret": b"12345678901234567890", + }, + { + "time": b"59", + "totp": b"46119246", + "mode": b"SHA256", + "secret": b"12345678901234567890", + }, + { + "time": b"59", + "totp": b"90693936", + "mode": b"SHA512", + "secret": b"12345678901234567890", + }, + ] + + +def test_load_rsa_nist_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 11.4 + # "SigGen PKCS#1 RSASSA-PSS" information + # Mod sizes selected: 1024 1536 2048 3072 4096 + # SHA Algorithm selected:SHA1 SHA224 SHA256 SHA384 SHA512 + # Salt len: 20 + + [mod = 1024] + + n = bcb47b2e0dafcba81ff2a2b5cb115ca7e757184c9d72bcdcda707a146b3b4e29989d + + e = 00000000000000000000000000000000000000000000000000000000000000000010001 + SHAAlg = SHA1 + Msg = 1248f62a4389f42f7b4bb131053d6c88a994db2075b912ccbe3ea7dc611714f14e + S = 682cf53c1145d22a50caa9eb1a9ba70670c5915e0fdfde6457a765de2a8fe12de97 + + SHAAlg = SHA384 + Msg = e511903c2f1bfba245467295ac95413ac4746c984c3750a728c388aa628b0ebf + S = 9c748702bbcc1f9468864cd360c8c39d007b2d8aaee833606c70f7593cf0d1519 + + [mod = 1024] + + n = 1234567890 + + e = 0010001 + + SHAAlg = SHA512 + Msg = 3456781293fab829 + S = deadbeef0000 + """).splitlines() + + vectors = load_rsa_nist_vectors(vector_data) + assert vectors == [ + { + "modulus": int("bcb47b2e0dafcba81ff2a2b5cb115ca7e757184c9d72bcdcda" + "707a146b3b4e29989d", 16), + "public_exponent": 65537, + "algorithm": "SHA1", + "salt_length": 20, + "msg": b"1248f62a4389f42f7b4bb131053d6c88a994db2075b912ccbe3ea7dc6" + b"11714f14e", + "s": b"682cf53c1145d22a50caa9eb1a9ba70670c5915e0fdfde6457a765de2a8" + b"fe12de97", + "fail": False + }, + { + "modulus": int("bcb47b2e0dafcba81ff2a2b5cb115ca7e757184c9d72bcdcda" + "707a146b3b4e29989d", 16), + "public_exponent": 65537, + "algorithm": "SHA384", + "salt_length": 20, + "msg": b"e511903c2f1bfba245467295ac95413ac4746c984c3750a728c388aa6" + b"28b0ebf", + "s": b"9c748702bbcc1f9468864cd360c8c39d007b2d8aaee833606c70f7593cf" + b"0d1519", + "fail": False + }, + { + "modulus": 78187493520, + "public_exponent": 65537, + "algorithm": "SHA512", + "salt_length": 20, + "msg": b"3456781293fab829", + "s": b"deadbeef0000", + "fail": False + }, + ] + + +def test_load_rsa_nist_pkcs1v15_verification_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 11.0 + # "SigVer PKCS#1 Ver 1.5" information + # Mod sizes selected: 1024 1536 2048 3072 4096 + # SHA Algorithm selected:SHA1 SHA224 SHA256 SHA384 SHA512 + # Generated on Wed Mar 02 00:13:02 2011 + + [mod = 1024] + + n = be499b5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b9bfeb7aa72db126411 + + p = e7a80c5d211c06acb900939495f26d365fc2b4825b75e356f89003eaa5931e6be5c3 + q = d248aa248000f720258742da67b711940c8f76e1ecd52b67a6ffe1e49354d66ff84f + + SHAAlg = SHA1 + e = 00000000000000000000000000000000000000000000000000000000000000000011 + d = 0d0f17362bdad181db4e1fe03e8de1a3208989914e14bf269558826bfa20faf4b68d + Msg = 6b9cfac0ba1c7890b13e381ce752195cc1375237db2afcf6a9dcd1f95ec733a80c + S = 562d87b5781c01d166fef3972669a0495c145b898a17df4743fbefb0a1582bd6ba9d + SaltVal = 11223344555432167890 + Result = F (3 - Signature changed ) + + SHAAlg = SHA1 + e = 0000000000003 + d = bfa20faf4b68d + Msg = 2a67c70ff14f9b34ddb42e6f89d5971057a0da980fc9ae70c81a84da0c0ac42737 + S = 2b91c6ae2b3c46ff18d5b7abe239634cb752d0acb53eea0ccd8ea8483036a50e8faf + SaltVal = 11223344555432167890 + Result = P + """).splitlines() + + vectors = load_rsa_nist_vectors(vector_data) + assert vectors == [ + { + "modulus": int("be499b5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b" + "9bfeb7aa72db126411", 16), + "p": int("e7a80c5d211c06acb900939495f26d365fc2b4825b75e356f89003ea" + "a5931e6be5c3", 16), + "q": int("d248aa248000f720258742da67b711940c8f76e1ecd52b67a6ffe1e4" + "9354d66ff84f", 16), + "public_exponent": 17, + "algorithm": "SHA1", + "private_exponent": int("0d0f17362bdad181db4e1fe03e8de1a3208989914" + "e14bf269558826bfa20faf4b68d", 16), + "msg": b"6b9cfac0ba1c7890b13e381ce752195cc1375237db2afcf6a9dcd1f95" + b"ec733a80c", + "s": b"562d87b5781c01d166fef3972669a0495c145b898a17df4743fbefb0a15" + b"82bd6ba9d", + "saltval": b"11223344555432167890", + "fail": True + }, + { + "modulus": int("be499b5e7f06c83fa0293e31465c8eb6b58af920bae52a7b5b" + "9bfeb7aa72db126411", 16), + "p": int("e7a80c5d211c06acb900939495f26d365fc2b4825b75e356f89003ea" + "a5931e6be5c3", 16), + "q": int("d248aa248000f720258742da67b711940c8f76e1ecd52b67a6ffe1e4" + "9354d66ff84f", 16), + "public_exponent": 3, + "algorithm": "SHA1", + "private_exponent": int("bfa20faf4b68d", 16), + "msg": b"2a67c70ff14f9b34ddb42e6f89d5971057a0da980fc9ae70c81a84da0" + b"c0ac42737", + "s": b"2b91c6ae2b3c46ff18d5b7abe239634cb752d0acb53eea0ccd8ea848303" + b"6a50e8faf", + "saltval": b"11223344555432167890", + "fail": False + }, + ] + + +def test_load_rsa_nist_pss_verification_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 11.0 + # "SigVer PKCS#1 RSASSA-PSS" information + # Mod sizes selected: 1024 1536 2048 3072 4096 + # SHA Algorithm selected:SHA1 SHA224 SHA256 SHA384 SHA512 + # Salt len: 10 + # Generated on Wed Mar 02 00:25:22 2011 + + [mod = 1024] + + n = be499b5e7f06c83fa0293e31465c8eb6b5 + + p = e7a80c5d211c06acb900939495f26d365f + q = d248aa248000f720258742da67b711940c + + SHAAlg = SHA1 + e = 00000000000000011 + d = c8e26a88239672cf49b3422a07c4d834ba + Msg = 6b9cfac0ba1c7890b13e381ce752195c + S = 562d87b5781c01d166fef3972669a0495c + SaltVal = 11223344555432167890 + Result = F (3 - Signature changed ) + + SHAAlg = SHA384 + e = 000003 + d = 0d0f17362bdad181db4e1fe03e8de1a320 + Msg = 2a67c70ff14f9b34ddb42e6f89d59710 + S = 2b91c6ae2b3c46ff18d5b7abe239634cb7 + SaltVal = 11223344555432167890 + Result = P + """).splitlines() + + vectors = load_rsa_nist_vectors(vector_data) + assert vectors == [ + { + "modulus": int("be499b5e7f06c83fa0293e31465c8eb6b5", 16), + "p": int("e7a80c5d211c06acb900939495f26d365f", 16), + "q": int("d248aa248000f720258742da67b711940c", 16), + "public_exponent": 17, + "algorithm": "SHA1", + "private_exponent": int("c8e26a88239672cf49b3422a07c4d834ba", 16), + "msg": b"6b9cfac0ba1c7890b13e381ce752195c", + "s": b"562d87b5781c01d166fef3972669a0495c", + "saltval": b"11223344555432167890", + "salt_length": 10, + "fail": True + }, + { + "modulus": int("be499b5e7f06c83fa0293e31465c8eb6b5", 16), + "p": int("e7a80c5d211c06acb900939495f26d365f", 16), + "q": int("d248aa248000f720258742da67b711940c", 16), + "public_exponent": 3, + "algorithm": "SHA384", + "private_exponent": int("0d0f17362bdad181db4e1fe03e8de1a320", 16), + "msg": b"2a67c70ff14f9b34ddb42e6f89d59710", + "s": b"2b91c6ae2b3c46ff18d5b7abe239634cb7", + "saltval": b"11223344555432167890", + "salt_length": 10, + "fail": False + }, + ] + + +def test_load_fips_dsa_key_pair_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 11.1 + # "KeyPair" information + # Mod sizes selected: L=1024, N=160:: L=2048, N=224 :: L=2048, N=256 :: L +=3072, N=256 + # Generated on Wed May 04 08:50:52 2011 + + + [mod = L=1024, N=160] + + P = d38311e2cd388c3ed698e82fdf88eb92b5a9a483dc88005d4b725ef341eabb47cf8a7a\ +8a41e792a156b7ce97206c4f9c5ce6fc5ae7912102b6b502e59050b5b21ce263dddb2044b65223\ +6f4d42ab4b5d6aa73189cef1ace778d7845a5c1c1c7147123188f8dc551054ee162b634d60f097\ +f719076640e20980a0093113a8bd73 + Q = 96c5390a8b612c0e422bb2b0ea194a3ec935a281 + G = 06b7861abbd35cc89e79c52f68d20875389b127361ca66822138ce4991d2b862259d6b\ +4548a6495b195aa0e0b6137ca37eb23b94074d3c3d300042bdf15762812b6333ef7b07ceba7860\ +7610fcc9ee68491dbc1e34cd12615474e52b18bc934fb00c61d39e7da8902291c4434a4e2224c3\ +f4fd9f93cd6f4f17fc076341a7e7d9 + + X = 8185fee9cc7c0e91fd85503274f1cd5a3fd15a49 + Y = 6f26d98d41de7d871b6381851c9d91fa03942092ab6097e76422070edb71db44ff5682\ +80fdb1709f8fc3feab39f1f824adaeb2a298088156ac31af1aa04bf54f475bdcfdcf2f8a2dd973\ +e922d83e76f016558617603129b21c70bf7d0e5dc9e68fe332e295b65876eb9a12fe6fca9f1a1c\ +e80204646bf99b5771d249a6fea627 + + X = 85322d6ea73083064376099ca2f65f56e8522d9b + Y = 21f8690f717c9f4dcb8f4b6971de2f15b9231fcf41b7eeb997d781f240bfdddfd2090d\ +22083c26cca39bf37c9caf1ec89518ea64845a50d747b49131ffff6a2fd11ea7bacbb93c7d0513\ +7383a06365af82225dd3713ca5a45006316f53bd12b0e260d5f79795e5a4c9f353f12867a1d320\ +2394673ada8563b71555e53f415254 + + [mod = L=2048, N=224] + + P = 904ef8e31e14721910fa0969e77c99b79f190071a86026e37a887a6053960dbfb74390\ +a6641319fe0af32c4e982934b0f1f4c5bc57534e8e56d77c36f0a99080c0d5bc9022fa34f58922\ +81d7b1009571cb5b35699303f912b276d86b1b0722fc0b1500f0ffb2e4d90867a3bdca181a9734\ +617a8a9f991aa7c14dec1cf45ceba00600f8425440ed0c3b52c82e3aa831932a98b477da220867\ +eb2d5e0ca34580b33b1b65e558411ed09c369f4717bf03b551787e13d9e47c267c91c697225265\ +da157945cd8b32e84fc45b80533265239aa00a2dd3d05f5cb231b7daf724b7ecdce170360a8397\ +2e5be94626273d449f441be300a7345db387bebadad67d8060a7 + Q = d7d0a83e84d13032b830ed74a6a88592ec9a4cf42bf37080c6600aad + G = 2050b18d3c9f39fac396c009310d6616f9309b67b59aef9aee813d6b4f12ee29ba8a6b\ +350b11d4336d44b4641230002d870f1e6b1d8728bdd40262df0d2440999185ae077f7034c61679\ +f4360fbb5d181569e7cb8acb04371c11ba55f1bbd777b74304b99b66d4405303e7120dc8bc4785\ +f56e9533e65b63a0c77cce7bba0d5d6069df5edffa927c5a255a09405a008258ed93506a843366\ +2154f6f67e922d7c9788f04d4ec09581063950d9cde8e373ea59a58b2a6df6ba8663345574fabb\ +a9ca981696d83aeac1f34f14f1a813ba900b3f0341dea23f7d3297f919a97e1ae00ac0728c93fe\ +0a88b66591baf4eb0bc6900f39ba5feb41cbbeea7eb7919aa4d3 + + X = 3f19424da3b4f0cafca3fc5019fcd225dd7e496ffdf6b77e364f45be + Y = 7681ed0ac257ab7ff17c52de4638c0614749792707a0c0d23883697e34963df15c806f\ +a6206f7fafb3269018e7703bd1e6f518d13544331a017713dbbe0cee8da6c095271fbf24edb74a\ +44e18b1d3b835622f68d31921c67c83e8479d1972ed0cb106c68188fe22c044254251ebf880b90\ +49dc3b7958ef61e1e67d2f677d2a7d2ab6b7c42b70cc5dedc3e5de7459a2dbc70c69008553d7ff\ +b6bf81c012c8bd67bdddeaab9a4a4373027912a7c7d9cd9cfc6c81dffe0cc7a6d40c3b2065aee7\ +be80e3c35497d64c8045bc511edaf7314c84c56bd9f0fecf62262ea5b45b49a0cffb223713bdbd\ +3ad03a25a0bb2211eba41ffcd08ab0e1ad485c29a3fc25ee8359 + + X = 241396352dd26efe0e2e184da52fe2b61d9d51b91b5009674c447854 + Y = 2f07a3aa9884c65288e5fef56c7b7f4445632273290bae6fcaab87c90058b2bef81ad3\ +34958657cf649ffb976d618b34ce69ef6d68c0d8bfe275cf097a301e8dd5595958e0c668c15f67\ +b5c0b0d01983057ce61593635aab5e0564ed720b0336f055a86755c76be22df3b8487f16e2ba0b\ +5136fd30d7e3b1d30c3bd298d3acc0a1988a11756c94e9a53184d0d3edfbb649caf03eace3083d\ +e9933921e627f4b2e011d1c79e45d8ea1eb7e4e59a1cbd8382b3238474eb949749c985200fbb25\ +41e2dce080aa881945d4d935076e48a0846dc5513bb4da8563b946af54f546455931e79c065ce7\ +ca223a98f8fde40091d38eb2c3eb8e3b81d88374f3146b0afc42 + + [mod = L=2048, N=256] + + P = ea1fb1af22881558ef93be8a5f8653c5a559434c49c8c2c12ace5e9c41434c9cf0a8e9\ +498acb0f4663c08b4484eace845f6fb17dac62c98e706af0fc74e4da1c6c2b3fbf5a1d58ff82fc\ +1a66f3e8b12252c40278fff9dd7f102eed2cb5b7323ebf1908c234d935414dded7f8d244e54561\ +b0dca39b301de8c49da9fb23df33c6182e3f983208c560fb5119fbf78ebe3e6564ee235c6a15cb\ +b9ac247baba5a423bc6582a1a9d8a2b4f0e9e3d9dbac122f750dd754325135257488b1f6ecabf2\ +1bff2947fe0d3b2cb7ffe67f4e7fcdf1214f6053e72a5bb0dd20a0e9fe6db2df0a908c36e95e60\ +bf49ca4368b8b892b9c79f61ef91c47567c40e1f80ac5aa66ef7 + Q = 8ec73f3761caf5fdfe6e4e82098bf10f898740dcb808204bf6b18f507192c19d + G = e4c4eca88415b23ecf811c96e48cd24200fe916631a68a684e6ccb6b1913413d344d1d\ +8d84a333839d88eee431521f6e357c16e6a93be111a98076739cd401bab3b9d565bf4fb99e9d18\ +5b1e14d61c93700133f908bae03e28764d107dcd2ea7674217622074bb19efff482f5f5c1a86d5\ +551b2fc68d1c6e9d8011958ef4b9c2a3a55d0d3c882e6ad7f9f0f3c61568f78d0706b10a26f23b\ +4f197c322b825002284a0aca91807bba98ece912b80e10cdf180cf99a35f210c1655fbfdd74f13\ +b1b5046591f8403873d12239834dd6c4eceb42bf7482e1794a1601357b629ddfa971f2ed273b14\ +6ec1ca06d0adf55dd91d65c37297bda78c6d210c0bc26e558302 + + X = 405772da6e90d809e77d5de796562a2dd4dfd10ef00a83a3aba6bd818a0348a1 + Y = 6b32e31ab9031dc4dd0b5039a78d07826687ab087ae6de4736f5b0434e1253092e8a0b\ +231f9c87f3fc8a4cb5634eb194bf1b638b7a7889620ce6711567e36aa36cda4604cfaa601a4591\ +8371d4ccf68d8b10a50a0460eb1dc0fff62ef5e6ee4d473e18ea4a66c196fb7e677a49b48241a0\ +b4a97128eff30fa437050501a584f8771e7280d26d5af30784039159c11ebfea10b692fd0a5821\ +5eeb18bff117e13f08db792ed4151a218e4bed8dddfb0793225bd1e9773505166f4bd8cedbb286\ +ea28232972da7bae836ba97329ba6b0a36508e50a52a7675e476d4d4137eae13f22a9d2fefde70\ +8ba8f34bf336c6e76331761e4b0617633fe7ec3f23672fb19d27 + + X = 0e0b95e31fda3f888059c46c3002ef8f2d6be112d0209aeb9e9545da67aeea80 + Y = 778082b77ddba6f56597cc74c3a612abf2ddbd85cc81430c99ab843c1f630b9db01399\ +65f563978164f9bf3a8397256be714625cd41cd7fa0067d94ea66d7e073f7125af692ad01371d4\ +a17f4550590378f2b074030c20e36911598a1018772f61be3b24de4be5a388ccc09e15a92819c3\ +1dec50de9fde105b49eaa097b9d13d9219eeb33b628facfd1c78a7159c8430d0647c506e7e3de7\ +4763cb351eada72c00bef3c9641881e6254870c1e6599f8ca2f1bbb74f39a905e3a34e4544168e\ +6e50c9e3305fd09cab6ed4aff6fda6e0d5bf375c81ac9054406d9193b003c89272f1bd83d48250\ +134b65c77c2b6332d38d34d9016f0e8975536ad6c348a1faedb0 + + [mod = L=3072, N=256] + + P = f335666dd1339165af8b9a5e3835adfe15c158e4c3c7bd53132e7d5828c352f593a9a7\ +87760ce34b789879941f2f01f02319f6ae0b756f1a842ba54c85612ed632ee2d79ef17f06b77c6\ +41b7b080aff52a03fc2462e80abc64d223723c236deeb7d201078ec01ca1fbc1763139e25099a8\ +4ec389159c409792080736bd7caa816b92edf23f2c351f90074aa5ea2651b372f8b58a0a65554d\ +b2561d706a63685000ac576b7e4562e262a14285a9c6370b290e4eb7757527d80b6c0fd5df831d\ +36f3d1d35f12ab060548de1605fd15f7c7aafed688b146a02c945156e284f5b71282045aba9844\ +d48b5df2e9e7a5887121eae7d7b01db7cdf6ff917cd8eb50c6bf1d54f90cce1a491a9c74fea88f\ +7e7230b047d16b5a6027881d6f154818f06e513faf40c8814630e4e254f17a47bfe9cb519b9828\ +9935bf17673ae4c8033504a20a898d0032ee402b72d5986322f3bdfb27400561f7476cd715eaab\ +b7338b854e51fc2fa026a5a579b6dcea1b1c0559c13d3c1136f303f4b4d25ad5b692229957 + Q = d3eba6521240694015ef94412e08bf3cf8d635a455a398d6f210f6169041653b + G = ce84b30ddf290a9f787a7c2f1ce92c1cbf4ef400e3cd7ce4978db2104d7394b493c183\ +32c64cec906a71c3778bd93341165dee8e6cd4ca6f13afff531191194ada55ecf01ff94d6cf7c4\ +768b82dd29cd131aaf202aefd40e564375285c01f3220af4d70b96f1395420d778228f1461f5d0\ +b8e47357e87b1fe3286223b553e3fc9928f16ae3067ded6721bedf1d1a01bfd22b9ae85fce7782\ +0d88cdf50a6bde20668ad77a707d1c60fcc5d51c9de488610d0285eb8ff721ff141f93a9fb23c1\ +d1f7654c07c46e58836d1652828f71057b8aff0b0778ef2ca934ea9d0f37daddade2d823a4d8e3\ +62721082e279d003b575ee59fd050d105dfd71cd63154efe431a0869178d9811f4f231dc5dcf3b\ +0ec0f2b0f9896c32ec6c7ee7d60aa97109e09224907328d4e6acd10117e45774406c4c947da802\ +0649c3168f690e0bd6e91ac67074d1d436b58ae374523deaf6c93c1e6920db4a080b744804bb07\ +3cecfe83fa9398cf150afa286dc7eb7949750cf5001ce104e9187f7e16859afa8fd0d775ae + + X = b2764c46113983777d3e7e97589f1303806d14ad9f2f1ef033097de954b17706 + Y = 814824e435e1e6f38daa239aad6dad21033afce6a3ebd35c1359348a0f2418871968c2\ +babfc2baf47742148828f8612183178f126504da73566b6bab33ba1f124c15aa461555c2451d86\ +c94ee21c3e3fc24c55527e01b1f03adcdd8ec5cb08082803a7b6a829c3e99eeb332a2cf5c035b0\ +ce0078d3d414d31fa47e9726be2989b8d06da2e6cd363f5a7d1515e3f4925e0b32adeae3025cc5\ +a996f6fd27494ea408763de48f3bb39f6a06514b019899b312ec570851637b8865cff3a52bf5d5\ +4ad5a19e6e400a2d33251055d0a440b50d53f4791391dc754ad02b9eab74c46b4903f9d76f8243\ +39914db108057af7cde657d41766a99991ac8787694f4185d6f91d7627048f827b405ec67bf2fe\ +56141c4c581d8c317333624e073e5879a82437cb0c7b435c0ce434e15965db1315d64895991e6b\ +be7dac040c42052408bbc53423fd31098248a58f8a67da3a39895cd0cc927515d044c1e3cb6a32\ +59c3d0da354cce89ea3552c59609db10ee989986527436af21d9485ddf25f90f7dff6d2bae + + X = 52e3e040efb30e1befd909a0bdbcfd140d005b1bff094af97186080262f1904d + Y = a5ae6e8f9b7a68ab0516dad4d7b7d002126f811d5a52e3d35c6d387fcb43fd19bf7792\ +362f9c98f8348aa058bb62376685f3d0c366c520d697fcd8416947151d4bbb6f32b53528a01647\ +9e99d2cd48d1fc679027c15f0042f207984efe05c1796bca8eba678dfdd00b80418e3ea840557e\ +73b09e003882f9a68edba3431d351d1ca07a8150b018fdbdf6c2f1ab475792a3ccaa6594472a45\ +f8dc777b60bf67de3e0f65c20d11b7d59faedf83fbce52617f500d9e514947c455274c6e900464\ +767fb56599b81344cf6d12c25cb2b7d038d7b166b6cf30534811c15d0e8ab880a2ac06786ae2dd\ +de61329a78d526f65245380ce877e979c5b50de66c9c30d66382c8f254653d25a1eb1d3a4897d7\ +623399b473ce712a2184cf2da1861706c41466806aefe41b497db82aca6c31c8f4aa68c17d1d9e\ +380b57998917655783ec96e5234a131f7299398d36f1f5f84297a55ff292f1f060958c358fed34\ +6db2de45127ca728a9417b2c54203e33e53b9a061d924395b09afab8daf3e8dd7eedcec3ac + """).splitlines() + + expected = [ + {'g': int('06b7861abbd35cc89e79c52f68d20875389b127361ca66822138ce499' + '1d2b862259d6b4548a6495b195aa0e0b6137ca37eb23b94074d3c3d3000' + '42bdf15762812b6333ef7b07ceba78607610fcc9ee68491dbc1e34cd12' + '615474e52b18bc934fb00c61d39e7da8902291c4434a4e2224c3f' + '4fd9f93cd6f4f17fc076341a7e7d9', 16), + 'p': int('d38311e2cd388c3ed698e82fdf88eb92b5a9a483dc88005d4b725e' + 'f341eabb47cf8a7a8a41e792a156b7ce97206c4f9c5ce6fc5ae791210' + '2b6b502e59050b5b21ce263dddb2044b652236f4d42ab4b5d6aa73189c' + 'ef1ace778d7845a5c1c1c7147123188f8dc551054ee162b634d60f097f7' + '19076640e20980a0093113a8bd73', 16), + 'q': int('96c5390a8b612c0e422bb2b0ea194a3ec935a281', 16), + 'x': int('8185fee9cc7c0e91fd85503274f1cd5a3fd15a49', 16), + 'y': int('6f26d98d41de7d871b6381851c9d91fa03942092ab6097e76422' + '070edb71db44ff568280fdb1709f8fc3feab39f1f824adaeb2a29808815' + '6ac31af1aa04bf54f475bdcfdcf2f8a2dd973e922d83e76f01655861760' + '3129b21c70bf7d0e5dc9e68fe332e295b65876eb9a12fe6fca9f1a1ce80' + '204646bf99b5771d249a6fea627', 16)}, + {'g': int('06b7861abbd35cc89e79c52f68d20875389b127361ca66822138ce4991d' + '2b862259d6b4548a6495b195aa0e0b6137ca37eb23b94074d3c3d30004' + '2bdf15762812b6333ef7b07ceba78607610fcc9ee68491dbc1e34cd126' + '15474e52b18bc934fb00c61d39e7da8902291c4434a4e2224c3f4fd9' + 'f93cd6f4f17fc076341a7e7d9', 16), + 'p': int('d38311e2cd388c3ed698e82fdf88eb92b5a9a483dc88005d4b725ef341e' + 'abb47cf8a7a8a41e792a156b7ce97206c4f9c5ce6fc5ae7912102b6b50' + '2e59050b5b21ce263dddb2044b652236f4d42ab4b5d6aa73189cef1a' + 'ce778d7845a5c1c1c7147123188f8dc551054ee162b634d6' + '0f097f719076640e20980a0093113a8bd73', 16), + 'q': int('96c5390a8b612c0e422bb2b0ea194a3ec935a281', 16), + 'x': int('85322d6ea73083064376099ca2f65f56e8522d9b', 16), + 'y': int('21f8690f717c9f4dcb8f4b6971de2f15b9231fcf41b7eeb997d781f240' + 'bfdddfd2090d22083c26cca39bf37c9caf1ec89518ea64845a50d747b49' + '131ffff6a2fd11ea7bacbb93c7d05137383a06365af82225dd3713c' + 'a5a45006316f53bd12b0e260d5f79795e5a4c9f353f12867a1d3' + '202394673ada8563b71555e53f415254', 16)}, + + {'g': int('e4c4eca88415b23ecf811c96e48cd24200fe916631a68a684e6ccb6b191' + '3413d344d1d8d84a333839d88eee431521f6e357c16e6a93be111a9807' + '6739cd401bab3b9d565bf4fb99e9d185b1e14d61c93700133f908bae0' + '3e28764d107dcd2ea7674217622074bb19efff482f5f5c1a86d5551b2' + 'fc68d1c6e9d8011958ef4b9c2a3a55d0d3c882e6ad7f9f0f3c61568f78' + 'd0706b10a26f23b4f197c322b825002284a0aca91807bba98ece912' + 'b80e10cdf180cf99a35f210c1655fbfdd74f13b1b5046591f8403873d' + '12239834dd6c4eceb42bf7482e1794a1601357b629ddfa971f2ed273b1' + '46ec1ca06d0adf55dd91d65c37297bda78c6d210c0bc26e558302', 16), + 'p': int('ea1fb1af22881558ef93be8a5f8653c5a559434c49c8c2c12ace' + '5e9c41434c9cf0a8e9498acb0f4663c08b4484eace845f6fb17d' + 'ac62c98e706af0fc74e4da1c6c2b3fbf5a1d58ff82fc1a66f3e8b122' + '52c40278fff9dd7f102eed2cb5b7323ebf1908c234d935414dded7f8d2' + '44e54561b0dca39b301de8c49da9fb23df33c6182e3f983208c560fb5' + '119fbf78ebe3e6564ee235c6a15cbb9ac247baba5a423bc6582a1a9d8a' + '2b4f0e9e3d9dbac122f750dd754325135257488b1f6ecabf21bff2947' + 'fe0d3b2cb7ffe67f4e7fcdf1214f6053e72a5bb0dd20a0e9fe6db2df0a' + '908c36e95e60bf49ca4368b8b892b9c79f61ef91c47567c40e1f80ac' + '5aa66ef7', 16), + 'q': int('8ec73f3761caf5fdfe6e4e82098bf10f898740dcb808204bf6b1' + '8f507192c19d', 16), + 'x': int('405772da6e90d809e77d5de796562a2dd4dfd10ef00a83a3aba6' + 'bd818a0348a1', 16), + 'y': int('6b32e31ab9031dc4dd0b5039a78d07826687ab087ae6de4736f5' + 'b0434e1253092e8a0b231f9c87f3fc8a4cb5634eb194bf1b638' + 'b7a7889620ce6711567e36aa36cda4604cfaa601a45918371d' + '4ccf68d8b10a50a0460eb1dc0fff62ef5e6ee4d473e18ea4a6' + '6c196fb7e677a49b48241a0b4a97128eff30fa437050501a584' + 'f8771e7280d26d5af30784039159c11ebfea10b692fd0a58215ee' + 'b18bff117e13f08db792ed4151a218e4bed8dddfb0793225bd1e97' + '73505166f4bd8cedbb286ea28232972da7bae836ba97329ba6b0a36508' + 'e50a52a7675e476d4d4137eae13f22a9d2fefde708ba8f34bf336c6e7' + '6331761e4b0617633fe7ec3f23672fb19d27', 16)}, + {'g': int('e4c4eca88415b23ecf811c96e48cd24200fe916631a68a684e6ccb6b191' + '3413d344d1d8d84a333839d88eee431521f6e357c16e6a93be111a9807' + '6739cd401bab3b9d565bf4fb99e9d185b1e14d61c93700133f908bae0' + '3e28764d107dcd2ea7674217622074bb19efff482f5f5c1a86d5551b2' + 'fc68d1c6e9d8011958ef4b9c2a3a55d0d3c882e6ad7f9f0f3c61568f78' + 'd0706b10a26f23b4f197c322b825002284a0aca91807bba98ece912' + 'b80e10cdf180cf99a35f210c1655fbfdd74f13b1b5046591f8403873d' + '12239834dd6c4eceb42bf7482e1794a1601357b629ddfa971f2ed273b1' + '46ec1ca06d0adf55dd91d65c37297bda78c6d210c0bc26e558302', 16), + 'p': int('ea1fb1af22881558ef93be8a5f8653c5a559434c49c8c2c12ace' + '5e9c41434c9cf0a8e9498acb0f4663c08b4484eace845f6fb17d' + 'ac62c98e706af0fc74e4da1c6c2b3fbf5a1d58ff82fc1a66f3e8b122' + '52c40278fff9dd7f102eed2cb5b7323ebf1908c234d935414dded7f8d2' + '44e54561b0dca39b301de8c49da9fb23df33c6182e3f983208c560fb5' + '119fbf78ebe3e6564ee235c6a15cbb9ac247baba5a423bc6582a1a9d8a' + '2b4f0e9e3d9dbac122f750dd754325135257488b1f6ecabf21bff2947' + 'fe0d3b2cb7ffe67f4e7fcdf1214f6053e72a5bb0dd20a0e9fe6db2df0a' + '908c36e95e60bf49ca4368b8b892b9c79f61ef91c47567c40e1f80ac' + '5aa66ef7', 16), + 'q': int('8ec73f3761caf5fdfe6e4e82098bf10f898740dcb808204bf6b1' + '8f507192c19d', 16), + 'x': int('0e0b95e31fda3f888059c46c3002ef8f2d6be112d0209aeb9e95' + '45da67aeea80', 16), + 'y': int('778082b77ddba6f56597cc74c3a612abf2ddbd85cc81430c99ab' + '843c1f630b9db0139965f563978164f9bf3a8397256be714625' + 'cd41cd7fa0067d94ea66d7e073f7125af692ad01371d4a17f45' + '50590378f2b074030c20e36911598a1018772f61be3b24de4be' + '5a388ccc09e15a92819c31dec50de9fde105b49eaa097b9d13d' + '9219eeb33b628facfd1c78a7159c8430d0647c506e7e3de74763c' + 'b351eada72c00bef3c9641881e6254870c1e6599f8ca2f1bbb74f' + '39a905e3a34e4544168e6e50c9e3305fd09cab6ed4aff6fda6e0d' + '5bf375c81ac9054406d9193b003c89272f1bd83d48250134b65c77' + 'c2b6332d38d34d9016f0e8975536ad6c348a1faedb0', 16)}, + + {'g': int('ce84b30ddf290a9f787a7c2f1ce92c1cbf4ef400e3cd7ce4978d' + 'b2104d7394b493c18332c64cec906a71c3778bd93341165dee8' + 'e6cd4ca6f13afff531191194ada55ecf01ff94d6cf7c4768b82' + 'dd29cd131aaf202aefd40e564375285c01f3220af4d70b96f1' + '395420d778228f1461f5d0b8e47357e87b1fe3286223b553e3' + 'fc9928f16ae3067ded6721bedf1d1a01bfd22b9ae85fce77820d88cdf' + '50a6bde20668ad77a707d1c60fcc5d51c9de488610d0285eb8ff721f' + 'f141f93a9fb23c1d1f7654c07c46e58836d1652828f71057b8aff0b077' + '8ef2ca934ea9d0f37daddade2d823a4d8e362721082e279d003b575ee' + '59fd050d105dfd71cd63154efe431a0869178d9811f4f231dc5dcf3b' + '0ec0f2b0f9896c32ec6c7ee7d60aa97109e09224907328d4e6acd1011' + '7e45774406c4c947da8020649c3168f690e0bd6e91ac67074d1d436b' + '58ae374523deaf6c93c1e6920db4a080b744804bb073cecfe83fa939' + '8cf150afa286dc7eb7949750cf5001ce104e9187f7e16859afa8fd0d' + '775ae', 16), + 'p': int('f335666dd1339165af8b9a5e3835adfe15c158e4c3c7bd53132e7d5828' + 'c352f593a9a787760ce34b789879941f2f01f02319f6ae0b756f1a842' + 'ba54c85612ed632ee2d79ef17f06b77c641b7b080aff52a03fc2462e8' + '0abc64d223723c236deeb7d201078ec01ca1fbc1763139e25099a84ec' + '389159c409792080736bd7caa816b92edf23f2c351f90074aa5ea2651' + 'b372f8b58a0a65554db2561d706a63685000ac576b7e4562e262a1428' + '5a9c6370b290e4eb7757527d80b6c0fd5df831d36f3d1d35f12ab0605' + '48de1605fd15f7c7aafed688b146a02c945156e284f5b71282045aba9' + '844d48b5df2e9e7a5887121eae7d7b01db7cdf6ff917cd8eb50c6bf1d' + '54f90cce1a491a9c74fea88f7e7230b047d16b5a6027881d6f154818f' + '06e513faf40c8814630e4e254f17a47bfe9cb519b98289935bf17673a' + 'e4c8033504a20a898d0032ee402b72d5986322f3bdfb27400561f7476' + 'cd715eaabb7338b854e51fc2fa026a5a579b6dcea1b1c0559c13d3c11' + '36f303f4b4d25ad5b692229957', 16), + 'q': int('d3eba6521240694015ef94412e08bf3cf8d635a455a398d6f210' + 'f6169041653b', 16), + 'x': int('b2764c46113983777d3e7e97589f1303806d14ad9f2f1ef03309' + '7de954b17706', 16), + 'y': int('814824e435e1e6f38daa239aad6dad21033afce6a3ebd35c1359348a0f2' + '418871968c2babfc2baf47742148828f8612183178f126504da73566b6' + 'bab33ba1f124c15aa461555c2451d86c94ee21c3e3fc24c55527e' + '01b1f03adcdd8ec5cb08082803a7b6a829c3e99eeb332a2cf5c035b0c' + 'e0078d3d414d31fa47e9726be2989b8d06da2e6cd363f5a7d1515e3f4' + '925e0b32adeae3025cc5a996f6fd27494ea408763de48f3bb39f6a06' + '514b019899b312ec570851637b8865cff3a52bf5d54ad5a19e6e400' + 'a2d33251055d0a440b50d53f4791391dc754ad02b9eab74c46b4903' + 'f9d76f824339914db108057af7cde657d41766a99991ac8787694f' + '4185d6f91d7627048f827b405ec67bf2fe56141c4c581d8c317333' + '624e073e5879a82437cb0c7b435c0ce434e15965db1315d648959' + '91e6bbe7dac040c42052408bbc53423fd31098248a58f8a67da3a' + '39895cd0cc927515d044c1e3cb6a3259c3d0da354cce89ea3552c' + '59609db10ee989986527436af21d9485ddf25f90f7dff6d2bae', 16)}, + {'g': int('ce84b30ddf290a9f787a7c2f1ce92c1cbf4ef400e3cd7ce4978d' + 'b2104d7394b493c18332c64cec906a71c3778bd93341165dee8' + 'e6cd4ca6f13afff531191194ada55ecf01ff94d6cf7c4768b82' + 'dd29cd131aaf202aefd40e564375285c01f3220af4d70b96f1' + '395420d778228f1461f5d0b8e47357e87b1fe3286223b553e3' + 'fc9928f16ae3067ded6721bedf1d1a01bfd22b9ae85fce77820d88cdf' + '50a6bde20668ad77a707d1c60fcc5d51c9de488610d0285eb8ff721f' + 'f141f93a9fb23c1d1f7654c07c46e58836d1652828f71057b8aff0b077' + '8ef2ca934ea9d0f37daddade2d823a4d8e362721082e279d003b575ee' + '59fd050d105dfd71cd63154efe431a0869178d9811f4f231dc5dcf3b' + '0ec0f2b0f9896c32ec6c7ee7d60aa97109e09224907328d4e6acd1011' + '7e45774406c4c947da8020649c3168f690e0bd6e91ac67074d1d436b' + '58ae374523deaf6c93c1e6920db4a080b744804bb073cecfe83fa939' + '8cf150afa286dc7eb7949750cf5001ce104e9187f7e16859afa8fd0d' + '775ae', 16), + 'p': int('f335666dd1339165af8b9a5e3835adfe15c158e4c3c7bd53132e7d5828' + 'c352f593a9a787760ce34b789879941f2f01f02319f6ae0b756f1a842' + 'ba54c85612ed632ee2d79ef17f06b77c641b7b080aff52a03fc2462e8' + '0abc64d223723c236deeb7d201078ec01ca1fbc1763139e25099a84ec' + '389159c409792080736bd7caa816b92edf23f2c351f90074aa5ea2651' + 'b372f8b58a0a65554db2561d706a63685000ac576b7e4562e262a1428' + '5a9c6370b290e4eb7757527d80b6c0fd5df831d36f3d1d35f12ab0605' + '48de1605fd15f7c7aafed688b146a02c945156e284f5b71282045aba9' + '844d48b5df2e9e7a5887121eae7d7b01db7cdf6ff917cd8eb50c6bf1d' + '54f90cce1a491a9c74fea88f7e7230b047d16b5a6027881d6f154818f' + '06e513faf40c8814630e4e254f17a47bfe9cb519b98289935bf17673a' + 'e4c8033504a20a898d0032ee402b72d5986322f3bdfb27400561f7476' + 'cd715eaabb7338b854e51fc2fa026a5a579b6dcea1b1c0559c13d3c11' + '36f303f4b4d25ad5b692229957', 16), + 'q': int('d3eba6521240694015ef94412e08bf3cf8d635a455a398d6f210' + 'f6169041653b', 16), + 'x': int('52e3e040efb30e1befd909a0bdbcfd140d005b1bff094af97186' + '080262f1904d', 16), + 'y': int('a5ae6e8f9b7a68ab0516dad4d7b7d002126f811d5a52e3d35c6d' + '387fcb43fd19bf7792362f9c98f8348aa058bb62376685f3d0c3' + '66c520d697fcd8416947151d4bbb6f32b53528a016479e99d2cd' + '48d1fc679027c15f0042f207984efe05c1796bca8eba678dfdd0' + '0b80418e3ea840557e73b09e003882f9a68edba3431d351d1ca0' + '7a8150b018fdbdf6c2f1ab475792a3ccaa6594472a45f8dc777b' + '60bf67de3e0f65c20d11b7d59faedf83fbce52617f500d9e5149' + '47c455274c6e900464767fb56599b81344cf6d12c25cb2b7d038' + 'd7b166b6cf30534811c15d0e8ab880a2ac06786ae2ddde61329a' + '78d526f65245380ce877e979c5b50de66c9c30d66382c8f25465' + '3d25a1eb1d3a4897d7623399b473ce712a2184cf2da1861706c4' + '1466806aefe41b497db82aca6c31c8f4aa68c17d1d9e380b5799' + '8917655783ec96e5234a131f7299398d36f1f5f84297a55ff292' + 'f1f060958c358fed346db2de45127ca728a9417b2c54203e33e5' + '3b9a061d924395b09afab8daf3e8dd7eedcec3ac', 16)} + ] + + assert expected == load_fips_dsa_key_pair_vectors(vector_data) + + +def test_load_fips_dsa_sig_ver_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 11.0 + # "SigVer" information + # Mod sizes selected: SHA-1 L=1024, N=160,SHA-384 L=2048, N=256 + # Generated on Fri Apr 01 08:37:15 2011 + + [mod = L=1024, N=160, SHA-1] + + P = dc5bf3a88b2d99e4c95cdd7a0501cc38630d425cf5c390af3429cff1f35147b795cae\ +a923f0d3577158f8a0c89dabd1962c2c453306b5d70cacfb01430aceb54e5a5fa6f93\ +40d3bd2da612fceeb76b0ec1ebfae635a56ab141b108e00dc76eefe2edd0c514c21c4\ +57457c39065dba9d0ecb7569c247172d8438ad2827b60435b + Q = e956602b83d195dbe945b3ac702fc61f81571f1d + G = d7eb9ca20a3c7a079606bafc4c9261ccaba303a5dc9fe9953f197dfe548c234895baa\ +77f441ee6a2d97b909cbbd26ff7b869d24cae51b5c6edb127a4b5d75cd8b46608bfa1\ +48249dffdb59807c5d7dde3fe3080ca3a2d28312142becb1fa8e24003e21c72871081\ +74b95d5bc711e1c8d9b1076784f5dc37a964a5e51390da713 + + Msg = 0fe1bfee500bdb76026099b1d37553f6bdfe48c82094ef98cb309dd777330bedfaa\ +2f94c823ef74ef4074b50d8706041ac0e371c7c22dcf70263b8d60e17a86c7c379c\ +fda8f22469e0df9d49d59439fc99891873628fff25dda5fac5ac794e948babdde96\ +8143ba05f1128f34fdad5875edc4cd71c6c24ba2060ffbd439ce2b3 + X = 1d93010c29ecfc432188942f46f19f44f0e1bb5d + Y = 6240ea0647117c38fe705106d56db578f3e10130928452d4f3587881b8a2bc6873a8b\ +efc3237f20914e2a91c7f07a928ee22adeed23d74ab7f82ea11f70497e578f7a9b4cb\ +d6f10226222b0b4da2ea1e49813d6bb9882fbf675c0846bb80cc891857b89b0ef1beb\ +6cce3378a9aab5d66ad4cb9277cf447dfe1e64434749432fb + R = b5af307867fb8b54390013cc67020ddf1f2c0b81 + S = 620d3b22ab5031440c3e35eab6f481298f9e9f08 + Result = P + + Msg = 97d50898025d2f9ba633866e968ca75e969d394edba6517204cb3dd537c2ba38778\ +a2dc9dbc685a915e5676fcd43bc3726bc59ce3d7a9fae35565082a069c139fa37c9\ +0d922b126933db3fa6c5ef6b1edf00d174a51887bb76909c6a94fe994ecc7b7fc8f\ +26113b17f30f9d01693df99a125b4f17e184331c6b6e8ca00f54f3a + X = 350e13534692a7e0c4b7d58836046c436fbb2322 + Y = 69974de550fe6bd3099150faea1623ad3fb6d9bf23a07215093f319725ad0877accff\ +d291b6da18eb0cbe51676ceb0977504eb97c27c0b191883f72fb2710a9fbd8bcf13be\ +0bf854410b32f42b33ec89d3cc1cf892bcd536c4195ca9ada302ad600c3408739935d\ +77dc247529ca47f844cc86f5016a2fe962c6e20ca7c4d4e8f + R = b5d05faa7005764e8dae0327c5bf1972ff7681b9 + S = 18ea15bd9f00475b25204cbc23f8c23e01588015 + Result = F (3 - R changed ) + + [mod = L=2048, N=224, SHA-1] + + # unsupported so we ignore this + + Msg = f9d01693df99a125b4f17e184331c6b6e8ca00f54f3a + X = e0c4b7d58836046c436fbb2322 + Y = fb6d9bf23a07215093f319725ad0877accff + R = 5764e8dae0327c5bf1972ff7681b9 + S = 475b25204cbc23f8c23e01588015 + Result = F (3 - R changed ) + + [mod = L=2048, N=256, SHA-384] + + P = e7c1c86125db9ef417da1ced7ea0861bdad629216a3f3c745df42a46b989e59f4d984\ +25ee3c932fa3c2b6f637bdb6545bec526faa037e11f5578a4363b9fca5eba60d6a9cb\ +aa2befd04141d989c7356285132c2eaf74f2d868521cdc0a17ae9a2546ef863027d3f\ +8cc7949631fd0e2971417a912c8b8c5c989730db6ea6e8baee0e667850429038093c8\ +51ccb6fb173bb081e0efe0bd7450e0946888f89f75e443ab93ef2da293a01622cf43c\ +6dd79625d41ba8f9ef7e3086ab39134283d8e96c89249488120fd061e4a87d34af410\ +69c0b4fd3934c31b589cbe85b68b912718d5dab859fda7082511fad1d152044905005\ +546e19b14aa96585a55269bf2b831 + Q = 8e056ec9d4b7acb580087a6ed9ba3478711bb025d5b8d9c731ef9b38bd43db2f + G = dc2bfb9776786ad310c8b0cdcbba3062402613c67e6959a8d8d1b05aab636528b7b1f\ +e9cd33765f853d6dbe13d09f2681f8c7b1ed7886aaed70c7bd76dbe858ffb8bd86235\ +ddf759244678f428c6519af593dc94eeadbd9852ba2b3d61664e8d58c29d2039af3c3\ +d6d16f90988f6a8c824569f3d48050e30896a9e17cd0232ef01ab8790008f6973b84c\ +763a72f4ae8b485abfb7e8efeb86808fa2b281d3e5d65d28f5992a34c077c5aa8026c\ +b2fbc34a45f7e9bd216b10e6f12ecb172e9a6eb8f2e91316905b6add1fd22e83bc2f0\ +89f1d5e6a6e6707c18ff55ddcb7954e8bceaf0efc4e8314910c03b0e51175f344faaf\ +ee476a373ac95743cec712b72cf2e + + Msg = 6cd6ccfd66bcd832189c5f0c77994210e3bf2c43416f0fe77c4e92f31c5369538dc\ +2c003f146c5ac79df43194ccf3c44d470d9f1083bd15b99b5bcf88c32d8a9021f09\ +ea2288d7b3bf345a12aef3949c1e121b9fb371a67c2d1377364206ac839dd784835\ +61426bda0303f285aa12e9c45d3cdfc6beae3549703b187deeb3296 + X = 56c897b5938ad5b3d437d7e4826da586a6b3be15e893fa1aaa946f20a028b6b3 + Y = 38ad44489e1a5778b9689f4dcf40e2acf23840fb954e987d6e8cb629106328ac64e1f\ +3c3eba48b21176ad4afe3b733bead382ee1597e1b83e4b43424f2daaba04e5bd79e14\ +36693ac2bddb79a298f026e57e200a252efd1e848a4a2e90be6e78f5242b468b9c0c6\ +d2615047a5a40b9ae7e57a519114db55bf3bed65e580f894b094630ca9c217f6accd0\ +91e72d2f22da620044ff372d7273f9445017fad492959e59600b7494dbe766a03e401\ +25d4e6747c76f68a5b0cdc0e7d7cee12d08c6fb7d0fb049e420a33405075ed4463296\ +345ca695fb7feab7c1b5333ae519fcd4bb6a043f4555378969114743d4face96cad31\ +c0e0089da4e3f61b6d7dabc088ab7 + R = 3b85b17be240ed658beb3652c9d93e8e9eea160d35ee2459614305802963374e + S = 726800a5174a53b56dce86064109c0273cd11fcfa3c92c5cd6aa910260c0e3c7 + Result = F (1 - Message changed) + + Msg = 3ad6b0884f358dea09c31a9abc40c45a6000611fc2b907b30eac00413fd2819de70\ +15488a411609d46c499b8f7afa1b78b352ac7f8535bd805b8ff2a5eae557098c668\ +f7ccd73af886d6823a6d456c29931ee864ed46d767382785728c2a83fcff5271007\ +d2a67d06fa205fd7b9d1a42ea5d6dc76e5e18a9eb148cd1e8b262ae + X = 2faf566a9f057960f1b50c69508f483d9966d6e35743591f3a677a9dc40e1555 + Y = 926425d617babe87c442b03903e32ba5bbf0cd9d602b59c4df791a4d64a6d4333ca0c\ +0d370552539197d327dcd1bbf8c454f24b03fc7805f862db34c7b066ddfddbb11dbd0\ +10b27123062d028fe041cb56a2e77488348ae0ab6705d87aac4d4e9e6600e9e706326\ +d9979982cffa839beb9eacc3963bcca455a507e80c1c37ad4e765b2c9c0477a075e9b\ +c584feacdf3a35a9391d4711f14e197c54022282bfed9a191213d64127f17a9c5affe\ +c26e0c71f15d3a5b16098fec118c45bf8bb2f3b1560df0949254c1c0aeb0a16d5a95a\ +40fab8521fbe8ea77c51169b587cc3360e5733e6a23b9fded8c40724ea1f9e93614b3\ +a6c9b4f8dbbe915b794497227ba62 + R = 343ea0a9e66277380f604d5880fca686bffab69ca97bfba015a102a7e23dce0e + S = 6258488c770e0f5ad7b9da8bade5023fc0d17c6ec517bd08d53e6dc01ac5c2b3 + Result = P + """).splitlines() + + expected = [ + { + 'p': int('dc5bf3a88b2d99e4c95cdd7a0501cc38630d425cf5c390af3429cff1' + 'f35147b795caea923f0d3577158f8a0c89dabd1962c2c453306b5d70' + 'cacfb01430aceb54e5a5fa6f9340d3bd2da612fceeb76b0ec1ebfae6' + '35a56ab141b108e00dc76eefe2edd0c514c21c457457c39065dba9d0' + 'ecb7569c247172d8438ad2827b60435b', 16), + 'q': int('e956602b83d195dbe945b3ac702fc61f81571f1d', 16), + 'g': int('d7eb9ca20a3c7a079606bafc4c9261ccaba303a5dc9fe9953f197dfe' + '548c234895baa77f441ee6a2d97b909cbbd26ff7b869d24cae51b5c6' + 'edb127a4b5d75cd8b46608bfa148249dffdb59807c5d7dde3fe3080c' + 'a3a2d28312142becb1fa8e24003e21c7287108174b95d5bc711e1c8d' + '9b1076784f5dc37a964a5e51390da713', 16), + 'digest_algorithm': 'SHA-1', + 'msg': binascii.unhexlify( + b'0fe1bfee500bdb76026099b1d37553f6bdfe48c82094ef98cb309dd77733' + b'0bedfaa2f94c823ef74ef4074b50d8706041ac0e371c7c22dcf70263b8d6' + b'0e17a86c7c379cfda8f22469e0df9d49d59439fc99891873628fff25dda5' + b'fac5ac794e948babdde968143ba05f1128f34fdad5875edc4cd71c6c24ba' + b'2060ffbd439ce2b3'), + 'x': int('1d93010c29ecfc432188942f46f19f44f0e1bb5d', 16), + 'y': int('6240ea0647117c38fe705106d56db578f3e10130928452d4f3587881' + 'b8a2bc6873a8befc3237f20914e2a91c7f07a928ee22adeed23d74ab' + '7f82ea11f70497e578f7a9b4cbd6f10226222b0b4da2ea1e49813d6b' + 'b9882fbf675c0846bb80cc891857b89b0ef1beb6cce3378a9aab5d66' + 'ad4cb9277cf447dfe1e64434749432fb', 16), + 'r': int('b5af307867fb8b54390013cc67020ddf1f2c0b81', 16), + 's': int('620d3b22ab5031440c3e35eab6f481298f9e9f08', 16), + 'result': 'P'}, + { + 'p': int('dc5bf3a88b2d99e4c95cdd7a0501cc38630d425cf5c390af3429cff1' + 'f35147b795caea923f0d3577158f8a0c89dabd1962c2c453306b5d70' + 'cacfb01430aceb54e5a5fa6f9340d3bd2da612fceeb76b0ec1ebfae6' + '35a56ab141b108e00dc76eefe2edd0c514c21c457457c39065dba9d0' + 'ecb7569c247172d8438ad2827b60435b', 16), + 'q': int('e956602b83d195dbe945b3ac702fc61f81571f1d', 16), + 'g': int('d7eb9ca20a3c7a079606bafc4c9261ccaba303a5dc9fe9953f197dfe' + '548c234895baa77f441ee6a2d97b909cbbd26ff7b869d24cae51b5c6' + 'edb127a4b5d75cd8b46608bfa148249dffdb59807c5d7dde3fe3080c' + 'a3a2d28312142becb1fa8e24003e21c7287108174b95d5bc711e1c8d' + '9b1076784f5dc37a964a5e51390da713', 16), + 'digest_algorithm': 'SHA-1', + 'msg': binascii.unhexlify( + b'97d50898025d2f9ba633866e968ca75e969d394edba6517204cb3dd537c2' + b'ba38778a2dc9dbc685a915e5676fcd43bc3726bc59ce3d7a9fae35565082' + b'a069c139fa37c90d922b126933db3fa6c5ef6b1edf00d174a51887bb7690' + b'9c6a94fe994ecc7b7fc8f26113b17f30f9d01693df99a125b4f17e184331' + b'c6b6e8ca00f54f3a'), + 'x': int('350e13534692a7e0c4b7d58836046c436fbb2322', 16), + 'y': int('69974de550fe6bd3099150faea1623ad3fb6d9bf23a07215093f3197' + '25ad0877accffd291b6da18eb0cbe51676ceb0977504eb97c27c0b19' + '1883f72fb2710a9fbd8bcf13be0bf854410b32f42b33ec89d3cc1cf8' + '92bcd536c4195ca9ada302ad600c3408739935d77dc247529ca47f84' + '4cc86f5016a2fe962c6e20ca7c4d4e8f', 16), + 'r': int('b5d05faa7005764e8dae0327c5bf1972ff7681b9', 16), + 's': int('18ea15bd9f00475b25204cbc23f8c23e01588015', 16), + 'result': 'F'}, + { + 'p': int('e7c1c86125db9ef417da1ced7ea0861bdad629216a3f3c745df42a4' + '6b989e59f4d98425ee3c932fa3c2b6f637bdb6545bec526faa037e1' + '1f5578a4363b9fca5eba60d6a9cbaa2befd04141d989c7356285132' + 'c2eaf74f2d868521cdc0a17ae9a2546ef863027d3f8cc7949631fd0' + 'e2971417a912c8b8c5c989730db6ea6e8baee0e667850429038093c' + '851ccb6fb173bb081e0efe0bd7450e0946888f89f75e443ab93ef2d' + 'a293a01622cf43c6dd79625d41ba8f9ef7e3086ab39134283d8e96c' + '89249488120fd061e4a87d34af41069c0b4fd3934c31b589cbe85b6' + '8b912718d5dab859fda7082511fad1d152044905005546e19b14aa9' + '6585a55269bf2b831', 16), + 'q': int('8e056ec9d4b7acb580087a6ed9ba3478711bb025d5b8d9c731ef9b3' + '8bd43db2f', 16), + 'g': int('dc2bfb9776786ad310c8b0cdcbba3062402613c67e6959a8d8d1b05' + 'aab636528b7b1fe9cd33765f853d6dbe13d09f2681f8c7b1ed7886a' + 'aed70c7bd76dbe858ffb8bd86235ddf759244678f428c6519af593d' + 'c94eeadbd9852ba2b3d61664e8d58c29d2039af3c3d6d16f90988f6' + 'a8c824569f3d48050e30896a9e17cd0232ef01ab8790008f6973b84' + 'c763a72f4ae8b485abfb7e8efeb86808fa2b281d3e5d65d28f5992a' + '34c077c5aa8026cb2fbc34a45f7e9bd216b10e6f12ecb172e9a6eb8' + 'f2e91316905b6add1fd22e83bc2f089f1d5e6a6e6707c18ff55ddcb' + '7954e8bceaf0efc4e8314910c03b0e51175f344faafee476a373ac9' + '5743cec712b72cf2e', 16), + 'digest_algorithm': 'SHA-384', + 'msg': binascii.unhexlify( + b'6cd6ccfd66bcd832189c5f0c77994210e3bf2c43416f0fe77c4e92f31c5' + b'369538dc2c003f146c5ac79df43194ccf3c44d470d9f1083bd15b99b5bc' + b'f88c32d8a9021f09ea2288d7b3bf345a12aef3949c1e121b9fb371a67c2' + b'd1377364206ac839dd78483561426bda0303f285aa12e9c45d3cdfc6bea' + b'e3549703b187deeb3296'), + 'x': int('56c897b5938ad5b3d437d7e4826da586a6b3be15e893fa1aaa946f2' + '0a028b6b3', 16), + 'y': int('38ad44489e1a5778b9689f4dcf40e2acf23840fb954e987d6e8cb62' + '9106328ac64e1f3c3eba48b21176ad4afe3b733bead382ee1597e1b' + '83e4b43424f2daaba04e5bd79e1436693ac2bddb79a298f026e57e2' + '00a252efd1e848a4a2e90be6e78f5242b468b9c0c6d2615047a5a40' + 'b9ae7e57a519114db55bf3bed65e580f894b094630ca9c217f6accd' + '091e72d2f22da620044ff372d7273f9445017fad492959e59600b74' + '94dbe766a03e40125d4e6747c76f68a5b0cdc0e7d7cee12d08c6fb7' + 'd0fb049e420a33405075ed4463296345ca695fb7feab7c1b5333ae5' + '19fcd4bb6a043f4555378969114743d4face96cad31c0e0089da4e3' + 'f61b6d7dabc088ab7', 16), + 'r': int('3b85b17be240ed658beb3652c9d93e8e9eea160d35ee24596143058' + '02963374e', 16), + 's': int('726800a5174a53b56dce86064109c0273cd11fcfa3c92c5cd6aa910' + '260c0e3c7', 16), + 'result': 'F'}, + { + 'p': int('e7c1c86125db9ef417da1ced7ea0861bdad629216a3f3c745df42a4' + '6b989e59f4d98425ee3c932fa3c2b6f637bdb6545bec526faa037e1' + '1f5578a4363b9fca5eba60d6a9cbaa2befd04141d989c7356285132' + 'c2eaf74f2d868521cdc0a17ae9a2546ef863027d3f8cc7949631fd0' + 'e2971417a912c8b8c5c989730db6ea6e8baee0e667850429038093c' + '851ccb6fb173bb081e0efe0bd7450e0946888f89f75e443ab93ef2d' + 'a293a01622cf43c6dd79625d41ba8f9ef7e3086ab39134283d8e96c' + '89249488120fd061e4a87d34af41069c0b4fd3934c31b589cbe85b6' + '8b912718d5dab859fda7082511fad1d152044905005546e19b14aa9' + '6585a55269bf2b831', 16), + 'q': int('8e056ec9d4b7acb580087a6ed9ba3478711bb025d5b8d9c731ef9b3' + '8bd43db2f', 16), + 'g': int('dc2bfb9776786ad310c8b0cdcbba3062402613c67e6959a8d8d1b05' + 'aab636528b7b1fe9cd33765f853d6dbe13d09f2681f8c7b1ed7886a' + 'aed70c7bd76dbe858ffb8bd86235ddf759244678f428c6519af593d' + 'c94eeadbd9852ba2b3d61664e8d58c29d2039af3c3d6d16f90988f6' + 'a8c824569f3d48050e30896a9e17cd0232ef01ab8790008f6973b84' + 'c763a72f4ae8b485abfb7e8efeb86808fa2b281d3e5d65d28f5992a' + '34c077c5aa8026cb2fbc34a45f7e9bd216b10e6f12ecb172e9a6eb8' + 'f2e91316905b6add1fd22e83bc2f089f1d5e6a6e6707c18ff55ddcb' + '7954e8bceaf0efc4e8314910c03b0e51175f344faafee476a373ac9' + '5743cec712b72cf2e', 16), + 'digest_algorithm': 'SHA-384', + 'msg': binascii.unhexlify( + b'3ad6b0884f358dea09c31a9abc40c45a6000611fc2b907b30eac00413fd' + b'2819de7015488a411609d46c499b8f7afa1b78b352ac7f8535bd805b8ff' + b'2a5eae557098c668f7ccd73af886d6823a6d456c29931ee864ed46d7673' + b'82785728c2a83fcff5271007d2a67d06fa205fd7b9d1a42ea5d6dc76e5e' + b'18a9eb148cd1e8b262ae'), + 'x': int('2faf566a9f057960f1b50c69508f483d9966d6e35743591f3a677a9' + 'dc40e1555', 16), + 'y': int('926425d617babe87c442b03903e32ba5bbf0cd9d602b59c4df791a4d' + '64a6d4333ca0c0d370552539197d327dcd1bbf8c454f24b03fc7805f' + '862db34c7b066ddfddbb11dbd010b27123062d028fe041cb56a2e774' + '88348ae0ab6705d87aac4d4e9e6600e9e706326d9979982cffa839be' + 'b9eacc3963bcca455a507e80c1c37ad4e765b2c9c0477a075e9bc584' + 'feacdf3a35a9391d4711f14e197c54022282bfed9a191213d64127f1' + '7a9c5affec26e0c71f15d3a5b16098fec118c45bf8bb2f3b1560df09' + '49254c1c0aeb0a16d5a95a40fab8521fbe8ea77c51169b587cc3360e' + '5733e6a23b9fded8c40724ea1f9e93614b3a6c9b4f8dbbe915b79449' + '7227ba62', 16), + 'r': int('343ea0a9e66277380f604d5880fca686bffab69ca97bfba015a102a' + '7e23dce0e', 16), + 's': int('6258488c770e0f5ad7b9da8bade5023fc0d17c6ec517bd08d53e6dc' + '01ac5c2b3', 16), + 'result': 'P'} + ] + + assert expected == load_fips_dsa_sig_vectors(vector_data) + + +def test_load_fips_dsa_sig_gen_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 11.2 + # "SigGen" information for "dsa2_values" + # Mod sizes selected: SHA-1 L=1024, N=160, SHA-256 L=2048, N=256 + + [mod = L=1024, N=160, SHA-1] + + P = a8f9cd201e5e35d892f85f80e4db2599a5676a3b1d4f190330ed3256b26d0e80a0e49\ +a8fffaaad2a24f472d2573241d4d6d6c7480c80b4c67bb4479c15ada7ea8424d2502fa01472e7\ +60241713dab025ae1b02e1703a1435f62ddf4ee4c1b664066eb22f2e3bf28bb70a2a76e4fd5eb\ +e2d1229681b5b06439ac9c7e9d8bde283 + Q = f85f0f83ac4df7ea0cdf8f469bfeeaea14156495 + G = 2b3152ff6c62f14622b8f48e59f8af46883b38e79b8c74deeae9df131f8b856e3ad6c\ +8455dab87cc0da8ac973417ce4f7878557d6cdf40b35b4a0ca3eb310c6a95d68ce284ad4e25ea\ +28591611ee08b8444bd64b25f3f7c572410ddfb39cc728b9c936f85f419129869929cdb909a6a\ +3a99bbe089216368171bd0ba81de4fe33 + + Msg = 3b46736d559bd4e0c2c1b2553a33ad3c6cf23cac998d3d0c0e8fa4b19bca06f2f38\ +6db2dcff9dca4f40ad8f561ffc308b46c5f31a7735b5fa7e0f9e6cb512e63d7eea05538d66a75\ +cd0d4234b5ccf6c1715ccaaf9cdc0a2228135f716ee9bdee7fc13ec27a03a6d11c5c5b3685f51\ +900b1337153bc6c4e8f52920c33fa37f4e7 + Y = 313fd9ebca91574e1c2eebe1517c57e0c21b0209872140c5328761bbb2450b33f1b18\ +b409ce9ab7c4cd8fda3391e8e34868357c199e16a6b2eba06d6749def791d79e95d3a4d09b24c\ +392ad89dbf100995ae19c01062056bb14bce005e8731efde175f95b975089bdcdaea562b32786\ +d96f5a31aedf75364008ad4fffebb970b + R = 50ed0e810e3f1c7cb6ac62332058448bd8b284c0 + S = c6aded17216b46b7e4b6f2a97c1ad7cc3da83fde + + Msg = d2bcb53b044b3e2e4b61ba2f91c0995fb83a6a97525e66441a3b489d9594238bc74\ +0bdeea0f718a769c977e2de003877b5d7dc25b182ae533db33e78f2c3ff0645f2137abc137d4e\ +7d93ccf24f60b18a820bc07c7b4b5fe08b4f9e7d21b256c18f3b9d49acc4f93e2ce6f3754c780\ +7757d2e1176042612cb32fc3f4f70700e25 + Y = 29bdd759aaa62d4bf16b4861c81cf42eac2e1637b9ecba512bdbc13ac12a80ae8de25\ +26b899ae5e4a231aef884197c944c732693a634d7659abc6975a773f8d3cd5a361fe2492386a3\ +c09aaef12e4a7e73ad7dfc3637f7b093f2c40d6223a195c136adf2ea3fbf8704a675aa7817aa7\ +ec7f9adfb2854d4e05c3ce7f76560313b + R = a26c00b5750a2d27fe7435b93476b35438b4d8ab + S = 61c9bfcb2938755afa7dad1d1e07c6288617bf70 + + [mod = L=2048, N=256, SHA-256] + + P = a8adb6c0b4cf9588012e5deff1a871d383e0e2a85b5e8e03d814fe13a059705e66323\ +0a377bf7323a8fa117100200bfd5adf857393b0bbd67906c081e585410e38480ead51684dac3a\ +38f7b64c9eb109f19739a4517cd7d5d6291e8af20a3fbf17336c7bf80ee718ee087e322ee4104\ +7dabefbcc34d10b66b644ddb3160a28c0639563d71993a26543eadb7718f317bf5d9577a61565\ +61b082a10029cd44012b18de6844509fe058ba87980792285f2750969fe89c2cd6498db354563\ +8d5379d125dccf64e06c1af33a6190841d223da1513333a7c9d78462abaab31b9f96d5f34445c\ +eb6309f2f6d2c8dde06441e87980d303ef9a1ff007e8be2f0be06cc15f + Q = e71f8567447f42e75f5ef85ca20fe557ab0343d37ed09edc3f6e68604d6b9dfb + G = 5ba24de9607b8998e66ce6c4f812a314c6935842f7ab54cd82b19fa104abfb5d84579\ +a623b2574b37d22ccae9b3e415e48f5c0f9bcbdff8071d63b9bb956e547af3a8df99e5d306197\ +9652ff96b765cb3ee493643544c75dbe5bb39834531952a0fb4b0378b3fcbb4c8b5800a533039\ +2a2a04e700bb6ed7e0b85795ea38b1b962741b3f33b9dde2f4ec1354f09e2eb78e95f037a5804\ +b6171659f88715ce1a9b0cc90c27f35ef2f10ff0c7c7a2bb0154d9b8ebe76a3d764aa879af372\ +f4240de8347937e5a90cec9f41ff2f26b8da9a94a225d1a913717d73f10397d2183f1ba3b7b45\ +a68f1ff1893caf69a827802f7b6a48d51da6fbefb64fd9a6c5b75c4561 + + Msg = 4e3a28bcf90d1d2e75f075d9fbe55b36c5529b17bc3a9ccaba6935c9e20548255b3\ +dfae0f91db030c12f2c344b3a29c4151c5b209f5e319fdf1c23b190f64f1fe5b330cb7c8fa952\ +f9d90f13aff1cb11d63181da9efc6f7e15bfed4862d1a62c7dcf3ba8bf1ff304b102b1ec3f149\ +7dddf09712cf323f5610a9d10c3d9132659 + Y = 5a55dceddd1134ee5f11ed85deb4d634a3643f5f36dc3a70689256469a0b651ad2288\ +0f14ab85719434f9c0e407e60ea420e2a0cd29422c4899c416359dbb1e592456f2b3cce233259\ +c117542fd05f31ea25b015d9121c890b90e0bad033be1368d229985aac7226d1c8c2eab325ef3\ +b2cd59d3b9f7de7dbc94af1a9339eb430ca36c26c46ecfa6c5481711496f624e188ad7540ef5d\ +f26f8efacb820bd17a1f618acb50c9bc197d4cb7ccac45d824a3bf795c234b556b06aeb929173\ +453252084003f69fe98045fe74002ba658f93475622f76791d9b2623d1b5fff2cc16844746efd\ +2d30a6a8134bfc4c8cc80a46107901fb973c28fc553130f3286c1489da + R = 633055e055f237c38999d81c397848c38cce80a55b649d9e7905c298e2a51447 + S = 2bbf68317660ec1e4b154915027b0bc00ee19cfc0bf75d01930504f2ce10a8b0 + + Msg = a733b3f588d5ac9b9d4fe2f804df8c256403a9f8eef6f191fc48e1267fb5b4d546b\ +a11e77b667844e489bf0d5f72990aeb061d01ccd7949a23def74a803b7d92d51abfadeb4885ff\ +d8ffd58ab87548a15c087a39b8993b2fa64c9d31a594eeb7512da16955834336a234435c5a9d0\ +dd9b15a94e116154dea63fdc8dd7a512181 + Y = 356ed47537fbf02cb30a8cee0537f300dff1d0c467399ce70b87a8758d5ec9dd25624\ +6fccaeb9dfe109f2a984f2ddaa87aad54ce0d31f907e504521baf4207d7073b0a4a9fc67d8ddd\ +a99f87aed6e0367cec27f9c608af743bf1ee6e11d55a182d43b024ace534029b866f6422828bb\ +81a39aae9601ee81c7f81dd358e69f4e2edfa4654d8a65bc64311dc86aac4abc1fc7a3f651596\ +61a0d8e288eb8d665cb0adf5ac3d6ba8e9453facf7542393ae24fd50451d3828086558f7ec528\ +e284935a53f67a1aa8e25d8ad5c4ad55d83aef883a4d9eeb6297e6a53f65049ba9e2c6b7953a7\ +60bc1dc46f78ceaaa2c02f5375dd82e708744aa40b15799eb81d7e5b1a + R = bcd490568c0a89ba311bef88ea4f4b03d273e793722722327095a378dd6f3522 + S = 74498fc43091fcdd2d1ef0775f8286945a01cd72b805256b0451f9cbd943cf82 + """).splitlines() + + expected = [ + { + 'p': int('a8f9cd201e5e35d892f85f80e4db2599a5676a3b1d4f190330ed325' + '6b26d0e80a0e49a8fffaaad2a24f472d2573241d4d6d6c7480c80b4' + 'c67bb4479c15ada7ea8424d2502fa01472e760241713dab025ae1b0' + '2e1703a1435f62ddf4ee4c1b664066eb22f2e3bf28bb70a2a76e4fd' + '5ebe2d1229681b5b06439ac9c7e9d8bde283', 16), + 'q': int('f85f0f83ac4df7ea0cdf8f469bfeeaea14156495', 16), + 'g': int('2b3152ff6c62f14622b8f48e59f8af46883b38e79b8c74deeae9df1' + '31f8b856e3ad6c8455dab87cc0da8ac973417ce4f7878557d6cdf40' + 'b35b4a0ca3eb310c6a95d68ce284ad4e25ea28591611ee08b8444bd' + '64b25f3f7c572410ddfb39cc728b9c936f85f419129869929cdb909' + 'a6a3a99bbe089216368171bd0ba81de4fe33', 16), + 'digest_algorithm': 'SHA-1', + 'msg': binascii.unhexlify( + b'3b46736d559bd4e0c2c1b2553a33ad3c6cf23cac998d3d0c0e8fa4b19bc' + b'a06f2f386db2dcff9dca4f40ad8f561ffc308b46c5f31a7735b5fa7e0f9' + b'e6cb512e63d7eea05538d66a75cd0d4234b5ccf6c1715ccaaf9cdc0a222' + b'8135f716ee9bdee7fc13ec27a03a6d11c5c5b3685f51900b1337153bc6c' + b'4e8f52920c33fa37f4e7'), + 'y': int('313fd9ebca91574e1c2eebe1517c57e0c21b0209872140c5328761b' + 'bb2450b33f1b18b409ce9ab7c4cd8fda3391e8e34868357c199e16a' + '6b2eba06d6749def791d79e95d3a4d09b24c392ad89dbf100995ae1' + '9c01062056bb14bce005e8731efde175f95b975089bdcdaea562b32' + '786d96f5a31aedf75364008ad4fffebb970b', 16), + 'r': int('50ed0e810e3f1c7cb6ac62332058448bd8b284c0', 16), + 's': int('c6aded17216b46b7e4b6f2a97c1ad7cc3da83fde', 16)}, + { + 'p': int('a8f9cd201e5e35d892f85f80e4db2599a5676a3b1d4f190330ed325' + '6b26d0e80a0e49a8fffaaad2a24f472d2573241d4d6d6c7480c80b4' + 'c67bb4479c15ada7ea8424d2502fa01472e760241713dab025ae1b0' + '2e1703a1435f62ddf4ee4c1b664066eb22f2e3bf28bb70a2a76e4fd' + '5ebe2d1229681b5b06439ac9c7e9d8bde283', 16), + 'q': int('f85f0f83ac4df7ea0cdf8f469bfeeaea14156495', 16), + 'g': int('2b3152ff6c62f14622b8f48e59f8af46883b38e79b8c74deeae9df1' + '31f8b856e3ad6c8455dab87cc0da8ac973417ce4f7878557d6cdf40' + 'b35b4a0ca3eb310c6a95d68ce284ad4e25ea28591611ee08b8444bd' + '64b25f3f7c572410ddfb39cc728b9c936f85f419129869929cdb909' + 'a6a3a99bbe089216368171bd0ba81de4fe33', 16), + 'digest_algorithm': 'SHA-1', + 'msg': binascii.unhexlify( + b'd2bcb53b044b3e2e4b61ba2f91c0995fb83a6a97525e66441a3b489d959' + b'4238bc740bdeea0f718a769c977e2de003877b5d7dc25b182ae533db33e' + b'78f2c3ff0645f2137abc137d4e7d93ccf24f60b18a820bc07c7b4b5fe08' + b'b4f9e7d21b256c18f3b9d49acc4f93e2ce6f3754c7807757d2e11760426' + b'12cb32fc3f4f70700e25'), + 'y': int('29bdd759aaa62d4bf16b4861c81cf42eac2e1637b9ecba512bdbc13' + 'ac12a80ae8de2526b899ae5e4a231aef884197c944c732693a634d7' + '659abc6975a773f8d3cd5a361fe2492386a3c09aaef12e4a7e73ad7' + 'dfc3637f7b093f2c40d6223a195c136adf2ea3fbf8704a675aa7817' + 'aa7ec7f9adfb2854d4e05c3ce7f76560313b', 16), + 'r': int('a26c00b5750a2d27fe7435b93476b35438b4d8ab', 16), + 's': int('61c9bfcb2938755afa7dad1d1e07c6288617bf70', 16)}, + { + 'p': int('a8adb6c0b4cf9588012e5deff1a871d383e0e2a85b5e8e03d814fe1' + '3a059705e663230a377bf7323a8fa117100200bfd5adf857393b0bb' + 'd67906c081e585410e38480ead51684dac3a38f7b64c9eb109f1973' + '9a4517cd7d5d6291e8af20a3fbf17336c7bf80ee718ee087e322ee4' + '1047dabefbcc34d10b66b644ddb3160a28c0639563d71993a26543e' + 'adb7718f317bf5d9577a6156561b082a10029cd44012b18de684450' + '9fe058ba87980792285f2750969fe89c2cd6498db3545638d5379d1' + '25dccf64e06c1af33a6190841d223da1513333a7c9d78462abaab31' + 'b9f96d5f34445ceb6309f2f6d2c8dde06441e87980d303ef9a1ff00' + '7e8be2f0be06cc15f', 16), + 'q': int('e71f8567447f42e75f5ef85ca20fe557ab0343d37ed09edc3f6e686' + '04d6b9dfb', 16), + 'g': int('5ba24de9607b8998e66ce6c4f812a314c6935842f7ab54cd82b19fa' + '104abfb5d84579a623b2574b37d22ccae9b3e415e48f5c0f9bcbdff' + '8071d63b9bb956e547af3a8df99e5d3061979652ff96b765cb3ee49' + '3643544c75dbe5bb39834531952a0fb4b0378b3fcbb4c8b5800a533' + '0392a2a04e700bb6ed7e0b85795ea38b1b962741b3f33b9dde2f4ec' + '1354f09e2eb78e95f037a5804b6171659f88715ce1a9b0cc90c27f3' + '5ef2f10ff0c7c7a2bb0154d9b8ebe76a3d764aa879af372f4240de8' + '347937e5a90cec9f41ff2f26b8da9a94a225d1a913717d73f10397d' + '2183f1ba3b7b45a68f1ff1893caf69a827802f7b6a48d51da6fbefb' + '64fd9a6c5b75c4561', 16), + 'digest_algorithm': 'SHA-256', + 'msg': binascii.unhexlify( + b'4e3a28bcf90d1d2e75f075d9fbe55b36c5529b17bc3a9ccaba6935c9e20' + b'548255b3dfae0f91db030c12f2c344b3a29c4151c5b209f5e319fdf1c23' + b'b190f64f1fe5b330cb7c8fa952f9d90f13aff1cb11d63181da9efc6f7e1' + b'5bfed4862d1a62c7dcf3ba8bf1ff304b102b1ec3f1497dddf09712cf323' + b'f5610a9d10c3d9132659'), + 'y': int('5a55dceddd1134ee5f11ed85deb4d634a3643f5f36dc3a706892564' + '69a0b651ad22880f14ab85719434f9c0e407e60ea420e2a0cd29422' + 'c4899c416359dbb1e592456f2b3cce233259c117542fd05f31ea25b' + '015d9121c890b90e0bad033be1368d229985aac7226d1c8c2eab325' + 'ef3b2cd59d3b9f7de7dbc94af1a9339eb430ca36c26c46ecfa6c548' + '1711496f624e188ad7540ef5df26f8efacb820bd17a1f618acb50c9' + 'bc197d4cb7ccac45d824a3bf795c234b556b06aeb92917345325208' + '4003f69fe98045fe74002ba658f93475622f76791d9b2623d1b5fff' + '2cc16844746efd2d30a6a8134bfc4c8cc80a46107901fb973c28fc5' + '53130f3286c1489da', 16), + 'r': int('633055e055f237c38999d81c397848c38cce80a55b649d9e7905c29' + '8e2a51447', 16), + 's': int('2bbf68317660ec1e4b154915027b0bc00ee19cfc0bf75d01930504f' + '2ce10a8b0', 16)}, + { + 'p': int('a8adb6c0b4cf9588012e5deff1a871d383e0e2a85b5e8e03d814fe1' + '3a059705e663230a377bf7323a8fa117100200bfd5adf857393b0bb' + 'd67906c081e585410e38480ead51684dac3a38f7b64c9eb109f1973' + '9a4517cd7d5d6291e8af20a3fbf17336c7bf80ee718ee087e322ee4' + '1047dabefbcc34d10b66b644ddb3160a28c0639563d71993a26543e' + 'adb7718f317bf5d9577a6156561b082a10029cd44012b18de684450' + '9fe058ba87980792285f2750969fe89c2cd6498db3545638d5379d1' + '25dccf64e06c1af33a6190841d223da1513333a7c9d78462abaab31' + 'b9f96d5f34445ceb6309f2f6d2c8dde06441e87980d303ef9a1ff00' + '7e8be2f0be06cc15f', 16), + 'q': int('e71f8567447f42e75f5ef85ca20fe557ab0343d37ed09edc3f6e686' + '04d6b9dfb', 16), + 'g': int('5ba24de9607b8998e66ce6c4f812a314c6935842f7ab54cd82b19fa' + '104abfb5d84579a623b2574b37d22ccae9b3e415e48f5c0f9bcbdff' + '8071d63b9bb956e547af3a8df99e5d3061979652ff96b765cb3ee49' + '3643544c75dbe5bb39834531952a0fb4b0378b3fcbb4c8b5800a533' + '0392a2a04e700bb6ed7e0b85795ea38b1b962741b3f33b9dde2f4ec' + '1354f09e2eb78e95f037a5804b6171659f88715ce1a9b0cc90c27f3' + '5ef2f10ff0c7c7a2bb0154d9b8ebe76a3d764aa879af372f4240de8' + '347937e5a90cec9f41ff2f26b8da9a94a225d1a913717d73f10397d' + '2183f1ba3b7b45a68f1ff1893caf69a827802f7b6a48d51da6fbefb' + '64fd9a6c5b75c4561', 16), + 'digest_algorithm': 'SHA-256', + 'msg': binascii.unhexlify( + b'a733b3f588d5ac9b9d4fe2f804df8c256403a9f8eef6f191fc48e1267fb' + b'5b4d546ba11e77b667844e489bf0d5f72990aeb061d01ccd7949a23def7' + b'4a803b7d92d51abfadeb4885ffd8ffd58ab87548a15c087a39b8993b2fa' + b'64c9d31a594eeb7512da16955834336a234435c5a9d0dd9b15a94e11615' + b'4dea63fdc8dd7a512181'), + 'y': int('356ed47537fbf02cb30a8cee0537f300dff1d0c467399ce70b87a87' + '58d5ec9dd256246fccaeb9dfe109f2a984f2ddaa87aad54ce0d31f9' + '07e504521baf4207d7073b0a4a9fc67d8ddda99f87aed6e0367cec2' + '7f9c608af743bf1ee6e11d55a182d43b024ace534029b866f642282' + '8bb81a39aae9601ee81c7f81dd358e69f4e2edfa4654d8a65bc6431' + '1dc86aac4abc1fc7a3f65159661a0d8e288eb8d665cb0adf5ac3d6b' + 'a8e9453facf7542393ae24fd50451d3828086558f7ec528e284935a' + '53f67a1aa8e25d8ad5c4ad55d83aef883a4d9eeb6297e6a53f65049' + 'ba9e2c6b7953a760bc1dc46f78ceaaa2c02f5375dd82e708744aa40' + 'b15799eb81d7e5b1a', 16), + 'r': int('bcd490568c0a89ba311bef88ea4f4b03d273e793722722327095a37' + '8dd6f3522', 16), + 's': int('74498fc43091fcdd2d1ef0775f8286945a01cd72b805256b0451f9c' + 'bd943cf82', 16)} + ] + assert expected == load_fips_dsa_sig_vectors(vector_data) + + +def test_load_fips_ecdsa_key_pair_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 11.0 + # "Key Pair" information + # Curves selected: P-192 K-233 B-571 + # Generated on Wed Mar 16 16:16:42 2011 + + + [P-192] + + [B.4.2 Key Pair Generation by Testing Candidates] + N = 2 + + d = e5ce89a34adddf25ff3bf1ffe6803f57d0220de3118798ea + Qx = 8abf7b3ceb2b02438af19543d3e5b1d573fa9ac60085840f + Qy = a87f80182dcd56a6a061f81f7da393e7cffd5e0738c6b245 + + d = 7d14435714ad13ff23341cb567cc91198ff8617cc39751b2 + Qx = 39dc723b19527daa1e80425209c56463481b9b47c51f8cbd + Qy = 432a3e84f2a16418834fabaf6b7d2341669512951f1672ad + + + [K-233] + + [B.4.2 Key Pair Generation by Testing Candidates] + N = 2 + + d = 01da7422b50e3ff051f2aaaed10acea6cbf6110c517da2f4eaca8b5b87 + Qx = 01c7475da9a161e4b3f7d6b086494063543a979e34b8d7ac44204d47bf9f + Qy = 0131cbd433f112871cc175943991b6a1350bf0cdd57ed8c831a2a7710c92 + + d = 530951158f7b1586978c196603c12d25607d2cb0557efadb23cd0ce8 + Qx = d37500a0391d98d3070d493e2b392a2c79dc736c097ed24b7dd5ddec44 + Qy = 01d996cc79f37d8dba143d4a8ad9a8a60ed7ea760aae1ddba34d883f65d9 + + + [B-571] + + [B.4.2 Key Pair Generation by Testing Candidates] + N = 2 + + d = 01443e93c7ef6802655f641ecbe95e75f1f15b02d2e172f49a32e22047d5c00ebe1b3f\ +f0456374461360667dbf07bc67f7d6135ee0d1d46a226a530fefe8ebf3b926e9fbad8d57a6 + Qx = 053e3710d8e7d4138db0a369c97e5332c1be38a20a4a84c36f5e55ea9fd6f34545b86\ +4ea64f319e74b5ee9e4e1fa1b7c5b2db0e52467518f8c45b658824871d5d4025a6320ca06f8 + Qy = 03a22cfd370c4a449b936ae97ab97aab11c57686cca99d14ef184f9417fad8bedae4d\ +f8357e3710bcda1833b30e297d4bf637938b995d231e557d13f062e81e830af5ab052208ead + + d = 03d2bd44ca9eeee8c860a4873ed55a54bdfdf5dab4060df7292877960b85d1fd496aa3\ +3c587347213d7f6bf208a6ab4b430546e7b6ffbc3135bd12f44a28517867ca3c83a821d6f8 + Qx = 07a7af10f6617090bade18b2e092d0dfdc87cd616db7f2db133477a82bfe3ea421ebb\ +7d6289980819292a719eb247195529ea60ad62862de0a26c72bfc49ecc81c2f9ed704e3168f + Qy = 0721496cf16f988b1aabef3368450441df8439a0ca794170f270ead56203d675b57f5\ +a4090a3a2f602a77ff3bac1417f7e25a683f667b3b91f105016a47afad46a0367b18e2bdf0c + """).splitlines() + + expected = [ + { + "curve": "secp192r1", + "d": int("e5ce89a34adddf25ff3bf1ffe6803f57d0220de3118798ea", 16), + "x": int("8abf7b3ceb2b02438af19543d3e5b1d573fa9ac60085840f", 16), + "y": int("a87f80182dcd56a6a061f81f7da393e7cffd5e0738c6b245", 16) + }, + + { + "curve": "secp192r1", + "d": int("7d14435714ad13ff23341cb567cc91198ff8617cc39751b2", 16), + "x": int("39dc723b19527daa1e80425209c56463481b9b47c51f8cbd", 16), + "y": int("432a3e84f2a16418834fabaf6b7d2341669512951f1672ad", 16), + }, + + { + "curve": "sect233k1", + "d": int("1da7422b50e3ff051f2aaaed10acea6cbf6110c517da2f4e" + "aca8b5b87", 16), + "x": int("1c7475da9a161e4b3f7d6b086494063543a979e34b8d7ac4" + "4204d47bf9f", 16), + "y": int("131cbd433f112871cc175943991b6a1350bf0cdd57ed8c83" + "1a2a7710c92", 16), + }, + + { + "curve": "sect233k1", + "d": int("530951158f7b1586978c196603c12d25607d2cb0557efadb" + "23cd0ce8", 16), + "x": int("d37500a0391d98d3070d493e2b392a2c79dc736c097ed24b" + "7dd5ddec44", 16), + "y": int("1d996cc79f37d8dba143d4a8ad9a8a60ed7ea760aae1ddba" + "34d883f65d9", 16), + }, + + { + "curve": "sect571r1", + "d": int("1443e93c7ef6802655f641ecbe95e75f1f15b02d2e172f49" + "a32e22047d5c00ebe1b3ff0456374461360667dbf07bc67f" + "7d6135ee0d1d46a226a530fefe8ebf3b926e9fbad8d57a6", 16), + "x": int("53e3710d8e7d4138db0a369c97e5332c1be38a20a4a84c36" + "f5e55ea9fd6f34545b864ea64f319e74b5ee9e4e1fa1b7c5" + "b2db0e52467518f8c45b658824871d5d4025a6320ca06f8", 16), + "y": int("3a22cfd370c4a449b936ae97ab97aab11c57686cca99d14e" + "f184f9417fad8bedae4df8357e3710bcda1833b30e297d4b" + "f637938b995d231e557d13f062e81e830af5ab052208ead", 16), + }, + + { + "curve": "sect571r1", + "d": int("3d2bd44ca9eeee8c860a4873ed55a54bdfdf5dab4060df72" + "92877960b85d1fd496aa33c587347213d7f6bf208a6ab4b4" + "30546e7b6ffbc3135bd12f44a28517867ca3c83a821d6f8", 16), + "x": int("7a7af10f6617090bade18b2e092d0dfdc87cd616db7f2db1" + "33477a82bfe3ea421ebb7d6289980819292a719eb2471955" + "29ea60ad62862de0a26c72bfc49ecc81c2f9ed704e3168f", 16), + "y": int("721496cf16f988b1aabef3368450441df8439a0ca794170f" + "270ead56203d675b57f5a4090a3a2f602a77ff3bac1417f7" + "e25a683f667b3b91f105016a47afad46a0367b18e2bdf0c", 16), + }, + ] + + assert expected == load_fips_ecdsa_key_pair_vectors(vector_data) + + +def test_load_fips_ecdsa_signing_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 11.2 + # "SigVer" information for "ecdsa_values" + # Curves/SHAs selected: P-192, B-571,SHA-512 + # Generated on Tue Aug 16 15:27:42 2011 + + [P-192,SHA-1] + + Msg = ebf748d748ebbca7d29fb473698a6e6b4fb10c865d4af024cc39ae3df3464ba4f1d6\ +d40f32bf9618a91bb5986fa1a2af048a0e14dc51e5267eb05e127d689d0ac6f1a7f156ce066316\ +b971cc7a11d0fd7a2093e27cf2d08727a4e6748cc32fd59c7810c5b9019df21cdcc0bca432c0a3\ +eed0785387508877114359cee4a071cf + d = e14f37b3d1374ff8b03f41b9b3fdd2f0ebccf275d660d7f3 + Qx = 07008ea40b08dbe76432096e80a2494c94982d2d5bcf98e6 + Qy = 76fab681d00b414ea636ba215de26d98c41bd7f2e4d65477 + k = cb0abc7043a10783684556fb12c4154d57bc31a289685f25 + R = 6994d962bdd0d793ffddf855ec5bf2f91a9698b46258a63e + S = 02ba6465a234903744ab02bc8521405b73cf5fc00e1a9f41 + Result = F (3 - S changed) + + Msg = 0dcb3e96d77ee64e9d0a350d31563d525755fc675f0c833504e83fc69c030181b42f\ +e80c378e86274a93922c570d54a7a358c05755ec3ae91928e02236e81b43e596e4ccbf6a910488\ +9c388072bec4e1faeae11fe4eb24fa4f9573560dcf2e3abc703c526d46d502c7a7222583431cc8\ +178354ae7dbb84e3479917707bce0968 + d = 7a0235bea3d70445f14d56f9b7fb80ec8ff4eb2f76865244 + Qx = 0ea3c1fa1f124f26530cbfddeb831eecc67df31e08889d1d + Qy = 7215a0cce0501b47903bd8fe1179c2dfe07bd076f89f5225 + k = 3c646b0f03f5575e5fd463d4319817ce8bd3022eaf551cef + R = a3ba51c39c43991d87dff0f34d0bec7c883299e04f60f95e + S = 8a7f9c59c6d65ad390e4c19636ba92b53be5d0f848b4e1f7 + + [B-571,SHA-512] + + Msg = 10d2e00ae57176c79cdfc746c0c887abe799ee445b151b008e3d9f81eb69be40298d\ +df37b5c45a9b6e5ff83785d8c140cf11e6a4c3879a2845796872363da24b10f1f8d9cc48f8af20\ +681dceb60dd62095d6d3b1779a4a805de3d74e38983b24c0748618e2f92ef7cac257ff4bd1f411\ +13f2891eb13c47930e69ddbe91f270fb + d = 03e1b03ffca4399d5b439fac8f87a5cb06930f00d304193d7daf83d5947d0c1e293f74\ +aef8e56849f16147133c37a6b3d1b1883e5d61d6b871ea036c5291d9a74541f28878cb986 + Qx = 3b236fc135d849d50140fdaae1045e6ae35ef61091e98f5059b30eb16acdd0deb2bc0\ +d3544bc3a666e0014e50030134fe5466a9e4d3911ed580e28851f3747c0010888e819d3d1f + Qy = 3a8b6627a587d289032bd76374d16771188d7ff281c39542c8977f6872fa932e5daa1\ +4e13792dea9ffe8e9f68d6b525ec99b81a5a60cfb0590cc6f297cfff8d7ba1a8bb81fe2e16 + k = 2e56a94cfbbcd293e242f0c2a2e9df289a9480e6ba52e0f00fa19bcf2a7769bd155e6b\ +79ddbd6a8646b0e69c8baea27f8034a18796e8eb4fe6e0e2358c383521d9375d2b6b437f9 + R = 2eb1c5c1fc93cf3c8babed12c031cf1504e094174fd335104cbe4a2abd210b5a14b1c3\ +a455579f1ed0517c31822340e4dd3c1f967e1b4b9d071a1072afc1a199f8c548cd449a634 + S = 22f97bb48641235826cf4e597fa8de849402d6bd6114ad2d7fbcf53a08247e5ee921f1\ +bd5994dffee36eedff5592bb93b8bb148214da3b7baebffbd96b4f86c55b3f6bbac142442 + Result = P (0 ) + + Msg = b61a0849a28672cb536fcf61ea2eb389d02ff7a09aa391744cae6597bd56703c40c5\ +0ca2dee5f7ee796acfd47322f03d8dbe4d99dc8eec588b4e5467f123075b2d74b2a0b0bbfd3ac5\ +487a905fad6d6ac1421c2e564c0cf15e1f0f10bc31c249b7b46edd2462a55f85560d99bde9d5b0\ +6b97817d1dbe0a67c701d6e6e7878272 + d = 2e09ffd8b434bb7f67d1d3ccf482164f1653c6e4ec64dec2517aa21b7a93b2b21ea1ee\ +bb54734882f29303e489f02e3b741a87287e2dcdf3858eb6d2ec668f8b5b26f442ce513a2 + Qx = 36f1be8738dd7dae4486b86a08fe90424f3673e76b10e739442e15f3bfafaf841842a\ +c98e490521b7e7bb94c127529f6ec6a42cc6f06fc80606f1210fe020ff508148f93301c9d3 + Qy = 4d39666ebe99fe214336ad440d776c88eb916f2f4a3433548b87d2aebed840b424d15\ +c8341b4a0a657bf6a234d4fe78631c8e07ac1f4dc7474cd6b4545d536b7b17c160db4562d9 + k = 378e7801566d7b77db7a474717ab2195b02957cc264a9449d4126a7cc574728ed5a476\ +9abd5dde987ca66cfe3d45b5fc52ffd266acb8a8bb3fcb4b60f7febbf48aebe33bd3efbdd + R = 3d8105f87fe3166046c08e80a28acc98a80b8b7a729623053c2a9e80afd06756edfe09\ +bdcf3035f6829ede041b745955d219dc5d30ddd8b37f6ba0f6d2857504cdc68a1ed812a10 + S = 34db9998dc53527114518a7ce3783d674ca8cced823fa05e2942e7a0a20b3cc583dcd9\ +30c43f9b93079c5ee18a1f5a66e7c3527c18610f9b47a4da7e245ef803e0662e4d2ad721c + """).splitlines() + + expected = [ + { + "curve": "secp192r1", + "digest_algorithm": "SHA-1", + "message": binascii.unhexlify( + b"ebf748d748ebbca7d29fb473698a6e6b4fb10c865d4af024cc39ae3df346" + b"4ba4f1d6d40f32bf9618a91bb5986fa1a2af048a0e14dc51e5267eb05e12" + b"7d689d0ac6f1a7f156ce066316b971cc7a11d0fd7a2093e27cf2d08727a4" + b"e6748cc32fd59c7810c5b9019df21cdcc0bca432c0a3eed0785387508877" + b"114359cee4a071cf" + ), + "d": int("e14f37b3d1374ff8b03f41b9b3fdd2f0ebccf275d660d7f3", 16), + "x": int("7008ea40b08dbe76432096e80a2494c94982d2d5bcf98e6", 16), + "y": int("76fab681d00b414ea636ba215de26d98c41bd7f2e4d65477", 16), + "r": int("6994d962bdd0d793ffddf855ec5bf2f91a9698b46258a63e", 16), + "s": int("02ba6465a234903744ab02bc8521405b73cf5fc00e1a9f41", 16), + "fail": True + }, + { + "curve": "secp192r1", + "digest_algorithm": "SHA-1", + "message": binascii.unhexlify( + b"0dcb3e96d77ee64e9d0a350d31563d525755fc675f0c833504e83fc69c03" + b"0181b42fe80c378e86274a93922c570d54a7a358c05755ec3ae91928e022" + b"36e81b43e596e4ccbf6a9104889c388072bec4e1faeae11fe4eb24fa4f95" + b"73560dcf2e3abc703c526d46d502c7a7222583431cc8178354ae7dbb84e3" + b"479917707bce0968" + ), + "d": int("7a0235bea3d70445f14d56f9b7fb80ec8ff4eb2f76865244", 16), + "x": int("ea3c1fa1f124f26530cbfddeb831eecc67df31e08889d1d", 16), + "y": int("7215a0cce0501b47903bd8fe1179c2dfe07bd076f89f5225", 16), + "r": int("a3ba51c39c43991d87dff0f34d0bec7c883299e04f60f95e", 16), + "s": int("8a7f9c59c6d65ad390e4c19636ba92b53be5d0f848b4e1f7", 16), + }, + { + "curve": "sect571r1", + "digest_algorithm": "SHA-512", + "message": binascii.unhexlify( + b"10d2e00ae57176c79cdfc746c0c887abe799ee445b151b008e3d9f81eb69" + b"be40298ddf37b5c45a9b6e5ff83785d8c140cf11e6a4c3879a2845796872" + b"363da24b10f1f8d9cc48f8af20681dceb60dd62095d6d3b1779a4a805de3" + b"d74e38983b24c0748618e2f92ef7cac257ff4bd1f41113f2891eb13c4793" + b"0e69ddbe91f270fb" + ), + "d": int("3e1b03ffca4399d5b439fac8f87a5cb06930f00d304193d7daf83d59" + "47d0c1e293f74aef8e56849f16147133c37a6b3d1b1883e5d61d6b87" + "1ea036c5291d9a74541f28878cb986", 16), + "x": int("3b236fc135d849d50140fdaae1045e6ae35ef61091e98f5059b30eb1" + "6acdd0deb2bc0d3544bc3a666e0014e50030134fe5466a9e4d3911ed" + "580e28851f3747c0010888e819d3d1f", 16), + "y": int("3a8b6627a587d289032bd76374d16771188d7ff281c39542c8977f68" + "72fa932e5daa14e13792dea9ffe8e9f68d6b525ec99b81a5a60cfb05" + "90cc6f297cfff8d7ba1a8bb81fe2e16", 16), + "r": int("2eb1c5c1fc93cf3c8babed12c031cf1504e094174fd335104cbe4a2a" + "bd210b5a14b1c3a455579f1ed0517c31822340e4dd3c1f967e1b4b9d" + "071a1072afc1a199f8c548cd449a634", 16), + "s": int("22f97bb48641235826cf4e597fa8de849402d6bd6114ad2d7fbcf53a" + "08247e5ee921f1bd5994dffee36eedff5592bb93b8bb148214da3b7b" + "aebffbd96b4f86c55b3f6bbac142442", 16), + "fail": False + }, + { + "curve": "sect571r1", + "digest_algorithm": "SHA-512", + "message": binascii.unhexlify( + b"b61a0849a28672cb536fcf61ea2eb389d02ff7a09aa391744cae6597bd56" + b"703c40c50ca2dee5f7ee796acfd47322f03d8dbe4d99dc8eec588b4e5467" + b"f123075b2d74b2a0b0bbfd3ac5487a905fad6d6ac1421c2e564c0cf15e1f" + b"0f10bc31c249b7b46edd2462a55f85560d99bde9d5b06b97817d1dbe0a67" + b"c701d6e6e7878272" + ), + "d": int("2e09ffd8b434bb7f67d1d3ccf482164f1653c6e4ec64dec2517aa21b" + "7a93b2b21ea1eebb54734882f29303e489f02e3b741a87287e2dcdf3" + "858eb6d2ec668f8b5b26f442ce513a2", 16), + "x": int("36f1be8738dd7dae4486b86a08fe90424f3673e76b10e739442e15f3" + "bfafaf841842ac98e490521b7e7bb94c127529f6ec6a42cc6f06fc80" + "606f1210fe020ff508148f93301c9d3", 16), + "y": int("4d39666ebe99fe214336ad440d776c88eb916f2f4a3433548b87d2ae" + "bed840b424d15c8341b4a0a657bf6a234d4fe78631c8e07ac1f4dc74" + "74cd6b4545d536b7b17c160db4562d9", 16), + "r": int("3d8105f87fe3166046c08e80a28acc98a80b8b7a729623053c2a9e80" + "afd06756edfe09bdcf3035f6829ede041b745955d219dc5d30ddd8b3" + "7f6ba0f6d2857504cdc68a1ed812a10", 16), + "s": int("34db9998dc53527114518a7ce3783d674ca8cced823fa05e2942e7a0" + "a20b3cc583dcd930c43f9b93079c5ee18a1f5a66e7c3527c18610f9b" + "47a4da7e245ef803e0662e4d2ad721c", 16) + } + ] + assert expected == load_fips_ecdsa_signing_vectors(vector_data) + + +def test_load_kasvs_dh_vectors(): + vector_data = textwrap.dedent(""" + [SHA(s) supported (Used for hashing Z): SHA256 ] + # Generated on Thu Mar 17 20:44:26 2011 + + + + [FA - SHA1] + P = da3a8085d372437805de95b88b675122f575df976610c6a844de99f1df82a06848bf7a\ +42f18895c97402e81118e01a00d0855d51922f434c022350861d58ddf60d65bc6941fc6064b147\ +071a4c30426d82fc90d888f94990267c64beef8c304a4b2b26fb93724d6a9472fa16bc50c5b9b8\ +b59afb62cfe9ea3ba042c73a6ade35 + Q = f2ca7621eb250aa5f22cef1907011295defc50a7 + G = a51883e9ac0539859df3d25c716437008bb4bd8ec4786eb4bc643299daef5e3e5af586\ +3a6ac40a597b83a27583f6a658d408825105b16d31b6ed088fc623f648fd6d95e9cefcb0745763\ +cddf564c87bcf4ba7928e74fd6a3080481f588d535e4c026b58a21e1e5ec412ff241b436043e29\ +173f1dc6cb943c09742de989547288 + + + + COUNT = 0 + XstatCAVS = 42c6ee70beb7465928a1efe692d2281b8f7b53d6 + YstatCAVS = 5a7890f6d20ee9c7162cd84222cb0c7cb5b4f29244a58fc95327fc41045f47\ +6fb3da42fca76a1dd59222a7a7c3872d5af7d8dc254e003eccdb38f291619c51911df2b6ed67d0\ +b459f4bc25819c0078777b9a1a24c72e7c037a3720a1edad5863ef5ac75ce816869c820859558d\ +5721089ddbe331f55bef741396a3bbf85c6c1a + XstatIUT = 54081a8fef2127a1f22ed90440b1b09c331d0614 + YstatIUT = 0b92af0468b841ea5de4ca91d895b5e922245421de57ed7a88d2de41610b208\ +e8e233705f17b2e9eb91914bad2fa87f0a58519a7da2980bc06e7411c925a6050526bd86e62150\ +5e6f610b63fdcd9afcfaa96bd087afca44d9197cc35b559f731357a5b979250c0f3a254bb8165f\ +5072156e3fd6f9a6e69bcf4b4578f78b3bde7 + Z = 8d8f4175e16e15a42eb9099b11528af88741cc206a088971d3064bb291eda608d1600b\ +ff829624db258fd15e95d96d3e74c6be3232afe5c855b9c59681ce13b7aea9ff2b16707e4c02f0\ +e82bf6dadf2149ac62630f6c62dea0e505e3279404da5ffd5a088e8474ae0c8726b8189cb3d2f0\ +4baffe700be849df9f91567fc2ebb8 + CAVSHashZZ = eb99e77ac2272c7a2ee70c59375ac4d167312c20 + Result = P (0 - Correct) + + + + COUNT = 2 + XstatCAVS = 32e642683d745a23dccf4f12f989d8dfd1fd9894c422930950cb4c71 + YstatCAVS = 8cd371363b32fcc2e936e345f2278b77001f2efdf78512c3ee75c12f88507e\ +2d5c0e5cdded3bb78435506c8028a3f4d6f028c0f49a0d61f1285795197e56deac80279e723f2b\ +3746e213ac8ec60f1cefc2308ff17a7e9e2efab537e17406d2829fd85e0c54dda2d9f0b4fcda3d\ +2776110e096a817588e19588b77be8b41bafdd41ad91b0edf629333bd6ac1e461208ead124c31b\ +8a7935c723e1c450c5798dc05f8265ad9e35095ff112af9e889f00315fa337a76a450670866eca\ +12cc6ad0778576962eb9cdc12721d3c15e4d87b67488a145d400240670eb26695a42879cd3940a\ +55087f6527667277e1212a202dbe455c45c64b9be4a38153557bbb8fd755 + XstatIUT = 7d8ae93df3bc09d399a4157ec562126acf51092c3269ab27f60a3a2b + YstatIUT = 22127e9728e906ea4b1512c8b1e80474b58446210c23ccfc800f83c2c15da81\ +59940e494b235266f6a9d5f80529067794f1a9edd566755d23d0a3060fe074c5a10122df3e4729\ +73bba39ea3a988e8387f5f0491e590b6b5edc299b4598ab1e79b72681a0be8cd8735a5adb85fa3\ +1310f29ec407c9654f1bb83bcdf7f771b68d176817f662e8d798b53ebb4e5dd407b7b1d8fdb62e\ +a9e1b60d6c3d75d9bcf83f4b8d1ed39408bd8d973b4ea81e8e832eac361dcd530713388a60971e\ +a9f8b1e69c1e99df1cca12bdaf293dacfa1419c5692ceffa91988aef3321ac8cbc2efae6c4337c\ +8808310fb5a240395a98e6004fe613c39e84f4177341746d9e388dcb2e8 + Z = 0efeaa399a182e0a603baf0dd95aa0fae5289ebd47d5f0f60c86bc936839c31c9f7f37\ +bf04f76ab02f4094a8ab10ed907ec7291585cc085c3e8981df2bd46a01c19ec9a2f66709df1d4f\ +efbeb48c8263554e46890f59eb642bf95ff7f0de70138621c22c4cc32be6c3d5c82c0c9a76a9f5\ +a65bffe0c096a350f96a9da945d7e5095b15b566ce3cb8b0377cd9375b6c046afa9ea0bc084677\ +3445f16566b2c84cae4f6d212e89ee539a1ce7ea325273fd228053efce2a585eb9e8f308b48cf4\ +e29593b6f7a02e8625e1e8bff1ea1405f8c8c34b8339a9a99c7c9de4eb9895df7719ccda9394f5\ +3080eff1226f6b9c7ae0a38941e18b1a137aabbb62308eb35ba2 + CAVSHashZZ = 76dedc997d5113573bbeeaf991f62b257511b7d9aa83270dfc4fec40 + Result = P (10 - Z value should have leading 0 nibble ) + + + + COUNT = 3 + XstatCAVS = 66502429aba271e2f2ee2197a2b336e5f0467f192aa28b60dcbf1194 + YstatCAVS = dfb001294215423d7146a2453cdb8598ccef01e1d931a913c3e4ed4a3cf38a\ +912066c28e4eaf77dd80ff07183a6160bd95932f513402f864dcf7a70cbedc9b60bbfbc67f72a8\ +3d5f6463a2b5a4fc906d3e921f5e1069126113265b440e15ccf2d7164bad7131f1613fec35df7f\ +470d45888e0c91be091f3f9552d670b8b7f479853193cb3c39f35fc7bd547ccb1bc579a67302b4\ +ba948e6db51043d351bb74a952e6a694e6e7456f714c47d7c8eeeb4fd83ad93c86b78445f9393f\ +dfd65c7dbd7fd6eba9794ddf183901b1d213321fd0ab3f7588ab0f6b3692f365a87131eda0e062\ +505861988f6ce63150207545ecf9678e0971330253dfb7cfd546c5346fec + XstatIUT = 106b358be4f068348ac240ecbb454e5c39ca80b078cb0fafd856e9c5 + YstatIUT = 715d0781975b7b03162f4401c1eda343fd9bf1140006034573b31828a618c35\ +6163554cd27da956f7179a69e860fb6efeaa2e2aa9f1261506a8344c4929953621381b13d6426e\ +152c0f2f94bfcd2b758eca24923596d427ed8f957e8bc9b1c7d21a87ef02222a1477cf3bfaadc6\ +8106456ab9706026006eccd290b21543de6bb97d5b8cf4ccee1c081a6d1dd27aaef060fa93888a\ +47a4a416ad5c5bd490ea600e04379232fb1077fbf394f4579accdbe352714e25b88916dca8d8f7\ +e0c4ed9594f7693f656a235a2e88ebda48b0d557e32da9f12d2a4c3180f05b16b4fba9bec79278\ +a3971b77f9223b5ab78b857e0376c5008211592c8c72d521373ee3b22b8 + Z = cf879ebd107bb877457809c3fc410218b7acba3c5967495a8f1c3370d57f038a48dd69\ +f9f69b9f4dd855e7c58a1e4ec32646a978266eb314db468ea1dfcee8a85a1644a5732498c4fbcd\ +f85098c6ed0ce12e431e99142fd2335369b3f56620ada21aa69d883e82a0b5e35484dde32d17c2\ +dc873f2cc5518eb7fc19695dff9fc94c9d9432bb4b09d8180323cfc561ebc2d6eff8dd5f8496f2\ +b22377700a22bbfe61a6969c198129397454843e4fc3540026986039665095490056287e4fc49e\ +6cb3181cb2bf06444fd0040150271c9ce1f61c13ecd5dd022194a2dbf3e1c7fbc6bd19497c7b88\ +8b4da613d28fa6f378a43369cb8795a1c823f7d6cf4d84bba578 + CAVSHashZZ = ebac4fb70699224f85d9e3c799b1f3a56dab268b882aba49525df02d + Result = F (5 - Z changed ) + + + + [FB - SHA224] + P = f3722b9b911c6aede9eaeeaa406283de66a097f39a7225df6c3c916e57920d356e5047\ +8d307dbfd146bfb91b6f68ecbbcf54b3d19c33a4b17293fea3e3d6bff8ac4cca93a805386f062a\ +8a27ae906ef5da94d279fd7b3d7289e00956f76bae9c0d2b8d11742ca5809630632aae58f9c6dc\ +e00c7380581deffde2187b022f83c6ceaeaadb0844a17fcbb04039ca6843c91f0c9058b22434b2\ +63c3dfda8de8429e087c5be97fc5c9db9526031ad3a218bd9916fb4a3c27966d208b1e360014c0\ +1e95530c148fb3cd27e6a7250d3c3b81dcd220ca14548dbccf99ebb9e334db6bcd14e632c98dd3\ +f9860af7ae450f1b7809b45f0ec10e6f27672beebc9963befc73 + Q = a9a17de95a29091bf8e07dab53ea1aba9403be3c61027c6c8f48bac5 + G = 035513ec441402b78353ab1bba550b21c76c89973885a627170262ef52497d5d137b89\ +27a212aaab2f051198c90bb81dffd9eb10b36b7ca3b63565b4c1025aea3b5e9c4a348c9cfa17f3\ +907a1e4469701c0dedb8a4b9e96c5965b1fb8c229b0c34baac774bf9dda4fc5ee8764358b3c848\ +12878aab7464bc09e97aecab7d7e3fbb4870e2a3b89667a4158bf1ed1a90dfaf47019fbb52b1b9\ +6365bb4e1e9474993fe382fd23480dc875861be152997a621fdb7aef977ea5b4d3d74486b162dc\ +28f95a64cf65587a919a57eef92934fc9410df7f09fa82f975328ed82ff29cc3e15a971f56f4ac\ +2dcb289252575e02a6cdb7fcc6cddd7b0dca9c422e63eb2b8f05 + + + + COUNT = 0 + XstatCAVS = 1610eaa4e0ccc8857e2b53149e008492b1fbd9025a6e8d95aaee9c0f + YstatCAVS = 51ee21cd9f97015180f258fad5c94ff5a458806b1412087236bf77fe87aae1\ +a36735816ed6e2160a731159814b6ae1f3f52c478dd9207094adfb62f7667d5c366327e66d2309\ +6395e938504db330953a708015f861fe9d9487611093b9fe7327518a7cc15994ab573313e15411\ +7c1a3ae88b8bdd1e316748249e4a9cbd1947f159836d13613d1f9449fc3442171d1970bc28958c\ +1cafa2776a6f14ccdb29db02f64911bd83bfdcdfc843dd14a4cab9acb0bda8b293d2f5f7050768\ +e57533cbc415a29e6f31cc365e107f91ae3722484e2c7329a85af69055a5a104da37e810878896\ +d1b247b02b75234ecff82b1958f42d7b031622e9394c98b5229112f7f620 + XstatIUT = 0c4c83d75b27864b052cadc556e500e25aabf0c9d1bc01f0e1fe3862 + YstatIUT = 467a857337a82472a1307a64dccc8e9994c5c63ec4312936885d17be419051a\ +5f037fbb052d7010ebe01634d9e8b8b522d9ab4749fdc274f465369b89e360df8f70b7865a3c71\ +d2dbcd2df19e9293dab1153d3d63fcb7deb559b684dde6c6eed63214444807041c9a0ce3f52ca4\ +39ec16dd231995b5dc6f18e6801b6bd6454babccf9abbfacffb49c71e6494a4779cbfa550c5d71\ +44114e6fc193f460dcd0be7e6e06e546da7653770dc5859df87029e722dbe81361030569148d16\ +36988926bf0dcfe47c9d8a54698c08b3b5c70afe86b5c6f643463f8f34889d27d6cfd2d478c2d7\ +b3d008a985c7380f0b43f10024b59c3543880883c42d0e7e0a07326ba3a + Z = 10a30bacab82e652415376baffdbc008c7eb2e5a3aa68bc10ce486ca84983fd89b1b02\ +7bb40e75333406361005f5e756526a95fe01202df9217d81b1713d5187c368fdd4c9c2433d9e6c\ +18844769479b725c4140c92a304ee1bc5726d8f5321b5b1c54a1a6b67c527e6817c0ed613a0d4e\ +60db55de898788b7e8d4aa9a81ab5ed7f6282962c433d246ed640555bdd76d29c2874551264d74\ +c76373f8a88871b41b041c98041b16f94f983ddf00f5bc7d2416d19168c90178974a0602436cd1\ +86748bcc63a629edc3a0db59415cccd37a65130ea477c89da92d41371f5972891cf41f9c7f0e75\ +ccbff9893225384db30daa5e310f08e3e0fad98bcdf8ecf35fe5 + CAVSHashZZ = 014f5daea733d0e9e100f852e74d64a319f741cfbdb47975ab9dd3d0 + Result = F (3 - IUT's Static public key fails PKV 5.6.2.4) + + + COUNT = 1 + XstatCAVS = 9ee22ac51664e40e0a24dbb94142dba40605e2b6eeaaa0268a0f6847 + YstatCAVS = c2630c9d38ed5c825d1c6a3eba7143f3fc8a049c8bcd1efc212d2af64eca99\ +4308208691d330aa8f27fc4a1e55de4e512113996d21375a667f8c26d76dee2f6809b15432a33f\ +b735aca5c2263940f58712bded08f55443dee300b9489589e0462bd6bce19deaec4adc12fa61a6\ +94c8c5c999b28211d7835bac0ffd2b316850823e2dc1d1f58e05cbf75c673036d116b3f03b9687\ +c89f9c2a0d43c4ffc9a605addbdcce0cb3790c6db846156bb857a7b3df40dc6ed04d19cc9eaebb\ +6bbc034e77c3d882a1a62317cce25b6130f0803e3bc49b5e36768260073a617034872be0b50bed\ +32740224beaf582d67fbcfef3b3ecc18f9c71c782e9a68495ef31dc7986e + XstatIUT = 438093a468236658821bf64eb08456139963d4fb27121c3ed6c55876 + YstatIUT = e192da8e1244e27221c1765344a5bb379dce741d427a734b4bdb6c4d16b2490\ +bd37564d745008e63ae46ef332331d79887ac63298ce143e125f8b320c0f859b7f5f2c1e0053e4\ +a7a16997e6143ff702300c9863ae7caef5c1dfca0ecf5197c557745b793f0790a4fe678aeb93fd\ +b52490d4f273a5553944dda3ac8b9b792c9b67f8d7b9496398e432a423ae87ebeba688be3ed67e\ +ddd7575fa56431cd48579bf53c903bbe066dd78b23c0996ef3a880f0d91315104366a82f01abde\ +cce96fd371f94e8420f8bc5b896c801df573554f749b03d0d28b1e1a990bc61c7e9659342ac7e2\ +68e9c0b7c40fdaab394f29cf0a54f780022f9a03b0bd28eb7db8b0b1b47 + Z = 56f8f40fa4b8f3580f9014b30d60a42933a53a62182a690142f458dc275c3b2f0e721b\ +c5ee6e890b14516419110f5252ff1cceea8e274b2987aa78e3bae90c1935b276b7a1f1c944f79d\ +4774b7a85b3355bdf25cb02bddfbda4ee7918bc93a5c9ca6d7e8fdedbda8e6c8a6ca794bad055a\ +52b19c148958227344cbddd70271d4610316cfea1e559b0bc3a12d15023b30d9f2db602053a056\ +9c3bd2ce1faf59280ecd339f845dbcaaf2e883c5cc6263996f866b18b75d049d4c82097af8a5ce\ +353e14416b3eeb31ba9bc4f6f3dbd846c5299fb5c0043a1b95b9149b39d14df9e6a69547abf8a4\ +d518475576730ed528779366568e46b7dd4ed787cb72d0733c93 + CAVSHashZZ = 17dbbaa7a20c1390cd8cb3d31ee947bf9dde87739e067b9861ffeea9 + Result = P (0 - Correct) + """).splitlines() + + expected = [ + { + 'fail_agree': False, + 'fail_z': False, + 'g': int( + "a51883e9ac0539859df3d25c716437008bb4bd8ec4786eb4bc643299daef5" + "e3e5af5863a6ac40a597b83a27583f6a658d408825105b16d31b6ed088fc6" + "23f648fd6d95e9cefcb0745763cddf564c87bcf4ba7928e74fd6a3080481f" + "588d535e4c026b58a21e1e5ec412ff241b436043e29173f1dc6cb943c0974" + "2de989547288", 16), + 'p': int( + "da3a8085d372437805de95b88b675122f575df976610c6a844de99f1df82a" + "06848bf7a42f18895c97402e81118e01a00d0855d51922f434c022350861d" + "58ddf60d65bc6941fc6064b147071a4c30426d82fc90d888f94990267c64b" + "eef8c304a4b2b26fb93724d6a9472fa16bc50c5b9b8b59afb62cfe9ea3ba0" + "42c73a6ade35", 16), + 'q': 1386090807861091316803998193774751098153687863463, + 'x1': 381229709512864262422021151581620734547375903702, + 'x2': 479735944608461101114916716909067001453470352916, + 'y1': int( + "5a7890f6d20ee9c7162cd84222cb0c7cb5b4f29244a58fc95327fc41045f4" + "76fb3da42fca76a1dd59222a7a7c3872d5af7d8dc254e003eccdb38f29161" + "9c51911df2b6ed67d0b459f4bc25819c0078777b9a1a24c72e7c037a3720a" + "1edad5863ef5ac75ce816869c820859558d5721089ddbe331f55bef741396" + "a3bbf85c6c1a", 16), + 'y2': int( + "b92af0468b841ea5de4ca91d895b5e922245421de57ed7a88d2de41610b20" + "8e8e233705f17b2e9eb91914bad2fa87f0a58519a7da2980bc06e7411c925" + "a6050526bd86e621505e6f610b63fdcd9afcfaa96bd087afca44d9197cc35" + "b559f731357a5b979250c0f3a254bb8165f5072156e3fd6f9a6e69bcf4b45" + "78f78b3bde7", 16), + 'z': binascii.unhexlify( + b"8d8f4175e16e15a42eb9099b11528af88741cc206a088971d3064bb291ed" + b"a608d1600bff829624db258fd15e95d96d3e74c6be3232afe5c855b9c596" + b"81ce13b7aea9ff2b16707e4c02f0e82bf6dadf2149ac62630f6c62dea0e5" + b"05e3279404da5ffd5a088e8474ae0c8726b8189cb3d2f04baffe700be849" + b"df9f91567fc2ebb8" + ) + }, + { + 'fail_agree': False, + 'fail_z': False, + 'g': int( + "a51883e9ac0539859df3d25c716437008bb4bd8ec4786eb4bc643299daef5" + "e3e5af5863a6ac40a597b83a27583f6a658d408825105b16d31b6ed088fc6" + "23f648fd6d95e9cefcb0745763cddf564c87bcf4ba7928e74fd6a3080481f" + "588d535e4c026b58a21e1e5ec412ff241b436043e29173f1dc6cb943c0974" + "2de989547288", 16), + 'p': int( + "da3a8085d372437805de95b88b675122f575df976610c6a844de99f1df82a" + "06848bf7a42f18895c97402e81118e01a00d0855d51922f434c022350861d" + "58ddf60d65bc6941fc6064b147071a4c30426d82fc90d888f94990267c64b" + "eef8c304a4b2b26fb93724d6a9472fa16bc50c5b9b8b59afb62cfe9ea3ba0" + "42c73a6ade35", 16), + 'q': 1386090807861091316803998193774751098153687863463, + 'x1': int( + "32e642683d745a23dccf4f12f989d8dfd1fd9894c422930950cb4c71", + 16), + 'x2': int( + "7d8ae93df3bc09d399a4157ec562126acf51092c3269ab27f60a3a2b", + 16), + 'y1': int( + "8cd371363b32fcc2e936e345f2278b77001f2efdf78512c3ee75c12f88507" + "e2d5c0e5cdded3bb78435506c8028a3f4d6f028c0f49a0d61f1285795197e" + "56deac80279e723f2b3746e213ac8ec60f1cefc2308ff17a7e9e2efab537e" + "17406d2829fd85e0c54dda2d9f0b4fcda3d2776110e096a817588e19588b7" + "7be8b41bafdd41ad91b0edf629333bd6ac1e461208ead124c31b8a7935c72" + "3e1c450c5798dc05f8265ad9e35095ff112af9e889f00315fa337a76a4506" + "70866eca12cc6ad0778576962eb9cdc12721d3c15e4d87b67488a145d4002" + "40670eb26695a42879cd3940a55087f6527667277e1212a202dbe455c45c6" + "4b9be4a38153557bbb8fd755", 16), + 'y2': int( + "22127e9728e906ea4b1512c8b1e80474b58446210c23ccfc800f83c2c15da" + "8159940e494b235266f6a9d5f80529067794f1a9edd566755d23d0a3060fe" + "074c5a10122df3e472973bba39ea3a988e8387f5f0491e590b6b5edc299b4" + "598ab1e79b72681a0be8cd8735a5adb85fa31310f29ec407c9654f1bb83bc" + "df7f771b68d176817f662e8d798b53ebb4e5dd407b7b1d8fdb62ea9e1b60d" + "6c3d75d9bcf83f4b8d1ed39408bd8d973b4ea81e8e832eac361dcd5307133" + "88a60971ea9f8b1e69c1e99df1cca12bdaf293dacfa1419c5692ceffa9198" + "8aef3321ac8cbc2efae6c4337c8808310fb5a240395a98e6004fe613c39e8" + "4f4177341746d9e388dcb2e8", 16), + 'z': binascii.unhexlify( + b"0efeaa399a182e0a603baf0dd95aa0fae5289ebd47d5f0f60c86bc936839" + b"c31c9f7f37bf04f76ab02f4094a8ab10ed907ec7291585cc085c3e8981df" + b"2bd46a01c19ec9a2f66709df1d4fefbeb48c8263554e46890f59eb642bf9" + b"5ff7f0de70138621c22c4cc32be6c3d5c82c0c9a76a9f5a65bffe0c096a3" + b"50f96a9da945d7e5095b15b566ce3cb8b0377cd9375b6c046afa9ea0bc08" + b"46773445f16566b2c84cae4f6d212e89ee539a1ce7ea325273fd228053ef" + b"ce2a585eb9e8f308b48cf4e29593b6f7a02e8625e1e8bff1ea1405f8c8c3" + b"4b8339a9a99c7c9de4eb9895df7719ccda9394f53080eff1226f6b9c7ae0" + b"a38941e18b1a137aabbb62308eb35ba2" + ) + }, + { + 'fail_agree': False, + 'fail_z': True, + 'g': int( + "a51883e9ac0539859df3d25c716437008bb4bd8ec4786eb4bc643299daef5" + "e3e5af5863a6ac40a597b83a27583f6a658d408825105b16d31b6ed088fc6" + "23f648fd6d95e9cefcb0745763cddf564c87bcf4ba7928e74fd6a3080481f" + "588d535e4c026b58a21e1e5ec412ff241b436043e29173f1dc6cb943c0974" + "2de989547288", 16), + 'p': int( + "da3a8085d372437805de95b88b675122f575df976610c6a844de99f1df82a" + "06848bf7a42f18895c97402e81118e01a00d0855d51922f434c022350861d" + "58ddf60d65bc6941fc6064b147071a4c30426d82fc90d888f94990267c64b" + "eef8c304a4b2b26fb93724d6a9472fa16bc50c5b9b8b59afb62cfe9ea3ba0" + "42c73a6ade35", 16), + 'q': 1386090807861091316803998193774751098153687863463, + 'x1': int( + "66502429aba271e2f2ee2197a2b336e5f0467f192aa28b60dcbf1194", + 16), + 'x2': int( + "106b358be4f068348ac240ecbb454e5c39ca80b078cb0fafd856e9c5", + 16), + 'y1': int( + "dfb001294215423d7146a2453cdb8598ccef01e1d931a913c3e4ed4a3cf38" + "a912066c28e4eaf77dd80ff07183a6160bd95932f513402f864dcf7a70cbe" + "dc9b60bbfbc67f72a83d5f6463a2b5a4fc906d3e921f5e1069126113265b4" + "40e15ccf2d7164bad7131f1613fec35df7f470d45888e0c91be091f3f9552" + "d670b8b7f479853193cb3c39f35fc7bd547ccb1bc579a67302b4ba948e6db" + "51043d351bb74a952e6a694e6e7456f714c47d7c8eeeb4fd83ad93c86b784" + "45f9393fdfd65c7dbd7fd6eba9794ddf183901b1d213321fd0ab3f7588ab0" + "f6b3692f365a87131eda0e062505861988f6ce63150207545ecf9678e0971" + "330253dfb7cfd546c5346fec", 16), + 'y2': int( + "715d0781975b7b03162f4401c1eda343fd9bf1140006034573b31828a618c" + "356163554cd27da956f7179a69e860fb6efeaa2e2aa9f1261506a8344c492" + "9953621381b13d6426e152c0f2f94bfcd2b758eca24923596d427ed8f957e" + "8bc9b1c7d21a87ef02222a1477cf3bfaadc68106456ab9706026006eccd29" + "0b21543de6bb97d5b8cf4ccee1c081a6d1dd27aaef060fa93888a47a4a416" + "ad5c5bd490ea600e04379232fb1077fbf394f4579accdbe352714e25b8891" + "6dca8d8f7e0c4ed9594f7693f656a235a2e88ebda48b0d557e32da9f12d2a" + "4c3180f05b16b4fba9bec79278a3971b77f9223b5ab78b857e0376c500821" + "1592c8c72d521373ee3b22b8", 16), + 'z': binascii.unhexlify( + b"cf879ebd107bb877457809c3fc410218b7acba3c5967495a8f1c3370d57f" + b"038a48dd69f9f69b9f4dd855e7c58a1e4ec32646a978266eb314db468ea1" + b"dfcee8a85a1644a5732498c4fbcdf85098c6ed0ce12e431e99142fd23353" + b"69b3f56620ada21aa69d883e82a0b5e35484dde32d17c2dc873f2cc5518e" + b"b7fc19695dff9fc94c9d9432bb4b09d8180323cfc561ebc2d6eff8dd5f84" + b"96f2b22377700a22bbfe61a6969c198129397454843e4fc3540026986039" + b"665095490056287e4fc49e6cb3181cb2bf06444fd0040150271c9ce1f61c" + b"13ecd5dd022194a2dbf3e1c7fbc6bd19497c7b888b4da613d28fa6f378a4" + b"3369cb8795a1c823f7d6cf4d84bba578" + ) + }, + { + 'fail_agree': True, + 'fail_z': False, + 'g': int( + "35513ec441402b78353ab1bba550b21c76c89973885a627170262ef52497d" + "5d137b8927a212aaab2f051198c90bb81dffd9eb10b36b7ca3b63565b4c10" + "25aea3b5e9c4a348c9cfa17f3907a1e4469701c0dedb8a4b9e96c5965b1fb" + "8c229b0c34baac774bf9dda4fc5ee8764358b3c84812878aab7464bc09e97" + "aecab7d7e3fbb4870e2a3b89667a4158bf1ed1a90dfaf47019fbb52b1b963" + "65bb4e1e9474993fe382fd23480dc875861be152997a621fdb7aef977ea5b" + "4d3d74486b162dc28f95a64cf65587a919a57eef92934fc9410df7f09fa82" + "f975328ed82ff29cc3e15a971f56f4ac2dcb289252575e02a6cdb7fcc6cdd" + "d7b0dca9c422e63eb2b8f05", 16), + 'p': int( + "f3722b9b911c6aede9eaeeaa406283de66a097f39a7225df6c3c916e57920" + "d356e50478d307dbfd146bfb91b6f68ecbbcf54b3d19c33a4b17293fea3e3" + "d6bff8ac4cca93a805386f062a8a27ae906ef5da94d279fd7b3d7289e0095" + "6f76bae9c0d2b8d11742ca5809630632aae58f9c6dce00c7380581deffde2" + "187b022f83c6ceaeaadb0844a17fcbb04039ca6843c91f0c9058b22434b26" + "3c3dfda8de8429e087c5be97fc5c9db9526031ad3a218bd9916fb4a3c2796" + "6d208b1e360014c01e95530c148fb3cd27e6a7250d3c3b81dcd220ca14548" + "dbccf99ebb9e334db6bcd14e632c98dd3f9860af7ae450f1b7809b45f0ec1" + "0e6f27672beebc9963befc73", 16), + 'q': int( + "a9a17de95a29091bf8e07dab53ea1aba9403be3c61027c6c8f48bac5", + 16), + 'x1': int( + "1610eaa4e0ccc8857e2b53149e008492b1fbd9025a6e8d95aaee9c0f", + 16), + 'x2': int( + "c4c83d75b27864b052cadc556e500e25aabf0c9d1bc01f0e1fe3862", + 16), + 'y1': int( + "51ee21cd9f97015180f258fad5c94ff5a458806b1412087236bf77fe87aae" + "1a36735816ed6e2160a731159814b6ae1f3f52c478dd9207094adfb62f766" + "7d5c366327e66d23096395e938504db330953a708015f861fe9d948761109" + "3b9fe7327518a7cc15994ab573313e154117c1a3ae88b8bdd1e316748249e" + "4a9cbd1947f159836d13613d1f9449fc3442171d1970bc28958c1cafa2776" + "a6f14ccdb29db02f64911bd83bfdcdfc843dd14a4cab9acb0bda8b293d2f5" + "f7050768e57533cbc415a29e6f31cc365e107f91ae3722484e2c7329a85af" + "69055a5a104da37e810878896d1b247b02b75234ecff82b1958f42d7b0316" + "22e9394c98b5229112f7f620", 16), + 'y2': int( + "467a857337a82472a1307a64dccc8e9994c5c63ec4312936885d17be41905" + "1a5f037fbb052d7010ebe01634d9e8b8b522d9ab4749fdc274f465369b89e" + "360df8f70b7865a3c71d2dbcd2df19e9293dab1153d3d63fcb7deb559b684" + "dde6c6eed63214444807041c9a0ce3f52ca439ec16dd231995b5dc6f18e68" + "01b6bd6454babccf9abbfacffb49c71e6494a4779cbfa550c5d7144114e6f" + "c193f460dcd0be7e6e06e546da7653770dc5859df87029e722dbe81361030" + "569148d1636988926bf0dcfe47c9d8a54698c08b3b5c70afe86b5c6f64346" + "3f8f34889d27d6cfd2d478c2d7b3d008a985c7380f0b43f10024b59c35438" + "80883c42d0e7e0a07326ba3a", 16), + 'z': binascii.unhexlify( + b"10a30bacab82e652415376baffdbc008c7eb2e5a3aa68bc10ce486ca8498" + b"3fd89b1b027bb40e75333406361005f5e756526a95fe01202df9217d81b1" + b"713d5187c368fdd4c9c2433d9e6c18844769479b725c4140c92a304ee1bc" + b"5726d8f5321b5b1c54a1a6b67c527e6817c0ed613a0d4e60db55de898788" + b"b7e8d4aa9a81ab5ed7f6282962c433d246ed640555bdd76d29c287455126" + b"4d74c76373f8a88871b41b041c98041b16f94f983ddf00f5bc7d2416d191" + b"68c90178974a0602436cd186748bcc63a629edc3a0db59415cccd37a6513" + b"0ea477c89da92d41371f5972891cf41f9c7f0e75ccbff9893225384db30d" + b"aa5e310f08e3e0fad98bcdf8ecf35fe5" + ) + }, + { + 'fail_agree': False, + 'fail_z': False, + 'g': int("35513ec441402b78353ab1bba550b21c76c89973885a627170262ef5" + "2497d5d137b8927a212aaab2f051198c90bb81dffd9eb10b36b7ca3b" + "63565b4c1025aea3b5e9c4a348c9cfa17f3907a1e4469701c0dedb8a" + "4b9e96c5965b1fb8c229b0c34baac774bf9dda4fc5ee8764358b3c84" + "812878aab7464bc09e97aecab7d7e3fbb4870e2a3b89667a4158bf1e" + "d1a90dfaf47019fbb52b1b96365bb4e1e9474993fe382fd23480dc87" + "5861be152997a621fdb7aef977ea5b4d3d74486b162dc28f95a64cf6" + "5587a919a57eef92934fc9410df7f09fa82f975328ed82ff29cc3e15" + "a971f56f4ac2dcb289252575e02a6cdb7fcc6cddd7b0dca9c422e63e" + "b2b8f05", 16), + 'p': int("f3722b9b911c6aede9eaeeaa406283de66a097f39a7225df6c3c916e" + "57920d356e50478d307dbfd146bfb91b6f68ecbbcf54b3d19c33a4b1" + "7293fea3e3d6bff8ac4cca93a805386f062a8a27ae906ef5da94d279" + "fd7b3d7289e00956f76bae9c0d2b8d11742ca5809630632aae58f9c6" + "dce00c7380581deffde2187b022f83c6ceaeaadb0844a17fcbb04039" + "ca6843c91f0c9058b22434b263c3dfda8de8429e087c5be97fc5c9db" + "9526031ad3a218bd9916fb4a3c27966d208b1e360014c01e95530c14" + "8fb3cd27e6a7250d3c3b81dcd220ca14548dbccf99ebb9e334db6bcd" + "14e632c98dd3f9860af7ae450f1b7809b45f0ec10e6f27672beebc99" + "63befc73", 16), + 'q': int( + "a9a17de95a29091bf8e07dab53ea1aba9403be3c61027c6c8f48bac5", + 16), + 'x1': int( + "9ee22ac51664e40e0a24dbb94142dba40605e2b6eeaaa0268a0f6847", + 16), + 'x2': int( + "438093a468236658821bf64eb08456139963d4fb27121c3ed6c55876", + 16), + 'y1': int( + "c2630c9d38ed5c825d1c6a3eba7143f3fc8a049c8bcd1efc212d2af64eca9" + "94308208691d330aa8f27fc4a1e55de4e512113996d21375a667f8c26d76d" + "ee2f6809b15432a33fb735aca5c2263940f58712bded08f55443dee300b94" + "89589e0462bd6bce19deaec4adc12fa61a694c8c5c999b28211d7835bac0f" + "fd2b316850823e2dc1d1f58e05cbf75c673036d116b3f03b9687c89f9c2a0" + "d43c4ffc9a605addbdcce0cb3790c6db846156bb857a7b3df40dc6ed04d19" + "cc9eaebb6bbc034e77c3d882a1a62317cce25b6130f0803e3bc49b5e36768" + "260073a617034872be0b50bed32740224beaf582d67fbcfef3b3ecc18f9c7" + "1c782e9a68495ef31dc7986e", 16), + 'y2': int( + "e192da8e1244e27221c1765344a5bb379dce741d427a734b4bdb6c4d16b24" + "90bd37564d745008e63ae46ef332331d79887ac63298ce143e125f8b320c0" + "f859b7f5f2c1e0053e4a7a16997e6143ff702300c9863ae7caef5c1dfca0e" + "cf5197c557745b793f0790a4fe678aeb93fdb52490d4f273a5553944dda3a" + "c8b9b792c9b67f8d7b9496398e432a423ae87ebeba688be3ed67eddd7575f" + "a56431cd48579bf53c903bbe066dd78b23c0996ef3a880f0d91315104366a" + "82f01abdecce96fd371f94e8420f8bc5b896c801df573554f749b03d0d28b" + "1e1a990bc61c7e9659342ac7e268e9c0b7c40fdaab394f29cf0a54f780022" + "f9a03b0bd28eb7db8b0b1b47", 16), + 'z': binascii.unhexlify( + b"56f8f40fa4b8f3580f9014b30d60a42933a53a62182a690142f458dc275c" + b"3b2f0e721bc5ee6e890b14516419110f5252ff1cceea8e274b2987aa78e3" + b"bae90c1935b276b7a1f1c944f79d4774b7a85b3355bdf25cb02bddfbda4e" + b"e7918bc93a5c9ca6d7e8fdedbda8e6c8a6ca794bad055a52b19c14895822" + b"7344cbddd70271d4610316cfea1e559b0bc3a12d15023b30d9f2db602053" + b"a0569c3bd2ce1faf59280ecd339f845dbcaaf2e883c5cc6263996f866b18" + b"b75d049d4c82097af8a5ce353e14416b3eeb31ba9bc4f6f3dbd846c5299f" + b"b5c0043a1b95b9149b39d14df9e6a69547abf8a4d518475576730ed52877" + b"9366568e46b7dd4ed787cb72d0733c93" + ) + } + ] + + assert expected == load_kasvs_dh_vectors(vector_data) + + +def test_load_kasvs_ecdh_vectors_empty_vector_data(): + assert [] == load_kasvs_ecdh_vectors([]) + + +def test_load_kasvs_ecdh_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 11.0 + # Parameter set(s) supported: EA EB EC ED EE + # CAVSid: CAVSid (in hex: 434156536964) + # IUTid: In hex: a1b2c3d4e5 + [EA] + + [Curve selected: P-192] + [SHA(s) supported (Used for hashing Z): SHA1] + [EB] + + [Curve selected: P-224] + [SHA(s) supported (Used for hashing Z): SHA224] + [EC] + + [Curve selected: P-256] + [SHA(s) supported (Used for hashing Z): SHA256] + [ED] + + [Curve selected: P-384] + [SHA(s) supported (Used for hashing Z): SHA384] + [EE] + + [Curve selected: P-521] + [SHA(s) supported (Used for hashing Z): SHA512] + # Generated on Thu Mar 17 19:46:10 2011 + + + + [EA - SHA1] + + + COUNT = 0 + dsCAVS = f70c297a683d6b7ef82b5af7349606c4447c8b4fc6fa5e80 + QsCAVSx = f7b5061fb557e516c50abf541d97dbfd76ca7172b22cf590 + QsCAVSy = 135e15e21f9e85c76205fd148a92ac19f9e6243ddab322d1 + dsIUT = a5b4bbad57f101ca48021cb7440cd681a9d40cd51b99d917 + QsIUTx = 79a77fcb18a32cdb59ed5d87740f29e8565d649dbf01ce86 + QsIUTy = f7187efaa0b1573f1fb00905d46810b880bf738b4c720bb7 + Z = 26382468d721761e14a87dc3bee67340095c6455962d1ba3 + CAVSHashZZ = af52ba700d3bbba7ce2916d6b729422c26c32364 + Result = P (0 - Correct) + + + + COUNT = 2 + dsCAVS = 5f909dcb0ccce58c82fada748c47297579e6a981b5518a96 + QsCAVSx = 537f1ecfda0e366de393a9bc8188fcc280311bffefe21ecf + QsCAVSy = a1fa1f98498d65f2754caff4e5303a4066a5ff89fde95381 + dsIUT = 3357aa7f47f3e09421602cc12cdce4434c68e330d44de05e + QsIUTx = 6a33d43d9c72173eabc7a771a5687748c4774c62762e96ec + QsIUTy = 8033f238b3abc69470aad4be8dbe4f60a2fd50207626c56a + Z = 3153034f6617326f19c35be8c99a0585431adf09d2f8e0fd + CAVSHashZZ = f8414e30c2d382e28d2a57a2447fdc203baa416b + Result = F (8 - Z changed ) + + + + COUNT = 8 + dsCAVS = 8fcfaf0524cc868fad20e50410a2205319f1327308d98dc8 + QsCAVSx = 9b0243d80a9e328738080fb4d46bc450243d0efb7ead0c92 + QsCAVSy = ad5bebad7f03849693071537f60ef858cad214123beee7c7 + dsIUT = bba95dac90289cb68ca2b006f9757219b70579c299ad7a7d + QsIUTx = 7733dc0cb365cd6312724196b9b4eb491fd4d2e31b9afdb1 + QsIUTy = 92ffa3722acc5b94d772258ba2d471b06c0f53f56fcd8662 + Z = 0f3c6e4a29a08296ae730f56a1ebf819ea2edfa6f0434e40 + CAVSHashZZ = c124545eed4b83a799e7e90371d806b5684a1bd2 + Result = P (13 - Z value should have leading 0 nibble ) + + + [EB - SHA224] + + + COUNT = 0 + dsCAVS = e53a88af7cf8ce6bf13c8b9ad191494e37a6acc1368c71f4306e39e5 + QsCAVSx = 3a24217c4b957fea922eec9d9ac52d5cb4b3fcd95efde1e4fa0dd6e2 + QsCAVSy = 775b94025a808eb6f4af14ea4b57dca576c35373c6dc198b15b981df + dsIUT = 09f51e302c6a0fe6ff48f34c208c6af91e70f65f88102e6fcab9af4a + QsIUTx = c5d5706ccd7424c74fd616e699865af96e56f39adea6aa059e5092b5 + QsIUTy = f0729077bb602404d56d2f7e2ba5bb2f383df4a5425567881ff0165d + Z = b1259ceedfb663d9515089cf727e7024fb3d86cbcec611b4ba0b4ab6 + CAVSHashZZ = 8b21fd05a4b50e401908cd8f26757f5c57f22b69f170aa7381f8596d + Result = P (0 - Correct) + + + + [EC - SHA256] + + + COUNT = 0 + dsCAVS = 305dfb4a8850cc59280891147baf457bfe5e2bae984571634a77dc8d3472fa9b + QsCAVSx = 202cb5a224e6c2a84e624094486edf04116c8d68ec1f4a0e0ed9ee090e1a900b + QsCAVSy = cacf3a5789bb33954be600425d62d9eae5371f90f88167258814213e4a4f4b1a + dsIUT = 72cc52808f294b64b6f7233c3d2f5d96cc1d29287320e39e1c151deef0bc14eb + QsIUTx = 49a768c9a4ca56e374f685dd76a461b1016c59dcded2c8d8cbd9f23ca453831f + QsIUTy = b1e3bb9b5f12a3b5ae788535d4554bd8c46e0e6130075e4e437d3854cf8f1c34 + Z = c0147c3c2691b450b5edc08b51aea224d9f4359ff67aab6da3146f396dbceaea + CAVSHashZZ = ea9ffd54511979ab8c4b387784972cbd05fc5fd4ff78e048b0026557b56a5\ +1dd + Result = F (2 - CAVS's Static public key Y fails PKV 5.6.2.5) + + + + [ED - SHA384] + + + COUNT = 0 + dsCAVS = 0e5c98ff2d2a3aab14ad0067b60dbe64e4f541ab5bed11c5a0c55ae1e60b51ff5\ +faaf377837977d80cbfdc33c2ff542b + QsCAVSx = d1bf2ac21637d66d6398aac01dcd56ac6f065fb45d1f6f16747bab9e9b01b463\ +0b59b20927aea147355bf41838acb482 + QsCAVSy = 4c9e23f1c5a41647d094086bf4ed31708651f21d996c47780688ac10f77deee2\ +e43b5241b6caecd2fd5444bc50472e0e + dsIUT = f865418473e5bf7d2e1bbcd9bd5a9270c003a9dd35e778133ca59fcab4bb64fe24\ +d6800e7047bdd033abc8bfa8db35b5 + QsIUTx = 32b72ab9b558249dcbc6cbade234f58e4f7aa5d3f6420ea99a5f997e8c2a91fb7\ +fd83779d0d2169428683771c745fd1a + QsIUTy = c749e02a3719bb56bf1dfc4ba3820309c01ab6e84cb29db7cdd80f127233f5295\ +687f8178f3a8704c1063b84c2ee472f + Z = a781430e6078a179df3f9ee27cd8fdc6188f161b6c4ccc4053ef6c6ca6fc222946883a\ +53c06db08f0a020023ced055aa + CAVSHashZZ = ccb70d0adbabe4d8956519db0d536605cbb366aed58fc55718f56ae3648fa\ +5c9ee7bae56cc463587cb74e2f9c6ace1cb + Result = P (0 - Correct) + + + + [EE - SHA512] + + + COUNT = 0 + dsCAVS = 0000002fef62381162942889a6094a6bb9ac1f4ddf66d9cda9f618232d31b90c5\ +0d7da78a47ed91d40cae946898571db972dc294b109815f38feee9eaac0d5f7c3250728 + QsCAVSx = 0000004b05ffa025113390797f2736174aa1c784f4dd34e764ee40d40e4d2442\ +677ebea3498086c9473e5c92789cbdb02bb327bbd61d58690f6a83d9ca73bccbde37dec4 + QsCAVSy = 0000004da67cffc98070b82af61feba78787efefb13bd810d80ff92304788e49\ +a4e5b634b3565474a8ecb1615d7b1b77a7a27875adb73a8a5d8f3f84e5e8b744cda250b0 + dsIUT = 00000311a5e520e238141527671a38cb6f776d96a9f82ef70dffa11dc0895f4060\ +f1abbb9ad6fd259e4a7beaf5f7266ea1bb45bcbfebfda2705e5c551e710fb1d745f57e + QsIUTx = 0000010ba3778cb2cc965834c0a9593adc6a222692656d657fb0d15293edf0ab3\ +3762384a96a16fddea7540b7ccbcca46ec4ac9bcf95fdb5aa18e158aab4d91981bd733e + QsIUTy = 0000018522df93ddd636e5bc94daecdc600fa241686ec18634fd30b7cbdfdc9ff\ +ba1166ac08df34a31896f6fad191414929261ebd7187afb72919f8a0c926be37f99c1e5 + Z = 01a5e4b31be4b1346e53906b6767b1fe94ec1a8a5abc28fb6f01518c056959af3bc933\ +5dddab178b52318cc5512559931b8dc18de0ce810c2c7f15769d7ce70e719c + CAVSHashZZ = d2d6538feb65d609f377b81a027dc800eed07b69c0e9eedb243369202ed47\ +f47021022a6c9b45ed791d09d9540eb81ea065fc1959eca365001ee39928c343d75 + Result = F (7 - IUT's Static private key d changed-prikey validity) + + + + """).splitlines() + + expected = [ + {'errno': 0, + 'fail': False, + 'COUNT': 0, + 'CAVS': { + 'd': int("f70c297a683d6b7ef82b5af7349606c4447c8b4fc6fa5e80", 16), + 'x': int("f7b5061fb557e516c50abf541d97dbfd76ca7172b22cf590", 16), + 'y': int("135e15e21f9e85c76205fd148a92ac19f9e6243ddab322d1", 16)}, + 'IUT': { + 'd': int("a5b4bbad57f101ca48021cb7440cd681a9d40cd51b99d917", 16), + 'x': int("79a77fcb18a32cdb59ed5d87740f29e8565d649dbf01ce86", 16), + 'y': int("f7187efaa0b1573f1fb00905d46810b880bf738b4c720bb7", 16)}, + 'Z': int("26382468d721761e14a87dc3bee67340095c6455962d1ba3", 16), + 'curve': 'secp192r1'}, + + {'errno': 8, + 'fail': True, + 'COUNT': 2, + 'CAVS': { + 'd': int("5f909dcb0ccce58c82fada748c47297579e6a981b5518a96", 16), + 'x': int("537f1ecfda0e366de393a9bc8188fcc280311bffefe21ecf", 16), + 'y': int("a1fa1f98498d65f2754caff4e5303a4066a5ff89fde95381", 16)}, + 'IUT': { + 'd': int("3357aa7f47f3e09421602cc12cdce4434c68e330d44de05e", 16), + 'x': int("6a33d43d9c72173eabc7a771a5687748c4774c62762e96ec", 16), + 'y': int("8033f238b3abc69470aad4be8dbe4f60a2fd50207626c56a", 16)}, + 'Z': int("3153034f6617326f19c35be8c99a0585431adf09d2f8e0fd", 16), + 'curve': 'secp192r1'}, + + {'errno': 13, + 'fail': False, + 'COUNT': 8, + 'CAVS': { + 'd': int("8fcfaf0524cc868fad20e50410a2205319f1327308d98dc8", 16), + 'x': int("9b0243d80a9e328738080fb4d46bc450243d0efb7ead0c92", 16), + 'y': int("ad5bebad7f03849693071537f60ef858cad214123beee7c7", 16)}, + 'IUT': { + 'd': int("bba95dac90289cb68ca2b006f9757219b70579c299ad7a7d", 16), + 'x': int("7733dc0cb365cd6312724196b9b4eb491fd4d2e31b9afdb1", 16), + 'y': int("92ffa3722acc5b94d772258ba2d471b06c0f53f56fcd8662", 16)}, + 'Z': int("0f3c6e4a29a08296ae730f56a1ebf819ea2edfa6f0434e40", 16), + 'curve': 'secp192r1'}, + + {'errno': 0, + 'fail': False, + 'COUNT': 0, + 'CAVS': { + 'd': int("e53a88af7cf8ce6bf13c8b9ad191494e37a6acc1368c71f4" + "306e39e5", 16), + 'x': int("3a24217c4b957fea922eec9d9ac52d5cb4b3fcd95efde1e4" + "fa0dd6e2", 16), + 'y': int("775b94025a808eb6f4af14ea4b57dca576c35373c6dc198b" + "15b981df", 16)}, + 'IUT': { + 'd': int("09f51e302c6a0fe6ff48f34c208c6af91e70f65f88102e6f" + "cab9af4a", 16), + 'x': int("c5d5706ccd7424c74fd616e699865af96e56f39adea6aa05" + "9e5092b5", 16), + 'y': int("f0729077bb602404d56d2f7e2ba5bb2f383df4a542556788" + "1ff0165d", 16)}, + 'Z': int("b1259ceedfb663d9515089cf727e7024fb3d86cbcec611b4" + "ba0b4ab6", 16), + 'curve': 'secp224r1'}, + + {'errno': 2, + 'fail': True, + 'COUNT': 0, + 'CAVS': { + 'd': int("305dfb4a8850cc59280891147baf457bfe5e2bae98457163" + "4a77dc8d3472fa9b", 16), + 'x': int("202cb5a224e6c2a84e624094486edf04116c8d68ec1f4a0e" + "0ed9ee090e1a900b", 16), + 'y': int("cacf3a5789bb33954be600425d62d9eae5371f90f8816725" + "8814213e4a4f4b1a", 16)}, + 'IUT': { + 'd': int("72cc52808f294b64b6f7233c3d2f5d96cc1d29287320e39e" + "1c151deef0bc14eb", 16), + 'x': int("49a768c9a4ca56e374f685dd76a461b1016c59dcded2c8d8" + "cbd9f23ca453831f", 16), + 'y': int("b1e3bb9b5f12a3b5ae788535d4554bd8c46e0e6130075e4e" + "437d3854cf8f1c34", 16)}, + 'Z': int("c0147c3c2691b450b5edc08b51aea224d9f4359ff67aab6d" + "a3146f396dbceaea", 16), + 'curve': 'secp256r1'}, + + {'errno': 0, + 'fail': False, + 'COUNT': 0, + 'CAVS': { + 'd': int("0e5c98ff2d2a3aab14ad0067b60dbe64e4f541ab5bed11c5" + "a0c55ae1e60b51ff5faaf377837977d80cbfdc33c2ff542b", 16), + 'x': int("d1bf2ac21637d66d6398aac01dcd56ac6f065fb45d1f6f16" + "747bab9e9b01b4630b59b20927aea147355bf41838acb482", 16), + 'y': int("4c9e23f1c5a41647d094086bf4ed31708651f21d996c4778" + "0688ac10f77deee2e43b5241b6caecd2fd5444bc50472e0e", 16)}, + 'IUT': { + 'd': int("f865418473e5bf7d2e1bbcd9bd5a9270c003a9dd35e77813" + "3ca59fcab4bb64fe24d6800e7047bdd033abc8bfa8db35b5", 16), + 'x': int("32b72ab9b558249dcbc6cbade234f58e4f7aa5d3f6420ea9" + "9a5f997e8c2a91fb7fd83779d0d2169428683771c745fd1a", 16), + 'y': int("c749e02a3719bb56bf1dfc4ba3820309c01ab6e84cb29db7" + "cdd80f127233f5295687f8178f3a8704c1063b84c2ee472f", 16)}, + 'Z': int("a781430e6078a179df3f9ee27cd8fdc6188f161b6c4ccc40" + "53ef6c6ca6fc222946883a53c06db08f0a020023ced055aa", 16), + 'curve': 'secp384r1'}, + + {'errno': 7, + 'fail': True, + 'COUNT': 0, + 'CAVS': { + 'd': int("0000002fef62381162942889a6094a6bb9ac1f4ddf66d9cd" + "a9f618232d31b90c50d7da78a47ed91d40cae946898571db" + "972dc294b109815f38feee9eaac0d5f7c3250728", 16), + 'x': int("0000004b05ffa025113390797f2736174aa1c784f4dd34e7" + "64ee40d40e4d2442677ebea3498086c9473e5c92789cbdb0" + "2bb327bbd61d58690f6a83d9ca73bccbde37dec4", 16), + 'y': int("0000004da67cffc98070b82af61feba78787efefb13bd810" + "d80ff92304788e49a4e5b634b3565474a8ecb1615d7b1b77" + "a7a27875adb73a8a5d8f3f84e5e8b744cda250b0", 16)}, + 'IUT': { + 'd': int("00000311a5e520e238141527671a38cb6f776d96a9f82ef7" + "0dffa11dc0895f4060f1abbb9ad6fd259e4a7beaf5f7266e" + "a1bb45bcbfebfda2705e5c551e710fb1d745f57e", 16), + 'x': int("0000010ba3778cb2cc965834c0a9593adc6a222692656d65" + "7fb0d15293edf0ab33762384a96a16fddea7540b7ccbcca4" + "6ec4ac9bcf95fdb5aa18e158aab4d91981bd733e", 16), + 'y': int("0000018522df93ddd636e5bc94daecdc600fa241686ec186" + "34fd30b7cbdfdc9ffba1166ac08df34a31896f6fad191414" + "929261ebd7187afb72919f8a0c926be37f99c1e5", 16)}, + 'Z': int("01a5e4b31be4b1346e53906b6767b1fe94ec1a8a5abc28fb" + "6f01518c056959af3bc9335dddab178b52318cc551255993" + "1b8dc18de0ce810c2c7f15769d7ce70e719c", 16), + 'curve': 'secp521r1'} + ] + + assert expected == load_kasvs_ecdh_vectors(vector_data) + + +def test_load_kasvs_ecdh_kdf_vectors(): + vector_data = textwrap.dedent(""" + # Parameter set(s) supported: EB EC ED EE + # CAVSid: CAVSid (in hex: 434156536964) + # IUTid: In hex: a1b2c3d4e5 + [EB] + + [Curve selected: P-224] + [SHA(s) supported (Used in the KDF function): SHA224 SHA256 SHA384 SHA512] + [MAC algorithm supported: HMAC] + [HMAC SHAs supported: SHA512] + [HMACKeySize(in bits): 112] + [HMAC Tag length(in bits): 64] + + # Generated on Mon Dec 22 11:45:18 2014 + + + + [EB - SHA224] + + + COUNT = 50 + dsCAVS = 540904b67b3716823dd621ed72ad3dbc615887b4f56f910b78a57199 + QsCAVSx = 28e5f3a72d8f6b8499dd1bcdfceafcecec68a0d715789bcf4b55fe15 + QsCAVSy = 8c8006a7da7c1a19f5328d7e865522b0c0dfb9a29b2c46dc96590d2a + Nonce = 4eefb2a29a0e89c3898a7affdfa60dd7 + dsIUT = 5e717ae889fc8d67be11c2ebe1a7d3550051448d68a040b2dee8e327 + QsIUTx = ae7f3db340b647d61713f5374c019f1be2b28573cb6219bb7b747223 + QsIUTy = 800e6bffcf97c15864ec6e5673fb83359b45f89b8a26a27f6f3dfbff + NonceDKMIUT = bb7f1b40d14ebd70443393990b57 + OI = a1b2c3d4e5bb7f1b40d14ebd70443393990b574341565369645b1582daab9cc6c30d6\ +1fdcf1cdfc7e9a304651e0fdb + CAVSTag = 84de198c3a958c62 + Z = 43f23b2c760d686fc99cc008b63aea92f866e224265af60d2d8ae540 + MacData = 5374616e646172642054657374204d6573736167654eefb2a29a0e89c3898a7a\ +ffdfa60dd7 + DKM = ad65fa2d12541c3a21f3cd223efb + Result = F (12 - Tag changed ) + """).splitlines() + + expected = [ + {'errno': 12, + 'fail': True, + 'COUNT': 50, + 'CAVS': { + 'd': int("540904b67b3716823dd621ed72ad3dbc615887b4f56f910b" + "78a57199", 16), + 'x': int("28e5f3a72d8f6b8499dd1bcdfceafcecec68a0d715789bcf" + "4b55fe15", 16), + 'y': int("8c8006a7da7c1a19f5328d7e865522b0c0dfb9a29b2c46dc" + "96590d2a", 16)}, + 'IUT': { + 'd': int("5e717ae889fc8d67be11c2ebe1a7d3550051448d68a040b2" + "dee8e327", 16), + 'x': int("ae7f3db340b647d61713f5374c019f1be2b28573cb6219bb" + "7b747223", 16), + 'y': int("800e6bffcf97c15864ec6e5673fb83359b45f89b8a26a27f" + "6f3dfbff", 16)}, + 'OI': int("a1b2c3d4e5bb7f1b40d14ebd70443393990b574341565369" + "645b1582daab9cc6c30d61fdcf1cdfc7e9a304651e0fdb", 16), + 'Z': int("43f23b2c760d686fc99cc008b63aea92f866e224265af60d" + "2d8ae540", 16), + 'DKM': int("ad65fa2d12541c3a21f3cd223efb", 16), + 'curve': 'secp224r1'} + ] + + assert expected == load_kasvs_ecdh_vectors(vector_data) + + +def test_load_x963_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 12.0 + # 'ANS X9.63-2001' information for sample + + [SHA-1] + [shared secret length = 192] + [SharedInfo length = 0] + [key data length = 128] + + COUNT = 0 + Z = 1c7d7b5f0597b03d06a018466ed1a93e30ed4b04dc64ccdd + SharedInfo = + Counter = 00000001 + Hash input 1 = 1c7d7b5f0597b03d06a018466ed1a93e30ed4b04dc64ccdd00000001 + K1 = bf71dffd8f4d99223936beb46fee8ccc60439b7e + key_data = bf71dffd8f4d99223936beb46fee8ccc + + COUNT = 1 + Z = 5ed096510e3fcf782ceea98e9737993e2b21370f6cda2ab1 + SharedInfo = + Counter = 00000001 + Hash input 1 = 5ed096510e3fcf782ceea98e9737993e2b21370f6cda2ab100000001 + K1 = ec3e224446bfd7b3be1df404104af953c1b2d0f5 + key_data = ec3e224446bfd7b3be1df404104af953 + + [SHA-512] + [shared secret length = 521] + [SharedInfo length = 128] + [key data length = 1024] + + COUNT = 0 + Z = 00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9adb369348b8150739\ +2f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e753f55ef05a2d + SharedInfo = e3b5b4c1b0d5cf1d2b3a2f9937895d31 + Counter = 00000001 + Hash input 1 = 00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9ad\ +b369348b81507392f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e753f55ef05a2d0\ +0000001e3b5b4c1b0d5cf1d2b3a2f9937895d31 + K1 = 4463f869f3cc18769b52264b0112b5858f7ad32a5a2d96d8cffabf7fa733633d6\ +e4dd2a599acceb3ea54a6217ce0b50eef4f6b40a5c30250a5a8eeee20800226 + Counter = 00000002 + Hash input 2 = 00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9ad\ +b369348b81507392f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e753f55ef05a2d0\ +0000002e3b5b4c1b0d5cf1d2b3a2f9937895d31 + K2 = 7089dbf351f3f5022aa9638bf1ee419dea9c4ff745a25ac27bda33ca08bd56dd1\ +a59b4106cf2dbbc0ab2aa8e2efa7b17902d34276951ceccab87f9661c3e8816 + key_data = 4463f869f3cc18769b52264b0112b5858f7ad32a5a2d96d8cffabf7fa733633\ +d6e4dd2a599acceb3ea54a6217ce0b50eef4f6b40a5c30250a5a8eeee208002267089dbf351f3f\ +5022aa9638bf1ee419dea9c4ff745a25ac27bda33ca08bd56dd1a59b4106cf2dbbc0ab2aa8e2ef\ +a7b17902d34276951ceccab87f9661c3e8816 + """).splitlines() + + assert load_x963_vectors(vector_data) == [ + {"hash": "SHA-1", "count": 0, + "shared_secret_length": 192, + "Z": "1c7d7b5f0597b03d06a018466ed1a93e30ed4b04dc64ccdd", + "sharedinfo_length": 0, + "key_data_length": 128, + "key_data": "bf71dffd8f4d99223936beb46fee8ccc"}, + {"hash": "SHA-1", "count": 1, + "shared_secret_length": 192, + "Z": "5ed096510e3fcf782ceea98e9737993e2b21370f6cda2ab1", + "sharedinfo_length": 0, + "key_data_length": 128, + "key_data": "ec3e224446bfd7b3be1df404104af953"}, + {"hash": "SHA-512", "count": 0, + "shared_secret_length": 521, + "Z": "00aa5bb79b33e389fa58ceadc047197f14e73712f452caa9fc4c9adb369348b\ +81507392f1a86ddfdb7c4ff8231c4bd0f44e44a1b55b1404747a9e2e753f55ef05a2d", + "sharedinfo_length": 128, + "sharedinfo": "e3b5b4c1b0d5cf1d2b3a2f9937895d31", + "key_data_length": 1024, + "key_data": "4463f869f3cc18769b52264b0112b5858f7ad32a5a2d96d8cffabf7f\ +a733633d6e4dd2a599acceb3ea54a6217ce0b50eef4f6b40a5c30250a5a8eeee208002267089db\ +f351f3f5022aa9638bf1ee419dea9c4ff745a25ac27bda33ca08bd56dd1a59b4106cf2dbbc0ab2\ +aa8e2efa7b17902d34276951ceccab87f9661c3e8816"}, + ] + + +def test_load_kbkdf_vectors(): + vector_data = textwrap.dedent(""" + # CAVS 14.4 + # "SP800-108 - KDF" information for "test1" + # KDF Mode Supported: Counter Mode + # Location of counter tested: (Before Fixed Input Data)\ +( After Fixed Input Data)(In Middle of Fixed Input Data before Context) + # PRFs tested: CMAC with key sizes: AES128 AES192 AES256 TDES2 TDES3\ +HMAC with key sizes: SHA1 SHA224 SHA256 SHA384 SHA512 + # Generated on Tue Apr 23 12:20:16 2013 + + [PRF=HMAC_SHA1] + [CTRLOCATION=BEFORE_FIXED] + [RLEN=8_BITS] + + COUNT=0 + L = 128 + KI = 00a39bd547fb88b2d98727cf64c195c61e1cad6c + FixedInputDataByteLen = 60 + FixedInputData = 98132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc30056f68\ +76f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a68242 + Binary rep of i = 01 + instring = 0198132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc30056f68\ +76f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a68242 + KO = 0611e1903609b47ad7a5fc2c82e47702 + + COUNT=1 + L = 128 + KI = a39bdf744ed7e33fdec060c8736e9725179885a8 + FixedInputDataByteLen = 60 + FixedInputData = af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9c591e182\ +35019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e832 + Binary rep of i = 01 + instring = 01af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9c591e182\ +35019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e832 + KO = 51dc4668947e3685099bc3b5f8527468 + + [PRF=HMAC_SHA224] + [CTRLOCATION=AFTER_FIXED] + [RLEN=8_BITS] + + COUNT=0 + L = 128 + KI = ab56556b107a3a79fe084df0f1bb3ad049a6cc1490f20da4b3df282c + FixedInputDataByteLen = 60 + FixedInputData = 7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43aa1b91ee\ +b5730d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4a7 + Binary rep of i = 01 + instring = 7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43aa1b91eeb5\ +730d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4a701 + KO = b8894c6133a46701909b5c8a84322dec + """).splitlines() + + assert load_nist_kbkdf_vectors(vector_data) == [ + {'prf': 'hmac_sha1', + 'ctrlocation': 'before_fixed', + 'rlen': 8, + 'l': 128, + 'ki': b'00a39bd547fb88b2d98727cf64c195c61e1cad6c', + 'fixedinputdatabytelen': b'60', + 'fixedinputdata': b'98132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc\ +30056f6876f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a68242', + 'binary rep of i': b'01', + 'instring': b'0198132c1ffaf59ae5cbc0a3133d84c551bb97e0c75ecaddfc3005\ +6f6876f59803009bffc7d75c4ed46f40b8f80426750d15bc1ddb14ac5dcb69a68242', + 'ko': b'0611e1903609b47ad7a5fc2c82e47702'}, + {'prf': 'hmac_sha1', + 'ctrlocation': 'before_fixed', + 'rlen': 8, + 'l': 128, + 'ki': b'a39bdf744ed7e33fdec060c8736e9725179885a8', + 'fixedinputdatabytelen': b'60', + 'fixedinputdata': b'af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9\ +c591e18235019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e832', + 'binary rep of i': b'01', + 'instring': b'01af71b44940acff98949ad17f1ca20e8fdb3957cacdcd41e9c591\ +e18235019f90b9f8ee6e75700bcab2f8407525a104799b3e9725e27d738a9045e832', + 'ko': b'51dc4668947e3685099bc3b5f8527468'}, + {'prf': 'hmac_sha224', + 'ctrlocation': 'after_fixed', + 'rlen': 8, + 'l': 128, + 'ki': b'ab56556b107a3a79fe084df0f1bb3ad049a6cc1490f20da4b3df282c', + 'fixedinputdatabytelen': b'60', + 'fixedinputdata': b'7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43\ +aa1b91eeb5730d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4a7', + 'binary rep of i': b'01', + 'instring': b'7f50fc1f77c3ac752443154c1577d3c47b86fccffe82ff43aa1b91\ +eeb5730d7e9e6aab78374d854aecb7143faba6b1eb90d3d9e7a2f6d78dd9a6c4a701', + 'ko': b'b8894c6133a46701909b5c8a84322dec'} + ] + + +def test_vector_version(): + assert cryptography.__version__ == cryptography_vectors.__version__ + + +def test_raises_unsupported_algorithm_wrong_type(): + # Check that it raises if the wrong type of exception is raised. + class TestException(Exception): + pass + + with pytest.raises(TestException): + with raises_unsupported_algorithm(None): + raise TestException + + +def test_raises_unsupported_algorithm_wrong_reason(): + # Check that it fails if the wrong reason code is raised. + with pytest.raises(AssertionError): + with raises_unsupported_algorithm(None): + raise UnsupportedAlgorithm("An error.", + _Reasons.BACKEND_MISSING_INTERFACE) + + +def test_raises_unsupported_no_exc(): + # Check that it fails if no exception is raised. + with pytest.raises(pytest.fail.Exception): + with raises_unsupported_algorithm( + _Reasons.BACKEND_MISSING_INTERFACE + ): + pass + + +def test_raises_unsupported_algorithm(): + # Check that it doesn't assert if the right things are raised. + with raises_unsupported_algorithm( + _Reasons.BACKEND_MISSING_INTERFACE + ) as exc_info: + raise UnsupportedAlgorithm("An error.", + _Reasons.BACKEND_MISSING_INTERFACE) + assert exc_info.type is UnsupportedAlgorithm diff --git a/tests/test_warnings.py b/tests/test_warnings.py new file mode 100644 index 0000000..d27e757 --- /dev/null +++ b/tests/test_warnings.py @@ -0,0 +1,88 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import sys +import types +import warnings + +import pytest + +from cryptography.utils import deprecated + + +class TestDeprecated(object): + def test_deprecated(self, monkeypatch): + mod = types.ModuleType("TestDeprecated/test_deprecated") + monkeypatch.setitem(sys.modules, mod.__name__, mod) + mod.X = deprecated( + value=1, + module_name=mod.__name__, + message="deprecated message text", + warning_class=DeprecationWarning + ) + mod.Y = deprecated( + value=2, + module_name=mod.__name__, + message="more deprecated text", + warning_class=PendingDeprecationWarning, + ) + mod = sys.modules[mod.__name__] + mod.Z = 3 + + with warnings.catch_warnings(record=True) as log: + warnings.simplefilter("always", PendingDeprecationWarning) + warnings.simplefilter("always", DeprecationWarning) + assert mod.X == 1 + assert mod.Y == 2 + assert mod.Z == 3 + + [msg1, msg2] = log + assert msg1.category is DeprecationWarning + assert msg1.message.args == ("deprecated message text",) + + assert msg2.category is PendingDeprecationWarning + assert msg2.message.args == ("more deprecated text",) + + assert "Y" in dir(mod) + + def test_deleting_deprecated_members(self, monkeypatch): + mod = types.ModuleType("TestDeprecated/test_deprecated") + monkeypatch.setitem(sys.modules, mod.__name__, mod) + mod.X = deprecated( + value=1, + module_name=mod.__name__, + message="deprecated message text", + warning_class=DeprecationWarning + ) + mod.Y = deprecated( + value=2, + module_name=mod.__name__, + message="more deprecated text", + warning_class=PendingDeprecationWarning, + ) + mod = sys.modules[mod.__name__] + mod.Z = 3 + + with warnings.catch_warnings(record=True) as log: + warnings.simplefilter("always", PendingDeprecationWarning) + warnings.simplefilter("always", DeprecationWarning) + del mod.X + del mod.Y + del mod.Z + + [msg1, msg2] = log + assert msg1.category is DeprecationWarning + assert msg1.message.args == ("deprecated message text",) + + assert msg2.category is PendingDeprecationWarning + assert msg2.message.args == ("more deprecated text",) + + assert "X" not in dir(mod) + assert "Y" not in dir(mod) + assert "Z" not in dir(mod) + + with pytest.raises(AttributeError): + del mod.X diff --git a/tests/test_x509.py b/tests/test_x509.py new file mode 100644 index 0000000..966cba6 --- /dev/null +++ b/tests/test_x509.py @@ -0,0 +1,3901 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import datetime +import ipaddress +import os +import sys +import warnings + +from pyasn1.codec.der import decoder + +from pyasn1_modules import rfc2459 + +import pytest + +import pytz + +import six + +from cryptography import utils, x509 +from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat.backends.interfaces import ( + DSABackend, EllipticCurveBackend, RSABackend, X509Backend +) +from cryptography.hazmat.primitives import hashes, serialization +from cryptography.hazmat.primitives.asymmetric import dsa, ec, padding, rsa +from cryptography.hazmat.primitives.asymmetric.utils import ( + decode_dss_signature +) +from cryptography.x509.oid import ( + AuthorityInformationAccessOID, ExtendedKeyUsageOID, ExtensionOID, + NameOID, SignatureAlgorithmOID +) + +from .hazmat.primitives.fixtures_dsa import DSA_KEY_2048 +from .hazmat.primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 +from .hazmat.primitives.test_ec import _skip_curve_unsupported +from .utils import load_vectors_from_file + + +@utils.register_interface(x509.ExtensionType) +class DummyExtension(object): + oid = x509.ObjectIdentifier("1.2.3.4") + + +@utils.register_interface(x509.GeneralName) +class FakeGeneralName(object): + def __init__(self, value): + self._value = value + + value = utils.read_only_property("_value") + + +def _load_cert(filename, loader, backend): + cert = load_vectors_from_file( + filename=filename, + loader=lambda pemfile: loader(pemfile.read(), backend), + mode="rb" + ) + return cert + + +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestCertificateRevocationList(object): + def test_load_pem_crl(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + assert isinstance(crl, x509.CertificateRevocationList) + fingerprint = binascii.hexlify(crl.fingerprint(hashes.SHA1())) + assert fingerprint == b"3234b0cb4c0cedf6423724b736729dcfc9e441ef" + assert isinstance(crl.signature_hash_algorithm, hashes.SHA256) + assert ( + crl.signature_algorithm_oid == + SignatureAlgorithmOID.RSA_WITH_SHA256 + ) + + def test_load_der_crl(self, backend): + crl = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + assert isinstance(crl, x509.CertificateRevocationList) + fingerprint = binascii.hexlify(crl.fingerprint(hashes.SHA1())) + assert fingerprint == b"dd3db63c50f4c4a13e090f14053227cb1011a5ad" + assert isinstance(crl.signature_hash_algorithm, hashes.SHA256) + + def test_invalid_pem(self, backend): + with pytest.raises(ValueError): + x509.load_pem_x509_crl(b"notacrl", backend) + + def test_invalid_der(self, backend): + with pytest.raises(ValueError): + x509.load_der_x509_crl(b"notacrl", backend) + + def test_unknown_signature_algorithm(self, backend): + crl = _load_cert( + os.path.join( + "x509", "custom", "crl_md2_unknown_crit_entry_ext.pem" + ), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(UnsupportedAlgorithm): + crl.signature_hash_algorithm() + + def test_issuer(self, backend): + crl = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + assert isinstance(crl.issuer, x509.Name) + assert list(crl.issuer) == [ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US'), + x509.NameAttribute( + x509.OID_ORGANIZATION_NAME, u'Test Certificates 2011' + ), + x509.NameAttribute(x509.OID_COMMON_NAME, u'Good CA') + ] + assert crl.issuer.get_attributes_for_oid(x509.OID_COMMON_NAME) == [ + x509.NameAttribute(x509.OID_COMMON_NAME, u'Good CA') + ] + + def test_equality(self, backend): + crl1 = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + crl2 = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + crl3 = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + assert crl1 == crl2 + assert crl1 != crl3 + assert crl1 != object() + + def test_update_dates(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + assert isinstance(crl.next_update, datetime.datetime) + assert isinstance(crl.last_update, datetime.datetime) + + assert crl.next_update.isoformat() == "2016-01-01T00:00:00" + assert crl.last_update.isoformat() == "2015-01-01T00:00:00" + + def test_revoked_cert_retrieval(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + for r in crl: + assert isinstance(r, x509.RevokedCertificate) + + # Check that len() works for CRLs. + assert len(crl) == 12 + + def test_revoked_cert_retrieval_retain_only_revoked(self, backend): + """ + This test attempts to trigger the crash condition described in + https://github.com/pyca/cryptography/issues/2557 + PyPy does gc at its own pace, so it will only be reliable on CPython. + """ + revoked = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + )[11] + assert revoked.revocation_date == datetime.datetime(2015, 1, 1, 0, 0) + assert revoked.serial_number == 11 + + def test_extensions(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_ian_aia_aki.pem"), + x509.load_pem_x509_crl, + backend + ) + + crl_number = crl.extensions.get_extension_for_oid( + ExtensionOID.CRL_NUMBER + ) + aki = crl.extensions.get_extension_for_class( + x509.AuthorityKeyIdentifier + ) + aia = crl.extensions.get_extension_for_class( + x509.AuthorityInformationAccess + ) + ian = crl.extensions.get_extension_for_class( + x509.IssuerAlternativeName + ) + assert crl_number.value == x509.CRLNumber(1) + assert crl_number.critical is False + assert aki.value == x509.AuthorityKeyIdentifier( + key_identifier=( + b'yu\xbb\x84:\xcb,\xdez\t\xbe1\x1bC\xbc\x1c*MSX' + ), + authority_cert_issuer=None, + authority_cert_serial_number=None + ) + assert aia.value == x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.DNSName(u"cryptography.io") + ) + ]) + assert ian.value == x509.IssuerAlternativeName([ + x509.UniformResourceIdentifier(u"https://cryptography.io"), + ]) + + def test_signature(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + assert crl.signature == binascii.unhexlify( + b"536a5a0794f68267361e7bc2f19167a3e667a2ab141535616855d8deb2ba1af" + b"9fd4546b1fe76b454eb436af7b28229fedff4634dfc9dd92254266219ae0ea8" + b"75d9ff972e9a2da23d5945f073da18c50a4265bfed9ca16586347800ef49dd1" + b"6856d7265f4f3c498a57f04dc04404e2bd2e2ada1f5697057aacef779a18371" + b"c621edc9a5c2b8ec1716e8fa22feeb7fcec0ce9156c8d344aa6ae8d1a5d99d0" + b"9386df36307df3b63c83908f4a61a0ff604c1e292ad63b349d1082ddd7ae1b7" + b"c178bba995523ec6999310c54da5706549797bfb1230f5593ba7b4353dade4f" + b"d2be13a57580a6eb20b5c4083f000abac3bf32cd8b75f23e4c8f4b3a79e1e2d" + b"58a472b0" + ) + + def test_tbs_certlist_bytes(self, backend): + crl = _load_cert( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + backend + ) + + ca_cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + verifier = ca_cert.public_key().verifier( + crl.signature, padding.PKCS1v15(), crl.signature_hash_algorithm + ) + verifier.update(crl.tbs_certlist_bytes) + verifier.verify() + + def test_public_bytes_pem(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_empty.pem"), + x509.load_pem_x509_crl, + backend + ) + + # Encode it to PEM and load it back. + crl = x509.load_pem_x509_crl(crl.public_bytes( + encoding=serialization.Encoding.PEM, + ), backend) + + assert len(crl) == 0 + assert crl.last_update == datetime.datetime(2015, 12, 20, 23, 44, 47) + assert crl.next_update == datetime.datetime(2015, 12, 28, 0, 44, 47) + + def test_public_bytes_der(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + # Encode it to DER and load it back. + crl = x509.load_der_x509_crl(crl.public_bytes( + encoding=serialization.Encoding.DER, + ), backend) + + assert len(crl) == 12 + assert crl.last_update == datetime.datetime(2015, 1, 1, 0, 0, 0) + assert crl.next_update == datetime.datetime(2016, 1, 1, 0, 0, 0) + + @pytest.mark.parametrize( + ("cert_path", "loader_func", "encoding"), + [ + ( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + serialization.Encoding.PEM, + ), + ( + os.path.join("x509", "PKITS_data", "crls", "GoodCACRL.crl"), + x509.load_der_x509_crl, + serialization.Encoding.DER, + ), + ] + ) + def test_public_bytes_match(self, cert_path, loader_func, encoding, + backend): + crl_bytes = load_vectors_from_file( + cert_path, lambda pemfile: pemfile.read(), mode="rb" + ) + crl = loader_func(crl_bytes, backend) + serialized = crl.public_bytes(encoding) + assert serialized == crl_bytes + + def test_public_bytes_invalid_encoding(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_empty.pem"), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(TypeError): + crl.public_bytes('NotAnEncoding') + + +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestRevokedCertificate(object): + def test_revoked_basics(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + for i, rev in enumerate(crl): + assert isinstance(rev, x509.RevokedCertificate) + assert isinstance(rev.serial_number, int) + assert isinstance(rev.revocation_date, datetime.datetime) + assert isinstance(rev.extensions, x509.Extensions) + + assert rev.serial_number == i + assert rev.revocation_date.isoformat() == "2015-01-01T00:00:00" + + def test_revoked_extensions(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + exp_issuer = [ + x509.DirectoryName(x509.Name([ + x509.NameAttribute(x509.OID_COUNTRY_NAME, u"US"), + x509.NameAttribute(x509.OID_COMMON_NAME, u"cryptography.io"), + ])) + ] + + # First revoked cert doesn't have extensions, test if it is handled + # correctly. + rev0 = crl[0] + # It should return an empty Extensions object. + assert isinstance(rev0.extensions, x509.Extensions) + assert len(rev0.extensions) == 0 + with pytest.raises(x509.ExtensionNotFound): + rev0.extensions.get_extension_for_oid(x509.OID_CRL_REASON) + with pytest.raises(x509.ExtensionNotFound): + rev0.extensions.get_extension_for_oid(x509.OID_CERTIFICATE_ISSUER) + with pytest.raises(x509.ExtensionNotFound): + rev0.extensions.get_extension_for_oid(x509.OID_INVALIDITY_DATE) + + # Test manual retrieval of extension values. + rev1 = crl[1] + assert isinstance(rev1.extensions, x509.Extensions) + + reason = rev1.extensions.get_extension_for_class( + x509.CRLReason).value + assert reason == x509.CRLReason(x509.ReasonFlags.unspecified) + + issuer = rev1.extensions.get_extension_for_class( + x509.CertificateIssuer).value + assert issuer == x509.CertificateIssuer(exp_issuer) + + date = rev1.extensions.get_extension_for_class( + x509.InvalidityDate).value + assert date == x509.InvalidityDate(datetime.datetime(2015, 1, 1, 0, 0)) + + # Check if all reason flags can be found in the CRL. + flags = set(x509.ReasonFlags) + for rev in crl: + try: + r = rev.extensions.get_extension_for_class(x509.CRLReason) + except x509.ExtensionNotFound: + # Not all revoked certs have a reason extension. + pass + else: + flags.discard(r.value.reason) + + assert len(flags) == 0 + + def test_no_revoked_certs(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_empty.pem"), + x509.load_pem_x509_crl, + backend + ) + assert len(crl) == 0 + + def test_duplicate_entry_ext(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_dup_entry_ext.pem"), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(x509.DuplicateExtension): + crl[0].extensions + + def test_unsupported_crit_entry_ext(self, backend): + crl = _load_cert( + os.path.join( + "x509", "custom", "crl_md2_unknown_crit_entry_ext.pem" + ), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(x509.UnsupportedExtension): + crl[0].extensions + + def test_unsupported_reason(self, backend): + crl = _load_cert( + os.path.join( + "x509", "custom", "crl_unsupported_reason.pem" + ), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(ValueError): + crl[0].extensions + + def test_invalid_cert_issuer_ext(self, backend): + crl = _load_cert( + os.path.join( + "x509", "custom", "crl_inval_cert_issuer_entry_ext.pem" + ), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(ValueError): + crl[0].extensions + + def test_indexing(self, backend): + crl = _load_cert( + os.path.join("x509", "custom", "crl_all_reasons.pem"), + x509.load_pem_x509_crl, + backend + ) + + with pytest.raises(IndexError): + crl[-13] + with pytest.raises(IndexError): + crl[12] + + assert crl[-1].serial_number == crl[11].serial_number + assert len(crl[2:4]) == 2 + assert crl[2:4][0].serial_number == crl[2].serial_number + assert crl[2:4][1].serial_number == crl[3].serial_number + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestRSACertificate(object): + def test_load_pem_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert isinstance(cert, x509.Certificate) + assert cert.serial_number == 11559813051657483483 + fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) + assert fingerprint == b"2b619ed04bfc9c3b08eb677d272192286a0947a8" + assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) + assert ( + cert.signature_algorithm_oid == SignatureAlgorithmOID.RSA_WITH_SHA1 + ) + + def test_alternate_rsa_with_sha1_oid(self, backend): + cert = _load_cert( + os.path.join("x509", "alternate-rsa-sha1-oid.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) + assert ( + cert.signature_algorithm_oid == + SignatureAlgorithmOID._RSA_WITH_SHA1 + ) + + def test_cert_serial_number(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + with warnings.catch_warnings(): + warnings.simplefilter("always", utils.DeprecatedIn10) + assert cert.serial == 2 + assert cert.serial_number == 2 + + def test_cert_serial_warning(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + with warnings.catch_warnings(): + warnings.simplefilter("always", utils.DeprecatedIn10) + with pytest.deprecated_call(): + cert.serial + + def test_load_der_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + assert isinstance(cert, x509.Certificate) + assert cert.serial_number == 2 + fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) + assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" + assert isinstance(cert.signature_hash_algorithm, hashes.SHA256) + + def test_signature(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.signature == binascii.unhexlify( + b"8e0f72fcbebe4755abcaf76c8ce0bae17cde4db16291638e1b1ce04a93cdb4c" + b"44a3486070986c5a880c14fdf8497e7d289b2630ccb21d24a3d1aa1b2d87482" + b"07f3a1e16ccdf8daa8a7ea1a33d49774f513edf09270bd8e665b6300a10f003" + b"66a59076905eb63cf10a81a0ca78a6ef3127f6cb2f6fb7f947fce22a30d8004" + b"8c243ba2c1a54c425fe12310e8a737638f4920354d4cce25cbd9dea25e6a2fe" + b"0d8579a5c8d929b9275be221975479f3f75075bcacf09526523b5fd67f7683f" + b"3cda420fabb1e9e6fc26bc0649cf61bb051d6932fac37066bb16f55903dfe78" + b"53dc5e505e2a10fbba4f9e93a0d3b53b7fa34b05d7ba6eef869bfc34b8e514f" + b"d5419f75" + ) + assert len(cert.signature) == cert.public_key().key_size // 8 + + def test_tbs_certificate_bytes(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.tbs_certificate_bytes == binascii.unhexlify( + b"308202d8a003020102020900a06cb4b955f7f4db300d06092a864886f70d010" + b"10505003058310b3009060355040613024155311330110603550408130a536f" + b"6d652d53746174653121301f060355040a1318496e7465726e6574205769646" + b"769747320507479204c74643111300f0603550403130848656c6c6f20434130" + b"1e170d3134313132363231343132305a170d3134313232363231343132305a3" + b"058310b3009060355040613024155311330110603550408130a536f6d652d53" + b"746174653121301f060355040a1318496e7465726e657420576964676974732" + b"0507479204c74643111300f0603550403130848656c6c6f2043413082012230" + b"0d06092a864886f70d01010105000382010f003082010a0282010100b03af70" + b"2059e27f1e2284b56bbb26c039153bf81f295b73a49132990645ede4d2da0a9" + b"13c42e7d38d3589a00d3940d194f6e6d877c2ef812da22a275e83d8be786467" + b"48b4e7f23d10e873fd72f57a13dec732fc56ab138b1bb308399bb412cd73921" + b"4ef714e1976e09603405e2556299a05522510ac4574db5e9cb2cf5f99e8f48c" + b"1696ab3ea2d6d2ddab7d4e1b317188b76a572977f6ece0a4ad396f0150e7d8b" + b"1a9986c0cb90527ec26ca56e2914c270d2a198b632fa8a2fda55079d3d39864" + b"b6fb96ddbe331cacb3cb8783a8494ccccd886a3525078847ca01ca5f803e892" + b"14403e8a4b5499539c0b86f7a0daa45b204a8e079d8a5b03db7ba1ba3d7011a" + b"70203010001a381bc3081b9301d0603551d0e04160414d8e89dc777e4472656" + b"f1864695a9f66b7b0400ae3081890603551d23048181307f8014d8e89dc777e" + b"4472656f1864695a9f66b7b0400aea15ca45a3058310b300906035504061302" + b"4155311330110603550408130a536f6d652d53746174653121301f060355040" + b"a1318496e7465726e6574205769646769747320507479204c74643111300f06" + b"03550403130848656c6c6f204341820900a06cb4b955f7f4db300c0603551d1" + b"3040530030101ff" + ) + verifier = cert.public_key().verifier( + cert.signature, padding.PKCS1v15(), cert.signature_hash_algorithm + ) + verifier.update(cert.tbs_certificate_bytes) + verifier.verify() + + def test_issuer(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "Validpre2000UTCnotBeforeDateTest3EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + issuer = cert.issuer + assert isinstance(issuer, x509.Name) + assert list(issuer) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u'Test Certificates 2011' + ), + x509.NameAttribute(NameOID.COMMON_NAME, u'Good CA') + ] + assert issuer.get_attributes_for_oid(NameOID.COMMON_NAME) == [ + x509.NameAttribute(NameOID.COMMON_NAME, u'Good CA') + ] + + def test_all_issuer_name_types(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", + "all_supported_names.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + issuer = cert.issuer + + assert isinstance(issuer, x509.Name) + assert list(issuer) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'CA'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Illinois'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Chicago'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Zero, LLC'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'One, LLC'), + x509.NameAttribute(NameOID.COMMON_NAME, u'common name 0'), + x509.NameAttribute(NameOID.COMMON_NAME, u'common name 1'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'OU 0'), + x509.NameAttribute(NameOID.ORGANIZATIONAL_UNIT_NAME, u'OU 1'), + x509.NameAttribute(NameOID.DN_QUALIFIER, u'dnQualifier0'), + x509.NameAttribute(NameOID.DN_QUALIFIER, u'dnQualifier1'), + x509.NameAttribute(NameOID.SERIAL_NUMBER, u'123'), + x509.NameAttribute(NameOID.SERIAL_NUMBER, u'456'), + x509.NameAttribute(NameOID.TITLE, u'Title 0'), + x509.NameAttribute(NameOID.TITLE, u'Title 1'), + x509.NameAttribute(NameOID.SURNAME, u'Surname 0'), + x509.NameAttribute(NameOID.SURNAME, u'Surname 1'), + x509.NameAttribute(NameOID.GIVEN_NAME, u'Given Name 0'), + x509.NameAttribute(NameOID.GIVEN_NAME, u'Given Name 1'), + x509.NameAttribute(NameOID.PSEUDONYM, u'Incognito 0'), + x509.NameAttribute(NameOID.PSEUDONYM, u'Incognito 1'), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u'Last Gen'), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u'Next Gen'), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'dc0'), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'dc1'), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, u'test0@test.local'), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, u'test1@test.local'), + ] + + def test_subject(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "Validpre2000UTCnotBeforeDateTest3EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + subject = cert.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u'Test Certificates 2011' + ), + x509.NameAttribute( + NameOID.COMMON_NAME, + u'Valid pre2000 UTC notBefore Date EE Certificate Test3' + ) + ] + assert subject.get_attributes_for_oid(NameOID.COMMON_NAME) == [ + x509.NameAttribute( + NameOID.COMMON_NAME, + u'Valid pre2000 UTC notBefore Date EE Certificate Test3' + ) + ] + + def test_unicode_name(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", + "utf8_common_name.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + assert cert.subject.get_attributes_for_oid(NameOID.COMMON_NAME) == [ + x509.NameAttribute( + NameOID.COMMON_NAME, + u'We heart UTF8!\u2122' + ) + ] + assert cert.issuer.get_attributes_for_oid(NameOID.COMMON_NAME) == [ + x509.NameAttribute( + NameOID.COMMON_NAME, + u'We heart UTF8!\u2122' + ) + ] + + def test_all_subject_name_types(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", + "all_supported_names.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + subject = cert.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'AU'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'DE'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'California'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'New York'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'San Francisco'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Ithaca'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Org Zero, LLC'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Org One, LLC'), + x509.NameAttribute(NameOID.COMMON_NAME, u'CN 0'), + x509.NameAttribute(NameOID.COMMON_NAME, u'CN 1'), + x509.NameAttribute( + NameOID.ORGANIZATIONAL_UNIT_NAME, u'Engineering 0' + ), + x509.NameAttribute( + NameOID.ORGANIZATIONAL_UNIT_NAME, u'Engineering 1' + ), + x509.NameAttribute(NameOID.DN_QUALIFIER, u'qualified0'), + x509.NameAttribute(NameOID.DN_QUALIFIER, u'qualified1'), + x509.NameAttribute(NameOID.SERIAL_NUMBER, u'789'), + x509.NameAttribute(NameOID.SERIAL_NUMBER, u'012'), + x509.NameAttribute(NameOID.TITLE, u'Title IX'), + x509.NameAttribute(NameOID.TITLE, u'Title X'), + x509.NameAttribute(NameOID.SURNAME, u'Last 0'), + x509.NameAttribute(NameOID.SURNAME, u'Last 1'), + x509.NameAttribute(NameOID.GIVEN_NAME, u'First 0'), + x509.NameAttribute(NameOID.GIVEN_NAME, u'First 1'), + x509.NameAttribute(NameOID.PSEUDONYM, u'Guy Incognito 0'), + x509.NameAttribute(NameOID.PSEUDONYM, u'Guy Incognito 1'), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u'32X'), + x509.NameAttribute(NameOID.GENERATION_QUALIFIER, u'Dreamcast'), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'dc2'), + x509.NameAttribute(NameOID.DOMAIN_COMPONENT, u'dc3'), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, u'test2@test.local'), + x509.NameAttribute(NameOID.EMAIL_ADDRESS, u'test3@test.local'), + ] + + def test_load_good_ca_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) + assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + assert cert.serial_number == 2 + public_key = cert.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + assert cert.version is x509.Version.v3 + fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) + assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" + + def test_utc_pre_2000_not_before_cert(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "Validpre2000UTCnotBeforeDateTest3EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + + assert cert.not_valid_before == datetime.datetime(1950, 1, 1, 12, 1) + + def test_pre_2000_utc_not_after_cert(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "Invalidpre2000UTCEEnotAfterDateTest7EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + + assert cert.not_valid_after == datetime.datetime(1999, 1, 1, 12, 1) + + def test_post_2000_utc_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.not_valid_before == datetime.datetime( + 2014, 11, 26, 21, 41, 20 + ) + assert cert.not_valid_after == datetime.datetime( + 2014, 12, 26, 21, 41, 20 + ) + + def test_generalized_time_not_before_cert(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "ValidGeneralizedTimenotBeforeDateTest4EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + assert cert.not_valid_before == datetime.datetime(2002, 1, 1, 12, 1) + assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + assert cert.version is x509.Version.v3 + + def test_generalized_time_not_after_cert(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "ValidGeneralizedTimenotAfterDateTest8EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) + assert cert.not_valid_after == datetime.datetime(2050, 1, 1, 12, 1) + assert cert.version is x509.Version.v3 + + def test_invalid_version_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "invalid_version.pem"), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(x509.InvalidVersion) as exc: + cert.version + + assert exc.value.parsed_version == 7 + + def test_eq(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + cert2 = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert == cert2 + + def test_ne(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + cert2 = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "ValidGeneralizedTimenotAfterDateTest8EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + assert cert != cert2 + assert cert != object() + + def test_hash(self, backend): + cert1 = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + cert2 = _load_cert( + os.path.join("x509", "custom", "post2000utctime.pem"), + x509.load_pem_x509_certificate, + backend + ) + cert3 = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", + "ValidGeneralizedTimenotAfterDateTest8EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + + assert hash(cert1) == hash(cert2) + assert hash(cert1) != hash(cert3) + + def test_version_1_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "v1_cert.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.version is x509.Version.v1 + + def test_invalid_pem(self, backend): + with pytest.raises(ValueError): + x509.load_pem_x509_certificate(b"notacert", backend) + + def test_invalid_der(self, backend): + with pytest.raises(ValueError): + x509.load_der_x509_certificate(b"notacert", backend) + + def test_unsupported_signature_hash_algorithm_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "verisign_md2_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(UnsupportedAlgorithm): + cert.signature_hash_algorithm + + def test_public_bytes_pem(self, backend): + # Load an existing certificate. + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + # Encode it to PEM and load it back. + cert = x509.load_pem_x509_certificate(cert.public_bytes( + encoding=serialization.Encoding.PEM, + ), backend) + + # We should recover what we had to start with. + assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) + assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + assert cert.serial_number == 2 + public_key = cert.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + assert cert.version is x509.Version.v3 + fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) + assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" + + def test_public_bytes_der(self, backend): + # Load an existing certificate. + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + # Encode it to DER and load it back. + cert = x509.load_der_x509_certificate(cert.public_bytes( + encoding=serialization.Encoding.DER, + ), backend) + + # We should recover what we had to start with. + assert cert.not_valid_before == datetime.datetime(2010, 1, 1, 8, 30) + assert cert.not_valid_after == datetime.datetime(2030, 12, 31, 8, 30) + assert cert.serial_number == 2 + public_key = cert.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + assert cert.version is x509.Version.v3 + fingerprint = binascii.hexlify(cert.fingerprint(hashes.SHA1())) + assert fingerprint == b"6f49779533d565e8b7c1062503eab41492c38e4d" + + def test_public_bytes_invalid_encoding(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + + with pytest.raises(TypeError): + cert.public_bytes('NotAnEncoding') + + @pytest.mark.parametrize( + ("cert_path", "loader_func", "encoding"), + [ + ( + os.path.join("x509", "v1_cert.pem"), + x509.load_pem_x509_certificate, + serialization.Encoding.PEM, + ), + ( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + serialization.Encoding.DER, + ), + ] + ) + def test_public_bytes_match(self, cert_path, loader_func, encoding, + backend): + cert_bytes = load_vectors_from_file( + cert_path, lambda pemfile: pemfile.read(), mode="rb" + ) + cert = loader_func(cert_bytes, backend) + serialized = cert.public_bytes(encoding) + assert serialized == cert_bytes + + def test_certificate_repr(self, backend): + cert = _load_cert( + os.path.join( + "x509", "cryptography.io.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + if six.PY3: + assert repr(cert) == ( + ", value='GT487" + "42965')>, , value='See www.rapidssl.com/re" + "sources/cps (c)14')>, , value='Domain Cont" + "rol Validated - RapidSSL(R)')>, , value='www.cryptograp" + "hy.io')>])>, ...)>" + ) + else: + assert repr(cert) == ( + ", value=u'GT48" + "742965')>, , value=u'See www.rapidssl.com/" + "resources/cps (c)14')>, , value=u'Domain C" + "ontrol Validated - RapidSSL(R)')>, , value=u'www.crypto" + "graphy.io')>])>, ...)>" + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestRSACertificateRequest(object): + @pytest.mark.parametrize( + ("path", "loader_func"), + [ + [ + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr + ], + [ + os.path.join("x509", "requests", "rsa_sha1.der"), + x509.load_der_x509_csr + ], + ] + ) + def test_load_rsa_certificate_request(self, path, loader_func, backend): + request = _load_cert(path, loader_func, backend) + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + assert ( + request.signature_algorithm_oid == + SignatureAlgorithmOID.RSA_WITH_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(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ] + extensions = request.extensions + assert isinstance(extensions, x509.Extensions) + assert list(extensions) == [] + + @pytest.mark.parametrize( + "loader_func", + [x509.load_pem_x509_csr, x509.load_der_x509_csr] + ) + def test_invalid_certificate_request(self, loader_func, backend): + with pytest.raises(ValueError): + loader_func(b"notacsr", backend) + + def test_unsupported_signature_hash_algorithm_request(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "rsa_md4.pem"), + x509.load_pem_x509_csr, + backend + ) + with pytest.raises(UnsupportedAlgorithm): + request.signature_hash_algorithm + + def test_duplicate_extension(self, backend): + request = _load_cert( + os.path.join( + "x509", "requests", "two_basic_constraints.pem" + ), + x509.load_pem_x509_csr, + backend + ) + with pytest.raises(x509.DuplicateExtension) as exc: + request.extensions + + assert exc.value.oid == ExtensionOID.BASIC_CONSTRAINTS + + def test_unsupported_critical_extension(self, backend): + request = _load_cert( + os.path.join( + "x509", "requests", "unsupported_extension_critical.pem" + ), + x509.load_pem_x509_csr, + backend + ) + with pytest.raises(x509.UnsupportedExtension) as exc: + request.extensions + + assert exc.value.oid == x509.ObjectIdentifier('1.2.3.4') + + def test_unsupported_extension(self, backend): + request = _load_cert( + os.path.join( + "x509", "requests", "unsupported_extension.pem" + ), + x509.load_pem_x509_csr, + backend + ) + extensions = request.extensions + assert len(extensions) == 1 + assert extensions[0].oid == x509.ObjectIdentifier("1.2.3.4") + assert extensions[0].value == x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"value" + ) + + def test_request_basic_constraints(self, backend): + request = _load_cert( + os.path.join( + "x509", "requests", "basic_constraints.pem" + ), + x509.load_pem_x509_csr, + backend + ) + extensions = request.extensions + assert isinstance(extensions, x509.Extensions) + assert list(extensions) == [ + x509.Extension( + ExtensionOID.BASIC_CONSTRAINTS, + True, + x509.BasicConstraints(ca=True, path_length=1), + ), + ] + + def test_subject_alt_name(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "san_rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend, + ) + ext = request.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert list(ext.value) == [ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"sub.cryptography.io"), + ] + + 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(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COMMON_NAME, u'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(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ] + + def test_signature(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.signature == binascii.unhexlify( + b"8364c86ffbbfe0bfc9a21f831256658ca8989741b80576d36f08a934603a43b1" + b"837246d00167a518abb1de7b51a1e5b7ebea14944800818b1a923c804f120a0d" + b"624f6310ef79e8612755c2b01dcc7f59dfdbce0db3f2630f185f504b8c17af80" + b"cbd364fa5fda68337153930948226cd4638287a0aed6524d3006885c19028a1e" + b"e2f5a91d6e77dbaa0b49996ee0a0c60b55b61bd080a08bb34aa7f3e07e91f37f" + b"6a11645be2d8654c1570dcda145ed7cc92017f7d53225d7f283f3459ec5bda41" + b"cf6dd75d43676c543483385226b7e4fa29c8739f1b0eaf199613593991979862" + b"e36181e8c4c270c354b7f52c128db1b70639823324c7ea24791b7bc3d7005f3b" + ) + + def test_tbs_certrequest_bytes(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.tbs_certrequest_bytes == binascii.unhexlify( + b"308201840201003057310b3009060355040613025553310e300c060355040813" + b"055465786173310f300d0603550407130641757374696e310d300b060355040a" + b"130450794341311830160603550403130f63727970746f6772617068792e696f" + b"30820122300d06092a864886f70d01010105000382010f003082010a02820101" + b"00a840a78460cb861066dfa3045a94ba6cf1b7ab9d24c761cffddcc2cb5e3f1d" + b"c3e4be253e7039ef14fe9d6d2304f50d9f2e1584c51530ab75086f357138bff7" + b"b854d067d1d5f384f1f2f2c39cc3b15415e2638554ef8402648ae3ef08336f22" + b"b7ecc6d4331c2b21c3091a7f7a9518180754a646640b60419e4cc6f5c798110a" + b"7f030a639fe87e33b4776dfcd993940ec776ab57a181ad8598857976dc303f9a" + b"573ca619ab3fe596328e92806b828683edc17cc256b41948a2bfa8d047d2158d" + b"3d8e069aa05fa85b3272abb1c4b4422b6366f3b70e642377b145cd6259e5d3e7" + b"db048d51921e50766a37b1b130ee6b11f507d20a834001e8de16a92c14f2e964" + b"a30203010001a000" + ) + verifier = request.public_key().verifier( + request.signature, + padding.PKCS1v15(), + request.signature_hash_algorithm + ) + verifier.update(request.tbs_certrequest_bytes) + verifier.verify() + + 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') + + def test_signature_invalid(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "invalid_signature.pem"), + x509.load_pem_x509_csr, + backend + ) + assert not request.is_signature_valid + + def test_signature_valid(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "rsa_sha256.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.is_signature_valid + + @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 + + def test_eq(self, backend): + request1 = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + request2 = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + + assert request1 == request2 + + def test_ne(self, backend): + request1 = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + request2 = _load_cert( + os.path.join("x509", "requests", "san_rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + + assert request1 != request2 + assert request1 != object() + + def test_hash(self, backend): + request1 = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + request2 = _load_cert( + os.path.join("x509", "requests", "rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + request3 = _load_cert( + os.path.join("x509", "requests", "san_rsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + + assert hash(request1) == hash(request2) + assert hash(request1) != hash(request3) + + def test_build_cert(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] + + def test_build_cert_printable_string_country_name(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + + parsed, _ = decoder.decode( + cert.public_bytes(serialization.Encoding.DER), + asn1Spec=rfc2459.Certificate() + ) + tbs_cert = parsed.getComponentByName('tbsCertificate') + subject = tbs_cert.getComponentByName('subject') + issuer = tbs_cert.getComponentByName('issuer') + # \x13 is printable string. The first byte of the value of the + # node corresponds to the ASN.1 string type. + assert subject[0][0][0][1][0] == b"\x13"[0] + assert issuer[0][0][0][1][0] == b"\x13"[0] + + +class TestCertificateBuilder(object): + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_checks_for_unsupported_extensions(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + private_key.public_key() + ).serial_number( + 777 + ).not_valid_before( + datetime.datetime(1999, 1, 1) + ).not_valid_after( + datetime.datetime(2020, 1, 1) + ).add_extension( + DummyExtension(), False + ) + + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA1(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_encode_nonstandard_aia(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + x509.ObjectIdentifier("2.999.7"), + x509.UniformResourceIdentifier(u"http://example.com") + ), + ]) + + builder = x509.CertificateBuilder().subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + private_key.public_key() + ).serial_number( + 777 + ).not_valid_before( + datetime.datetime(1999, 1, 1) + ).not_valid_after( + datetime.datetime(2020, 1, 1) + ).add_extension( + aia, False + ) + + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.skipif(sys.platform != "win32", reason="Requires windows") + @pytest.mark.parametrize( + ("not_valid_before", "not_valid_after"), + [ + [datetime.datetime(1999, 1, 1), datetime.datetime(9999, 1, 1)], + [datetime.datetime(9999, 1, 1), datetime.datetime(9999, 12, 31)], + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_invalid_time_windows(self, not_valid_before, not_valid_after, + backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + private_key.public_key() + ).serial_number( + 777 + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_subject_name(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + with pytest.raises(ValueError): + builder.sign(subject_private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_issuer_name(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + 777 + ).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + with pytest.raises(ValueError): + builder.sign(subject_private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_public_key(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + with pytest.raises(ValueError): + builder.sign(subject_private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_not_valid_before(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + with pytest.raises(ValueError): + builder.sign(subject_private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_not_valid_after(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.sign(subject_private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_serial_number(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + with pytest.raises(ValueError): + builder.sign(subject_private_key, hashes.SHA256(), backend) + + def test_issuer_name_must_be_a_name_type(self): + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.issuer_name("subject") + + with pytest.raises(TypeError): + builder.issuer_name(object) + + def test_issuer_name_may_only_be_set_once(self): + name = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + builder = x509.CertificateBuilder().issuer_name(name) + + with pytest.raises(ValueError): + builder.issuer_name(name) + + def test_subject_name_must_be_a_name_type(self): + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.subject_name("subject") + + with pytest.raises(TypeError): + builder.subject_name(object) + + def test_subject_name_may_only_be_set_once(self): + name = x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + builder = x509.CertificateBuilder().subject_name(name) + + with pytest.raises(ValueError): + builder.subject_name(name) + + def test_not_valid_before_after_not_valid_after(self): + builder = x509.CertificateBuilder() + + builder = builder.not_valid_after( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.not_valid_before( + datetime.datetime(2003, 1, 1, 12, 1) + ) + + def test_not_valid_after_before_not_valid_before(self): + builder = x509.CertificateBuilder() + + builder = builder.not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.not_valid_after( + datetime.datetime(2001, 1, 1, 12, 1) + ) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_public_key_must_be_public_key(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.public_key(private_key) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_public_key_may_only_be_set_once(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + public_key = private_key.public_key() + builder = x509.CertificateBuilder().public_key(public_key) + + with pytest.raises(ValueError): + builder.public_key(public_key) + + def test_serial_number_must_be_an_integer_type(self): + with pytest.raises(TypeError): + x509.CertificateBuilder().serial_number(10.0) + + def test_serial_number_must_be_non_negative(self): + with pytest.raises(ValueError): + x509.CertificateBuilder().serial_number(-1) + + def test_serial_number_must_be_positive(self): + with pytest.raises(ValueError): + x509.CertificateBuilder().serial_number(0) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_minimal_serial_number(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + 1 + ).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'RU'), + ])).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'RU'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + cert = builder.sign(subject_private_key, hashes.SHA256(), backend) + assert cert.serial_number == 1 + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_biggest_serial_number(self, backend): + subject_private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder().serial_number( + (1 << 159) - 1 + ).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'RU'), + ])).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'RU'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + cert = builder.sign(subject_private_key, hashes.SHA256(), backend) + assert cert.serial_number == (1 << 159) - 1 + + def test_serial_number_must_be_less_than_160_bits_long(self): + with pytest.raises(ValueError): + x509.CertificateBuilder().serial_number(1 << 159) + + def test_serial_number_may_only_be_set_once(self): + builder = x509.CertificateBuilder().serial_number(10) + + with pytest.raises(ValueError): + builder.serial_number(20) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_aware_not_valid_after(self, backend): + time = datetime.datetime(2012, 1, 16, 22, 43) + tz = pytz.timezone("US/Pacific") + time = tz.localize(time) + utc_time = datetime.datetime(2012, 1, 17, 6, 43) + private_key = RSA_KEY_2048.private_key(backend) + cert_builder = x509.CertificateBuilder().not_valid_after(time) + cert_builder = cert_builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_before( + utc_time - datetime.timedelta(days=365) + ) + + cert = cert_builder.sign(private_key, hashes.SHA256(), backend) + assert cert.not_valid_after == utc_time + + def test_invalid_not_valid_after(self): + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_after(104204304504) + + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_after(datetime.time()) + + with pytest.raises(ValueError): + x509.CertificateBuilder().not_valid_after( + datetime.datetime(1960, 8, 10) + ) + + def test_not_valid_after_may_only_be_set_once(self): + builder = x509.CertificateBuilder().not_valid_after( + datetime.datetime.now() + ) + + with pytest.raises(ValueError): + builder.not_valid_after( + datetime.datetime.now() + ) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_aware_not_valid_before(self, backend): + time = datetime.datetime(2012, 1, 16, 22, 43) + tz = pytz.timezone("US/Pacific") + time = tz.localize(time) + utc_time = datetime.datetime(2012, 1, 17, 6, 43) + private_key = RSA_KEY_2048.private_key(backend) + cert_builder = x509.CertificateBuilder().not_valid_before(time) + cert_builder = cert_builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_after( + utc_time + datetime.timedelta(days=366) + ) + + cert = cert_builder.sign(private_key, hashes.SHA256(), backend) + assert cert.not_valid_before == utc_time + + def test_invalid_not_valid_before(self): + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_before(104204304504) + + with pytest.raises(TypeError): + x509.CertificateBuilder().not_valid_before(datetime.time()) + + with pytest.raises(ValueError): + x509.CertificateBuilder().not_valid_before( + datetime.datetime(1960, 8, 10) + ) + + def test_not_valid_before_may_only_be_set_once(self): + builder = x509.CertificateBuilder().not_valid_before( + datetime.datetime.now() + ) + + with pytest.raises(ValueError): + builder.not_valid_before( + datetime.datetime.now() + ) + + def test_add_extension_checks_for_duplicates(self): + builder = x509.CertificateBuilder().add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ) + + with pytest.raises(ValueError): + builder.add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ) + + def test_add_invalid_extension_type(self): + builder = x509.CertificateBuilder() + + with pytest.raises(TypeError): + builder.add_extension(object(), False) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_unsupported_hash(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).serial_number( + 1 + ).public_key( + private_key.public_key() + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2032, 1, 1, 12, 1) + ) + + with pytest.raises(TypeError): + builder.sign(private_key, object(), backend) + + @pytest.mark.parametrize( + "cdp", + [ + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + NameOID.COMMON_NAME, + u"indirect CRL for indirectCRL CA3" + ), + ]), + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, + u"Test Certificates 2011" + ), + x509.NameAttribute( + NameOID.ORGANIZATIONAL_UNIT_NAME, + u"indirectCRL CA3 cRLIssuer" + ), + ]) + )], + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + ]) + )], + relative_name=None, + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, + u"cryptography Testing" + ), + ]) + )], + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[ + x509.UniformResourceIdentifier( + u"http://myhost.com/myca.crl" + ), + x509.UniformResourceIdentifier( + u"http://backup.myhost.com/myca.crl" + ) + ], + relative_name=None, + reasons=frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise + ]), + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography CA" + ), + ]) + )], + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"http://domain.com/some.crl" + )], + relative_name=None, + reasons=frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + x509.ReasonFlags.affiliation_changed, + x509.ReasonFlags.superseded, + x509.ReasonFlags.privilege_withdrawn, + x509.ReasonFlags.cessation_of_operation, + x509.ReasonFlags.aa_compromise, + x509.ReasonFlags.certificate_hold, + ]), + crl_issuer=None + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=None, + relative_name=None, + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography CA" + ), + ]) + )], + ) + ]), + x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"http://domain.com/some.crl" + )], + relative_name=None, + reasons=frozenset([x509.ReasonFlags.aa_compromise]), + crl_issuer=None + ) + ]) + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_crl_distribution_points(self, backend, cdp): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + builder = x509.CertificateBuilder().serial_number( + 4444444 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + cdp, + critical=False, + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ) + assert ext.critical is False + assert ext.value == cdp + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_dsa_private_key(self, backend): + if backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_101: + pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1") + + issuer_private_key = DSA_KEY_2048.private_key(backend) + subject_private_key = DSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_ec_private_key(self, backend): + if backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_101: + pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1") + + _skip_curve_unsupported(backend, ec.SECP256R1()) + issuer_private_key = ec.generate_private_key(ec.SECP256R1(), backend) + subject_private_key = ec.generate_private_key(ec.SECP256R1(), backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), True, + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + assert cert.version is x509.Version.v3 + assert cert.not_valid_before == not_valid_before + assert cert.not_valid_after == not_valid_after + basic_constraints = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + subject_alternative_name = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert list(subject_alternative_name.value) == [ + x509.DNSName(u"cryptography.io"), + ] + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_rsa_key_too_small(self, backend): + issuer_private_key = RSA_KEY_512.private_key(backend) + subject_private_key = RSA_KEY_512.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + with pytest.raises(ValueError): + builder.sign(issuer_private_key, hashes.SHA512(), backend) + + @pytest.mark.parametrize( + "cp", + [ + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [u"http://other.com/cps"] + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + None + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + u"http://example.com/cps", + u"http://other.com/cps", + x509.UserNotice( + x509.NoticeReference(u"my org", [1, 2, 3, 4]), + u"thing" + ) + ] + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + u"http://example.com/cps", + x509.UserNotice( + x509.NoticeReference(u"UTF8\u2122'", [1, 2, 3, 4]), + u"We heart UTF8!\u2122" + ) + ] + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [x509.UserNotice(None, u"thing")] + ) + ]), + x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + x509.UserNotice( + x509.NoticeReference(u"my org", [1, 2, 3, 4]), + None + ) + ] + ) + ]) + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_certificate_policies(self, cp, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US')]) + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).public_key( + subject_private_key.public_key() + ).serial_number( + 123 + ).add_extension( + cp, critical=False + ).sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid( + x509.OID_CERTIFICATE_POLICIES + ) + assert ext.value == cp + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_issuer_alt_name(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).public_key( + subject_private_key.public_key() + ).serial_number( + 123 + ).add_extension( + x509.IssuerAlternativeName([ + x509.DNSName(u"myissuer"), + x509.RFC822Name(u"email@domain.com"), + ]), critical=False + ).sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.ISSUER_ALTERNATIVE_NAME + ) + assert ext.critical is False + assert ext.value == x509.IssuerAlternativeName([ + x509.DNSName(u"myissuer"), + x509.RFC822Name(u"email@domain.com"), + ]) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_extended_key_usage(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).public_key( + subject_private_key.public_key() + ).serial_number( + 123 + ).add_extension( + x509.ExtendedKeyUsage([ + ExtendedKeyUsageOID.CLIENT_AUTH, + ExtendedKeyUsageOID.SERVER_AUTH, + ExtendedKeyUsageOID.CODE_SIGNING, + ]), critical=False + ).sign(issuer_private_key, hashes.SHA256(), backend) + + eku = cert.extensions.get_extension_for_oid( + ExtensionOID.EXTENDED_KEY_USAGE + ) + assert eku.critical is False + assert eku.value == x509.ExtendedKeyUsage([ + ExtendedKeyUsageOID.CLIENT_AUTH, + ExtendedKeyUsageOID.SERVER_AUTH, + ExtendedKeyUsageOID.CODE_SIGNING, + ]) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_inhibit_any_policy(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).public_key( + subject_private_key.public_key() + ).serial_number( + 123 + ).add_extension( + x509.InhibitAnyPolicy(3), critical=False + ).sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.INHIBIT_ANY_POLICY + ) + assert ext.value == x509.InhibitAnyPolicy(3) + + @pytest.mark.parametrize( + "pc", + [ + x509.PolicyConstraints( + require_explicit_policy=None, + inhibit_policy_mapping=1 + ), + x509.PolicyConstraints( + require_explicit_policy=3, + inhibit_policy_mapping=1 + ), + x509.PolicyConstraints( + require_explicit_policy=0, + inhibit_policy_mapping=None + ), + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_policy_constraints(self, backend, pc): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).public_key( + subject_private_key.public_key() + ).serial_number( + 123 + ).add_extension( + pc, critical=False + ).sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_class( + x509.PolicyConstraints + ) + assert ext.critical is False + assert ext.value == pc + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + @pytest.mark.parametrize( + "nc", + [ + x509.NameConstraints( + permitted_subtrees=[ + x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24")), + x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/29")), + x509.IPAddress(ipaddress.IPv4Network(u"127.0.0.1/32")), + x509.IPAddress(ipaddress.IPv4Network(u"8.0.0.0/8")), + x509.IPAddress(ipaddress.IPv4Network(u"0.0.0.0/0")), + x509.IPAddress( + ipaddress.IPv6Network(u"FF:0:0:0:0:0:0:0/96") + ), + x509.IPAddress( + ipaddress.IPv6Network(u"FF:FF:0:0:0:0:0:0/128") + ), + ], + excluded_subtrees=[x509.DNSName(u"name.local")] + ), + x509.NameConstraints( + permitted_subtrees=[ + x509.IPAddress(ipaddress.IPv4Network(u"0.0.0.0/0")), + ], + excluded_subtrees=None + ), + x509.NameConstraints( + permitted_subtrees=None, + excluded_subtrees=[x509.DNSName(u"name.local")] + ), + ] + ) + def test_name_constraints(self, nc, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).public_key( + subject_private_key.public_key() + ).serial_number( + 123 + ).add_extension( + nc, critical=False + ).sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ) + assert ext.value == nc + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_key_usage(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ).public_key( + subject_private_key.public_key() + ).serial_number( + 123 + ).add_extension( + x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ), + critical=False + ).sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) + assert ext.critical is False + assert ext.value == x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_ca_request_with_path_length_none(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u'PyCA'), + ]) + ).add_extension( + x509.BasicConstraints(ca=True, path_length=None), critical=True + ).sign(private_key, hashes.SHA1(), backend) + + loaded_request = x509.load_pem_x509_csr( + request.public_bytes(encoding=serialization.Encoding.PEM), backend + ) + subject = loaded_request.subject + assert isinstance(subject, x509.Name) + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.path_length is None + + @pytest.mark.parametrize( + "unrecognized", [ + x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4.5"), + b"abcdef", + ) + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_unrecognized_extension(self, backend, unrecognized): + private_key = RSA_KEY_2048.private_key(backend) + + cert = x509.CertificateBuilder().subject_name( + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US')]) + ).issuer_name( + x509.Name([x509.NameAttribute(x509.OID_COUNTRY_NAME, u'US')]) + ).not_valid_before( + datetime.datetime(2002, 1, 1, 12, 1) + ).not_valid_after( + datetime.datetime(2030, 12, 31, 8, 30) + ).public_key( + private_key.public_key() + ).serial_number( + 123 + ).add_extension( + unrecognized, critical=False + ).sign(private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid(unrecognized.oid) + + assert ext.value == unrecognized + + +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestCertificateSigningRequestBuilder(object): + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_sign_invalid_hash_algorithm(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([]) + ) + with pytest.raises(TypeError): + builder.sign(private_key, 'NotAHash', backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_no_subject_name(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + builder = x509.CertificateSigningRequestBuilder() + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_build_ca_request_with_rsa(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + ]) + ).add_extension( + x509.BasicConstraints(ca=True, path_length=2), critical=True + ).sign(private_key, hashes.SHA1(), backend) + + 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(NameOID.ORGANIZATION_NAME, u'PyCA'), + ] + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is True + assert basic_constraints.value.path_length == 2 + + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_build_ca_request_with_unicode(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u'PyCA\U0001f37a'), + ]) + ).add_extension( + x509.BasicConstraints(ca=True, path_length=2), critical=True + ).sign(private_key, hashes.SHA1(), backend) + + loaded_request = x509.load_pem_x509_csr( + request.public_bytes(encoding=serialization.Encoding.PEM), backend + ) + subject = loaded_request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA\U0001f37a'), + ] + + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_build_ca_request_with_multivalue_rdns(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + subject = x509.Name([ + x509.RelativeDistinguishedName([ + x509.NameAttribute(NameOID.TITLE, u'Test'), + x509.NameAttribute(NameOID.COMMON_NAME, u'Multivalue'), + x509.NameAttribute(NameOID.SURNAME, u'RDNs'), + ]), + x509.RelativeDistinguishedName([ + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA') + ]), + ]) + + request = x509.CertificateSigningRequestBuilder().subject_name( + subject + ).sign(private_key, hashes.SHA1(), backend) + + loaded_request = x509.load_pem_x509_csr( + request.public_bytes(encoding=serialization.Encoding.PEM), backend + ) + assert isinstance(loaded_request.subject, x509.Name) + assert loaded_request.subject == subject + + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_build_nonca_request_with_rsa(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ).add_extension( + x509.BasicConstraints(ca=False, path_length=None), critical=True, + ).sign(private_key, hashes.SHA1(), backend) + + 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(NameOID.COUNTRY_NAME, u'US'), + ] + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is False + assert basic_constraints.value.path_length is None + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + def test_build_ca_request_with_ec(self, backend): + if backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_101: + pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1") + + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ]) + ).add_extension( + x509.BasicConstraints(ca=True, path_length=2), critical=True + ).sign(private_key, hashes.SHA1(), backend) + + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, ec.EllipticCurvePublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ] + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is True + assert basic_constraints.value.path_length == 2 + + @pytest.mark.requires_backend_interface(interface=DSABackend) + def test_build_ca_request_with_dsa(self, backend): + if backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_101: + pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1") + + private_key = DSA_KEY_2048.private_key(backend) + + request = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ).add_extension( + x509.BasicConstraints(ca=True, path_length=2), critical=True + ).sign(private_key, hashes.SHA1(), backend) + + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, dsa.DSAPublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ] + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is True + assert basic_constraints.value.path_length == 2 + + def test_add_duplicate_extension(self): + builder = x509.CertificateSigningRequestBuilder().add_extension( + x509.BasicConstraints(True, 2), critical=True, + ) + with pytest.raises(ValueError): + builder.add_extension( + x509.BasicConstraints(True, 2), critical=True, + ) + + def test_set_invalid_subject(self): + builder = x509.CertificateSigningRequestBuilder() + with pytest.raises(TypeError): + builder.subject_name('NotAName') + + def test_add_invalid_extension_type(self): + builder = x509.CertificateSigningRequestBuilder() + + with pytest.raises(TypeError): + builder.add_extension(object(), False) + + def test_add_unsupported_extension(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateSigningRequestBuilder() + builder = builder.subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).add_extension( + DummyExtension(), False + ) + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA256(), backend) + + def test_key_usage(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateSigningRequestBuilder() + request = builder.subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ).add_extension( + x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ), + critical=False + ).sign(private_key, hashes.SHA256(), backend) + assert len(request.extensions) == 1 + ext = request.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) + assert ext.critical is False + assert ext.value == x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + + def test_key_usage_key_agreement_bit(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateSigningRequestBuilder() + request = builder.subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ).add_extension( + x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=True + ), + critical=False + ).sign(private_key, hashes.SHA256(), backend) + assert len(request.extensions) == 1 + ext = request.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) + assert ext.critical is False + assert ext.value == x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + + def test_add_two_extensions(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateSigningRequestBuilder() + request = builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(u"cryptography.io")]), + critical=False, + ).add_extension( + x509.BasicConstraints(ca=True, path_length=2), critical=True + ).sign(private_key, hashes.SHA1(), backend) + + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, rsa.RSAPublicKey) + basic_constraints = request.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert basic_constraints.value.ca is True + assert basic_constraints.value.path_length == 2 + ext = request.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert list(ext.value) == [x509.DNSName(u"cryptography.io")] + + def test_set_subject_twice(self): + builder = x509.CertificateSigningRequestBuilder() + builder = builder.subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ) + with pytest.raises(ValueError): + builder.subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ]) + ) + + def test_subject_alt_names(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + san = x509.SubjectAlternativeName([ + x509.DNSName(u"example.com"), + x509.DNSName(u"*.example.com"), + x509.RegisteredID(x509.ObjectIdentifier("1.2.3.4.5.6.7")), + x509.DirectoryName(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u'PyCA'), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u'We heart UTF8!\u2122' + ) + ])), + 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" + ), + x509.RFC822Name(u"test@example.com"), + x509.RFC822Name(u"email"), + x509.RFC822Name(u"email@em\xe5\xefl.com"), + x509.UniformResourceIdentifier( + u"https://\u043f\u044b\u043a\u0430.cryptography" + ), + x509.UniformResourceIdentifier( + u"gopher://cryptography:70/some/path" + ), + ]) + + csr = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"SAN"), + ]) + ).add_extension( + san, + critical=False, + ).sign(private_key, hashes.SHA256(), backend) + + assert len(csr.extensions) == 1 + ext = csr.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert not ext.critical + assert ext.oid == ExtensionOID.SUBJECT_ALTERNATIVE_NAME + assert ext.value == san + + def test_invalid_asn1_othername(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.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) + + builder = x509.CertificateSigningRequestBuilder().subject_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"SAN"), + ]) + ).add_extension( + x509.SubjectAlternativeName([FakeGeneralName("")]), + critical=False, + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + def test_extended_key_usage(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + eku = x509.ExtendedKeyUsage([ + ExtendedKeyUsageOID.CLIENT_AUTH, + ExtendedKeyUsageOID.SERVER_AUTH, + ExtendedKeyUsageOID.CODE_SIGNING, + ]) + builder = x509.CertificateSigningRequestBuilder() + request = builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).add_extension( + eku, critical=False + ).sign(private_key, hashes.SHA256(), backend) + + ext = request.extensions.get_extension_for_oid( + ExtensionOID.EXTENDED_KEY_USAGE + ) + assert ext.critical is False + assert ext.value == eku + + @pytest.mark.requires_backend_interface(interface=RSABackend) + def test_rsa_key_too_small(self, backend): + private_key = rsa.generate_private_key(65537, 512, backend) + builder = x509.CertificateSigningRequestBuilder() + builder = builder.subject_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ) + + with pytest.raises(ValueError) as exc: + builder.sign(private_key, hashes.SHA512(), backend) + + assert str(exc.value) == "Digest too big for RSA key" + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_aia(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + aia, critical=False + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_INFORMATION_ACCESS + ) + assert ext.value == aia + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_ski(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + ski = x509.SubjectKeyIdentifier.from_public_key( + subject_private_key.public_key() + ) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + ski, critical=False + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA1(), backend) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + assert ext.value == ski + + @pytest.mark.parametrize( + "aki", + [ + x509.AuthorityKeyIdentifier( + b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" + b"\xcbY", + None, + None + ), + x509.AuthorityKeyIdentifier( + b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" + b"\xcbY", + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u"PyCA" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography CA" + ) + ]) + ) + ], + 333 + ), + x509.AuthorityKeyIdentifier( + None, + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u"PyCA" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography CA" + ) + ]) + ) + ], + 333 + ), + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_build_cert_with_aki(self, aki, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + aki, critical=False + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ) + assert ext.value == aki + + def test_ocsp_nocheck(self, backend): + issuer_private_key = RSA_KEY_2048.private_key(backend) + subject_private_key = RSA_KEY_2048.private_key(backend) + + not_valid_before = datetime.datetime(2002, 1, 1, 12, 1) + not_valid_after = datetime.datetime(2030, 12, 31, 8, 30) + + builder = x509.CertificateBuilder().serial_number( + 777 + ).issuer_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).subject_name(x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + ])).public_key( + subject_private_key.public_key() + ).add_extension( + x509.OCSPNoCheck(), critical=False + ).not_valid_before( + not_valid_before + ).not_valid_after( + not_valid_after + ) + + cert = builder.sign(issuer_private_key, hashes.SHA256(), backend) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.OCSP_NO_CHECK + ) + assert isinstance(ext.value, x509.OCSPNoCheck) + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestDSACertificate(object): + def test_load_dsa_cert(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA1) + public_key = cert.public_key() + assert isinstance(public_key, dsa.DSAPublicKey) + num = public_key.public_numbers() + assert num.y == int( + "4c08bfe5f2d76649c80acf7d431f6ae2124b217abc8c9f6aca776ddfa94" + "53b6656f13e543684cd5f6431a314377d2abfa068b7080cb8ddc065afc2" + "dea559f0b584c97a2b235b9b69b46bc6de1aed422a6f341832618bcaae2" + "198aba388099dafb05ff0b5efecb3b0ae169a62e1c72022af50ae68af3b" + "033c18e6eec1f7df4692c456ccafb79cc7e08da0a5786e9816ceda651d6" + "1b4bb7b81c2783da97cea62df67af5e85991fdc13aff10fc60e06586386" + "b96bb78d65750f542f86951e05a6d81baadbcd35a2e5cad4119923ae6a2" + "002091a3d17017f93c52970113cdc119970b9074ca506eac91c3dd37632" + "5df4af6b3911ef267d26623a5a1c5df4a6d13f1c", 16 + ) + assert num.parameter_numbers.g == int( + "4b7ced71dc353965ecc10d441a9a06fc24943a32d66429dd5ef44d43e67" + "d789d99770aec32c0415dc92970880872da45fef8dd1e115a3e4801387b" + "a6d755861f062fd3b6e9ea8e2641152339b828315b1528ee6c7b79458d2" + "1f3db973f6fc303f9397174c2799dd2351282aa2d8842c357a73495bbaa" + "c4932786414c55e60d73169f5761036fba29e9eebfb049f8a3b1b7cee6f" + "3fbfa136205f130bee2cf5b9c38dc1095d4006f2e73335c07352c64130a" + "1ab2b89f13b48f628d3cc3868beece9bb7beade9f830eacc6fa241425c0" + "b3fcc0df416a0c89f7bf35668d765ec95cdcfbe9caff49cfc156c668c76" + "fa6247676a6d3ac945844a083509c6a1b436baca", 16 + ) + assert num.parameter_numbers.p == int( + "bfade6048e373cd4e48b677e878c8e5b08c02102ae04eb2cb5c46a523a3" + "af1c73d16b24f34a4964781ae7e50500e21777754a670bd19a7420d6330" + "84e5556e33ca2c0e7d547ea5f46a07a01bf8669ae3bdec042d9b2ae5e6e" + "cf49f00ba9dac99ab6eff140d2cedf722ee62c2f9736857971444c25d0a" + "33d2017dc36d682a1054fe2a9428dda355a851ce6e6d61e03e419fd4ca4" + "e703313743d86caa885930f62ed5bf342d8165627681e9cc3244ba72aa2" + "2148400a6bbe80154e855d042c9dc2a3405f1e517be9dea50562f56da93" + "f6085f844a7e705c1f043e65751c583b80d29103e590ccb26efdaa0893d" + "833e36468f3907cfca788a3cb790f0341c8a31bf", 16 + ) + assert num.parameter_numbers.q == int( + "822ff5d234e073b901cf5941f58e1f538e71d40d", 16 + ) + + def test_signature(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.signature == binascii.unhexlify( + b"302c021425c4a84a936ab311ee017d3cbd9a3c650bb3ae4a02145d30c64b4326" + b"86bdf925716b4ed059184396bcce" + ) + r, s = decode_dss_signature(cert.signature) + assert r == 215618264820276283222494627481362273536404860490 + assert s == 532023851299196869156027211159466197586787351758 + + def test_tbs_certificate_bytes(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.tbs_certificate_bytes == binascii.unhexlify( + b"3082051aa003020102020900a37352e0b2142f86300906072a8648ce3804033" + b"067310b3009060355040613025553310e300c06035504081305546578617331" + b"0f300d0603550407130641757374696e3121301f060355040a1318496e74657" + b"26e6574205769646769747320507479204c7464311430120603550403130b50" + b"79434120445341204341301e170d3134313132373035313431375a170d31343" + b"13232373035313431375a3067310b3009060355040613025553310e300c0603" + b"55040813055465786173310f300d0603550407130641757374696e3121301f0" + b"60355040a1318496e7465726e6574205769646769747320507479204c746431" + b"1430120603550403130b50794341204453412043413082033a3082022d06072" + b"a8648ce380401308202200282010100bfade6048e373cd4e48b677e878c8e5b" + b"08c02102ae04eb2cb5c46a523a3af1c73d16b24f34a4964781ae7e50500e217" + b"77754a670bd19a7420d633084e5556e33ca2c0e7d547ea5f46a07a01bf8669a" + b"e3bdec042d9b2ae5e6ecf49f00ba9dac99ab6eff140d2cedf722ee62c2f9736" + b"857971444c25d0a33d2017dc36d682a1054fe2a9428dda355a851ce6e6d61e0" + b"3e419fd4ca4e703313743d86caa885930f62ed5bf342d8165627681e9cc3244" + b"ba72aa22148400a6bbe80154e855d042c9dc2a3405f1e517be9dea50562f56d" + b"a93f6085f844a7e705c1f043e65751c583b80d29103e590ccb26efdaa0893d8" + b"33e36468f3907cfca788a3cb790f0341c8a31bf021500822ff5d234e073b901" + b"cf5941f58e1f538e71d40d028201004b7ced71dc353965ecc10d441a9a06fc2" + b"4943a32d66429dd5ef44d43e67d789d99770aec32c0415dc92970880872da45" + b"fef8dd1e115a3e4801387ba6d755861f062fd3b6e9ea8e2641152339b828315" + b"b1528ee6c7b79458d21f3db973f6fc303f9397174c2799dd2351282aa2d8842" + b"c357a73495bbaac4932786414c55e60d73169f5761036fba29e9eebfb049f8a" + b"3b1b7cee6f3fbfa136205f130bee2cf5b9c38dc1095d4006f2e73335c07352c" + b"64130a1ab2b89f13b48f628d3cc3868beece9bb7beade9f830eacc6fa241425" + b"c0b3fcc0df416a0c89f7bf35668d765ec95cdcfbe9caff49cfc156c668c76fa" + b"6247676a6d3ac945844a083509c6a1b436baca0382010500028201004c08bfe" + b"5f2d76649c80acf7d431f6ae2124b217abc8c9f6aca776ddfa9453b6656f13e" + b"543684cd5f6431a314377d2abfa068b7080cb8ddc065afc2dea559f0b584c97" + b"a2b235b9b69b46bc6de1aed422a6f341832618bcaae2198aba388099dafb05f" + b"f0b5efecb3b0ae169a62e1c72022af50ae68af3b033c18e6eec1f7df4692c45" + b"6ccafb79cc7e08da0a5786e9816ceda651d61b4bb7b81c2783da97cea62df67" + b"af5e85991fdc13aff10fc60e06586386b96bb78d65750f542f86951e05a6d81" + b"baadbcd35a2e5cad4119923ae6a2002091a3d17017f93c52970113cdc119970" + b"b9074ca506eac91c3dd376325df4af6b3911ef267d26623a5a1c5df4a6d13f1" + b"ca381cc3081c9301d0603551d0e04160414a4fb887a13fcdeb303bbae9a1dec" + b"a72f125a541b3081990603551d2304819130818e8014a4fb887a13fcdeb303b" + b"bae9a1deca72f125a541ba16ba4693067310b3009060355040613025553310e" + b"300c060355040813055465786173310f300d0603550407130641757374696e3" + b"121301f060355040a1318496e7465726e657420576964676974732050747920" + b"4c7464311430120603550403130b5079434120445341204341820900a37352e" + b"0b2142f86300c0603551d13040530030101ff" + ) + verifier = cert.public_key().verifier( + cert.signature, cert.signature_hash_algorithm + ) + verifier.update(cert.tbs_certificate_bytes) + verifier.verify() + + +@pytest.mark.requires_backend_interface(interface=DSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestDSACertificateRequest(object): + @pytest.mark.parametrize( + ("path", "loader_func"), + [ + [ + os.path.join("x509", "requests", "dsa_sha1.pem"), + x509.load_pem_x509_csr + ], + [ + os.path.join("x509", "requests", "dsa_sha1.der"), + x509.load_der_x509_csr + ], + ] + ) + def test_load_dsa_request(self, path, loader_func, backend): + request = _load_cert(path, loader_func, backend) + assert isinstance(request.signature_hash_algorithm, hashes.SHA1) + public_key = request.public_key() + assert isinstance(public_key, dsa.DSAPublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + ] + + def test_signature(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "dsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.signature == binascii.unhexlify( + b"302c021461d58dc028d0110818a7d817d74235727c4acfdf0214097b52e198e" + b"ce95de17273f0a924df23ce9d8188" + ) + + def test_tbs_certrequest_bytes(self, backend): + request = _load_cert( + os.path.join("x509", "requests", "dsa_sha1.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.tbs_certrequest_bytes == binascii.unhexlify( + b"3082021802010030573118301606035504030c0f63727970746f677261706879" + b"2e696f310d300b060355040a0c0450794341310b300906035504061302555331" + b"0e300c06035504080c055465786173310f300d06035504070c0641757374696e" + b"308201b63082012b06072a8648ce3804013082011e028181008d7fadbc09e284" + b"aafa69154cea24177004909e519f8b35d685cde5b4ecdc9583e74d370a0f88ad" + b"a98f026f27762fb3d5da7836f986dfcdb3589e5b925bea114defc03ef81dae30" + b"c24bbc6df3d588e93427bba64203d4a5b1687b2b5e3b643d4c614976f89f95a3" + b"8d3e4c89065fba97514c22c50adbbf289163a74b54859b35b7021500835de56b" + b"d07cf7f82e2032fe78949aed117aa2ef0281801f717b5a07782fc2e4e68e311f" + b"ea91a54edd36b86ac634d14f05a68a97eae9d2ef31fb1ef3de42c3d100df9ca6" + b"4f5bdc2aec7bfdfb474cf831fea05853b5e059f2d24980a0ac463f1e818af352" + b"3e3cb79a39d45fa92731897752842469cf8540b01491024eaafbce6018e8a1f4" + b"658c343f4ba7c0b21e5376a21f4beb8491961e038184000281800713f07641f6" + b"369bb5a9545274a2d4c01998367fb371bb9e13436363672ed68f82174c2de05c" + b"8e839bc6de568dd50ba28d8d9d8719423aaec5557df10d773ab22d6d65cbb878" + b"04a697bc8fd965b952f9f7e850edf13c8acdb5d753b6d10e59e0b5732e3c82ba" + b"fa140342bc4a3bba16bd0681c8a6a2dbbb7efe6ce2b8463b170ba000" + ) + verifier = request.public_key().verifier( + request.signature, + request.signature_hash_algorithm + ) + verifier.update(request.tbs_certrequest_bytes) + verifier.verify() + + +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestECDSACertificate(object): + def test_load_ecdsa_cert(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + cert = _load_cert( + os.path.join("x509", "ecdsa_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert isinstance(cert.signature_hash_algorithm, hashes.SHA384) + public_key = cert.public_key() + assert isinstance(public_key, ec.EllipticCurvePublicKey) + num = public_key.public_numbers() + assert num.x == int( + "dda7d9bb8ab80bfb0b7f21d2f0bebe73f3335d1abc34eadec69bbcd095f" + "6f0ccd00bba615b51467e9e2d9fee8e630c17", 16 + ) + assert num.y == int( + "ec0770f5cf842e40839ce83f416d3badd3a4145936789d0343ee10136c7" + "2deae88a7a16bb543ce67dc23ff031ca3e23e", 16 + ) + assert isinstance(num.curve, ec.SECP384R1) + + def test_signature(self, backend): + cert = _load_cert( + os.path.join("x509", "ecdsa_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.signature == binascii.unhexlify( + b"3065023100adbcf26c3f124ad12d39c30a099773f488368c8827bbe6888d5085" + b"a763f99e32de66930ff1ccb1098fdd6cabfa6b7fa0023039665bc2648db89e50" + b"dca8d549a2edc7dcd1497f1701b8c8868f4e8c882ba89aa98ac5d100bdf854e2" + b"9ae55b7cb32717" + ) + r, s = decode_dss_signature(cert.signature) + assert r == int( + "adbcf26c3f124ad12d39c30a099773f488368c8827bbe6888d5085a763f99e32" + "de66930ff1ccb1098fdd6cabfa6b7fa0", + 16 + ) + assert s == int( + "39665bc2648db89e50dca8d549a2edc7dcd1497f1701b8c8868f4e8c882ba89a" + "a98ac5d100bdf854e29ae55b7cb32717", + 16 + ) + + def test_tbs_certificate_bytes(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + cert = _load_cert( + os.path.join("x509", "ecdsa_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + assert cert.tbs_certificate_bytes == binascii.unhexlify( + b"308201c5a0030201020210055556bcf25ea43535c3a40fd5ab4572300a06082" + b"a8648ce3d0403033061310b300906035504061302555331153013060355040a" + b"130c446967694365727420496e6331193017060355040b13107777772e64696" + b"769636572742e636f6d3120301e06035504031317446967694365727420476c" + b"6f62616c20526f6f74204733301e170d3133303830313132303030305a170d3" + b"338303131353132303030305a3061310b300906035504061302555331153013" + b"060355040a130c446967694365727420496e6331193017060355040b1310777" + b"7772e64696769636572742e636f6d3120301e06035504031317446967694365" + b"727420476c6f62616c20526f6f742047333076301006072a8648ce3d0201060" + b"52b8104002203620004dda7d9bb8ab80bfb0b7f21d2f0bebe73f3335d1abc34" + b"eadec69bbcd095f6f0ccd00bba615b51467e9e2d9fee8e630c17ec0770f5cf8" + b"42e40839ce83f416d3badd3a4145936789d0343ee10136c72deae88a7a16bb5" + b"43ce67dc23ff031ca3e23ea3423040300f0603551d130101ff040530030101f" + b"f300e0603551d0f0101ff040403020186301d0603551d0e04160414b3db48a4" + b"f9a1c5d8ae3641cc1163696229bc4bc6" + ) + verifier = cert.public_key().verifier( + cert.signature, ec.ECDSA(cert.signature_hash_algorithm) + ) + verifier.update(cert.tbs_certificate_bytes) + verifier.verify() + + def test_load_ecdsa_no_named_curve(self, backend): + _skip_curve_unsupported(backend, ec.SECP256R1()) + cert = _load_cert( + os.path.join("x509", "custom", "ec_no_named_curve.pem"), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(NotImplementedError): + cert.public_key() + + +@pytest.mark.requires_backend_interface(interface=X509Backend) +@pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) +class TestECDSACertificateRequest(object): + @pytest.mark.parametrize( + ("path", "loader_func"), + [ + [ + os.path.join("x509", "requests", "ec_sha256.pem"), + x509.load_pem_x509_csr + ], + [ + os.path.join("x509", "requests", "ec_sha256.der"), + x509.load_der_x509_csr + ], + ] + ) + def test_load_ecdsa_certificate_request(self, path, loader_func, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + request = _load_cert(path, loader_func, backend) + assert isinstance(request.signature_hash_algorithm, hashes.SHA256) + public_key = request.public_key() + assert isinstance(public_key, ec.EllipticCurvePublicKey) + subject = request.subject + assert isinstance(subject, x509.Name) + assert list(subject) == [ + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + x509.NameAttribute(NameOID.COUNTRY_NAME, u'US'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + x509.NameAttribute(NameOID.LOCALITY_NAME, u'Austin'), + ] + + def test_signature(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + request = _load_cert( + os.path.join("x509", "requests", "ec_sha256.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.signature == binascii.unhexlify( + b"306502302c1a9f7de8c1787332d2307a886b476a59f172b9b0e250262f3238b1" + b"b45ee112bb6eb35b0fb56a123b9296eb212dffc302310094cf440c95c52827d5" + b"56ae6d76500e3008255d47c29f7ee782ed7558e51bfd76aa45df6d999ed5c463" + b"347fe2382d1751" + ) + + def test_tbs_certrequest_bytes(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + request = _load_cert( + os.path.join("x509", "requests", "ec_sha256.pem"), + x509.load_pem_x509_csr, + backend + ) + assert request.tbs_certrequest_bytes == binascii.unhexlify( + b"3081d602010030573118301606035504030c0f63727970746f6772617068792" + b"e696f310d300b060355040a0c0450794341310b300906035504061302555331" + b"0e300c06035504080c055465786173310f300d06035504070c0641757374696" + b"e3076301006072a8648ce3d020106052b8104002203620004de19b514c0b3c3" + b"ae9b398ea3e26b5e816bdcf9102cad8f12fe02f9e4c9248724b39297ed7582e" + b"04d8b32a551038d09086803a6d3fb91a1a1167ec02158b00efad39c9396462f" + b"accff0ffaf7155812909d3726bd59fde001cff4bb9b2f5af8cbaa000" + ) + verifier = request.public_key().verifier( + request.signature, + ec.ECDSA(request.signature_hash_algorithm) + ) + verifier.update(request.tbs_certrequest_bytes) + verifier.verify() + + +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestOtherCertificate(object): + def test_unsupported_subject_public_key_info(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "unsupported_subject_public_key_info.pem" + ), + x509.load_pem_x509_certificate, + backend, + ) + + with pytest.raises(ValueError): + cert.public_key() + + +class TestNameAttribute(object): + def test_init_bad_oid(self): + with pytest.raises(TypeError): + x509.NameAttribute(None, u'value') + + def test_init_bad_value(self): + with pytest.raises(TypeError): + x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), + b'bytes' + ) + + def test_init_bad_country_code_value(self): + with pytest.raises(ValueError): + x509.NameAttribute( + NameOID.COUNTRY_NAME, + u'United States' + ) + + # unicode string of length 2, but > 2 bytes + with pytest.raises(ValueError): + x509.NameAttribute( + NameOID.COUNTRY_NAME, + u'\U0001F37A\U0001F37A' + ) + + def test_eq(self): + assert x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), u'value' + ) == x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), u'value' + ) + + def test_ne(self): + assert x509.NameAttribute( + x509.ObjectIdentifier('2.5.4.3'), u'value' + ) != x509.NameAttribute( + x509.ObjectIdentifier('2.5.4.5'), u'value' + ) + assert x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), u'value' + ) != x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), u'value2' + ) + assert x509.NameAttribute( + x509.ObjectIdentifier('2.999.2'), u'value' + ) != object() + + def test_repr(self): + na = x509.NameAttribute(x509.ObjectIdentifier('2.5.4.3'), u'value') + if six.PY3: + assert repr(na) == ( + ", value='value')>" + ) + else: + assert repr(na) == ( + ", value=u'value')>" + ) + + +class TestRelativeDistinguishedName(object): + def test_init_empty(self): + with pytest.raises(ValueError): + x509.RelativeDistinguishedName([]) + + def test_init_not_nameattribute(self): + with pytest.raises(TypeError): + x509.RelativeDistinguishedName(["not-a-NameAttribute"]) + + def test_init_duplicate_attribute(self): + rdn = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + ]) + assert len(rdn) == 1 + + def test_hash(self): + rdn1 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), + ]) + rdn2 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + ]) + rdn3 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value3'), + ]) + assert hash(rdn1) == hash(rdn2) + assert hash(rdn1) != hash(rdn3) + + def test_eq(self): + rdn1 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), + ]) + rdn2 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + ]) + assert rdn1 == rdn2 + + def test_ne(self): + rdn1 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2'), + ]) + rdn2 = x509.RelativeDistinguishedName([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1'), + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value3'), + ]) + assert rdn1 != rdn2 + assert rdn1 != object() + + def test_iter_input(self): + attrs = [ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ] + rdn = x509.RelativeDistinguishedName(iter(attrs)) + assert list(rdn) == attrs + assert list(rdn) == attrs + + def test_get_attributes_for_oid(self): + oid = x509.ObjectIdentifier('2.999.1') + attr = x509.NameAttribute(oid, u'value1') + rdn = x509.RelativeDistinguishedName([attr]) + assert rdn.get_attributes_for_oid(oid) == [attr] + assert rdn.get_attributes_for_oid(x509.ObjectIdentifier('1.2.3')) == [] + + +class TestObjectIdentifier(object): + def test_eq(self): + oid1 = x509.ObjectIdentifier('2.999.1') + oid2 = x509.ObjectIdentifier('2.999.1') + assert oid1 == oid2 + + def test_ne(self): + oid1 = x509.ObjectIdentifier('2.999.1') + assert oid1 != x509.ObjectIdentifier('2.999.2') + assert oid1 != object() + + def test_repr(self): + oid = x509.ObjectIdentifier("2.5.4.3") + assert repr(oid) == "" + oid = x509.ObjectIdentifier("2.999.1") + assert repr(oid) == "" + + def test_name_property(self): + oid = x509.ObjectIdentifier("2.5.4.3") + assert oid._name == 'commonName' + oid = x509.ObjectIdentifier("2.999.1") + assert oid._name == 'Unknown OID' + + def test_too_short(self): + with pytest.raises(ValueError): + x509.ObjectIdentifier("1") + + def test_invalid_input(self): + with pytest.raises(ValueError): + x509.ObjectIdentifier("notavalidform") + + def test_invalid_node1(self): + with pytest.raises(ValueError): + x509.ObjectIdentifier("7.1.37") + + def test_invalid_node2(self): + with pytest.raises(ValueError): + x509.ObjectIdentifier("1.50.200") + + def test_valid(self): + x509.ObjectIdentifier("0.35.200") + x509.ObjectIdentifier("1.39.999") + x509.ObjectIdentifier("2.5.29.3") + x509.ObjectIdentifier("2.999.37.5.22.8") + x509.ObjectIdentifier("2.25.305821105408246119474742976030998643995") + + +class TestName(object): + def test_eq(self): + ava1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ava2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + name1 = x509.Name([ava1, ava2]) + name2 = x509.Name([ + x509.RelativeDistinguishedName([ava1]), + x509.RelativeDistinguishedName([ava2]), + ]) + name3 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])]) + name4 = x509.Name([x509.RelativeDistinguishedName([ava2, ava1])]) + assert name1 == name2 + assert name3 == name4 + + def test_ne(self): + ava1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ava2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + name1 = x509.Name([ava1, ava2]) + name2 = x509.Name([ava2, ava1]) + name3 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])]) + assert name1 != name2 + assert name1 != name3 + assert name1 != object() + + def test_hash(self): + ava1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ava2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + name1 = x509.Name([ava1, ava2]) + name2 = x509.Name([ + x509.RelativeDistinguishedName([ava1]), + x509.RelativeDistinguishedName([ava2]), + ]) + name3 = x509.Name([ava2, ava1]) + name4 = x509.Name([x509.RelativeDistinguishedName([ava1, ava2])]) + name5 = x509.Name([x509.RelativeDistinguishedName([ava2, ava1])]) + assert hash(name1) == hash(name2) + assert hash(name1) != hash(name3) + assert hash(name1) != hash(name4) + assert hash(name4) == hash(name5) + + def test_iter_input(self): + attrs = [ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ] + name = x509.Name(iter(attrs)) + assert list(name) == attrs + assert list(name) == attrs + + def test_rdns(self): + rdn1 = x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + rdn2 = x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + name1 = x509.Name([rdn1, rdn2]) + assert name1.rdns == [ + x509.RelativeDistinguishedName([rdn1]), + x509.RelativeDistinguishedName([rdn2]), + ] + name2 = x509.Name([x509.RelativeDistinguishedName([rdn1, rdn2])]) + assert name2.rdns == [x509.RelativeDistinguishedName([rdn1, rdn2])] + + def test_repr(self): + name = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + ]) + + if six.PY3: + assert repr(name) == ( + ", value='cryptography.io')>, , valu" + "e='PyCA')>])>" + ) + else: + assert repr(name) == ( + ", value=u'cryptography.io')>, , val" + "ue=u'PyCA')>])>" + ) + + def test_not_nameattribute(self): + with pytest.raises(TypeError): + x509.Name(["not-a-NameAttribute"]) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_bytes(self, backend): + name = x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u'cryptography.io'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'PyCA'), + ]) + assert name.public_bytes(backend) == binascii.unhexlify( + b"30293118301606035504030c0f63727970746f6772617068792e696f310d300" + b"b060355040a0c0450794341" + ) + + +def test_random_serial_number(monkeypatch): + sample_data = os.urandom(20) + + def notrandom(size): + assert size == len(sample_data) + return sample_data + + monkeypatch.setattr(os, "urandom", notrandom) + + serial_number = x509.random_serial_number() + + assert ( + serial_number == utils.int_from_bytes(sample_data, "big") >> 1 + ) + assert utils.bit_length(serial_number) < 160 diff --git a/tests/test_x509_crlbuilder.py b/tests/test_x509_crlbuilder.py new file mode 100644 index 0000000..0d29a3e --- /dev/null +++ b/tests/test_x509_crlbuilder.py @@ -0,0 +1,487 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import datetime + +import pytest + +import pytz + +from cryptography import x509 +from cryptography.hazmat.backends.interfaces import ( + DSABackend, EllipticCurveBackend, RSABackend, X509Backend +) +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.x509.oid import AuthorityInformationAccessOID, NameOID + +from .hazmat.primitives.fixtures_dsa import DSA_KEY_2048 +from .hazmat.primitives.fixtures_rsa import RSA_KEY_2048, RSA_KEY_512 +from .hazmat.primitives.test_ec import _skip_curve_unsupported + + +class TestCertificateRevocationListBuilder(object): + def test_issuer_name_invalid(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(TypeError): + builder.issuer_name("notanx509name") + + def test_set_issuer_name_twice(self): + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ) + with pytest.raises(ValueError): + builder.issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_aware_last_update(self, backend): + last_time = datetime.datetime(2012, 1, 16, 22, 43) + tz = pytz.timezone("US/Pacific") + last_time = tz.localize(last_time) + utc_last = datetime.datetime(2012, 1, 17, 6, 43) + next_time = datetime.datetime(2022, 1, 17, 6, 43) + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update(last_time).next_update(next_time) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert crl.last_update == utc_last + + def test_last_update_invalid(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(TypeError): + builder.last_update("notadatetime") + + def test_last_update_before_unix_epoch(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(ValueError): + builder.last_update(datetime.datetime(1960, 8, 10)) + + def test_set_last_update_twice(self): + builder = x509.CertificateRevocationListBuilder().last_update( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.last_update(datetime.datetime(2002, 1, 1, 12, 1)) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_aware_next_update(self, backend): + next_time = datetime.datetime(2022, 1, 16, 22, 43) + tz = pytz.timezone("US/Pacific") + next_time = tz.localize(next_time) + utc_next = datetime.datetime(2022, 1, 17, 6, 43) + last_time = datetime.datetime(2012, 1, 17, 6, 43) + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update(last_time).next_update(next_time) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert crl.next_update == utc_next + + def test_next_update_invalid(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(TypeError): + builder.next_update("notadatetime") + + def test_next_update_before_unix_epoch(self): + builder = x509.CertificateRevocationListBuilder() + with pytest.raises(ValueError): + builder.next_update(datetime.datetime(1960, 8, 10)) + + def test_set_next_update_twice(self): + builder = x509.CertificateRevocationListBuilder().next_update( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.next_update(datetime.datetime(2002, 1, 1, 12, 1)) + + def test_last_update_after_next_update(self): + builder = x509.CertificateRevocationListBuilder() + + builder = builder.next_update( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.last_update(datetime.datetime(2003, 1, 1, 12, 1)) + + def test_next_update_after_last_update(self): + builder = x509.CertificateRevocationListBuilder() + + builder = builder.last_update( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.next_update(datetime.datetime(2001, 1, 1, 12, 1)) + + def test_add_extension_checks_for_duplicates(self): + builder = x509.CertificateRevocationListBuilder().add_extension( + x509.CRLNumber(1), False + ) + + with pytest.raises(ValueError): + builder.add_extension(x509.CRLNumber(2), False) + + def test_add_invalid_extension(self): + builder = x509.CertificateRevocationListBuilder() + + with pytest.raises(TypeError): + builder.add_extension( + object(), False + ) + + def test_add_invalid_revoked_certificate(self): + builder = x509.CertificateRevocationListBuilder() + + with pytest.raises(TypeError): + builder.add_revoked_certificate(object()) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_issuer_name(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateRevocationListBuilder().last_update( + datetime.datetime(2002, 1, 1, 12, 1) + ).next_update( + datetime.datetime(2030, 1, 1, 12, 1) + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_last_update(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).next_update( + datetime.datetime(2030, 1, 1, 12, 1) + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_next_update(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u'US')]) + ).last_update( + datetime.datetime(2030, 1, 1, 12, 1) + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_empty_list(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update(last_update).next_update(next_update) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert len(crl) == 0 + assert crl.last_update == last_update + assert crl.next_update == next_update + + @pytest.mark.parametrize( + "extension", + [ + x509.CRLNumber(13), + x509.AuthorityKeyIdentifier( + b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08" + b"\xcbY", + None, + None + ), + x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.DNSName(u"cryptography.io") + ) + ]), + x509.IssuerAlternativeName([ + x509.UniformResourceIdentifier(u"https://cryptography.io"), + ]) + ] + ) + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_extensions(self, backend, extension): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_extension( + extension, False + ) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert len(crl) == 0 + assert len(crl.extensions) == 1 + ext = crl.extensions.get_extension_for_class(type(extension)) + assert ext.critical is False + assert ext.value == extension + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_multiple_extensions_critical(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + ian = x509.IssuerAlternativeName([ + x509.UniformResourceIdentifier(u"https://cryptography.io"), + ]) + crl_number = x509.CRLNumber(13) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_extension( + crl_number, False + ).add_extension( + ian, True + ) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert len(crl) == 0 + assert len(crl.extensions) == 2 + ext1 = crl.extensions.get_extension_for_class(x509.CRLNumber) + assert ext1.critical is False + assert ext1.value == crl_number + ext2 = crl.extensions.get_extension_for_class( + x509.IssuerAlternativeName + ) + assert ext2.critical is True + assert ext2.value == ian + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_add_unsupported_extension(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_extension( + x509.OCSPNoCheck(), False + ) + with pytest.raises(NotImplementedError): + builder.sign(private_key, hashes.SHA256(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_rsa_key_too_small(self, backend): + private_key = RSA_KEY_512.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ) + + with pytest.raises(ValueError): + builder.sign(private_key, hashes.SHA512(), backend) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_invalid_hash(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ) + + with pytest.raises(TypeError): + builder.sign(private_key, object(), backend) + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_dsa_key(self, backend): + if backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_101: + pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1") + private_key = DSA_KEY_2048.private_key(backend) + invalidity_date = x509.InvalidityDate( + datetime.datetime(2002, 1, 1, 0, 0) + ) + ian = x509.IssuerAlternativeName([ + x509.UniformResourceIdentifier(u"https://cryptography.io"), + ]) + revoked_cert0 = x509.RevokedCertificateBuilder().serial_number( + 2 + ).revocation_date( + datetime.datetime(2012, 1, 1, 1, 1) + ).add_extension( + invalidity_date, False + ).build(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_revoked_certificate( + revoked_cert0 + ).add_extension( + ian, False + ) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert crl.extensions.get_extension_for_class( + x509.IssuerAlternativeName + ).value == ian + assert crl[0].serial_number == revoked_cert0.serial_number + assert crl[0].revocation_date == revoked_cert0.revocation_date + assert len(crl[0].extensions) == 1 + ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) + assert ext.critical is False + assert ext.value == invalidity_date + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_ec_key_unsupported(self, backend): + if backend._lib.CRYPTOGRAPHY_OPENSSL_LESS_THAN_101: + pytest.skip("Requires a newer OpenSSL. Must be >= 1.0.1") + _skip_curve_unsupported(backend, ec.SECP256R1()) + private_key = ec.generate_private_key(ec.SECP256R1(), backend) + invalidity_date = x509.InvalidityDate( + datetime.datetime(2002, 1, 1, 0, 0) + ) + ian = x509.IssuerAlternativeName([ + x509.UniformResourceIdentifier(u"https://cryptography.io"), + ]) + revoked_cert0 = x509.RevokedCertificateBuilder().serial_number( + 2 + ).revocation_date( + datetime.datetime(2012, 1, 1, 1, 1) + ).add_extension( + invalidity_date, False + ).build(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_revoked_certificate( + revoked_cert0 + ).add_extension( + ian, False + ) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert crl.extensions.get_extension_for_class( + x509.IssuerAlternativeName + ).value == ian + assert crl[0].serial_number == revoked_cert0.serial_number + assert crl[0].revocation_date == revoked_cert0.revocation_date + assert len(crl[0].extensions) == 1 + ext = crl[0].extensions.get_extension_for_class(x509.InvalidityDate) + assert ext.critical is False + assert ext.value == invalidity_date + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_sign_with_revoked_certificates(self, backend): + private_key = RSA_KEY_2048.private_key(backend) + last_update = datetime.datetime(2002, 1, 1, 12, 1) + next_update = datetime.datetime(2030, 1, 1, 12, 1) + invalidity_date = x509.InvalidityDate( + datetime.datetime(2002, 1, 1, 0, 0) + ) + revoked_cert0 = x509.RevokedCertificateBuilder().serial_number( + 38 + ).revocation_date( + datetime.datetime(2011, 1, 1, 1, 1) + ).build(backend) + revoked_cert1 = x509.RevokedCertificateBuilder().serial_number( + 2 + ).revocation_date( + datetime.datetime(2012, 1, 1, 1, 1) + ).add_extension( + invalidity_date, False + ).build(backend) + builder = x509.CertificateRevocationListBuilder().issuer_name( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"cryptography.io CA") + ]) + ).last_update( + last_update + ).next_update( + next_update + ).add_revoked_certificate( + revoked_cert0 + ).add_revoked_certificate( + revoked_cert1 + ) + + crl = builder.sign(private_key, hashes.SHA256(), backend) + assert len(crl) == 2 + assert crl.last_update == last_update + assert crl.next_update == next_update + assert crl[0].serial_number == revoked_cert0.serial_number + assert crl[0].revocation_date == revoked_cert0.revocation_date + assert len(crl[0].extensions) == 0 + assert crl[1].serial_number == revoked_cert1.serial_number + assert crl[1].revocation_date == revoked_cert1.revocation_date + assert len(crl[1].extensions) == 1 + ext = crl[1].extensions.get_extension_for_class(x509.InvalidityDate) + assert ext.critical is False + assert ext.value == invalidity_date diff --git a/tests/test_x509_ext.py b/tests/test_x509_ext.py new file mode 100644 index 0000000..7104121 --- /dev/null +++ b/tests/test_x509_ext.py @@ -0,0 +1,3671 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import datetime +import ipaddress +import os + +import pytest + +import six + +from cryptography import x509 +from cryptography.hazmat.backends.interfaces import ( + DSABackend, EllipticCurveBackend, RSABackend, X509Backend +) +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.x509 import DNSName, NameConstraints, SubjectAlternativeName +from cryptography.x509.oid import ( + AuthorityInformationAccessOID, ExtendedKeyUsageOID, ExtensionOID, + NameOID, ObjectIdentifier +) + +from .hazmat.primitives.fixtures_rsa import RSA_KEY_2048 +from .hazmat.primitives.test_ec import _skip_curve_unsupported +from .test_x509 import _load_cert + + +def _make_certbuilder(private_key): + name = x509.Name( + [x509.NameAttribute(NameOID.COMMON_NAME, u'example.org')]) + return ( + x509.CertificateBuilder() + .subject_name(name) + .issuer_name(name) + .public_key(private_key.public_key()) + .serial_number(777) + .not_valid_before(datetime.datetime(1999, 1, 1)) + .not_valid_after(datetime.datetime(2020, 1, 1)) + ) + + +class TestExtension(object): + def test_not_an_oid(self): + bc = x509.BasicConstraints(ca=False, path_length=None) + with pytest.raises(TypeError): + x509.Extension("notanoid", True, bc) + + def test_critical_not_a_bool(self): + bc = x509.BasicConstraints(ca=False, path_length=None) + with pytest.raises(TypeError): + x509.Extension(ExtensionOID.BASIC_CONSTRAINTS, "notabool", bc) + + def test_repr(self): + bc = x509.BasicConstraints(ca=False, path_length=None) + ext = x509.Extension(ExtensionOID.BASIC_CONSTRAINTS, True, bc) + assert repr(ext) == ( + ", critical=True, value=)>" + ) + + def test_eq(self): + ext1 = x509.Extension( + x509.ObjectIdentifier('1.2.3.4'), False, 'value' + ) + ext2 = x509.Extension( + x509.ObjectIdentifier('1.2.3.4'), False, 'value' + ) + assert ext1 == ext2 + + def test_ne(self): + ext1 = x509.Extension( + x509.ObjectIdentifier('1.2.3.4'), False, 'value' + ) + ext2 = x509.Extension( + x509.ObjectIdentifier('1.2.3.5'), False, 'value' + ) + ext3 = x509.Extension( + x509.ObjectIdentifier('1.2.3.4'), True, 'value' + ) + ext4 = x509.Extension( + x509.ObjectIdentifier('1.2.3.4'), False, 'value4' + ) + assert ext1 != ext2 + assert ext1 != ext3 + assert ext1 != ext4 + assert ext1 != object() + + +class TestUnrecognizedExtension(object): + def test_invalid_oid(self): + with pytest.raises(TypeError): + x509.UnrecognizedExtension("notanoid", b"somedata") + + def test_eq(self): + ext1 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" + ) + ext2 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" + ) + assert ext1 == ext2 + + def test_ne(self): + ext1 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" + ) + ext2 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x02" + ) + ext3 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.5"), b"\x03\x02\x01" + ) + assert ext1 != ext2 + assert ext1 != ext3 + assert ext1 != object() + + def test_repr(self): + ext1 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" + ) + if six.PY3: + assert repr(ext1) == ( + ", value=b'\\x03\\x02\\x01')>" + ) + else: + assert repr(ext1) == ( + ", value='\\x03\\x02\\x01')>" + ) + + def test_hash(self): + ext1 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" + ) + ext2 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.4"), b"\x03\x02\x01" + ) + ext3 = x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.2.3.5"), b"\x03\x02\x01" + ) + assert hash(ext1) == hash(ext2) + assert hash(ext1) != hash(ext3) + + +class TestCertificateIssuer(object): + def test_iter_names(self): + ci = x509.CertificateIssuer([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ]) + assert len(ci) == 2 + assert list(ci) == [ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ] + + def test_indexing(self): + ci = x509.CertificateIssuer([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + x509.DNSName(u"another.local"), + x509.RFC822Name(u"email@another.local"), + x509.UniformResourceIdentifier(u"http://another.local"), + ]) + assert ci[-1] == ci[4] + assert ci[2:6:2] == [ci[2], ci[4]] + + def test_eq(self): + ci1 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) + ci2 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) + assert ci1 == ci2 + + def test_ne(self): + ci1 = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) + ci2 = x509.CertificateIssuer([x509.DNSName(u"somethingelse.tld")]) + assert ci1 != ci2 + assert ci1 != object() + + def test_repr(self): + ci = x509.CertificateIssuer([x509.DNSName(u"cryptography.io")]) + assert repr(ci) == ( + "])>)>" + ) + + def test_get_values_for_type(self): + ci = x509.CertificateIssuer( + [x509.DNSName(u"cryptography.io")] + ) + names = ci.get_values_for_type(x509.DNSName) + assert names == [u"cryptography.io"] + + +class TestCRLReason(object): + def test_invalid_reason_flags(self): + with pytest.raises(TypeError): + x509.CRLReason("notareason") + + def test_eq(self): + reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) + reason2 = x509.CRLReason(x509.ReasonFlags.unspecified) + assert reason1 == reason2 + + def test_ne(self): + reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) + reason2 = x509.CRLReason(x509.ReasonFlags.ca_compromise) + assert reason1 != reason2 + assert reason1 != object() + + def test_hash(self): + reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) + reason2 = x509.CRLReason(x509.ReasonFlags.unspecified) + reason3 = x509.CRLReason(x509.ReasonFlags.ca_compromise) + + assert hash(reason1) == hash(reason2) + assert hash(reason1) != hash(reason3) + + def test_repr(self): + reason1 = x509.CRLReason(x509.ReasonFlags.unspecified) + assert repr(reason1) == ( + "" + ) + + +class TestInvalidityDate(object): + def test_invalid_invalidity_date(self): + with pytest.raises(TypeError): + x509.InvalidityDate("notadate") + + def test_eq(self): + invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) + invalid2 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) + assert invalid1 == invalid2 + + def test_ne(self): + invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) + invalid2 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 2)) + assert invalid1 != invalid2 + assert invalid1 != object() + + def test_repr(self): + invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) + assert repr(invalid1) == ( + "" + ) + + def test_hash(self): + invalid1 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) + invalid2 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 1)) + invalid3 = x509.InvalidityDate(datetime.datetime(2015, 1, 1, 1, 2)) + assert hash(invalid1) == hash(invalid2) + assert hash(invalid1) != hash(invalid3) + + +class TestNoticeReference(object): + def test_notice_numbers_not_all_int(self): + with pytest.raises(TypeError): + x509.NoticeReference("org", [1, 2, "three"]) + + def test_notice_numbers_none(self): + with pytest.raises(TypeError): + x509.NoticeReference("org", None) + + def test_iter_input(self): + numbers = [1, 3, 4] + nr = x509.NoticeReference(u"org", iter(numbers)) + assert list(nr.notice_numbers) == numbers + + def test_repr(self): + nr = x509.NoticeReference(u"org", [1, 3, 4]) + + if six.PY3: + assert repr(nr) == ( + "" + ) + else: + assert repr(nr) == ( + "" + ) + + def test_eq(self): + nr = x509.NoticeReference("org", [1, 2]) + nr2 = x509.NoticeReference("org", [1, 2]) + assert nr == nr2 + + def test_ne(self): + nr = x509.NoticeReference("org", [1, 2]) + nr2 = x509.NoticeReference("org", [1]) + nr3 = x509.NoticeReference(None, [1, 2]) + assert nr != nr2 + assert nr != nr3 + assert nr != object() + + +class TestUserNotice(object): + def test_notice_reference_invalid(self): + with pytest.raises(TypeError): + x509.UserNotice("invalid", None) + + def test_notice_reference_none(self): + un = x509.UserNotice(None, "text") + assert un.notice_reference is None + assert un.explicit_text == "text" + + def test_repr(self): + un = x509.UserNotice(x509.NoticeReference(u"org", [1]), u"text") + if six.PY3: + assert repr(un) == ( + ", explicit_text='text')>" + ) + else: + assert repr(un) == ( + ", explicit_text=u'text')>" + ) + + def test_eq(self): + nr = x509.NoticeReference("org", [1, 2]) + nr2 = x509.NoticeReference("org", [1, 2]) + un = x509.UserNotice(nr, "text") + un2 = x509.UserNotice(nr2, "text") + assert un == un2 + + def test_ne(self): + nr = x509.NoticeReference("org", [1, 2]) + nr2 = x509.NoticeReference("org", [1]) + un = x509.UserNotice(nr, "text") + un2 = x509.UserNotice(nr2, "text") + un3 = x509.UserNotice(nr, "text3") + assert un != un2 + assert un != un3 + assert un != object() + + +class TestPolicyInformation(object): + def test_invalid_policy_identifier(self): + with pytest.raises(TypeError): + x509.PolicyInformation("notanoid", None) + + def test_none_policy_qualifiers(self): + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), None) + assert pi.policy_identifier == x509.ObjectIdentifier("1.2.3") + assert pi.policy_qualifiers is None + + def test_policy_qualifiers(self): + pq = [u"string"] + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) + assert pi.policy_identifier == x509.ObjectIdentifier("1.2.3") + assert pi.policy_qualifiers == pq + + def test_invalid_policy_identifiers(self): + with pytest.raises(TypeError): + x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), [1, 2]) + + def test_iter_input(self): + qual = [u"foo", u"bar"] + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), iter(qual)) + assert list(pi.policy_qualifiers) == qual + + def test_repr(self): + pq = [u"string", x509.UserNotice(None, u"hi")] + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) + if six.PY3: + assert repr(pi) == ( + ", policy_qualifiers=['string', ])>" + ) + else: + assert repr(pi) == ( + ", policy_qualifiers=[u'string', ])>" + ) + + def test_eq(self): + pi = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), + [u"string", x509.UserNotice(None, u"hi")] + ) + pi2 = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), + [u"string", x509.UserNotice(None, u"hi")] + ) + assert pi == pi2 + + def test_ne(self): + pi = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string"] + ) + pi2 = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string2"] + ) + pi3 = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3.4"), [u"string"] + ) + assert pi != pi2 + assert pi != pi3 + assert pi != object() + + +class TestCertificatePolicies(object): + def test_invalid_policies(self): + pq = [u"string"] + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) + with pytest.raises(TypeError): + x509.CertificatePolicies([1, pi]) + + def test_iter_len(self): + pq = [u"string"] + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) + cp = x509.CertificatePolicies([pi]) + assert len(cp) == 1 + for policyinfo in cp: + assert policyinfo == pi + + def test_iter_input(self): + policies = [ + x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), [u"string"]) + ] + cp = x509.CertificatePolicies(iter(policies)) + assert list(cp) == policies + + def test_repr(self): + pq = [u"string"] + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), pq) + cp = x509.CertificatePolicies([pi]) + if six.PY3: + assert repr(cp) == ( + ", policy_qualifi" + "ers=['string'])>])>" + ) + else: + assert repr(cp) == ( + ", policy_qualifi" + "ers=[u'string'])>])>" + ) + + def test_eq(self): + pi = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string"] + ) + cp = x509.CertificatePolicies([pi]) + pi2 = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string"] + ) + cp2 = x509.CertificatePolicies([pi2]) + assert cp == cp2 + + def test_ne(self): + pi = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string"] + ) + cp = x509.CertificatePolicies([pi]) + pi2 = x509.PolicyInformation( + x509.ObjectIdentifier("1.2.3"), [u"string2"] + ) + cp2 = x509.CertificatePolicies([pi2]) + assert cp != cp2 + assert cp != object() + + def test_indexing(self): + pi = x509.PolicyInformation(x509.ObjectIdentifier("1.2.3"), [u"test"]) + pi2 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.4"), [u"test"]) + pi3 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.5"), [u"test"]) + pi4 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.6"), [u"test"]) + pi5 = x509.PolicyInformation(x509.ObjectIdentifier("1.2.7"), [u"test"]) + cp = x509.CertificatePolicies([pi, pi2, pi3, pi4, pi5]) + assert cp[-1] == cp[4] + assert cp[2:6:2] == [cp[2], cp[4]] + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestCertificatePoliciesExtension(object): + def test_cps_uri_policy_qualifier(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "cp_cps_uri.pem"), + x509.load_pem_x509_certificate, + backend + ) + + cp = cert.extensions.get_extension_for_oid( + ExtensionOID.CERTIFICATE_POLICIES + ).value + + assert cp == x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [u"http://other.com/cps"] + ) + ]) + + def test_user_notice_with_notice_reference(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cp_user_notice_with_notice_reference.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cp = cert.extensions.get_extension_for_oid( + ExtensionOID.CERTIFICATE_POLICIES + ).value + + assert cp == x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + u"http://example.com/cps", + u"http://other.com/cps", + x509.UserNotice( + x509.NoticeReference(u"my org", [1, 2, 3, 4]), + u"thing" + ) + ] + ) + ]) + + def test_user_notice_with_explicit_text(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cp_user_notice_with_explicit_text.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cp = cert.extensions.get_extension_for_oid( + ExtensionOID.CERTIFICATE_POLICIES + ).value + + assert cp == x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [x509.UserNotice(None, u"thing")] + ) + ]) + + def test_user_notice_no_explicit_text(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cp_user_notice_no_explicit_text.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cp = cert.extensions.get_extension_for_oid( + ExtensionOID.CERTIFICATE_POLICIES + ).value + + assert cp == x509.CertificatePolicies([ + x509.PolicyInformation( + x509.ObjectIdentifier("2.16.840.1.12345.1.2.3.4.1"), + [ + x509.UserNotice( + x509.NoticeReference(u"my org", [1, 2, 3, 4]), + None + ) + ] + ) + ]) + + +class TestKeyUsage(object): + def test_key_agreement_false_encipher_decipher_true(self): + with pytest.raises(ValueError): + x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=True, + decipher_only=False + ) + + with pytest.raises(ValueError): + x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=True, + decipher_only=True + ) + + with pytest.raises(ValueError): + x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + + def test_properties_key_agreement_true(self): + ku = x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + assert ku.digital_signature is True + assert ku.content_commitment is True + assert ku.key_encipherment is False + assert ku.data_encipherment is False + assert ku.key_agreement is False + assert ku.key_cert_sign is True + assert ku.crl_sign is False + + def test_key_agreement_true_properties(self): + ku = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + assert ku.key_agreement is True + assert ku.encipher_only is False + assert ku.decipher_only is True + + def test_key_agreement_false_properties(self): + ku = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + assert ku.key_agreement is False + with pytest.raises(ValueError): + ku.encipher_only + + with pytest.raises(ValueError): + ku.decipher_only + + def test_repr_key_agreement_false(self): + ku = x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + assert repr(ku) == ( + "" + ) + + def test_repr_key_agreement_true(self): + ku = x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + assert repr(ku) == ( + "" + ) + + def test_eq(self): + ku = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + ku2 = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + assert ku == ku2 + + def test_ne(self): + ku = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=True, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=True + ) + ku2 = x509.KeyUsage( + digital_signature=False, + content_commitment=False, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=False, + crl_sign=False, + encipher_only=False, + decipher_only=False + ) + assert ku != ku2 + assert ku != object() + + +class TestSubjectKeyIdentifier(object): + def test_properties(self): + value = binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ski = x509.SubjectKeyIdentifier(value) + assert ski.digest == value + + def test_repr(self): + ski = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ) + ext = x509.Extension(ExtensionOID.SUBJECT_KEY_IDENTIFIER, False, ski) + if six.PY3: + assert repr(ext) == ( + ", critical=False, value=)>" + ) + else: + assert repr(ext) == ( + ", critical=False, value=)>" + ) + + def test_eq(self): + ski = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ) + ski2 = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ) + assert ski == ski2 + + def test_ne(self): + ski = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ) + ski2 = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"aa8098456f6ff7ff3ac9092384932230498bc980") + ) + assert ski != ski2 + assert ski != object() + + def test_hash(self): + ski1 = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ) + ski2 = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"092384932230498bc980aa8098456f6ff7ff3ac9") + ) + ski3 = x509.SubjectKeyIdentifier( + binascii.unhexlify(b"aa8098456f6ff7ff3ac9092384932230498bc980") + ) + + assert hash(ski1) == hash(ski2) + assert hash(ski1) != hash(ski3) + + +class TestAuthorityKeyIdentifier(object): + def test_authority_cert_issuer_not_generalname(self): + with pytest.raises(TypeError): + x509.AuthorityKeyIdentifier(b"identifier", ["notname"], 3) + + def test_authority_cert_serial_number_not_integer(self): + dirname = x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), + u'value1' + ), + x509.NameAttribute( + x509.ObjectIdentifier('2.999.2'), + u'value2' + ), + ]) + ) + with pytest.raises(TypeError): + x509.AuthorityKeyIdentifier(b"identifier", [dirname], "notanint") + + def test_authority_issuer_none_serial_not_none(self): + with pytest.raises(ValueError): + x509.AuthorityKeyIdentifier(b"identifier", None, 3) + + def test_authority_issuer_not_none_serial_none(self): + dirname = x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + x509.ObjectIdentifier('2.999.1'), + u'value1' + ), + x509.NameAttribute( + x509.ObjectIdentifier('2.999.2'), + u'value2' + ), + ]) + ) + with pytest.raises(ValueError): + x509.AuthorityKeyIdentifier(b"identifier", [dirname], None) + + def test_authority_cert_serial_and_issuer_none(self): + aki = x509.AuthorityKeyIdentifier(b"id", None, None) + assert aki.key_identifier == b"id" + assert aki.authority_cert_issuer is None + assert aki.authority_cert_serial_number is None + + def test_authority_cert_serial_zero(self): + dns = x509.DNSName(u"SomeIssuer") + aki = x509.AuthorityKeyIdentifier(b"id", [dns], 0) + assert aki.key_identifier == b"id" + assert aki.authority_cert_issuer == [dns] + assert aki.authority_cert_serial_number == 0 + + def test_iter_input(self): + dirnames = [ + x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'myCN')]) + ) + ] + aki = x509.AuthorityKeyIdentifier(b"digest", iter(dirnames), 1234) + assert list(aki.authority_cert_issuer) == dirnames + + def test_repr(self): + dirname = x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'myCN')]) + ) + aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) + + if six.PY3: + assert repr(aki) == ( + ", value='myC" + "N')>])>)>], authority_cert_serial_number=1234)>" + ) + else: + assert repr(aki) == ( + ", value=u'myCN')" + ">])>)>], authority_cert_serial_number=1234)>" + ) + + def test_eq(self): + dirname = x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'myCN')]) + ) + aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) + dirname2 = x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'myCN')]) + ) + aki2 = x509.AuthorityKeyIdentifier(b"digest", [dirname2], 1234) + assert aki == aki2 + + def test_ne(self): + dirname = x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'myCN')]) + ) + dirname5 = x509.DirectoryName( + x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'aCN')]) + ) + aki = x509.AuthorityKeyIdentifier(b"digest", [dirname], 1234) + aki2 = x509.AuthorityKeyIdentifier(b"diges", [dirname], 1234) + aki3 = x509.AuthorityKeyIdentifier(b"digest", None, None) + aki4 = x509.AuthorityKeyIdentifier(b"digest", [dirname], 12345) + aki5 = x509.AuthorityKeyIdentifier(b"digest", [dirname5], 12345) + assert aki != aki2 + assert aki != aki3 + assert aki != aki4 + assert aki != aki5 + assert aki != object() + + +class TestBasicConstraints(object): + def test_ca_not_boolean(self): + with pytest.raises(TypeError): + x509.BasicConstraints(ca="notbool", path_length=None) + + def test_path_length_not_ca(self): + with pytest.raises(ValueError): + x509.BasicConstraints(ca=False, path_length=0) + + def test_path_length_not_int(self): + with pytest.raises(TypeError): + x509.BasicConstraints(ca=True, path_length=1.1) + + with pytest.raises(TypeError): + x509.BasicConstraints(ca=True, path_length="notint") + + def test_path_length_negative(self): + with pytest.raises(TypeError): + x509.BasicConstraints(ca=True, path_length=-1) + + def test_repr(self): + na = x509.BasicConstraints(ca=True, path_length=None) + assert repr(na) == ( + "" + ) + + def test_hash(self): + na = x509.BasicConstraints(ca=True, path_length=None) + na2 = x509.BasicConstraints(ca=True, path_length=None) + na3 = x509.BasicConstraints(ca=True, path_length=0) + assert hash(na) == hash(na2) + assert hash(na) != hash(na3) + + def test_eq(self): + na = x509.BasicConstraints(ca=True, path_length=None) + na2 = x509.BasicConstraints(ca=True, path_length=None) + assert na == na2 + + def test_ne(self): + na = x509.BasicConstraints(ca=True, path_length=None) + na2 = x509.BasicConstraints(ca=True, path_length=1) + na3 = x509.BasicConstraints(ca=False, path_length=None) + assert na != na2 + assert na != na3 + assert na != object() + + +class TestExtendedKeyUsage(object): + def test_not_all_oids(self): + with pytest.raises(TypeError): + x509.ExtendedKeyUsage(["notoid"]) + + def test_iter_len(self): + eku = x509.ExtendedKeyUsage([ + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), + ]) + assert len(eku) == 2 + assert list(eku) == [ + ExtendedKeyUsageOID.SERVER_AUTH, + ExtendedKeyUsageOID.CLIENT_AUTH + ] + + def test_iter_input(self): + usages = [ + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), + ] + aia = x509.ExtendedKeyUsage(iter(usages)) + assert list(aia) == usages + + def test_repr(self): + eku = x509.ExtendedKeyUsage([ + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), + ]) + assert repr(eku) == ( + ", ])>" + ) + + def test_eq(self): + eku = x509.ExtendedKeyUsage([ + x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7") + ]) + eku2 = x509.ExtendedKeyUsage([ + x509.ObjectIdentifier("1.3.6"), x509.ObjectIdentifier("1.3.7") + ]) + assert eku == eku2 + + def test_ne(self): + eku = x509.ExtendedKeyUsage([x509.ObjectIdentifier("1.3.6")]) + eku2 = x509.ExtendedKeyUsage([x509.ObjectIdentifier("1.3.6.1")]) + assert eku != eku2 + assert eku != object() + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestExtensions(object): + def test_no_extensions(self, backend): + cert = _load_cert( + os.path.join("x509", "verisign_md2_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions + assert len(ext) == 0 + assert list(ext) == [] + with pytest.raises(x509.ExtensionNotFound) as exc: + ext.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS) + + assert exc.value.oid == ExtensionOID.BASIC_CONSTRAINTS + + def test_one_extension(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "basic_constraints_not_critical.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + extensions = cert.extensions + ext = extensions.get_extension_for_oid(ExtensionOID.BASIC_CONSTRAINTS) + assert ext is not None + assert ext.value.ca is False + + def test_duplicate_extension(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "two_basic_constraints.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(x509.DuplicateExtension) as exc: + cert.extensions + + assert exc.value.oid == ExtensionOID.BASIC_CONSTRAINTS + + def test_unsupported_critical_extension(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "unsupported_extension_critical.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(x509.UnsupportedExtension) as exc: + cert.extensions + + assert exc.value.oid == x509.ObjectIdentifier("1.2.3.4") + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + def test_unsupported_extension(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "unsupported_extension_2.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + extensions = cert.extensions + assert len(extensions) == 2 + assert extensions[0].critical is False + assert extensions[0].oid == x509.ObjectIdentifier( + "1.3.6.1.4.1.41482.2" + ) + assert extensions[0].value == x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.3.6.1.4.1.41482.2"), + b"1.3.6.1.4.1.41482.1.2" + ) + assert extensions[1].critical is False + assert extensions[1].oid == x509.ObjectIdentifier( + "1.3.6.1.4.1.45724.2.1.1" + ) + assert extensions[1].value == x509.UnrecognizedExtension( + x509.ObjectIdentifier("1.3.6.1.4.1.45724.2.1.1"), + b"\x03\x02\x040" + ) + + def test_no_extensions_get_for_class(self, backend): + cert = _load_cert( + os.path.join( + "x509", "cryptography.io.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + exts = cert.extensions + with pytest.raises(x509.ExtensionNotFound) as exc: + exts.get_extension_for_class(x509.IssuerAlternativeName) + assert exc.value.oid == ExtensionOID.ISSUER_ALTERNATIVE_NAME + + def test_unrecognized_extension_for_class(self): + exts = x509.Extensions([]) + with pytest.raises(TypeError): + exts.get_extension_for_class(x509.UnrecognizedExtension) + + def test_indexing(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + exts = cert.extensions + assert exts[-1] == exts[7] + assert exts[2:6:2] == [exts[2], exts[4]] + + def test_one_extension_get_for_class(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "basic_constraints_not_critical.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_class(x509.BasicConstraints) + assert ext is not None + assert isinstance(ext.value, x509.BasicConstraints) + + def test_repr(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "basic_constraints_not_critical.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + assert repr(cert.extensions) == ( + ", critical=False, value=)>])>" + ) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestBasicConstraintsExtension(object): + def test_ca_true_pathlen_6(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", "pathLenConstraint6CACert.crt" + ), + x509.load_der_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is True + assert ext.value.ca is True + assert ext.value.path_length == 6 + + def test_path_length_zero(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "bc_path_length_zero.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is True + assert ext.value.ca is True + assert ext.value.path_length == 0 + + def test_ca_true_no_pathlen(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is True + assert ext.value.ca is True + assert ext.value.path_length is None + + def test_ca_false(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is True + assert ext.value.ca is False + assert ext.value.path_length is None + + def test_no_basic_constraints(self, backend): + cert = _load_cert( + os.path.join( + "x509", + "PKITS_data", + "certs", + "ValidCertificatePathTest1EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + with pytest.raises(x509.ExtensionNotFound): + cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + + def test_basic_constraint_not_critical(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "basic_constraints_not_critical.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.BASIC_CONSTRAINTS + ) + assert ext is not None + assert ext.critical is False + assert ext.value.ca is False + + +class TestSubjectKeyIdentifierExtension(object): + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_subject_key_identifier(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + ski = ext.value + assert ext is not None + assert ext.critical is False + assert ski.digest == binascii.unhexlify( + b"580184241bbc2b52944a3da510721451f5af3ac9" + ) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_subject_key_identifier(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "bc_path_length_zero.pem"), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(x509.ExtensionNotFound): + cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + + @pytest.mark.requires_backend_interface(interface=RSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_from_rsa_public_key(self, backend): + cert = _load_cert( + os.path.join("x509", "PKITS_data", "certs", "GoodCACert.crt"), + x509.load_der_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + ski = x509.SubjectKeyIdentifier.from_public_key( + cert.public_key() + ) + assert ext.value == ski + + @pytest.mark.requires_backend_interface(interface=DSABackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_from_dsa_public_key(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "dsa_selfsigned_ca.pem"), + x509.load_pem_x509_certificate, + backend + ) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + ski = x509.SubjectKeyIdentifier.from_public_key( + cert.public_key() + ) + assert ext.value == ski + + @pytest.mark.requires_backend_interface(interface=EllipticCurveBackend) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_from_ec_public_key(self, backend): + _skip_curve_unsupported(backend, ec.SECP384R1()) + cert = _load_cert( + os.path.join("x509", "ecdsa_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_KEY_IDENTIFIER + ) + ski = x509.SubjectKeyIdentifier.from_public_key( + cert.public_key() + ) + assert ext.value == ski + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestKeyUsageExtension(object): + def test_no_key_usage(self, backend): + cert = _load_cert( + os.path.join("x509", "verisign_md2_root.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions + with pytest.raises(x509.ExtensionNotFound) as exc: + ext.get_extension_for_oid(ExtensionOID.KEY_USAGE) + + assert exc.value.oid == ExtensionOID.KEY_USAGE + + def test_all_purposes(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "all_key_usages.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + extensions = cert.extensions + ext = extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) + assert ext is not None + + ku = ext.value + assert ku.digital_signature is True + assert ku.content_commitment is True + assert ku.key_encipherment is True + assert ku.data_encipherment is True + assert ku.key_agreement is True + assert ku.key_cert_sign is True + assert ku.crl_sign is True + assert ku.encipher_only is True + assert ku.decipher_only is True + + def test_key_cert_sign_crl_sign(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", "pathLenConstraint6CACert.crt" + ), + x509.load_der_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid(ExtensionOID.KEY_USAGE) + assert ext is not None + assert ext.critical is True + + ku = ext.value + assert ku.digital_signature is False + assert ku.content_commitment is False + assert ku.key_encipherment is False + assert ku.data_encipherment is False + assert ku.key_agreement is False + assert ku.key_cert_sign is True + assert ku.crl_sign is True + + +@pytest.mark.parametrize( + "name", [ + x509.RFC822Name, + x509.DNSName, + x509.UniformResourceIdentifier + ] +) +class TestTextGeneralNames(object): + def test_not_text(self, name): + with pytest.raises(TypeError): + name(b"notaunicodestring") + + with pytest.raises(TypeError): + name(1.3) + + def test_repr(self, name): + gn = name(u"string") + assert repr(gn) == "<{0}(value=string)>".format(name.__name__) + + def test_eq(self, name): + gn = name(u"string") + gn2 = name(u"string") + assert gn == gn2 + + def test_ne(self, name): + gn = name(u"string") + gn2 = name(u"string2") + assert gn != gn2 + assert gn != object() + + +class TestDirectoryName(object): + def test_not_name(self): + with pytest.raises(TypeError): + x509.DirectoryName(b"notaname") + + with pytest.raises(TypeError): + x509.DirectoryName(1.3) + + def test_repr(self): + name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, u'value1')]) + gn = x509.DirectoryName(name) + if six.PY3: + assert repr(gn) == ( + ", value='value1')>])>)>" + ) + else: + assert repr(gn) == ( + ", value=u'value1')>])>)>" + ) + + def test_eq(self): + name = x509.Name([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ]) + name2 = x509.Name([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ]) + gn = x509.DirectoryName(name) + gn2 = x509.DirectoryName(name2) + assert gn == gn2 + + def test_ne(self): + name = x509.Name([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.1'), u'value1') + ]) + name2 = x509.Name([ + x509.NameAttribute(x509.ObjectIdentifier('2.999.2'), u'value2') + ]) + gn = x509.DirectoryName(name) + gn2 = x509.DirectoryName(name2) + assert gn != gn2 + assert gn != object() + + +class TestRFC822Name(object): + def test_invalid_email(self): + with pytest.raises(ValueError): + x509.RFC822Name(u"Name ") + + 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" + + def test_hash(self): + g1 = x509.RFC822Name(u"email@host.com") + g2 = x509.RFC822Name(u"email@host.com") + g3 = x509.RFC822Name(u"admin@host.com") + + assert hash(g1) == hash(g2) + assert hash(g1) != hash(g3) + + +class TestUniformResourceIdentifier(object): + def test_no_parsed_hostname(self): + gn = x509.UniformResourceIdentifier(u"singlelabel") + assert gn.value == u"singlelabel" + + def test_with_port(self): + gn = x509.UniformResourceIdentifier(u"singlelabel:443/test") + assert gn.value == u"singlelabel:443/test" + + def test_idna_no_port(self): + gn = x509.UniformResourceIdentifier( + u"http://\u043f\u044b\u043a\u0430.cryptography" + ) + assert gn.value == u"http://\u043f\u044b\u043a\u0430.cryptography" + assert gn._encoded == b"http://xn--80ato2c.cryptography" + + def test_idna_with_port(self): + gn = x509.UniformResourceIdentifier( + u"gopher://\u043f\u044b\u043a\u0430.cryptography:70/some/path" + ) + assert gn.value == ( + u"gopher://\u043f\u044b\u043a\u0430.cryptography:70/some/path" + ) + assert gn._encoded == b"gopher://xn--80ato2c.cryptography:70/some/path" + + def test_query_and_fragment(self): + gn = x509.UniformResourceIdentifier( + u"ldap://cryptography:90/path?query=true#somedata" + ) + assert gn.value == u"ldap://cryptography:90/path?query=true#somedata" + + def test_hash(self): + g1 = x509.UniformResourceIdentifier(u"http://host.com") + g2 = x509.UniformResourceIdentifier(u"http://host.com") + g3 = x509.UniformResourceIdentifier(u"http://other.com") + + assert hash(g1) == hash(g2) + assert hash(g1) != hash(g3) + + +class TestRegisteredID(object): + def test_not_oid(self): + with pytest.raises(TypeError): + x509.RegisteredID(b"notanoid") + + with pytest.raises(TypeError): + x509.RegisteredID(1.3) + + def test_repr(self): + gn = x509.RegisteredID(NameOID.COMMON_NAME) + assert repr(gn) == ( + ")>" + ) + + def test_eq(self): + gn = x509.RegisteredID(NameOID.COMMON_NAME) + gn2 = x509.RegisteredID(NameOID.COMMON_NAME) + assert gn == gn2 + + def test_ne(self): + gn = x509.RegisteredID(NameOID.COMMON_NAME) + gn2 = x509.RegisteredID(ExtensionOID.BASIC_CONSTRAINTS) + assert gn != gn2 + assert gn != object() + + +class TestIPAddress(object): + def test_not_ipaddress(self): + with pytest.raises(TypeError): + x509.IPAddress(b"notanipaddress") + + with pytest.raises(TypeError): + x509.IPAddress(1.3) + + def test_repr(self): + gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + assert repr(gn) == "" + + gn2 = x509.IPAddress(ipaddress.IPv6Address(u"ff::")) + assert repr(gn2) == "" + + gn3 = x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24")) + assert repr(gn3) == "" + + gn4 = x509.IPAddress(ipaddress.IPv6Network(u"ff::/96")) + assert repr(gn4) == "" + + def test_eq(self): + gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + gn2 = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + assert gn == gn2 + + def test_ne(self): + gn = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + gn2 = x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.2")) + assert gn != gn2 + assert gn != object() + + +class TestOtherName(object): + def test_invalid_args(self): + with pytest.raises(TypeError): + x509.OtherName(b"notanobjectidentifier", b"derdata") + + with pytest.raises(TypeError): + x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), u"notderdata") + + def test_repr(self): + gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") + if six.PY3: + assert repr(gn) == ( + ", value=b'derdata')>" + ) + else: + assert repr(gn) == ( + ", value='derdata')>" + ) + + gn = x509.OtherName(x509.ObjectIdentifier("2.5.4.65"), b"derdata") + if six.PY3: + assert repr(gn) == ( + ", value=b'derdata')>" + ) + else: + assert repr(gn) == ( + ", value='derdata')>" + ) + + def test_eq(self): + gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") + gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") + assert gn == gn2 + + def test_ne(self): + gn = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata") + assert gn != object() + + gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), b"derdata2") + assert gn != gn2 + + gn2 = x509.OtherName(x509.ObjectIdentifier("1.2.3.5"), b"derdata") + assert gn != gn2 + + +class TestGeneralNames(object): + def test_get_values_for_type(self): + gns = x509.GeneralNames( + [x509.DNSName(u"cryptography.io")] + ) + names = gns.get_values_for_type(x509.DNSName) + assert names == [u"cryptography.io"] + + def test_iter_names(self): + gns = x509.GeneralNames([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ]) + assert len(gns) == 2 + assert list(gns) == [ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ] + + def test_iter_input(self): + names = [ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ] + gns = x509.GeneralNames(iter(names)) + assert list(gns) == names + + def test_indexing(self): + gn = x509.GeneralNames([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + x509.DNSName(u"another.local"), + x509.RFC822Name(u"email@another.local"), + x509.UniformResourceIdentifier(u"http://another.local"), + ]) + assert gn[-1] == gn[4] + assert gn[2:6:2] == [gn[2], gn[4]] + + def test_invalid_general_names(self): + with pytest.raises(TypeError): + x509.GeneralNames( + [x509.DNSName(u"cryptography.io"), "invalid"] + ) + + def test_repr(self): + gns = x509.GeneralNames( + [ + x509.DNSName(u"cryptography.io") + ] + ) + assert repr(gns) == ( + "])>" + ) + + def test_eq(self): + gns = x509.GeneralNames( + [x509.DNSName(u"cryptography.io")] + ) + gns2 = x509.GeneralNames( + [x509.DNSName(u"cryptography.io")] + ) + assert gns == gns2 + + def test_ne(self): + gns = x509.GeneralNames( + [x509.DNSName(u"cryptography.io")] + ) + gns2 = x509.GeneralNames( + [x509.RFC822Name(u"admin@cryptography.io")] + ) + assert gns != gns2 + assert gns != object() + + +class TestIssuerAlternativeName(object): + def test_get_values_for_type(self): + san = x509.IssuerAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + names = san.get_values_for_type(x509.DNSName) + assert names == [u"cryptography.io"] + + def test_iter_names(self): + san = x509.IssuerAlternativeName([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ]) + assert len(san) == 2 + assert list(san) == [ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ] + + def test_indexing(self): + ian = x509.IssuerAlternativeName([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + x509.DNSName(u"another.local"), + x509.RFC822Name(u"email@another.local"), + x509.UniformResourceIdentifier(u"http://another.local"), + ]) + assert ian[-1] == ian[4] + assert ian[2:6:2] == [ian[2], ian[4]] + + def test_invalid_general_names(self): + with pytest.raises(TypeError): + x509.IssuerAlternativeName( + [x509.DNSName(u"cryptography.io"), "invalid"] + ) + + def test_repr(self): + san = x509.IssuerAlternativeName( + [ + x509.DNSName(u"cryptography.io") + ] + ) + assert repr(san) == ( + "])>)>" + ) + + def test_eq(self): + san = x509.IssuerAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + san2 = x509.IssuerAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + assert san == san2 + + def test_ne(self): + san = x509.IssuerAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + san2 = x509.IssuerAlternativeName( + [x509.RFC822Name(u"admin@cryptography.io")] + ) + assert san != san2 + assert san != object() + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestRSAIssuerAlternativeNameExtension(object): + def test_uri(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "ian_uri.pem"), + x509.load_pem_x509_certificate, + backend, + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.ISSUER_ALTERNATIVE_NAME + ) + assert list(ext.value) == [ + x509.UniformResourceIdentifier(u"http://path.to.root/root.crt"), + ] + + +class TestCRLNumber(object): + def test_eq(self): + crl_number = x509.CRLNumber(15) + assert crl_number == x509.CRLNumber(15) + + def test_ne(self): + crl_number = x509.CRLNumber(15) + assert crl_number != x509.CRLNumber(14) + assert crl_number != object() + + def test_repr(self): + crl_number = x509.CRLNumber(15) + assert repr(crl_number) == "" + + def test_invalid_number(self): + with pytest.raises(TypeError): + x509.CRLNumber("notanumber") + + def test_hash(self): + c1 = x509.CRLNumber(1) + c2 = x509.CRLNumber(1) + c3 = x509.CRLNumber(2) + assert hash(c1) == hash(c2) + assert hash(c1) != hash(c3) + + +class TestSubjectAlternativeName(object): + def test_get_values_for_type(self): + san = x509.SubjectAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + names = san.get_values_for_type(x509.DNSName) + assert names == [u"cryptography.io"] + + def test_iter_names(self): + san = x509.SubjectAlternativeName([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ]) + assert len(san) == 2 + assert list(san) == [ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + ] + + def test_indexing(self): + san = x509.SubjectAlternativeName([ + x509.DNSName(u"cryptography.io"), + x509.DNSName(u"crypto.local"), + x509.DNSName(u"another.local"), + x509.RFC822Name(u"email@another.local"), + x509.UniformResourceIdentifier(u"http://another.local"), + ]) + assert san[-1] == san[4] + assert san[2:6:2] == [san[2], san[4]] + + def test_invalid_general_names(self): + with pytest.raises(TypeError): + x509.SubjectAlternativeName( + [x509.DNSName(u"cryptography.io"), "invalid"] + ) + + def test_repr(self): + san = x509.SubjectAlternativeName( + [ + x509.DNSName(u"cryptography.io") + ] + ) + assert repr(san) == ( + "])>)>" + ) + + def test_eq(self): + san = x509.SubjectAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + san2 = x509.SubjectAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + assert san == san2 + + def test_ne(self): + san = x509.SubjectAlternativeName( + [x509.DNSName(u"cryptography.io")] + ) + san2 = x509.SubjectAlternativeName( + [x509.RFC822Name(u"admin@cryptography.io")] + ) + assert san != san2 + assert san != object() + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestRSASubjectAlternativeNameExtension(object): + def test_dns_name(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + san = ext.value + + dns = san.get_values_for_type(x509.DNSName) + assert dns == [u"www.cryptography.io", u"cryptography.io"] + + def test_wildcard_dns_name(self, backend): + cert = _load_cert( + os.path.join("x509", "wildcard_san.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + + dns = ext.value.get_values_for_type(x509.DNSName) + assert dns == [ + u'*.langui.sh', + u'langui.sh', + u'*.saseliminator.com', + u'saseliminator.com' + ] + + def test_san_empty_hostname(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_empty_hostname.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + san = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + + dns = san.value.get_values_for_type(x509.DNSName) + assert dns == [u''] + + def test_san_wildcard_idna_dns_name(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "san_wildcard_idna.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + + dns = ext.value.get_values_for_type(x509.DNSName) + assert dns == [u'*.\u043f\u044b\u043a\u0430.cryptography'] + + def test_unsupported_gn(self, backend): + cert = _load_cert( + os.path.join("x509", "san_x400address.der"), + x509.load_der_x509_certificate, + backend + ) + with pytest.raises(x509.UnsupportedGeneralNameType) as exc: + cert.extensions + + assert exc.value.type == 3 + + def test_registered_id(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_registered_id.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + san = ext.value + rid = san.get_values_for_type(x509.RegisteredID) + assert rid == [x509.ObjectIdentifier("1.2.3.4")] + + def test_uri(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_uri_with_port.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + uri = ext.value.get_values_for_type( + x509.UniformResourceIdentifier + ) + assert uri == [ + u"gopher://\u043f\u044b\u043a\u0430.cryptography:70/path?q=s#hel" + u"lo", + u"http://someregulardomain.com", + ] + + def test_ipaddress(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_ipaddr.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + san = ext.value + + ip = san.get_values_for_type(x509.IPAddress) + assert [ + ipaddress.ip_address(u"127.0.0.1"), + ipaddress.ip_address(u"ff::") + ] == ip + + def test_dirname(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_dirname.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + san = ext.value + + dirname = san.get_values_for_type(x509.DirectoryName) + assert [ + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u'test'), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, u'Org'), + x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u'Texas'), + ]) + ] == dirname + + def test_rfc822name(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_rfc822_idna.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + san = ext.value + + rfc822name = san.get_values_for_type(x509.RFC822Name) + assert [u"email@em\xe5\xefl.com"] == rfc822name + + def test_idna2003_invalid(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_idna2003_dnsname.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(UnicodeError): + cert.extensions + + def test_unicode_rfc822_name_dns_name_uri(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_idna_names.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + rfc822_name = ext.value.get_values_for_type(x509.RFC822Name) + dns_name = ext.value.get_values_for_type(x509.DNSName) + uri = ext.value.get_values_for_type(x509.UniformResourceIdentifier) + assert rfc822_name == [u"email@\u043f\u044b\u043a\u0430.cryptography"] + assert dns_name == [u"\u043f\u044b\u043a\u0430.cryptography"] + assert uri == [u"https://www.\u043f\u044b\u043a\u0430.cryptography"] + + def test_rfc822name_dnsname_ipaddress_directoryname_uri(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_email_dns_ip_dirname_uri.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + san = ext.value + + rfc822_name = san.get_values_for_type(x509.RFC822Name) + uri = san.get_values_for_type(x509.UniformResourceIdentifier) + dns = san.get_values_for_type(x509.DNSName) + ip = san.get_values_for_type(x509.IPAddress) + dirname = san.get_values_for_type(x509.DirectoryName) + assert [u"user@cryptography.io"] == rfc822_name + assert [u"https://cryptography.io"] == uri + assert [u"cryptography.io"] == dns + assert [ + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u'dirCN'), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u'Cryptographic Authority' + ), + ]) + ] == dirname + assert [ + ipaddress.ip_address(u"127.0.0.1"), + ipaddress.ip_address(u"ff::") + ] == ip + + def test_invalid_rfc822name(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_rfc822_names.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(ValueError) as exc: + cert.extensions + + assert 'Invalid rfc822name value' in str(exc.value) + + def test_other_name(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "san_other_name.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.SUBJECT_ALTERNATIVE_NAME + ) + assert ext is not None + assert ext.critical is False + + expected = x509.OtherName(x509.ObjectIdentifier("1.2.3.4"), + b'\x16\x0bHello World') + assert len(ext.value) == 1 + assert list(ext.value)[0] == expected + + othernames = ext.value.get_values_for_type(x509.OtherName) + assert othernames == [expected] + + def test_certbuilder(self, backend): + sans = [u'*.example.org', u'*.\xf5\xe4\xf6\xfc.example.com', + u'foobar.example.net'] + private_key = RSA_KEY_2048.private_key(backend) + builder = _make_certbuilder(private_key) + builder = builder.add_extension( + SubjectAlternativeName(list(map(DNSName, sans))), True) + + cert = builder.sign(private_key, hashes.SHA1(), backend) + result = [x.value for x in cert.extensions.get_extension_for_class( + SubjectAlternativeName).value] + assert result == sans + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestExtendedKeyUsageExtension(object): + def test_eku(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "extended_key_usage.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.EXTENDED_KEY_USAGE + ) + assert ext is not None + assert ext.critical is False + + assert [ + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.1"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.2"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.3"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.4"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.9"), + x509.ObjectIdentifier("1.3.6.1.5.5.7.3.8"), + x509.ObjectIdentifier("2.5.29.37.0"), + x509.ObjectIdentifier("2.16.840.1.113730.4.1"), + ] == list(ext.value) + + +class TestAccessDescription(object): + def test_invalid_access_method(self): + with pytest.raises(TypeError): + x509.AccessDescription("notanoid", x509.DNSName(u"test")) + + def test_invalid_access_location(self): + with pytest.raises(TypeError): + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, "invalid" + ) + + def test_valid_nonstandard_method(self): + ad = x509.AccessDescription( + ObjectIdentifier("2.999.1"), + x509.UniformResourceIdentifier(u"http://example.com") + ) + assert ad is not None + + def test_repr(self): + ad = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + assert repr(ad) == ( + ", access_location=)>" + ) + + def test_eq(self): + ad = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ad2 = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + assert ad == ad2 + + def test_ne(self): + ad = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ad2 = x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ad3 = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://notthesame") + ) + assert ad != ad2 + assert ad != ad3 + assert ad != object() + + def test_hash(self): + ad = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ad2 = x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ad3 = x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + assert hash(ad) == hash(ad2) + assert hash(ad) != hash(ad3) + + +class TestPolicyConstraints(object): + def test_invalid_explicit_policy(self): + with pytest.raises(TypeError): + x509.PolicyConstraints("invalid", None) + + def test_invalid_inhibit_policy(self): + with pytest.raises(TypeError): + x509.PolicyConstraints(None, "invalid") + + def test_both_none(self): + with pytest.raises(ValueError): + x509.PolicyConstraints(None, None) + + def test_repr(self): + pc = x509.PolicyConstraints(0, None) + + assert repr(pc) == ( + u"" + ) + + def test_eq(self): + pc = x509.PolicyConstraints(2, 1) + pc2 = x509.PolicyConstraints(2, 1) + assert pc == pc2 + + def test_ne(self): + pc = x509.PolicyConstraints(2, 1) + pc2 = x509.PolicyConstraints(2, 2) + pc3 = x509.PolicyConstraints(3, 1) + assert pc != pc2 + assert pc != pc3 + assert pc != object() + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestPolicyConstraintsExtension(object): + def test_inhibit_policy_mapping(self, backend): + cert = _load_cert( + os.path.join("x509", "department-of-state-root.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.POLICY_CONSTRAINTS, + ) + assert ext.critical is True + + assert ext.value == x509.PolicyConstraints( + require_explicit_policy=None, inhibit_policy_mapping=0, + ) + + def test_require_explicit_policy(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "policy_constraints_explicit.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.POLICY_CONSTRAINTS + ) + assert ext.critical is True + assert ext.value == x509.PolicyConstraints( + require_explicit_policy=1, inhibit_policy_mapping=None, + ) + + +class TestAuthorityInformationAccess(object): + def test_invalid_descriptions(self): + with pytest.raises(TypeError): + x509.AuthorityInformationAccess(["notanAccessDescription"]) + + def test_iter_len(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + assert len(aia) == 2 + assert list(aia) == [ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ] + + def test_iter_input(self): + desc = [ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ) + ] + aia = x509.AuthorityInformationAccess(iter(desc)) + assert list(aia) == desc + + def test_repr(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + assert repr(aia) == ( + ", access_locati" + "on=)>, <" + "AccessDescription(access_method=, access_location=)>])>" + ) + + def test_eq(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + aia2 = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + assert aia == aia2 + + def test_ne(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ) + ]) + aia2 = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + ]) + + assert aia != aia2 + assert aia != object() + + def test_indexing(self): + aia = x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://domain.com/ca.crt") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp2.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp3.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp4.domain.com") + ), + ]) + assert aia[-1] == aia[4] + assert aia[2:6:2] == [aia[2], aia[4]] + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestAuthorityInformationAccessExtension(object): + def test_aia_ocsp_ca_issuers(self, backend): + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_INFORMATION_ACCESS + ) + assert ext is not None + assert ext.critical is False + + assert ext.value == x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://gv.symcd.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.UniformResourceIdentifier(u"http://gv.symcb.com/gv.crt") + ), + ]) + + def test_aia_multiple_ocsp_ca_issuers(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "aia_ocsp_ca_issuers.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_INFORMATION_ACCESS + ) + assert ext is not None + assert ext.critical is False + + assert ext.value == x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp2.domain.com") + ), + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.DirectoryName(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"myCN"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u"some Org"), + ])) + ), + ]) + + def test_aia_ocsp_only(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "aia_ocsp.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_INFORMATION_ACCESS + ) + assert ext is not None + assert ext.critical is False + + assert ext.value == x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.OCSP, + x509.UniformResourceIdentifier(u"http://ocsp.domain.com") + ), + ]) + + def test_aia_ca_issuers_only(self, backend): + cert = _load_cert( + os.path.join("x509", "custom", "aia_ca_issuers.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_INFORMATION_ACCESS + ) + assert ext is not None + assert ext.critical is False + + assert ext.value == x509.AuthorityInformationAccess([ + x509.AccessDescription( + AuthorityInformationAccessOID.CA_ISSUERS, + x509.DirectoryName(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"myCN"), + x509.NameAttribute(NameOID.ORGANIZATION_NAME, + u"some Org"), + ])) + ), + ]) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestAuthorityKeyIdentifierExtension(object): + def test_aki_keyid(self, backend): + cert = _load_cert( + os.path.join( + "x509", "cryptography.io.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ) + assert ext is not None + assert ext.critical is False + + assert ext.value.key_identifier == ( + b"\xc3\x9c\xf3\xfc\xd3F\x084\xbb\xceF\x7f\xa0|[\xf3\xe2\x08\xcbY" + ) + assert ext.value.authority_cert_issuer is None + assert ext.value.authority_cert_serial_number is None + + def test_aki_all_fields(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "authority_key_identifier.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ) + assert ext is not None + assert ext.critical is False + + assert ext.value.key_identifier == ( + b"9E>\xca=b\x1d\xea\x86I\xf6Z\xab@\xb7\xa4p\x98\xf1\xec" + ) + assert ext.value.authority_cert_issuer == [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u"PyCA" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography.io" + ) + ]) + ) + ] + assert ext.value.authority_cert_serial_number == 3 + + def test_aki_no_keyid(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "authority_key_identifier_no_keyid.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ) + assert ext is not None + assert ext.critical is False + + assert ext.value.key_identifier is None + assert ext.value.authority_cert_issuer == [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u"PyCA" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography.io" + ) + ]) + ) + ] + assert ext.value.authority_cert_serial_number == 3 + + def test_from_certificate(self, backend): + issuer_cert = _load_cert( + os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), + x509.load_pem_x509_certificate, + backend + ) + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ) + aki = x509.AuthorityKeyIdentifier.from_issuer_public_key( + issuer_cert.public_key() + ) + assert ext.value == aki + + def test_from_issuer_subject_key_identifier(self, backend): + issuer_cert = _load_cert( + os.path.join("x509", "rapidssl_sha256_ca_g3.pem"), + x509.load_pem_x509_certificate, + backend + ) + cert = _load_cert( + os.path.join("x509", "cryptography.io.pem"), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.AUTHORITY_KEY_IDENTIFIER + ) + ski = issuer_cert.extensions.get_extension_for_class( + x509.SubjectKeyIdentifier + ) + aki = x509.AuthorityKeyIdentifier.from_issuer_subject_key_identifier( + ski + ) + assert ext.value == aki + + +class TestNameConstraints(object): + def test_ipaddress_wrong_type(self): + with pytest.raises(TypeError): + x509.NameConstraints( + permitted_subtrees=[ + x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + ], + excluded_subtrees=None + ) + + with pytest.raises(TypeError): + x509.NameConstraints( + permitted_subtrees=None, + excluded_subtrees=[ + x509.IPAddress(ipaddress.IPv4Address(u"127.0.0.1")) + ] + ) + + def test_ipaddress_allowed_type(self): + permitted = [x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/29"))] + excluded = [x509.IPAddress(ipaddress.IPv4Network(u"10.10.0.0/24"))] + nc = x509.NameConstraints( + permitted_subtrees=permitted, + excluded_subtrees=excluded + ) + assert nc.permitted_subtrees == permitted + assert nc.excluded_subtrees == excluded + + def test_invalid_permitted_subtrees(self): + with pytest.raises(TypeError): + x509.NameConstraints("badpermitted", None) + + def test_invalid_excluded_subtrees(self): + with pytest.raises(TypeError): + x509.NameConstraints(None, "badexcluded") + + def test_no_subtrees(self): + with pytest.raises(ValueError): + x509.NameConstraints(None, None) + + def test_permitted_none(self): + excluded = [x509.DNSName(u"name.local")] + nc = x509.NameConstraints( + permitted_subtrees=None, excluded_subtrees=excluded + ) + assert nc.permitted_subtrees is None + assert nc.excluded_subtrees is not None + + def test_excluded_none(self): + permitted = [x509.DNSName(u"name.local")] + nc = x509.NameConstraints( + permitted_subtrees=permitted, excluded_subtrees=None + ) + assert nc.permitted_subtrees is not None + assert nc.excluded_subtrees is None + + def test_iter_input(self): + subtrees = [x509.IPAddress(ipaddress.IPv4Network(u"192.168.0.0/24"))] + nc = x509.NameConstraints(iter(subtrees), iter(subtrees)) + assert list(nc.permitted_subtrees) == subtrees + assert list(nc.excluded_subtrees) == subtrees + + def test_repr(self): + permitted = [x509.DNSName(u"name.local"), x509.DNSName(u"name2.local")] + nc = x509.NameConstraints( + permitted_subtrees=permitted, + excluded_subtrees=None + ) + assert repr(nc) == ( + "" + ", ], excluded_subtrees=None)>" + ) + + def test_eq(self): + nc = x509.NameConstraints( + permitted_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=[x509.DNSName(u"name2.local")] + ) + nc2 = x509.NameConstraints( + permitted_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=[x509.DNSName(u"name2.local")] + ) + assert nc == nc2 + + def test_ne(self): + nc = x509.NameConstraints( + permitted_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=[x509.DNSName(u"name2.local")] + ) + nc2 = x509.NameConstraints( + permitted_subtrees=[x509.DNSName(u"name.local")], + excluded_subtrees=None + ) + nc3 = x509.NameConstraints( + permitted_subtrees=None, + excluded_subtrees=[x509.DNSName(u"name2.local")] + ) + + assert nc != nc2 + assert nc != nc3 + assert nc != object() + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestNameConstraintsExtension(object): + def test_permitted_excluded(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_permitted_excluded_2.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=[ + x509.DNSName(u"zombo.local"), + ], + excluded_subtrees=[ + x509.DirectoryName(x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"zombo") + ])) + ] + ) + + def test_permitted(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_permitted_2.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=[ + x509.DNSName(u"zombo.local"), + ], + excluded_subtrees=None + ) + + def test_permitted_with_leading_period(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_permitted.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=[ + x509.DNSName(u".cryptography.io"), + x509.UniformResourceIdentifier(u"ftp://cryptography.test") + ], + excluded_subtrees=None + ) + + def test_excluded_with_leading_period(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "nc_excluded.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + nc = cert.extensions.get_extension_for_oid( + ExtensionOID.NAME_CONSTRAINTS + ).value + assert nc == x509.NameConstraints( + permitted_subtrees=None, + excluded_subtrees=[ + x509.DNSName(u".cryptography.io"), + x509.UniformResourceIdentifier(u"gopher://cryptography.test") + ] + ) + + 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( + ExtensionOID.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( + ExtensionOID.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( + ExtensionOID.NAME_CONSTRAINTS + ) + + def test_certbuilder(self, backend): + permitted = [u'.example.org', u'.\xf5\xe4\xf6\xfc.example.com', + u'foobar.example.net'] + private_key = RSA_KEY_2048.private_key(backend) + builder = _make_certbuilder(private_key) + builder = builder.add_extension( + NameConstraints(permitted_subtrees=list(map(DNSName, permitted)), + excluded_subtrees=[]), True) + + cert = builder.sign(private_key, hashes.SHA1(), backend) + result = [x.value for x in cert.extensions.get_extension_for_class( + NameConstraints).value.permitted_subtrees] + assert result == permitted + + +class TestDistributionPoint(object): + def test_distribution_point_full_name_not_general_names(self): + with pytest.raises(TypeError): + x509.DistributionPoint(["notgn"], None, None, None) + + def test_distribution_point_relative_name_not_name(self): + with pytest.raises(TypeError): + x509.DistributionPoint(None, "notname", None, None) + + def test_distribution_point_full_and_relative_not_none(self): + with pytest.raises(ValueError): + x509.DistributionPoint("data", "notname", None, None) + + def test_relative_name_name_value_deprecated(self): + with pytest.deprecated_call(): + x509.DistributionPoint( + None, + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"myCN") + ]), + None, + None + ) + + def test_crl_issuer_not_general_names(self): + with pytest.raises(TypeError): + x509.DistributionPoint(None, None, None, ["notgn"]) + + def test_reason_not_reasonflags(self): + with pytest.raises(TypeError): + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset(["notreasonflags"]), + None + ) + + def test_reason_not_frozenset(self): + with pytest.raises(TypeError): + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + [x509.ReasonFlags.ca_compromise], + None + ) + + def test_disallowed_reasons(self): + with pytest.raises(ValueError): + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset([x509.ReasonFlags.unspecified]), + None + ) + + with pytest.raises(ValueError): + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset([x509.ReasonFlags.remove_from_crl]), + None + ) + + def test_reason_only(self): + with pytest.raises(ValueError): + x509.DistributionPoint( + None, + None, + frozenset([x509.ReasonFlags.aa_compromise]), + None + ) + + def test_eq(self): + dp = x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset([x509.ReasonFlags.superseded]), + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"Important CA" + ) + ]) + ) + ], + ) + dp2 = x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset([x509.ReasonFlags.superseded]), + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"Important CA" + ) + ]) + ) + ], + ) + assert dp == dp2 + + def test_ne(self): + dp = x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + frozenset([x509.ReasonFlags.superseded]), + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"Important CA" + ) + ]) + ) + ], + ) + dp2 = x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://crypt.og/crl")], + None, + None, + None + ) + assert dp != dp2 + assert dp != object() + + def test_iter_input(self): + name = [x509.UniformResourceIdentifier(u"http://crypt.og/crl")] + issuer = [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COMMON_NAME, u"Important CA") + ]) + ) + ] + dp = x509.DistributionPoint( + iter(name), + None, + frozenset([x509.ReasonFlags.ca_compromise]), + iter(issuer), + ) + assert list(dp.full_name) == name + assert list(dp.crl_issuer) == issuer + + def test_repr(self): + dp = x509.DistributionPoint( + None, + x509.RelativeDistinguishedName([ + x509.NameAttribute(NameOID.COMMON_NAME, u"myCN") + ]), + frozenset([x509.ReasonFlags.ca_compromise]), + [ + x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"Important CA" + ) + ]) + ) + ], + ) + if six.PY3: + assert repr(dp) == ( + ", value='myCN')>])>, reasons=frozenset(" + "{}), crl_issuer=[<" + "DirectoryName(value=, value='Important CA')>])>)" + ">])>" + ) + else: + assert repr(dp) == ( + ", value=u'myCN')>])>, reasons=frozenset" + "([]), crl_issuer=[" + ", value=u'Important CA')>])" + ">)>])>" + ) + + +class TestCRLDistributionPoints(object): + def test_invalid_distribution_points(self): + with pytest.raises(TypeError): + x509.CRLDistributionPoints(["notadistributionpoint"]) + + def test_iter_len(self): + cdp = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://domain")], + None, + None, + None + ), + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + None + ), + ]) + assert len(cdp) == 2 + assert list(cdp) == [ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://domain")], + None, + None, + None + ), + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + None + ), + ] + + def test_iter_input(self): + points = [ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"http://domain")], + None, + None, + None + ), + ] + cdp = x509.CRLDistributionPoints(iter(points)) + assert list(cdp) == points + + def test_repr(self): + cdp = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([x509.ReasonFlags.key_compromise]), + None + ), + ]) + if six.PY3: + assert repr(cdp) == ( + "], relative_name=No" + "ne, reasons=frozenset({}), crl_issuer=None)>])>" + ) + else: + assert repr(cdp) == ( + "], relative_name=No" + "ne, reasons=frozenset([]), crl_issuer=None)>])>" + ) + + def test_eq(self): + cdp = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + cdp2 = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + assert cdp == cdp2 + + def test_ne(self): + cdp = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + cdp2 = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain2")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + cdp3 = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([x509.ReasonFlags.key_compromise]), + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + ]) + cdp4 = x509.CRLDistributionPoints([ + x509.DistributionPoint( + [x509.UniformResourceIdentifier(u"ftp://domain")], + None, + frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + ]), + [x509.UniformResourceIdentifier(u"uri://thing2")], + ), + ]) + assert cdp != cdp2 + assert cdp != cdp3 + assert cdp != cdp4 + assert cdp != object() + + def test_indexing(self): + ci = x509.CRLDistributionPoints([ + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing2")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing3")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing4")], + ), + x509.DistributionPoint( + None, None, None, + [x509.UniformResourceIdentifier(u"uri://thing5")], + ), + ]) + assert ci[-1] == ci[4] + assert ci[2:6:2] == [ci[2], ci[4]] + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestCRLDistributionPointsExtension(object): + def test_fullname_and_crl_issuer(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", "ValidcRLIssuerTest28EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, + u"Test Certificates 2011" + ), + x509.NameAttribute( + NameOID.ORGANIZATIONAL_UNIT_NAME, + u"indirectCRL CA3 cRLIssuer" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, + u"indirect CRL for indirectCRL CA3" + ), + ]) + )], + relative_name=None, + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, + u"Test Certificates 2011" + ), + x509.NameAttribute( + NameOID.ORGANIZATIONAL_UNIT_NAME, + u"indirectCRL CA3 cRLIssuer" + ), + ]) + )], + ) + ]) + + def test_relativename_and_crl_issuer(self, backend): + cert = _load_cert( + os.path.join( + "x509", "PKITS_data", "certs", "ValidcRLIssuerTest29EE.crt" + ), + x509.load_der_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=None, + relative_name=x509.RelativeDistinguishedName([ + x509.NameAttribute( + NameOID.COMMON_NAME, + u"indirect CRL for indirectCRL CA3" + ), + ]), + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, + u"Test Certificates 2011" + ), + x509.NameAttribute( + NameOID.ORGANIZATIONAL_UNIT_NAME, + u"indirectCRL CA3 cRLIssuer" + ), + ]) + )], + ) + ]) + + def test_fullname_crl_issuer_reasons(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cdp_fullname_reasons_crl_issuer.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"http://myhost.com/myca.crl" + )], + relative_name=None, + reasons=frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise + ]), + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), + x509.NameAttribute( + NameOID.ORGANIZATION_NAME, u"PyCA" + ), + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography CA" + ), + ]) + )], + ) + ]) + + def test_all_reasons(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cdp_all_reasons.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"http://domain.com/some.crl" + )], + relative_name=None, + reasons=frozenset([ + x509.ReasonFlags.key_compromise, + x509.ReasonFlags.ca_compromise, + x509.ReasonFlags.affiliation_changed, + x509.ReasonFlags.superseded, + x509.ReasonFlags.privilege_withdrawn, + x509.ReasonFlags.cessation_of_operation, + x509.ReasonFlags.aa_compromise, + x509.ReasonFlags.certificate_hold, + ]), + crl_issuer=None + ) + ]) + + def test_single_reason(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cdp_reason_aa_compromise.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"http://domain.com/some.crl" + )], + relative_name=None, + reasons=frozenset([x509.ReasonFlags.aa_compromise]), + crl_issuer=None + ) + ]) + + def test_crl_issuer_only(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cdp_crl_issuer.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=None, + relative_name=None, + reasons=None, + crl_issuer=[x509.DirectoryName( + x509.Name([ + x509.NameAttribute( + NameOID.COMMON_NAME, u"cryptography CA" + ), + ]) + )], + ) + ]) + + def test_crl_empty_hostname(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cdp_empty_hostname.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + + cdps = cert.extensions.get_extension_for_oid( + ExtensionOID.CRL_DISTRIBUTION_POINTS + ).value + + assert cdps == x509.CRLDistributionPoints([ + x509.DistributionPoint( + full_name=[x509.UniformResourceIdentifier( + u"ldap:/CN=A,OU=B,dc=C,DC=D?E?F?G?H=I" + )], + relative_name=None, + reasons=None, + crl_issuer=None + ) + ]) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestOCSPNoCheckExtension(object): + def test_nocheck(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "ocsp_nocheck.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + ext = cert.extensions.get_extension_for_oid( + ExtensionOID.OCSP_NO_CHECK + ) + assert isinstance(ext.value, x509.OCSPNoCheck) + + +class TestInhibitAnyPolicy(object): + def test_not_int(self): + with pytest.raises(TypeError): + x509.InhibitAnyPolicy("notint") + + def test_negative_int(self): + with pytest.raises(ValueError): + x509.InhibitAnyPolicy(-1) + + def test_repr(self): + iap = x509.InhibitAnyPolicy(0) + assert repr(iap) == "" + + def test_eq(self): + iap = x509.InhibitAnyPolicy(1) + iap2 = x509.InhibitAnyPolicy(1) + assert iap == iap2 + + def test_ne(self): + iap = x509.InhibitAnyPolicy(1) + iap2 = x509.InhibitAnyPolicy(4) + assert iap != iap2 + assert iap != object() + + def test_hash(self): + iap = x509.InhibitAnyPolicy(1) + iap2 = x509.InhibitAnyPolicy(1) + iap3 = x509.InhibitAnyPolicy(4) + assert hash(iap) == hash(iap2) + assert hash(iap) != hash(iap3) + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestInhibitAnyPolicyExtension(object): + def test_nocheck(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "inhibit_any_policy_5.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + iap = cert.extensions.get_extension_for_oid( + ExtensionOID.INHIBIT_ANY_POLICY + ).value + assert iap.skip_certs == 5 + + +@pytest.mark.requires_backend_interface(interface=RSABackend) +@pytest.mark.requires_backend_interface(interface=X509Backend) +class TestInvalidExtension(object): + def test_invalid_certificate_policies_data(self, backend): + cert = _load_cert( + os.path.join( + "x509", "custom", "cp_invalid.pem" + ), + x509.load_pem_x509_certificate, + backend + ) + with pytest.raises(ValueError): + cert.extensions diff --git a/tests/test_x509_revokedcertbuilder.py b/tests/test_x509_revokedcertbuilder.py new file mode 100644 index 0000000..e3a0650 --- /dev/null +++ b/tests/test_x509_revokedcertbuilder.py @@ -0,0 +1,205 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import datetime + +import pytest + +import pytz + +from cryptography import x509 +from cryptography.hazmat.backends.interfaces import X509Backend + + +class TestRevokedCertificateBuilder(object): + def test_serial_number_must_be_integer(self): + with pytest.raises(TypeError): + x509.RevokedCertificateBuilder().serial_number("notanx509name") + + def test_serial_number_must_be_non_negative(self): + with pytest.raises(ValueError): + x509.RevokedCertificateBuilder().serial_number(-1) + + def test_serial_number_must_be_positive(self): + with pytest.raises(ValueError): + x509.RevokedCertificateBuilder().serial_number(0) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_minimal_serial_number(self, backend): + revocation_date = datetime.datetime(2002, 1, 1, 12, 1) + builder = x509.RevokedCertificateBuilder().serial_number( + 1 + ).revocation_date( + revocation_date + ) + + revoked_certificate = builder.build(backend) + assert revoked_certificate.serial_number == 1 + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_biggest_serial_number(self, backend): + revocation_date = datetime.datetime(2002, 1, 1, 12, 1) + builder = x509.RevokedCertificateBuilder().serial_number( + (1 << 159) - 1 + ).revocation_date( + revocation_date + ) + + revoked_certificate = builder.build(backend) + assert revoked_certificate.serial_number == (1 << 159) - 1 + + def test_serial_number_must_be_less_than_160_bits_long(self): + with pytest.raises(ValueError): + x509.RevokedCertificateBuilder().serial_number(1 << 159) + + def test_set_serial_number_twice(self): + builder = x509.RevokedCertificateBuilder().serial_number(3) + with pytest.raises(ValueError): + builder.serial_number(4) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_aware_revocation_date(self, backend): + time = datetime.datetime(2012, 1, 16, 22, 43) + tz = pytz.timezone("US/Pacific") + time = tz.localize(time) + utc_time = datetime.datetime(2012, 1, 17, 6, 43) + serial_number = 333 + builder = x509.RevokedCertificateBuilder().serial_number( + serial_number + ).revocation_date( + time + ) + + revoked_certificate = builder.build(backend) + assert revoked_certificate.revocation_date == utc_time + + def test_revocation_date_invalid(self): + with pytest.raises(TypeError): + x509.RevokedCertificateBuilder().revocation_date("notadatetime") + + def test_revocation_date_before_unix_epoch(self): + with pytest.raises(ValueError): + x509.RevokedCertificateBuilder().revocation_date( + datetime.datetime(1960, 8, 10) + ) + + def test_set_revocation_date_twice(self): + builder = x509.RevokedCertificateBuilder().revocation_date( + datetime.datetime(2002, 1, 1, 12, 1) + ) + with pytest.raises(ValueError): + builder.revocation_date(datetime.datetime(2002, 1, 1, 12, 1)) + + def test_add_extension_checks_for_duplicates(self): + builder = x509.RevokedCertificateBuilder().add_extension( + x509.CRLReason(x509.ReasonFlags.ca_compromise), False + ) + + with pytest.raises(ValueError): + builder.add_extension( + x509.CRLReason(x509.ReasonFlags.ca_compromise), False + ) + + def test_add_invalid_extension(self): + with pytest.raises(TypeError): + x509.RevokedCertificateBuilder().add_extension( + "notanextension", False + ) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_serial_number(self, backend): + builder = x509.RevokedCertificateBuilder().revocation_date( + datetime.datetime(2002, 1, 1, 12, 1) + ) + + with pytest.raises(ValueError): + builder.build(backend) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_no_revocation_date(self, backend): + builder = x509.RevokedCertificateBuilder().serial_number(3) + + with pytest.raises(ValueError): + builder.build(backend) + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_create_revoked(self, backend): + serial_number = 333 + revocation_date = datetime.datetime(2002, 1, 1, 12, 1) + builder = x509.RevokedCertificateBuilder().serial_number( + serial_number + ).revocation_date( + revocation_date + ) + + revoked_certificate = builder.build(backend) + assert revoked_certificate.serial_number == serial_number + assert revoked_certificate.revocation_date == revocation_date + assert len(revoked_certificate.extensions) == 0 + + @pytest.mark.parametrize( + "extension", + [ + x509.InvalidityDate(datetime.datetime(2015, 1, 1, 0, 0)), + x509.CRLReason(x509.ReasonFlags.ca_compromise), + x509.CertificateIssuer([ + x509.DNSName(u"cryptography.io"), + ]) + ] + ) + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_add_extensions(self, backend, extension): + serial_number = 333 + revocation_date = datetime.datetime(2002, 1, 1, 12, 1) + builder = x509.RevokedCertificateBuilder().serial_number( + serial_number + ).revocation_date( + revocation_date + ).add_extension( + extension, False + ) + + revoked_certificate = builder.build(backend) + assert revoked_certificate.serial_number == serial_number + assert revoked_certificate.revocation_date == revocation_date + assert len(revoked_certificate.extensions) == 1 + ext = revoked_certificate.extensions.get_extension_for_class( + type(extension) + ) + assert ext.critical is False + assert ext.value == extension + + @pytest.mark.requires_backend_interface(interface=X509Backend) + def test_add_multiple_extensions(self, backend): + serial_number = 333 + revocation_date = datetime.datetime(2002, 1, 1, 12, 1) + invalidity_date = x509.InvalidityDate( + datetime.datetime(2015, 1, 1, 0, 0) + ) + certificate_issuer = x509.CertificateIssuer([ + x509.DNSName(u"cryptography.io"), + ]) + crl_reason = x509.CRLReason(x509.ReasonFlags.aa_compromise) + builder = x509.RevokedCertificateBuilder().serial_number( + serial_number + ).revocation_date( + revocation_date + ).add_extension( + invalidity_date, True + ).add_extension( + crl_reason, True + ).add_extension( + certificate_issuer, True + ) + + revoked_certificate = builder.build(backend) + assert len(revoked_certificate.extensions) == 3 + for ext_data in [invalidity_date, certificate_issuer, crl_reason]: + ext = revoked_certificate.extensions.get_extension_for_class( + type(ext_data) + ) + assert ext.critical is True + assert ext.value == ext_data diff --git a/tests/utils.py b/tests/utils.py new file mode 100644 index 0000000..dc7377a --- /dev/null +++ b/tests/utils.py @@ -0,0 +1,848 @@ +# This file is dual licensed under the terms of the Apache License, Version +# 2.0, and the BSD License. See the LICENSE file in the root of this repository +# for complete details. + +from __future__ import absolute_import, division, print_function + +import binascii +import collections +import math +import re +from contextlib import contextmanager + +import pytest + +import six + +from cryptography.exceptions import UnsupportedAlgorithm + +import cryptography_vectors + + +HashVector = collections.namedtuple("HashVector", ["message", "digest"]) +KeyedHashVector = collections.namedtuple( + "KeyedHashVector", ["message", "digest", "key"] +) + + +def select_backends(names, backend_list): + if names is None: + return backend_list + split_names = [x.strip() for x in names.split(',')] + selected_backends = [] + for backend in backend_list: + if backend.name in split_names: + selected_backends.append(backend) + + if len(selected_backends) > 0: + return selected_backends + else: + raise ValueError( + "No backend selected. Tried to select: {0}".format(split_names) + ) + + +def skip_if_empty(backend_list, required_interfaces): + if not backend_list: + pytest.skip( + "No backends provided supply the interface: {0}".format( + ", ".join(iface.__name__ for iface in required_interfaces) + ) + ) + + +def check_backend_support(item): + supported = item.keywords.get("supported") + if supported and "backend" in item.funcargs: + for mark in supported: + if not mark.kwargs["only_if"](item.funcargs["backend"]): + pytest.skip("{0} ({1})".format( + mark.kwargs["skip_message"], item.funcargs["backend"] + )) + elif supported: + raise ValueError("This mark is only available on methods that take a " + "backend") + + +@contextmanager +def raises_unsupported_algorithm(reason): + with pytest.raises(UnsupportedAlgorithm) as exc_info: + yield exc_info + + assert exc_info.value._reason is reason + + +def load_vectors_from_file(filename, loader, mode="r"): + with cryptography_vectors.open_vector_file(filename, mode) as vector_file: + return loader(vector_file) + + +def load_nist_vectors(vector_data): + test_data = None + data = [] + + for line in vector_data: + line = line.strip() + + # Blank lines, comments, and section headers are ignored + if not line or line.startswith("#") or (line.startswith("[") and + line.endswith("]")): + continue + + if line.strip() == "FAIL": + test_data["fail"] = True + continue + + # Build our data using a simple Key = Value format + name, value = [c.strip() for c in line.split("=")] + + # Some tests (PBKDF2) contain \0, which should be interpreted as a + # null character rather than literal. + value = value.replace("\\0", "\0") + + # COUNT is a special token that indicates a new block of data + if name.upper() == "COUNT": + test_data = {} + data.append(test_data) + continue + # For all other tokens we simply want the name, value stored in + # the dictionary + else: + test_data[name.lower()] = value.encode("ascii") + + return data + + +def load_cryptrec_vectors(vector_data): + cryptrec_list = [] + + for line in vector_data: + line = line.strip() + + # Blank lines and comments are ignored + if not line or line.startswith("#"): + continue + + if line.startswith("K"): + key = line.split(" : ")[1].replace(" ", "").encode("ascii") + elif line.startswith("P"): + pt = line.split(" : ")[1].replace(" ", "").encode("ascii") + elif line.startswith("C"): + ct = line.split(" : ")[1].replace(" ", "").encode("ascii") + # after a C is found the K+P+C tuple is complete + # there are many P+C pairs for each K + cryptrec_list.append({ + "key": key, + "plaintext": pt, + "ciphertext": ct + }) + else: + raise ValueError("Invalid line in file '{}'".format(line)) + return cryptrec_list + + +def load_hash_vectors(vector_data): + vectors = [] + key = None + msg = None + md = None + + for line in vector_data: + line = line.strip() + + if not line or line.startswith("#") or line.startswith("["): + continue + + if line.startswith("Len"): + length = int(line.split(" = ")[1]) + elif line.startswith("Key"): + # HMAC vectors contain a key attribute. Hash vectors do not. + key = line.split(" = ")[1].encode("ascii") + elif line.startswith("Msg"): + # In the NIST vectors they have chosen to represent an empty + # string as hex 00, which is of course not actually an empty + # string. So we parse the provided length and catch this edge case. + msg = line.split(" = ")[1].encode("ascii") if length > 0 else b"" + elif line.startswith("MD"): + md = line.split(" = ")[1] + # after MD is found the Msg+MD (+ potential key) tuple is complete + if key is not None: + vectors.append(KeyedHashVector(msg, md, key)) + key = None + msg = None + md = None + else: + vectors.append(HashVector(msg, md)) + msg = None + md = None + else: + raise ValueError("Unknown line in hash vector") + return vectors + + +def load_pkcs1_vectors(vector_data): + """ + Loads data out of RSA PKCS #1 vector files. + """ + private_key_vector = None + public_key_vector = None + attr = None + key = None + example_vector = None + examples = [] + vectors = [] + for line in vector_data: + if ( + line.startswith("# PSS Example") or + line.startswith("# OAEP Example") or + line.startswith("# PKCS#1 v1.5") + ): + if example_vector: + for key, value in six.iteritems(example_vector): + hex_str = "".join(value).replace(" ", "").encode("ascii") + example_vector[key] = hex_str + examples.append(example_vector) + + attr = None + example_vector = collections.defaultdict(list) + + if line.startswith("# Message"): + attr = "message" + continue + elif line.startswith("# Salt"): + attr = "salt" + continue + elif line.startswith("# Seed"): + attr = "seed" + continue + elif line.startswith("# Signature"): + attr = "signature" + continue + elif line.startswith("# Encryption"): + attr = "encryption" + continue + elif ( + example_vector and + line.startswith("# =============================================") + ): + for key, value in six.iteritems(example_vector): + hex_str = "".join(value).replace(" ", "").encode("ascii") + example_vector[key] = hex_str + examples.append(example_vector) + example_vector = None + attr = None + elif example_vector and line.startswith("#"): + continue + else: + if attr is not None and example_vector is not None: + example_vector[attr].append(line.strip()) + continue + + if ( + line.startswith("# Example") or + line.startswith("# =============================================") + ): + if key: + assert private_key_vector + assert public_key_vector + + for key, value in six.iteritems(public_key_vector): + hex_str = "".join(value).replace(" ", "") + public_key_vector[key] = int(hex_str, 16) + + for key, value in six.iteritems(private_key_vector): + hex_str = "".join(value).replace(" ", "") + private_key_vector[key] = int(hex_str, 16) + + private_key_vector["examples"] = examples + examples = [] + + assert ( + private_key_vector['public_exponent'] == + public_key_vector['public_exponent'] + ) + + assert ( + private_key_vector['modulus'] == + public_key_vector['modulus'] + ) + + vectors.append( + (private_key_vector, public_key_vector) + ) + + public_key_vector = collections.defaultdict(list) + private_key_vector = collections.defaultdict(list) + key = None + attr = None + + if private_key_vector is None or public_key_vector is None: + continue + + if line.startswith("# Private key"): + key = private_key_vector + elif line.startswith("# Public key"): + key = public_key_vector + elif line.startswith("# Modulus:"): + attr = "modulus" + elif line.startswith("# Public exponent:"): + attr = "public_exponent" + elif line.startswith("# Exponent:"): + if key is public_key_vector: + attr = "public_exponent" + else: + assert key is private_key_vector + attr = "private_exponent" + elif line.startswith("# Prime 1:"): + attr = "p" + elif line.startswith("# Prime 2:"): + attr = "q" + elif line.startswith("# Prime exponent 1:"): + attr = "dmp1" + elif line.startswith("# Prime exponent 2:"): + attr = "dmq1" + elif line.startswith("# Coefficient:"): + attr = "iqmp" + elif line.startswith("#"): + attr = None + else: + if key is not None and attr is not None: + key[attr].append(line.strip()) + return vectors + + +def load_rsa_nist_vectors(vector_data): + test_data = None + p = None + salt_length = None + data = [] + + for line in vector_data: + line = line.strip() + + # Blank lines and section headers are ignored + if not line or line.startswith("["): + continue + + if line.startswith("# Salt len:"): + salt_length = int(line.split(":")[1].strip()) + continue + elif line.startswith("#"): + continue + + # Build our data using a simple Key = Value format + name, value = [c.strip() for c in line.split("=")] + + if name == "n": + n = int(value, 16) + elif name == "e" and p is None: + e = int(value, 16) + elif name == "p": + p = int(value, 16) + elif name == "q": + q = int(value, 16) + elif name == "SHAAlg": + if p is None: + test_data = { + "modulus": n, + "public_exponent": e, + "salt_length": salt_length, + "algorithm": value, + "fail": False + } + else: + test_data = { + "modulus": n, + "p": p, + "q": q, + "algorithm": value + } + if salt_length is not None: + test_data["salt_length"] = salt_length + data.append(test_data) + elif name == "e" and p is not None: + test_data["public_exponent"] = int(value, 16) + elif name == "d": + test_data["private_exponent"] = int(value, 16) + elif name == "Result": + test_data["fail"] = value.startswith("F") + # For all other tokens we simply want the name, value stored in + # the dictionary + else: + test_data[name.lower()] = value.encode("ascii") + + return data + + +def load_fips_dsa_key_pair_vectors(vector_data): + """ + Loads data out of the FIPS DSA KeyPair vector files. + """ + vectors = [] + # When reading_key_data is set to True it tells the loader to continue + # constructing dictionaries. We set reading_key_data to False during the + # blocks of the vectors of N=224 because we don't support it. + reading_key_data = True + for line in vector_data: + line = line.strip() + + if not line or line.startswith("#"): + continue + elif line.startswith("[mod = L=1024"): + continue + elif line.startswith("[mod = L=2048, N=224"): + reading_key_data = False + continue + elif line.startswith("[mod = L=2048, N=256"): + reading_key_data = True + continue + elif line.startswith("[mod = L=3072"): + continue + + if reading_key_data: + if line.startswith("P"): + vectors.append({'p': int(line.split("=")[1], 16)}) + elif line.startswith("Q"): + vectors[-1]['q'] = int(line.split("=")[1], 16) + elif line.startswith("G"): + vectors[-1]['g'] = int(line.split("=")[1], 16) + elif line.startswith("X") and 'x' not in vectors[-1]: + vectors[-1]['x'] = int(line.split("=")[1], 16) + elif line.startswith("X") and 'x' in vectors[-1]: + vectors.append({'p': vectors[-1]['p'], + 'q': vectors[-1]['q'], + 'g': vectors[-1]['g'], + 'x': int(line.split("=")[1], 16) + }) + elif line.startswith("Y"): + vectors[-1]['y'] = int(line.split("=")[1], 16) + + return vectors + + +def load_fips_dsa_sig_vectors(vector_data): + """ + Loads data out of the FIPS DSA SigVer vector files. + """ + vectors = [] + sha_regex = re.compile( + r"\[mod = L=...., N=..., SHA-(?P1|224|256|384|512)\]" + ) + # When reading_key_data is set to True it tells the loader to continue + # constructing dictionaries. We set reading_key_data to False during the + # blocks of the vectors of N=224 because we don't support it. + reading_key_data = True + + for line in vector_data: + line = line.strip() + + if not line or line.startswith("#"): + continue + + sha_match = sha_regex.match(line) + if sha_match: + digest_algorithm = "SHA-{0}".format(sha_match.group("sha")) + + if line.startswith("[mod = L=2048, N=224"): + reading_key_data = False + continue + elif line.startswith("[mod = L=2048, N=256"): + reading_key_data = True + continue + + if not reading_key_data or line.startswith("[mod"): + continue + + name, value = [c.strip() for c in line.split("=")] + + if name == "P": + vectors.append({'p': int(value, 16), + 'digest_algorithm': digest_algorithm}) + elif name == "Q": + vectors[-1]['q'] = int(value, 16) + elif name == "G": + vectors[-1]['g'] = int(value, 16) + elif name == "Msg" and 'msg' not in vectors[-1]: + hexmsg = value.strip().encode("ascii") + vectors[-1]['msg'] = binascii.unhexlify(hexmsg) + elif name == "Msg" and 'msg' in vectors[-1]: + hexmsg = value.strip().encode("ascii") + vectors.append({'p': vectors[-1]['p'], + 'q': vectors[-1]['q'], + 'g': vectors[-1]['g'], + 'digest_algorithm': + vectors[-1]['digest_algorithm'], + 'msg': binascii.unhexlify(hexmsg)}) + elif name == "X": + vectors[-1]['x'] = int(value, 16) + elif name == "Y": + vectors[-1]['y'] = int(value, 16) + elif name == "R": + vectors[-1]['r'] = int(value, 16) + elif name == "S": + vectors[-1]['s'] = int(value, 16) + elif name == "Result": + vectors[-1]['result'] = value.split("(")[0].strip() + + return vectors + + +# http://tools.ietf.org/html/rfc4492#appendix-A +_ECDSA_CURVE_NAMES = { + "P-192": "secp192r1", + "P-224": "secp224r1", + "P-256": "secp256r1", + "P-384": "secp384r1", + "P-521": "secp521r1", + + "K-163": "sect163k1", + "K-233": "sect233k1", + "K-256": "secp256k1", + "K-283": "sect283k1", + "K-409": "sect409k1", + "K-571": "sect571k1", + + "B-163": "sect163r2", + "B-233": "sect233r1", + "B-283": "sect283r1", + "B-409": "sect409r1", + "B-571": "sect571r1", +} + + +def load_fips_ecdsa_key_pair_vectors(vector_data): + """ + Loads data out of the FIPS ECDSA KeyPair vector files. + """ + vectors = [] + key_data = None + for line in vector_data: + line = line.strip() + + if not line or line.startswith("#"): + continue + + if line[1:-1] in _ECDSA_CURVE_NAMES: + curve_name = _ECDSA_CURVE_NAMES[line[1:-1]] + + elif line.startswith("d = "): + if key_data is not None: + vectors.append(key_data) + + key_data = { + "curve": curve_name, + "d": int(line.split("=")[1], 16) + } + + elif key_data is not None: + if line.startswith("Qx = "): + key_data["x"] = int(line.split("=")[1], 16) + elif line.startswith("Qy = "): + key_data["y"] = int(line.split("=")[1], 16) + + assert key_data is not None + vectors.append(key_data) + + return vectors + + +def load_fips_ecdsa_signing_vectors(vector_data): + """ + Loads data out of the FIPS ECDSA SigGen vector files. + """ + vectors = [] + + curve_rx = re.compile( + r"\[(?P[PKB]-[0-9]{3}),SHA-(?P1|224|256|384|512)\]" + ) + + data = None + for line in vector_data: + line = line.strip() + + curve_match = curve_rx.match(line) + if curve_match: + curve_name = _ECDSA_CURVE_NAMES[curve_match.group("curve")] + digest_name = "SHA-{0}".format(curve_match.group("sha")) + + elif line.startswith("Msg = "): + if data is not None: + vectors.append(data) + + hexmsg = line.split("=")[1].strip().encode("ascii") + + data = { + "curve": curve_name, + "digest_algorithm": digest_name, + "message": binascii.unhexlify(hexmsg) + } + + elif data is not None: + if line.startswith("Qx = "): + data["x"] = int(line.split("=")[1], 16) + elif line.startswith("Qy = "): + data["y"] = int(line.split("=")[1], 16) + elif line.startswith("R = "): + data["r"] = int(line.split("=")[1], 16) + elif line.startswith("S = "): + data["s"] = int(line.split("=")[1], 16) + elif line.startswith("d = "): + data["d"] = int(line.split("=")[1], 16) + elif line.startswith("Result = "): + data["fail"] = line.split("=")[1].strip()[0] == "F" + + assert data is not None + vectors.append(data) + return vectors + + +def load_kasvs_dh_vectors(vector_data): + """ + Loads data out of the KASVS key exchange vector data + """ + + result_rx = re.compile(r"([FP]) \(([0-9]+) -") + + vectors = [] + data = { + "fail_z": False, + "fail_agree": False + } + + for line in vector_data: + line = line.strip() + + if not line or line.startswith("#"): + continue + + if line.startswith("P = "): + data["p"] = int(line.split("=")[1], 16) + elif line.startswith("Q = "): + data["q"] = int(line.split("=")[1], 16) + elif line.startswith("G = "): + data["g"] = int(line.split("=")[1], 16) + elif line.startswith("Z = "): + z_hex = line.split("=")[1].strip().encode("ascii") + data["z"] = binascii.unhexlify(z_hex) + elif line.startswith("XstatCAVS = "): + data["x1"] = int(line.split("=")[1], 16) + elif line.startswith("YstatCAVS = "): + data["y1"] = int(line.split("=")[1], 16) + elif line.startswith("XstatIUT = "): + data["x2"] = int(line.split("=")[1], 16) + elif line.startswith("YstatIUT = "): + data["y2"] = int(line.split("=")[1], 16) + elif line.startswith("Result = "): + result_str = line.split("=")[1].strip() + match = result_rx.match(result_str) + + if match.group(1) == "F": + if int(match.group(2)) in (5, 10): + data["fail_z"] = True + else: + data["fail_agree"] = True + + vectors.append(data) + + data = { + "p": data["p"], + "q": data["q"], + "g": data["g"], + "fail_z": False, + "fail_agree": False + } + + return vectors + + +def load_kasvs_ecdh_vectors(vector_data): + """ + Loads data out of the KASVS key exchange vector data + """ + + curve_name_map = { + "P-192": "secp192r1", + "P-224": "secp224r1", + "P-256": "secp256r1", + "P-384": "secp384r1", + "P-521": "secp521r1", + } + + result_rx = re.compile(r"([FP]) \(([0-9]+) -") + + tags = [] + sets = {} + vectors = [] + + # find info in header + for line in vector_data: + line = line.strip() + + if line.startswith("#"): + parm = line.split("Parameter set(s) supported:") + if len(parm) == 2: + names = parm[1].strip().split() + for n in names: + tags.append("[%s]" % n) + break + + # Sets Metadata + tag = None + curve = None + for line in vector_data: + line = line.strip() + + if not line or line.startswith("#"): + continue + + if line in tags: + tag = line + curve = None + elif line.startswith("[Curve selected:"): + curve = curve_name_map[line.split(':')[1].strip()[:-1]] + + if tag is not None and curve is not None: + sets[tag.strip("[]")] = curve + tag = None + if len(tags) == len(sets): + break + + # Data + data = { + "CAVS": {}, + "IUT": {}, + } + tag = None + for line in vector_data: + line = line.strip() + + if not line or line.startswith("#"): + continue + + if line.startswith("["): + tag = line.split()[0][1:] + elif line.startswith("COUNT = "): + data["COUNT"] = int(line.split("=")[1]) + elif line.startswith("dsCAVS = "): + data["CAVS"]["d"] = int(line.split("=")[1], 16) + elif line.startswith("QsCAVSx = "): + data["CAVS"]["x"] = int(line.split("=")[1], 16) + elif line.startswith("QsCAVSy = "): + data["CAVS"]["y"] = int(line.split("=")[1], 16) + elif line.startswith("dsIUT = "): + data["IUT"]["d"] = int(line.split("=")[1], 16) + elif line.startswith("QsIUTx = "): + data["IUT"]["x"] = int(line.split("=")[1], 16) + elif line.startswith("QsIUTy = "): + data["IUT"]["y"] = int(line.split("=")[1], 16) + elif line.startswith("OI = "): + data["OI"] = int(line.split("=")[1], 16) + elif line.startswith("Z = "): + data["Z"] = int(line.split("=")[1], 16) + elif line.startswith("DKM = "): + data["DKM"] = int(line.split("=")[1], 16) + elif line.startswith("Result = "): + result_str = line.split("=")[1].strip() + match = result_rx.match(result_str) + + if match.group(1) == "F": + data["fail"] = True + else: + data["fail"] = False + data["errno"] = int(match.group(2)) + + data["curve"] = sets[tag] + + vectors.append(data) + + data = { + "CAVS": {}, + "IUT": {}, + } + + return vectors + + +def load_x963_vectors(vector_data): + """ + Loads data out of the X9.63 vector data + """ + + vectors = [] + + # Sets Metadata + hashname = None + vector = {} + for line in vector_data: + line = line.strip() + + if line.startswith("[SHA"): + hashname = line[1:-1] + shared_secret_len = 0 + shared_info_len = 0 + key_data_len = 0 + elif line.startswith("[shared secret length"): + shared_secret_len = int(line[1:-1].split("=")[1].strip()) + elif line.startswith("[SharedInfo length"): + shared_info_len = int(line[1:-1].split("=")[1].strip()) + elif line.startswith("[key data length"): + key_data_len = int(line[1:-1].split("=")[1].strip()) + elif line.startswith("COUNT"): + count = int(line.split("=")[1].strip()) + vector["hash"] = hashname + vector["count"] = count + vector["shared_secret_length"] = shared_secret_len + vector["sharedinfo_length"] = shared_info_len + vector["key_data_length"] = key_data_len + elif line.startswith("Z"): + vector["Z"] = line.split("=")[1].strip() + assert math.ceil(shared_secret_len / 8) * 2 == len(vector["Z"]) + elif line.startswith("SharedInfo"): + if shared_info_len != 0: + vector["sharedinfo"] = line.split("=")[1].strip() + silen = len(vector["sharedinfo"]) + assert math.ceil(shared_info_len / 8) * 2 == silen + elif line.startswith("key_data"): + vector["key_data"] = line.split("=")[1].strip() + assert math.ceil(key_data_len / 8) * 2 == len(vector["key_data"]) + vectors.append(vector) + vector = {} + + return vectors + + +def load_nist_kbkdf_vectors(vector_data): + """ + Load NIST SP 800-108 KDF Vectors + """ + vectors = [] + test_data = None + tag = {} + + for line in vector_data: + line = line.strip() + + if not line or line.startswith("#"): + continue + + if line.startswith("[") and line.endswith("]"): + tag_data = line[1:-1] + name, value = [c.strip() for c in tag_data.split("=")] + if value.endswith('_BITS'): + value = int(value.split('_')[0]) + tag.update({name.lower(): value}) + continue + + tag.update({name.lower(): value.lower()}) + elif line.startswith("COUNT="): + test_data = dict() + test_data.update(tag) + vectors.append(test_data) + elif line.startswith("L"): + name, value = [c.strip() for c in line.split("=")] + test_data[name.lower()] = int(value) + else: + name, value = [c.strip() for c in line.split("=")] + test_data[name.lower()] = value.encode("ascii") + + return vectors