pyOpenSSL-0.15.1/0000755000076500000240000000000012513316322013671 5ustar hynekstaff00000000000000pyOpenSSL-0.15.1/ChangeLog0000644000076500000240000007425512513316203015456 0ustar hynekstaff000000000000002011-09-02 Hynek Schlawack * Release 0.15.1 2015-04-14 Glyph Lefkowitz * OpenSSL/SSL.py, OpenSSL/test/test_ssl.py: Fix a regression present in 0.15, where when an error occurs and no errno() is set, a KeyError is raised. This happens, for example, if Connection.shutdown() is called when the underlying transport has gone away. 2011-09-02 Hynek Schlawack * Release 0.15 2015-04-12 Jean-Paul Calderone * OpenSSL/rand.py, OpenSSL/SSL.py: APIs which previously accepted filenames only as bytes now accept them as either bytes or unicode (and respect sys.getfilesystemencoding()). 2015-03-23 Jean-Paul Calderone * OpenSSL/SSL.py: Add Cory Benfield's next-protocol-negotiation (NPN) bindings. 2015-03-15 Jean-Paul Calderone * OpenSSL/SSL.py: Add ``Connection.recv_into``, mirroring the builtin ``socket.recv_into``. Based on work from Cory Benfield. * OpenSSL/test/test_ssl.py: Add tests for ``recv_into``. 2015-01-30 Stephen Holsapple * OpenSSL/crypto.py: Expose ``X509StoreContext`` for verifying certificates. * OpenSSL/test/test_crypto.py: Add intermediate certificates for 2015-01-08 Paul Aurich * OpenSSL/SSL.py: ``Connection.shutdown`` now propagates errors from the underlying socket. 2014-12-11 Jean-Paul Calderone * OpenSSL/SSL.py: Fixed a regression ``Context.check_privatekey`` causing it to always succeed - even if it should fail. 2014-08-21 Alex Gaynor * OpenSSL/crypto.py: Fixed a regression where calling ``load_pkcs7_data`` with ``FILETYPE_ASN1`` would fail with a ``NameError``. 2014-05-05 Jean-Paul Calderone * OpenSSL/SSL.py: Fix a regression in which the first argument of the "verify" callback was incorrectly passed a ``Context`` instance instead of the ``Connection`` instance. * OpenSSL/test/test_ssl.py: Add a test for the value passed as the first argument of the "verify" callback. 2014-04-19 Jean-Paul Calderone * OpenSSL/crypto.py: Based on work from Alex Gaynor, Andrew Lutomirski, Tobias Oberstein, Laurens Van Houtven, and Hynek Schlawack, add ``get_elliptic_curve`` and ``get_elliptic_curves`` to support TLS ECDHE modes. * OpenSSL/SSL.py: Add ``Context.set_tmp_ecdh`` to configure a TLS context with a particular elliptic curve for ECDHE modes. 2014-04-19 Markus Unterwaditzer * OpenSSL/SSL.py: ``Connection.send`` and ``Connection.sendall`` now also accept the ``buffer`` type as data. 2014-04-05 Stephen Holsapple * OpenSSL/crypto.py: Make ``load_pkcs12`` backwards compatible with pyOpenSSL 0.13 by making passphrase optional. 2014-03-30 Fedor Brunner * OpenSSL/SSL.py: Add ``get_finished``, ``get_peer_finished`` methods to ``Connection``. If you use these methods to implement TLS channel binding (RFC 5929) disable session resumption because triple handshake attacks against TLS. 2014-03-29 Fedor Brunner * OpenSSL/SSL.py: Add ``get_cipher_name``, ``get_cipher_bits``, and ``get_cipher_version`` to ``Connection``. 2014-03-28 Jean-Paul Calderone * OpenSSL/tsafe.py: Replace the use of ``apply`` (which has been removed in Python 3) with the equivalent syntax. 2014-03-28 Jonathan Giannuzzi * OpenSSL/crypto.py: Fix memory leak in _X509_REVOKED_dup. * leakcheck/crypto.py: Add checks for _X509_REVOKED_dup, CRL.add_revoked and CRL.get_revoked. * setup.py: Require cryptography 0.3 to have the ASN1_TIME_free binding. 2014-03-02 Stephen Holsapple * OpenSSL/crypto.py: Add ``get_extensions`` method to ``X509Req``. 2014-02-23 Jean-Paul Calderone * Release 0.14 2014-01-09 Jean-Paul Calderone * OpenSSL: Port to the cffi-based OpenSSL bindings provided by 2013-10-06 Jean-Paul Calderone * OpenSSL/ssl/context.c: Add support for negotiating TLS v1.1 or v1.2. 2013-10-03 Christian Heimes * OpenSSL/crypto/x509.c: Fix an inconsistency in memory management in X509.get_serial_number which leads to crashes on some runtimes (certain Windows/Python 3.3 environments, at least). 2013-08-11 Christian Heimes * OpenSSL/crypto/x509ext.c: Fix handling of NULL bytes inside subjectAltName general names when formatting an X509 extension as a string. * OpenSSL/crypto/x509.c: Fix memory leak in get_extension(). 2012-04-03 Jean-Paul Calderone * OpenSSL/crypto/pkey.c: Release the GIL around RSA and DSA key generation, based on code from INADA Naoki. 2012-02-13 Jean-Paul Calderone * OpenSSL/ssl/ssl.c: Add session cache related constants for use with the new Context.set_session_cache_mode method. * OpenSSL/ssl/context.c: Add new Context methods set_session_cache_mode and get_session_cache_mode. 2011-11-01 Jean-Paul Calderone * OpenSSL/crypto/pkey.c: Raise TypeError when trying to check a PKey instance which has no private component, instead of crashing. Based on fix by . 2011-09-14 Žiga Seilnacht * OpenSSL/crypto/crypto.c: Allow exceptions from passphrase callbacks to propagate up out of load_privatekey * OpenSSL/crypto/crypto.c: Raise an exception when a too-long passphrase is returned from a passphrase callback, instead of silently truncating it. * OpenSSL/crypto/crypto.c: Fix a memory leak when a passphrase callback returns the wrong type. 2011-09-13 Jean-Paul Calderone * OpenSSL/crypto/crl.c: Add error handling for the use of X509_CRL_sign. 2011-09-11 Jonathan Ballet * doc/: Convert the LaTeX documentation to Sphinx-using ReST. * OpenSSL/: Convert the epytext API documentation to Sphinx-using ReST. 2011-09-08 Guillermo Gonzalez * OpenSSL/ssl/context.c: Add Context.set_mode method. * OpenSSL/ssl/ssl.c: Add MODE_RELEASE_BUFFERS and OP_NO_COMPRESSION constants. 2011-09-02 Jean-Paul Calderone * Release 0.13 2011-06-12 Jean-Paul Calderone * OpenSSL/crypto/pkey.c: Add the PKey.check method, mostly implemented by Rick Dean, to verify the internal consistency of a PKey instance. 2011-06-12 Jean-Paul Calderone * OpenSSL/crypto/crypto.c: Fix the sign and verify functions so they handle data with embedded NULs. Fix by David Brodsky . 2011-05-20 Jean-Paul Calderone * OpenSSL/ssl/connection.c, OpenSSL/test/test_ssl.py: Add a new method to the Connection type, get_peer_cert_chain, for retrieving the peer's certificate chain. 2011-05-19 Jean-Paul Calderone * OpenSSL/crypto/x509.c, OpenSSL/test/test_crypto.py: Add a new method to the X509 type, get_signature_algorithm, for inspecting the signature algorithm field of the certificate. Based on a patch from . 2011-05-10 Jean-Paul Calderone * OpenSSL/crypto/crypto.h: Work around a Windows/OpenSSL 1.0 issue explicitly including a Windows header before any OpenSSL headers. * OpenSSL/crypto/pkcs12.c: Work around an OpenSSL 1.0 issue by explicitly flushing errors known to be uninteresting after calling PKCS12_parse. * OpenSSL/ssl/context.c: Remove SSLv2 support if the underlying OpenSSL library does not provide it. * OpenSSL/test/test_crypto.py: Support an OpenSSL 1.0 change from MD5 to SHA1 by allowing either hash algorithm's result as the return value of X509.subject_name_hash. * OpenSSL/test/test_ssl.py: Support an OpenSSL 1.0 change from MD5 to SHA1 by constructing certificate files named using both hash algorithms' results when testing Context.load_verify_locations. * Support OpenSSL 1.0.0a. 2011-04-15 Jean-Paul Calderone * OpenSSL/ssl/ssl.c: Add OPENSSL_VERSION_NUMBER, SSLeay_version and related constants for retrieving version information about the underlying OpenSSL library. 2011-04-07 Jean-Paul Calderone * Release 0.12 2011-04-06 Jean-Paul Calderone * OpenSSL/crypto/x509.c: Add get_extension_count and get_extension to the X509 type, allowing read access to certificate extensions. * OpenSSL/crypto/x509ext.c: Add get_short_name and get_data to the X509Extension type, allowing read access to the contents of an extension. 2011-03-21 Olivier Hervieu * OpenSSL/ssl/ssl.c: Expose a number of symbolic constants for values passed to the connection "info" callback. 2011-01-22 Jean-Paul Calderone * OpenSSL/ssl/connection.py: Add support for new-style buffers (primarily memoryviews) to Connection.send and Connection.sendall. 2010-11-01 Jean-Paul Calderone * Release 0.11 2010-10-07 Jean-Paul Calderone * Initial support for Python 3.x throughout the codebase. 2010-09-14 Jean-Paul Calderone * OpenSSL/crypto/netscape_spki.c: Fix an off-by-one mistake in the error handling for NetscapeSPKI.verify. Add additional error checking to NetscapeSPKI.sign to handle the case where there is no private key. * OpenSSL/crypto/x509.c: Fix an overflow bug in the subject_name_hash method of the X509 type which would cause it to return negative values on 32 bit systems. * OpenSSL/crypto/x509req.c: Fix an off-by-one mistake in the error handling for X509Req.verify. * OpenSSL/ssl/context.c: Fix the error handling in the load_tmp_dh method of the Context type which would cause it to always raise MemoryError, regardless of the actual error (such as a bad file name). * OpenSSL/test/: Numerous unit tests added, both for above fixes and for other previously untested code paths. 2010-07-27 Jean-Paul Calderone * Re-arrange the repository so that the package can be built and used in-place without requiring installation. 2010-02-27 James Yonan * src/crypto/crypto.c: Added crypto.sign and crypto.verify methods that wrap EVP_Sign and EVP_Verify function families, using code derived from Dave Cridland's PyOpenSSL branch. * test/test_crypto.py: Added unit tests for crypto.sign and crypto.verify. 2010-01-27 Jean-Paul Calderone * src/ssl/connection.c, src/util.h: Apply patch from Sandro Tosi to fix misspellings of "compatibility". 2009-11-13 Jean-Paul Calderone * Release 0.10 2009-11-07 Žiga Seilnacht, Jean-Paul Calderone * src/ssl/connection.c, src/ssl/context.c: Add set_client_ca_list, add_client_ca, and get_client_ca_list to Context for manipulating the list of certificate authority names which are sent by servers with the certificate request message. * src/util.h: Add ssize-related defines if the version of Python being used does not have them. * setup.py: Significant changes to the way Windows builds are done, particularly the way OpenSSL headers and libraries are found (with the new --with-openssl argument to build_ext). 2009-08-27 Rick Dean , Jean-Paul Calderone * src/crypto/pkcs12.c: Add setters to the PKCS12 type for the certificate, private key, ca certificate list, and friendly name, and add a getter for the friendly name. Also add a method for exporting a PKCS12 object as a string. * test/test_crypto.py: Add lots of additional tests for the PKCS12 type. * doc/pyOpenSSL.tex: Documentation for the new PKCS12 methods. 2009-07-17 Rick Dean , Jean-Paul Calderone * src/crypto/x509ext.c: Add subject and issuer parameters to X509Extension, allowing creation of extensions which require that information. Fixes LP#322813. 2009-07-16 Jean-Paul Calderone * test/util.py: Changed the base TestCase's tearDown to assert that no errors were left in the OpenSSL error queue by the test. * src/crypto/crypto.c: Add a private helper in support of the TestCase.tearDown change. * src/crypto/x509name.c: Changed X509Name's getattr implementation to clean up the error queue. Fixes LP#314814. * test/util.c: Changed flush_error_queue to avoid a reference counting bug caused by macro expansion. 2009-07-16 Rick Dean * src/rand.c: Added OpenSSL.rand.bytes to get random bytes directly. * src/util.c: Added generic exceptions_from_error_queue to replace the various other implementations of this function. Also updated the rest of the codebase to use this version instead. 2009-07-05 Jean-Paul Calderone * test/util.py, test/test_ssl.py, test/test_crypto.py: Fold the Python 2.3 compatibility TestCase mixin into the TestCase defined in util.py. 2009-07-05 Jean-Paul Calderone * test/util.py, test/test_ssl.py, test/test_crypto.py: Stop trying to use Twisted's TestCase even when it's available. Instead, always use the stdlib TestCase with a few enhancements. 2009-07-04 Jean-Paul Calderone * Changed most extension types so that they can be instantiated using the type object rather than a factory function. The old factory functions are now aliases for the type objects. Fixes LP#312786. 2009-05-27 Jean-Paul Calderone * Changed all docstrings in extension modules to be friendlier towards Python programmers. Fixes LP#312787. 2009-05-27 Jean-Paul Calderone * src/crypto/x509ext.c: Correctly deallocate the new Extension instance when there is an error initializing it and it is not going to be returned. Resolves LP#368043. 2009-05-11 Jean-Paul Calderone * test/test_crypto.py: Use binary mode for the pipe to talk to the external openssl binary. The data being transported over this pipe is indeed binary, so previously it would often be truncated or otherwise mangled. * src/ssl/connection.h, src/ssl/connection.c, test/test_ssl.py: Extend the Connection class with support for in-memory BIOs. This allows SSL to be run without a real socket, useful for implementing EAP-TLS or using SSL with Windows IO completion ports, for example. Based heavily on contributions from Rick Dean. 2009-04-25 Jean-Paul Calderone * Release 0.9 2009-04-01 Jean-Paul Calderone Samuele Pedroni * src/util.h: Delete the TLS key before trying to set a new value for it in case the current thread identifier is a recycled one (if it is recycled, the key won't be set because there is already a value from the previous thread to have this identifier and to use the pyOpenSSL API). 2009-04-01 Jean-Paul Calderone * src/crypto/crypto.c: Add FILETYPE_TEXT for dumping keys and certificates and certificate signature requests to a text format. 2008-12-31 Jean-Paul Calderone * src/crypto/x509ext.c, test/test_crypto.py: Add the get_short_name method to X509Extension based on patch from Alex Stapleton. 2008-12-31 Jean-Paul Calderone * src/crypto/x509ext.c, test/test_crypto.py: Fix X509Extension so that it is possible to instantiate extensions which use s2i or r2i instead of v2i (an extremely obscure extension implementation detail). 2008-12-30 Jean-Paul Calderone * MANIFEST.in, src/crypto/crypto.c, src/crypto/x509.c, src/crypto/x509name.c, src/rand/rand.c, src/ssl/context.c: Changes which eliminate compiler warnings but should not change any behavior. 2008-12-28 Jean-Paul Calderone * test/test_ssl.py, src/ssl/ssl.c: Expose DTLS-related constants, OP_NO_QUERY_MTU, OP_COOKIE_EXCHANGE, and OP_NO_TICKET. 2008-12-28 Jean-Paul Calderone * src/ssl/context.c: Add a capath parameter to Context.load_verify_locations to allow Python code to specify either or both arguments to the underlying SSL_CTX_load_verify_locations API. * src/ssl/context.c: Add Context.set_default_verify_paths, a wrapper around SSL_CTX_set_default_verify_paths. 2008-12-28 Jean-Paul Calderone * test/test_crypto.py, src/crypto/x509req.c: Added get_version and set_version_methods to X509ReqType based on patch from Wouter van Bommel. Resolves LP#274418. 2008-09-22 Jean-Paul Calderone * Release 0.8 2008-10-19 Jean-Paul Calderone * tsafe.py: Revert the deprecation of the thread-safe Connection wrapper. The Connection class should not segfault if used from multiple threads now, but it generally cannot be relied on to produce correct results if used without the thread-safe wrapper. * doc/pyOpenSSL.tex: Correct the documentation for the set_passwd_cb callback parameter so that it accurately describes the required signature. 2008-09-22 Jean-Paul Calderone * Release 0.8a1 2008-09-21 Jean-Paul Calderone * src/ssl/ssl.h, src/ssl/ssl.c: Add a thread-local storage key which will be used to store and retrieve PyThreadState pointers whenever it is necessary to release or re-acquire the GIL. * src/ssl/context.c: Change global_verify_callback so that it unconditionally manipulates the Python threadstate, rather than checking the tstate field which is now always NULL. 2008-04-26 Jean-Paul Calderone * src/ssl/context.c: Change global_passphrase_callback and global_info_callback so that they acquire the GIL before invoking any CPython APIs and do not release it until after they are finished invoking all of them (based heavily on on patch from Dan Williams). * src/ssl/crypto.c: Initialize OpenSSL thread support so that it is valid to use OpenSSL APIs from more than one thread (based on patch from Dan Williams). * test/test_crypto.py: Add tests for load_privatekey and dump_privatekey when a passphrase or a passphrase callback is supplied. * test/test_ssl.py: Add tests for Context.set_passwd_cb and Context.set_info_callback. 2008-04-11 Jean-Paul Calderone * Release 0.7 2008-03-26 Jean-Paul Calderone * src/crypto/x509name.c: Add X509Name.get_components 2008-03-25 Jean-Paul Calderone * src/crypto/x509name.c: Add hash and der methods to X509Name. * src/crypto/x509.c: Fix a bug in X509.get_notBefore and X509.get_notAfter preventing UTCTIME format timestamps from working. 2008-03-12 Jean-Paul Calderone * Fix coding problems in examples/. Remove keys and certificates and add a note about how to generate new ones. 2008-03-09 Jean-Paul Calderone * src/crypto/x509.c: Add getters and setters for the notBefore and notAfter attributes of X509s. * src/crypto/pkey.h, src/crypto/pkey.c, src/crypto/x509req.c, src/crypto/x509.c: Track the initialized and public/private state of EVP_PKEY structures underlying the crypto_PKeyObj type and reject X509Req signature operations on keys not suitable for the task. 2008-03-06 Jean-Paul Calderone * src/crypto/x509name.c: Fix tp_compare so it only returns -1, 0, or 1. This eliminates a RuntimeWarning emitted by Python. * src/crypto/x509req.c: Fix reference counting for X509Name returned by X509Req.get_subject. This removes a segfault when the subject name outlives the request object. * src/crypto/x509.c: Change get_serial_number and set_serial_number to accept Python longs. * doc/pyOpenSSL.tex: A number of minor corrections. 2008-03-03 Jean-Paul Calderone * src/crypto/crypto.c: Expose X509_verify_cert_error_string. (patch from Victor Stinner) 2008-02-22 Jean-Paul Calderone * src/ssl/connection.c src/ssl/context.c src/ssl/ssl.c: Fix compilation on Windows. (patch from Michael Schneider) 2008-02-21 Jean-Paul Calderone * src/ssl/connection.c: Expose SSL_get_shutdown and SSL_set_shutdown. (patch from James Knight) * src/ssl/ssl.c: Expose SSL_SENT_SHUTDOWN and SSL_RECEIVED_SHUTDOWN. (patch from James Knight) 2008-02-19 Jean-Paul Calderone * src/ssl/context.c: Expose SSL_CTX_add_extra_chain_cert. * src/crypto/x509name.c: Fix memory leaks in __getattr__ and __setattr_ implementations. * src/crypto/x509.c: Fix memory leak in X509.get_pubkey(). * leakcheck/: An attempt at a systematic approach to leak elimination. 2004-08-13 Martin Sjögren * Released version 0.6. 2004-08-11 Martin Sjögren * doc/pyOpenSSL.tex: Updates to the docs. 2004-08-10 Martin Sjögren * src/crypto/x509.c: Add X509.add_extensions based on a patch from Han S. Lee. * src/ssl/ssl.c: Add more SSL_OP_ constants. Patch from Mihai Ibanescu. 2004-08-09 Martin Sjögren * setup.py src/crypto/: Add support for Netscape SPKI extensions based on a patch from Tollef Fog Heen. * src/crypto/crypto.c: Add support for python passphrase callbacks based on a patch from Robert Olson. 2004-08-03 Martin Sjögren * src/ssl/context.c: Applied patch from Frederic Peters to add Context.use_certificate_chain_file. * src/crypto/x509.c: Applid patch from Tollef Fog Heen to add X509.subject_name_hash and X509.digest. 2004-08-02 Martin Sjögren * src/crypto/crypto.c src/ssl/ssl.c: Applied patch from Bastian Kleineidam to fix full names of exceptions. 2004-07-19 Martin Sjögren * doc/pyOpenSSL.tex: Fix the errors regarding X509Name's field names. 2004-07-18 Martin Sjögren * examples/certgen.py: Fixed wrong attributes in doc string, thanks Remy. (SFbug#913315) * __init__.py, setup.py, version.py: Add __version__, as suggested by Ronald Oussoren in SFbug#888729. * examples/proxy.py: Fix typos, thanks Mihai Ibanescu. (SFpatch#895820) 2003-01-09 Martin Sjögren * Use cyclic GC protocol in SSL.Connection, SSL.Context, crypto.PKCS12 and crypto.X509Name. 2002-12-02 Martin Sjögren * tsafe.py: Add some missing methods. 2002-10-06 Martin Sjögren * __init__.py: Import tsafe too! 2002-10-05 Martin Sjögren * src/crypto/x509name.c: Use unicode strings instead of ordinary strings in getattr/setattr. Note that plain ascii strings should still work. 2002-09-17 Martin Sjögren * Released version 0.5.1. 2002-09-09 Martin Sjögren * setup.cfg: Fixed build requirements for rpms. 2002-09-07 Martin Sjögren * src/ssl/connection.c: Fix sendall() method. It segfaulted because it was too generous about giving away the GIL. * Added SecureXMLRPCServer example, contributed by Michal Wallace. 2002-09-06 Martin Sjögren * setup.cfg: Updated the build requirements. * src/ssl/connection.c: Fix includes for AIX. 2002-09-04 Anders Hammarquist * Added type checks in all the other places where we expect specific types of objects passed. 2002-09-04 Martin Sjögren * src/crypto/crypto.c: Added an explicit type check in the dump_* functions, so that they won't die when e.g. None is passed in. 2002-08-25 Martin Sjögren * doc/pyOpenSSL.tex: Docs for PKCS12. 2002-08-24 Martin Sjögren * src/crypto: Added basic PKCS12 support, thanks to Mark Welch 2002-08-16 Martin Sjögren * D'oh! Fixes for python 1.5 and python 2.1. 2002-08-15 Martin Sjögren * Version 0.5. Yay! 2002-07-25 Martin Sjögren * src/ssl/context.c: Added set_options method. * src/ssl/ssl.c: Added constants for Context.set_options method. 2002-07-23 Martin Sjögren * Updated docs * src/ssl/connection.c: Changed the get_cipher_list method to actually return a list! WARNING: This change makes the API incompatible with earlier versions! 2002-07-15 Martin Sjögren * src/ssl/connection.[ch]: Removed the fileno method, it uses the transport object's fileno instead. 2002-07-09 Martin Sjögren * src/crypto/x509.c src/crypto/x509name.c: Fixed segfault bug where you used an X509Name after its X509 had been destroyed. * src/crypto/crypto.[ch] src/crypto/x509req.c src/crypto/x509ext.[ch]: Added X509 Extension support. Thanks to maas-Maarten Zeeman * src/crypto/pkey.c: Added bits() and type() methods. 2002-07-08 Martin Sjögren * src/ssl/connection.c: Moved the contents of setup_ssl into the constructor, thereby fixing some segfault bugs :) * src/ssl/connection.c: Added connect_ex and sendall methods. * src/crypto/x509name.c: Cleaned up comparisons and NID lookup. Thank you Maas-Maarten Zeeman * src/rand/rand.c: Fix RAND_screen import. * src/crypto/crypto.c src/crypto/pkcs7.[ch]: Added PKCS7 management, courtesy of Maas-Maarten Zeeman * src/crypto/x509req.c: Added verify method. 2002-06-17 Martin Sjögren * rpm/, setup.cfg: Added improved RPM-building stuff, thanks to Mihai Ibanescu 2002-06-14 Martin Sjögren * examples/proxy.py: Example code for using OpenSSL through a proxy contributed by Mihai Ibanescu * Updated installation instruction and added them to the TeX manual. 2002-06-13 Martin Sjögren * src/ssl/context.c: Changed global_verify_callback so that it uses PyObject_IsTrue instead of requring ints. * Added pymemcompat.h to make the memory management uniform and backwards-compatible. * src/util.h: Added conditional definition of PyModule_AddObject and PyModule_AddIntConstant * src/ssl/connection.c: Socket methods are no longer explicitly wrapped. fileno() is the only method the transport layer object HAS to support, but if you want to use connect, accept or sock_shutdown, then the transport layer object has to supply connect, accept and shutdown respectively. 2002-06-12 Martin Sjögren * Changed comments to docstrings that are visible in Python. * src/ssl/connection.c: Added set_connect_state and set_accept_state methods. Thanks to Mark Welch for this. 2002-06-11 Martin Sjögren * src/ssl/connection.c: accept and connect now use SSL_set_accept_state and SSL_set_connect_state respectively, instead of SSL_accept and SSL_connect. * src/ssl/connection.c: Added want_read and want_write methods. 2002-06-05 Martin Sjögren * src/ssl/connection.c: Added error messages for windows. The code is copied from Python's socketmodule.c. Ick. * src/ssl/connection.c: Changed the parameters to the SysCallError. It always has a tuple (number, string) now, even though the number might not always be useful. 2002-04-05 Martin Sjögren * Worked more on the Debian packaging, hopefully the packages are getting into the main Debian archive soon. 2002-01-10 Martin Sjögren * Worked some more on the Debian packaging, it's turning out real nice. * Changed format on this file, I'm going to try to be a bit more verbose about my changes, and this format makes it easier. 2002-01-08 Martin Sjögren * Version 0.4.1 * Added some example code * Added the thread safe Connection object in the 'tsafe' submodule * New Debian packaging 2001-08-09 Martin Sjögren * Version 0.4 * Added a compare function for X509Name structures. * Moved the submodules to separate .so files, with tiny C APIs so they can communicate * Skeletal OpenSSL/__init__.py * Removed the err submodule, use crypto.Error and SSL.Error instead 2001-08-06 Martin Sjögren * Version 0.3 * Added more types for dealing with certificates (X509Store, X509Req, PKey) * Functionality to load private keys, certificates and certificate requests from memory buffers, and store them too * X509 and X509Name objects can now be modified as well, very neat when creating certificates ;) * Added SSL_MODE_AUTO_RETRY to smooth things for blocking sockets * Added a sock_shutdown() method to the Connection type * I don't understand why, but I can't use Py_InitModule() to create submodules in Python 2.0, the interpreter segfaults on the cleanup process when I do. I added a conditional compile on the version number, falling back to my own routine. It would of course be nice to investigate what is happening, but I don't have the time to do so * Do INCREF on the type objects before inserting them in the dictionary, so they will never reach refcount 0 (they are, after all, statically allocated) 2001-07-30 Martin Sjögren * Version 0.2 * Lots of tweaking and comments in the code * Now uses distutils instead of the stupid Setup file * Hacked doc/tools/mkhowto, html generation should now work 2001-07-16 Martin Sjögren * Initial release (0.1, don't expect much from this one :-) pyOpenSSL-0.15.1/CONTRIBUTING.rst0000644000076500000240000000535112373217023016340 0ustar hynekstaff00000000000000Contributing ============ First of all, thank you for your interest in contributing to pyOpenSSL! Filing bug reports ------------------ Bug reports are very welcome. Please file them on the Github issue tracker. Good bug reports come with extensive descriptions of the error and how to reproduce it. Reporters are strongly encouraged to include an `short, self contained, correct example `_. Patches ------- All patches to pyOpenSSL should be submitted in the form of pull requests to the main pyOpenSSL repository, ``pyca/pyopenssl``. These pull requests should satisfy the following properties: - The branch referenced should be a `feature branch`_ focusing on one particular improvement to pyOpenSSL. Create different branches and different pull requests for unrelated features or bugfixes. - The branch referenced should have a distinctive name (in particular, please do not open pull requests for your ``master`` branch). - Code should follow `PEP 8`_, especially in the "do what code around you does" sense. One notable way pyOpenSSL code differs, for example, is that there should be three empty lines between module-level elements,and two empty lines between class-level elements. Methods and functions are named in ``snake_case``. Follow OpenSSL naming for callables whenever possible is preferred. - Pull requests that introduce code must test all new behavior they introduce as well as for previously untested or poorly tested behavior that they touch. - Pull requests are not allowed to break existing tests. - Pull requests that introduce features or fix bugs should note those changes in the ``ChangeLog`` text file in the root of the repository. They should also document the changes, both in docstrings and in the documentation in the ``doc/`` directory. Finally, pull requests must be reviewed before merging. This process mirrors the `cryptography code review process`_. Everyone can perform reviews; this is a very valuable way to contribute, and is highly encouraged. Pull requests are merged by members of the `pyopenssl-committers team `_. They should, of course, keep all the requirements detailed in this document as well as the pyca/cryptography merge requirements in mind. The final responsibility for the reviewing of merged code lies with the person merging it; since pyOpenSSL is obviously a sensitive project from a security perspective, so reviewers are strongly encouraged to take this review and merge process very seriously. .. _PEP 8: http://legacy.python.org/dev/peps/pep-0008/ .. _cryptography code review process: https://cryptography.io/en/latest/development/reviewing-patches/ .. _feature branch: http://nvie.com/posts/a-successful-git-branching-model/ pyOpenSSL-0.15.1/doc/0000755000076500000240000000000012513316322014436 5ustar hynekstaff00000000000000pyOpenSSL-0.15.1/doc/api/0000755000076500000240000000000012513316322015207 5ustar hynekstaff00000000000000pyOpenSSL-0.15.1/doc/api/crypto.rst0000644000076500000240000004475612513100143017271 0ustar hynekstaff00000000000000.. _openssl-crypto: :py:mod:`crypto` --- Generic cryptographic module ================================================= .. py:module:: OpenSSL.crypto :synopsis: Generic cryptographic module .. py:data:: X509Type See :py:class:`X509`. .. py:class:: X509() A class representing X.509 certificates. .. py:data:: X509NameType See :py:class:`X509Name`. .. py:class:: X509Name(x509name) A class representing X.509 Distinguished Names. This constructor creates a copy of *x509name* which should be an instance of :py:class:`X509Name`. .. py:data:: X509ReqType See :py:class:`X509Req`. .. py:class:: X509Req() A class representing X.509 certificate requests. .. py:data:: X509StoreType See :py:class:`X509Store` .. py:data X509Store A class representing the X.509 store. .. py:data:: X509StoreContext A class representing the X.509 store context. .. py:data:: PKeyType See :py:class:`PKey`. .. py:class:: PKey() A class representing DSA or RSA keys. .. py:data:: PKCS7Type A Python type object representing the PKCS7 object type. .. py:data:: PKCS12Type A Python type object representing the PKCS12 object type. .. py:data:: X509ExtensionType See :py:class:`X509Extension`. .. py:class:: X509Extension(typename, critical, value[, subject][, issuer]) A class representing an X.509 v3 certificate extensions. See http://openssl.org/docs/apps/x509v3_config.html#STANDARD_EXTENSIONS for *typename* strings and their options. Optional parameters *subject* and *issuer* must be X509 objects. .. py:data:: NetscapeSPKIType See :py:class:`NetscapeSPKI`. .. py:class:: NetscapeSPKI([enc]) A class representing Netscape SPKI objects. If the *enc* argument is present, it should be a base64-encoded string representing a NetscapeSPKI object, as returned by the :py:meth:`b64_encode` method. .. py:class:: CRL() A class representing Certifcate Revocation List objects. .. py:class:: Revoked() A class representing Revocation objects of CRL. .. py:data:: FILETYPE_PEM FILETYPE_ASN1 File type constants. .. py:data:: TYPE_RSA TYPE_DSA Key type constants. .. py:exception:: Error Generic exception used in the :py:mod:`.crypto` module. .. py:function:: get_elliptic_curves Return a set of objects representing the elliptic curves supported in the OpenSSL build in use. The curve objects have a :py:class:`unicode` ``name`` attribute by which they identify themselves. The curve objects are useful as values for the argument accepted by :py:meth:`Context.set_tmp_ecdh` to specify which elliptical curve should be used for ECDHE key exchange. .. py:function:: get_elliptic_curve Return a single curve object selected by name. See :py:func:`get_elliptic_curves` for information about curve objects. If the named curve is not supported then :py:class:`ValueError` is raised. .. py:function:: dump_certificate(type, cert) Dump the certificate *cert* into a buffer string encoded with the type *type*. .. py:function:: dump_certificate_request(type, req) Dump the certificate request *req* into a buffer string encoded with the type *type*. .. py:function:: dump_privatekey(type, pkey[, cipher, passphrase]) Dump the private key *pkey* into a buffer string encoded with the type *type*, optionally (if *type* is :py:const:`FILETYPE_PEM`) encrypting it using *cipher* and *passphrase*. *passphrase* must be either a string or a callback for providing the pass phrase. .. py:function:: load_certificate(type, buffer) Load a certificate (X509) from the string *buffer* encoded with the type *type*. .. py:function:: load_certificate_request(type, buffer) Load a certificate request (X509Req) from the string *buffer* encoded with the type *type*. .. py:function:: load_privatekey(type, buffer[, passphrase]) Load a private key (PKey) from the string *buffer* encoded with the type *type* (must be one of :py:const:`FILETYPE_PEM` and :py:const:`FILETYPE_ASN1`). *passphrase* must be either a string or a callback for providing the pass phrase. .. py:function:: load_crl(type, buffer) Load Certificate Revocation List (CRL) data from a string *buffer*. *buffer* encoded with the type *type*. The type *type* must either :py:const:`FILETYPE_PEM` or :py:const:`FILETYPE_ASN1`). .. py:function:: load_pkcs7_data(type, buffer) Load pkcs7 data from the string *buffer* encoded with the type *type*. .. py:function:: load_pkcs12(buffer[, passphrase]) Load pkcs12 data from the string *buffer*. If the pkcs12 structure is encrypted, a *passphrase* must be included. The MAC is always checked and thus required. See also the man page for the C function :py:func:`PKCS12_parse`. .. py:function:: sign(key, data, digest) Sign a data string using the given key and message digest. *key* is a :py:class:`PKey` instance. *data* is a ``str`` instance. *digest* is a ``str`` naming a supported message digest type, for example :py:const:`sha1`. .. versionadded:: 0.11 .. py:function:: verify(certificate, signature, data, digest) Verify the signature for a data string. *certificate* is a :py:class:`X509` instance corresponding to the private key which generated the signature. *signature* is a *str* instance giving the signature itself. *data* is a *str* instance giving the data to which the signature applies. *digest* is a *str* instance naming the message digest type of the signature, for example :py:const:`sha1`. .. versionadded:: 0.11 .. _openssl-x509: X509 objects ------------ X509 objects have the following methods: .. py:method:: X509.get_issuer() Return an X509Name object representing the issuer of the certificate. .. py:method:: X509.get_pubkey() Return a :py:class:`PKey` object representing the public key of the certificate. .. py:method:: X509.get_serial_number() Return the certificate serial number. .. py:method:: X509.get_signature_algorithm() Return the signature algorithm used in the certificate. If the algorithm is undefined, raise :py:data:`ValueError`. .. versionadded:: 0.13 .. py:method:: X509.get_subject() Return an :py:class:`X509Name` object representing the subject of the certificate. .. py:method:: X509.get_version() Return the certificate version. .. py:method:: X509.get_notBefore() Return a string giving the time before which the certificate is not valid. The string is formatted as an ASN1 GENERALIZEDTIME:: YYYYMMDDhhmmssZ YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm If no value exists for this field, :py:data:`None` is returned. .. py:method:: X509.get_notAfter() Return a string giving the time after which the certificate is not valid. The string is formatted as an ASN1 GENERALIZEDTIME:: YYYYMMDDhhmmssZ YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm If no value exists for this field, :py:data:`None` is returned. .. py:method:: X509.set_notBefore(when) Change the time before which the certificate is not valid. *when* is a string formatted as an ASN1 GENERALIZEDTIME:: YYYYMMDDhhmmssZ YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm .. py:method:: X509.set_notAfter(when) Change the time after which the certificate is not valid. *when* is a string formatted as an ASN1 GENERALIZEDTIME:: YYYYMMDDhhmmssZ YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm .. py:method:: X509.gmtime_adj_notBefore(time) Adjust the timestamp (in GMT) when the certificate starts being valid. .. py:method:: X509.gmtime_adj_notAfter(time) Adjust the timestamp (in GMT) when the certificate stops being valid. .. py:method:: X509.has_expired() Checks the certificate's time stamp against current time. Returns true if the certificate has expired and false otherwise. .. py:method:: X509.set_issuer(issuer) Set the issuer of the certificate to *issuer*. .. py:method:: X509.set_pubkey(pkey) Set the public key of the certificate to *pkey*. .. py:method:: X509.set_serial_number(serialno) Set the serial number of the certificate to *serialno*. .. py:method:: X509.set_subject(subject) Set the subject of the certificate to *subject*. .. py:method:: X509.set_version(version) Set the certificate version to *version*. .. py:method:: X509.sign(pkey, digest) Sign the certificate, using the key *pkey* and the message digest algorithm identified by the string *digest*. .. py:method:: X509.subject_name_hash() Return the hash of the certificate subject. .. py:method:: X509.digest(digest_name) Return a digest of the certificate, using the *digest_name* method. *digest_name* must be a string describing a digest algorithm supported by OpenSSL (by EVP_get_digestbyname, specifically). For example, :py:const:`"md5"` or :py:const:`"sha1"`. .. py:method:: X509.add_extensions(extensions) Add the extensions in the sequence *extensions* to the certificate. .. py:method:: X509.get_extension_count() Return the number of extensions on this certificate. .. versionadded:: 0.12 .. py:method:: X509.get_extension(index) Retrieve the extension on this certificate at the given index. Extensions on a certificate are kept in order. The index parameter selects which extension will be returned. The returned object will be an :py:class:`X509Extension` instance. .. versionadded:: 0.12 .. _openssl-x509name: X509Name objects ---------------- X509Name objects have the following methods: .. py:method:: X509Name.hash() Return an integer giving the first four bytes of the MD5 digest of the DER representation of the name. .. py:method:: X509Name.der() Return a string giving the DER representation of the name. .. py:method:: X509Name.get_components() Return a list of two-tuples of strings giving the components of the name. X509Name objects have the following members: .. py:attribute:: X509Name.countryName The country of the entity. :py:attr:`C` may be used as an alias for :py:attr:`countryName`. .. py:attribute:: X509Name.stateOrProvinceName The state or province of the entity. :py:attr:`ST` may be used as an alias for :py:attr:`stateOrProvinceName`. .. py:attribute:: X509Name.localityName The locality of the entity. :py:attr:`L` may be used as an alias for :py:attr:`localityName`. .. py:attribute:: X509Name.organizationName The organization name of the entity. :py:attr:`O` may be used as an alias for :py:attr:`organizationName`. .. py:attribute:: X509Name.organizationalUnitName The organizational unit of the entity. :py:attr:`OU` may be used as an alias for :py:attr:`organizationalUnitName`. .. py:attribute:: X509Name.commonName The common name of the entity. :py:attr:`CN` may be used as an alias for :py:attr:`commonName`. .. py:attribute:: X509Name.emailAddress The e-mail address of the entity. .. _openssl-x509req: X509Req objects --------------- X509Req objects have the following methods: .. py:method:: X509Req.get_pubkey() Return a :py:class:`PKey` object representing the public key of the certificate request. .. py:method:: X509Req.get_subject() Return an :py:class:`X509Name` object representing the subject of the certificate. .. py:method:: X509Req.set_pubkey(pkey) Set the public key of the certificate request to *pkey*. .. py:method:: X509Req.sign(pkey, digest) Sign the certificate request, using the key *pkey* and the message digest algorithm identified by the string *digest*. .. py:method:: X509Req.verify(pkey) Verify a certificate request using the public key *pkey*. .. py:method:: X509Req.set_version(version) Set the version (RFC 2459, 4.1.2.1) of the certificate request to *version*. .. py:method:: X509Req.get_version() Get the version (RFC 2459, 4.1.2.1) of the certificate request. .. py:method:: X509Req.get_extensions() Get extensions to the request. .. versionadded:: 0.15 .. _openssl-x509store: X509Store objects ----------------- The X509Store object has currently just one method: .. py:method:: X509Store.add_cert(cert) Add the certificate *cert* to the certificate store. X509StoreContextError objects ----------------------------- The X509StoreContextError is an exception raised from `X509StoreContext.verify_certificate` in circumstances where a certificate cannot be verified in a provided context. The certificate for which the verification error was detected is given by the ``certificate`` attribute of the exception instance as a :class:`X509` instance. Details about the verification error are given in the exception's ``args`` attribute. X509StoreContext objects ------------------------ The X509StoreContext object is used for verifying a certificate against a set of trusted certificates. .. py:method:: X509StoreContext.verify_certificate() Verify a certificate in the context of this initialized `X509StoreContext`. On error, raises `X509StoreContextError`, otherwise does nothing. .. versionadded:: 0.15 .. _openssl-pkey: PKey objects ------------ The PKey object has the following methods: .. py:method:: PKey.bits() Return the number of bits of the key. .. py:method:: PKey.generate_key(type, bits) Generate a public/private key pair of the type *type* (one of :py:const:`TYPE_RSA` and :py:const:`TYPE_DSA`) with the size *bits*. .. py:method:: PKey.type() Return the type of the key. .. py:method:: PKey.check() Check the consistency of this key, returning True if it is consistent and raising an exception otherwise. This is only valid for RSA keys. See the OpenSSL RSA_check_key man page for further limitations. .. _openssl-pkcs7: PKCS7 objects ------------- PKCS7 objects have the following methods: .. py:method:: PKCS7.type_is_signed() FIXME .. py:method:: PKCS7.type_is_enveloped() FIXME .. py:method:: PKCS7.type_is_signedAndEnveloped() FIXME .. py:method:: PKCS7.type_is_data() FIXME .. py:method:: PKCS7.get_type_name() Get the type name of the PKCS7. .. _openssl-pkcs12: PKCS12 objects -------------- PKCS12 objects have the following methods: .. py:method:: PKCS12.export([passphrase=None][, iter=2048][, maciter=1]) Returns a PKCS12 object as a string. The optional *passphrase* must be a string not a callback. See also the man page for the C function :py:func:`PKCS12_create`. .. py:method:: PKCS12.get_ca_certificates() Return CA certificates within the PKCS12 object as a tuple. Returns :py:const:`None` if no CA certificates are present. .. py:method:: PKCS12.get_certificate() Return certificate portion of the PKCS12 structure. .. py:method:: PKCS12.get_friendlyname() Return friendlyName portion of the PKCS12 structure. .. py:method:: PKCS12.get_privatekey() Return private key portion of the PKCS12 structure .. py:method:: PKCS12.set_ca_certificates(cacerts) Replace or set the CA certificates within the PKCS12 object with the sequence *cacerts*. Set *cacerts* to :py:const:`None` to remove all CA certificates. .. py:method:: PKCS12.set_certificate(cert) Replace or set the certificate portion of the PKCS12 structure. .. py:method:: PKCS12.set_friendlyname(name) Replace or set the friendlyName portion of the PKCS12 structure. .. py:method:: PKCS12.set_privatekey(pkey) Replace or set private key portion of the PKCS12 structure .. _openssl-509ext: X509Extension objects --------------------- X509Extension objects have several methods: .. py:method:: X509Extension.get_critical() Return the critical field of the extension object. .. py:method:: X509Extension.get_short_name() Retrieve the short descriptive name for this extension. The result is a byte string like :py:const:`basicConstraints`. .. versionadded:: 0.12 .. py:method:: X509Extension.get_data() Retrieve the data for this extension. The result is the ASN.1 encoded form of the extension data as a byte string. .. versionadded:: 0.12 .. _openssl-netscape-spki: NetscapeSPKI objects -------------------- NetscapeSPKI objects have the following methods: .. py:method:: NetscapeSPKI.b64_encode() Return a base64-encoded string representation of the object. .. py:method:: NetscapeSPKI.get_pubkey() Return the public key of object. .. py:method:: NetscapeSPKI.set_pubkey(key) Set the public key of the object to *key*. .. py:method:: NetscapeSPKI.sign(key, digest_name) Sign the NetscapeSPKI object using the given *key* and *digest_name*. *digest_name* must be a string describing a digest algorithm supported by OpenSSL (by EVP_get_digestbyname, specifically). For example, :py:const:`"md5"` or :py:const:`"sha1"`. .. py:method:: NetscapeSPKI.verify(key) Verify the NetscapeSPKI object using the given *key*. .. _crl: CRL objects ----------- CRL objects have the following methods: .. py:method:: CRL.add_revoked(revoked) Add a Revoked object to the CRL, by value not reference. .. py:method:: CRL.export(cert, key[, type=FILETYPE_PEM][, days=100][, digest=b'md5']) Use *cert* and *key* to sign the CRL and return the CRL as a string. *days* is the number of days before the next CRL is due. *digest* is the algorithm that will be used to sign CRL. .. py:method:: CRL.get_revoked() Return a tuple of Revoked objects, by value not reference. .. _revoked: Revoked objects --------------- Revoked objects have the following methods: .. py:method:: Revoked.all_reasons() Return a list of all supported reasons. .. py:method:: Revoked.get_reason() Return the revocation reason as a str. Can be None, which differs from "Unspecified". .. py:method:: Revoked.get_rev_date() Return the revocation date as a str. The string is formatted as an ASN1 GENERALIZEDTIME. .. py:method:: Revoked.get_serial() Return a str containing a hex number of the serial of the revoked certificate. .. py:method:: Revoked.set_reason(reason) Set the revocation reason. *reason* must be None or a string, but the values are limited. Spaces and case are ignored. See :py:meth:`all_reasons`. .. py:method:: Revoked.set_rev_date(date) Set the revocation date. The string is formatted as an ASN1 GENERALIZEDTIME. .. py:method:: Revoked.set_serial(serial) *serial* is a string containing a hex number of the serial of the revoked certificate. pyOpenSSL-0.15.1/doc/api/rand.rst0000644000076500000240000000460612263743062016702 0ustar hynekstaff00000000000000.. _openssl-rand: :py:mod:`rand` --- An interface to the OpenSSL pseudo random number generator ============================================================================= .. py:module:: OpenSSL.rand :synopsis: An interface to the OpenSSL pseudo random number generator This module handles the OpenSSL pseudo random number generator (PRNG) and declares the following: .. py:function:: add(string, entropy) Mix bytes from *string* into the PRNG state. The *entropy* argument is (the lower bound of) an estimate of how much randomness is contained in *string*, measured in bytes. For more information, see e.g. :rfc:`1750`. .. py:function:: bytes(num_bytes) Get some random bytes from the PRNG as a string. This is a wrapper for the C function :py:func:`RAND_bytes`. .. py:function:: cleanup() Erase the memory used by the PRNG. This is a wrapper for the C function :py:func:`RAND_cleanup`. .. py:function:: egd(path[, bytes]) Query the `Entropy Gathering Daemon `_ on socket *path* for *bytes* bytes of random data and uses :py:func:`add` to seed the PRNG. The default value of *bytes* is 255. .. py:function:: load_file(path[, bytes]) Read *bytes* bytes (or all of it, if *bytes* is negative) of data from the file *path* to seed the PRNG. The default value of *bytes* is -1. .. py:function:: screen() Add the current contents of the screen to the PRNG state. Availability: Windows. .. py:function:: seed(string) This is equivalent to calling :py:func:`add` with *entropy* as the length of the string. .. py:function:: status() Returns true if the PRNG has been seeded with enough data, and false otherwise. .. py:function:: write_file(path) Write a number of random bytes (currently 1024) to the file *path*. This file can then be used with :py:func:`load_file` to seed the PRNG again. .. py:exception:: Error If the current RAND method supports any errors, this is raised when needed. The default method does not raise this when the entropy pool is depleted. Whenever this exception is raised directly, it has a list of error messages from the OpenSSL error queue, where each item is a tuple *(lib, function, reason)*. Here *lib*, *function* and *reason* are all strings, describing where and what the problem is. See :manpage:`err(3)` for more information. pyOpenSSL-0.15.1/doc/api/ssl.rst0000644000076500000240000006664512513070656016572 0ustar hynekstaff00000000000000.. _openssl-ssl: :py:mod:`SSL` --- An interface to the SSL-specific parts of OpenSSL =================================================================== .. py:module:: OpenSSL.SSL :synopsis: An interface to the SSL-specific parts of OpenSSL This module handles things specific to SSL. There are two objects defined: Context, Connection. .. py:data:: SSLv2_METHOD SSLv3_METHOD SSLv23_METHOD TLSv1_METHOD TLSv1_1_METHOD TLSv1_2_METHOD These constants represent the different SSL methods to use when creating a context object. If the underlying OpenSSL build is missing support for any of these protocols, constructing a :py:class:`Context` using the corresponding :py:const:`*_METHOD` will raise an exception. .. py:data:: VERIFY_NONE VERIFY_PEER VERIFY_FAIL_IF_NO_PEER_CERT These constants represent the verification mode used by the Context object's :py:meth:`set_verify` method. .. py:data:: FILETYPE_PEM FILETYPE_ASN1 File type constants used with the :py:meth:`use_certificate_file` and :py:meth:`use_privatekey_file` methods of Context objects. .. py:data:: OP_SINGLE_DH_USE Constant used with :py:meth:`set_options` of Context objects. When this option is used, a new key will always be created when using ephemeral Diffie-Hellman. .. py:data:: OP_EPHEMERAL_RSA Constant used with :py:meth:`set_options` of Context objects. When this option is used, ephemeral RSA keys will always be used when doing RSA operations. .. py:data:: OP_NO_TICKET Constant used with :py:meth:`set_options` of Context objects. When this option is used, the session ticket extension will not be used. .. py:data:: OP_NO_COMPRESSION Constant used with :py:meth:`set_options` of Context objects. When this option is used, compression will not be used. .. py:data:: OP_NO_SSLv2 OP_NO_SSLv3 OP_NO_TLSv1 OP_NO_TLSv1_1 OP_NO_TLSv1_2 Constants used with :py:meth:`set_options` of Context objects. Each of these options disables one version of the SSL/TLS protocol. This is interesting if you're using e.g. :py:const:`SSLv23_METHOD` to get an SSLv2-compatible handshake, but don't want to use SSLv2. If the underlying OpenSSL build is missing support for any of these protocols, the :py:const:`OP_NO_*` constant may be undefined. .. py:data:: SSLEAY_VERSION SSLEAY_CFLAGS SSLEAY_BUILT_ON SSLEAY_PLATFORM SSLEAY_DIR Constants used with :py:meth:`SSLeay_version` to specify what OpenSSL version information to retrieve. See the man page for the :py:func:`SSLeay_version` C API for details. .. py:data:: SESS_CACHE_OFF SESS_CACHE_CLIENT SESS_CACHE_SERVER SESS_CACHE_BOTH SESS_CACHE_NO_AUTO_CLEAR SESS_CACHE_NO_INTERNAL_LOOKUP SESS_CACHE_NO_INTERNAL_STORE SESS_CACHE_NO_INTERNAL Constants used with :py:meth:`Context.set_session_cache_mode` to specify the behavior of the session cache and potential session reuse. See the man page for the :py:func:`SSL_CTX_set_session_cache_mode` C API for details. .. versionadded:: 0.14 .. py:data:: OPENSSL_VERSION_NUMBER An integer giving the version number of the OpenSSL library used to build this version of pyOpenSSL. See the man page for the :py:func:`SSLeay_version` C API for details. .. py:function:: SSLeay_version(type) Retrieve a string describing some aspect of the underlying OpenSSL version. The type passed in should be one of the :py:const:`SSLEAY_*` constants defined in this module. .. py:data:: ContextType See :py:class:`Context`. .. py:class:: Context(method) A class representing SSL contexts. Contexts define the parameters of one or more SSL connections. *method* should be :py:const:`SSLv2_METHOD`, :py:const:`SSLv3_METHOD`, :py:const:`SSLv23_METHOD`, :py:const:`TLSv1_METHOD`, :py:const:`TLSv1_1_METHOD`, or :py:const:`TLSv1_2_METHOD`. .. py:class:: Session() A class representing an SSL session. A session defines certain connection parameters which may be re-used to speed up the setup of subsequent connections. .. versionadded:: 0.14 .. py:data:: ConnectionType See :py:class:`Connection`. .. py:class:: Connection(context, socket) A class representing SSL connections. *context* should be an instance of :py:class:`Context` and *socket* should be a socket [#connection-context-socket]_ object. *socket* may be *None*; in this case, the Connection is created with a memory BIO: see the :py:meth:`bio_read`, :py:meth:`bio_write`, and :py:meth:`bio_shutdown` methods. .. py:exception:: Error This exception is used as a base class for the other SSL-related exceptions, but may also be raised directly. Whenever this exception is raised directly, it has a list of error messages from the OpenSSL error queue, where each item is a tuple *(lib, function, reason)*. Here *lib*, *function* and *reason* are all strings, describing where and what the problem is. See :manpage:`err(3)` for more information. .. py:exception:: ZeroReturnError This exception matches the error return code :py:data:`SSL_ERROR_ZERO_RETURN`, and is raised when the SSL Connection has been closed. In SSL 3.0 and TLS 1.0, this only occurs if a closure alert has occurred in the protocol, i.e. the connection has been closed cleanly. Note that this does not necessarily mean that the transport layer (e.g. a socket) has been closed. It may seem a little strange that this is an exception, but it does match an :py:data:`SSL_ERROR` code, and is very convenient. .. py:exception:: WantReadError The operation did not complete; the same I/O method should be called again later, with the same arguments. Any I/O method can lead to this since new handshakes can occur at any time. The wanted read is for **dirty** data sent over the network, not the **clean** data inside the tunnel. For a socket based SSL connection, **read** means data coming at us over the network. Until that read succeeds, the attempted :py:meth:`OpenSSL.SSL.Connection.recv`, :py:meth:`OpenSSL.SSL.Connection.send`, or :py:meth:`OpenSSL.SSL.Connection.do_handshake` is prevented or incomplete. You probably want to :py:meth:`select()` on the socket before trying again. .. py:exception:: WantWriteError See :py:exc:`WantReadError`. The socket send buffer may be too full to write more data. .. py:exception:: WantX509LookupError The operation did not complete because an application callback has asked to be called again. The I/O method should be called again later, with the same arguments. .. note:: This won't occur in this version, as there are no such callbacks in this version. .. py:exception:: SysCallError The :py:exc:`SysCallError` occurs when there's an I/O error and OpenSSL's error queue does not contain any information. This can mean two things: An error in the transport protocol, or an end of file that violates the protocol. The parameter to the exception is always a pair *(errnum, errstr)*. .. _openssl-context: Context objects --------------- Context objects have the following methods: .. :py:class:: OpenSSL.SSL.Context .. py:method:: Context.check_privatekey() Check if the private key (loaded with :py:meth:`use_privatekey`) matches the certificate (loaded with :py:meth:`use_certificate`). Returns :py:data:`None` if they match, raises :py:exc:`Error` otherwise. .. py:method:: Context.get_app_data() Retrieve application data as set by :py:meth:`set_app_data`. .. py:method:: Context.get_cert_store() Retrieve the certificate store (a X509Store object) that the context uses. This can be used to add "trusted" certificates without using the :py:meth:`load_verify_locations` method. .. py:method:: Context.get_timeout() Retrieve session timeout, as set by :py:meth:`set_timeout`. The default is 300 seconds. .. py:method:: Context.get_verify_depth() Retrieve the Context object's verify depth, as set by :py:meth:`set_verify_depth`. .. py:method:: Context.get_verify_mode() Retrieve the Context object's verify mode, as set by :py:meth:`set_verify`. .. py:method:: Context.load_client_ca(pemfile) Read a file with PEM-formatted certificates that will be sent to the client when requesting a client certificate. .. py:method:: Context.set_client_ca_list(certificate_authorities) Replace the current list of preferred certificate signers that would be sent to the client when requesting a client certificate with the *certificate_authorities* sequence of :py:class:`OpenSSL.crypto.X509Name`'s. .. versionadded:: 0.10 .. py:method:: Context.add_client_ca(certificate_authority) Extract a :py:class:`OpenSSL.crypto.X509Name` from the *certificate_authority* :py:class:`OpenSSL.crypto.X509` certificate and add it to the list of preferred certificate signers sent to the client when requesting a client certificate. .. versionadded:: 0.10 .. py:method:: Context.load_verify_locations(pemfile, capath) Specify where CA certificates for verification purposes are located. These are trusted certificates. Note that the certificates have to be in PEM format. If capath is passed, it must be a directory prepared using the ``c_rehash`` tool included with OpenSSL. Either, but not both, of *pemfile* or *capath* may be :py:data:`None`. .. py:method:: Context.set_default_verify_paths() Specify that the platform provided CA certificates are to be used for verification purposes. This method may not work properly on OS X. .. py:method:: Context.load_tmp_dh(dhfile) Load parameters for Ephemeral Diffie-Hellman from *dhfile*. .. py:method:: Context.set_tmp_ecdh(curve) Select a curve to use for ECDHE key exchange. The valid values of *curve* are the objects returned by :py:func:`OpenSSL.crypto.get_elliptic_curves` or :py:func:`OpenSSL.crypto.get_elliptic_curve`. .. py:method:: Context.set_app_data(data) Associate *data* with this Context object. *data* can be retrieved later using the :py:meth:`get_app_data` method. .. py:method:: Context.set_cipher_list(ciphers) Set the list of ciphers to be used in this context. See the OpenSSL manual for more information (e.g. :manpage:`ciphers(1)`) .. py:method:: Context.set_info_callback(callback) Set the information callback to *callback*. This function will be called from time to time during SSL handshakes. *callback* should take three arguments: a Connection object and two integers. The first integer specifies where in the SSL handshake the function was called, and the other the return code from a (possibly failed) internal function call. .. py:method:: Context.set_options(options) Add SSL options. Options you have set before are not cleared! This method should be used with the :py:const:`OP_*` constants. .. py:method:: Context.set_mode(mode) Add SSL mode. Modes you have set before are not cleared! This method should be used with the :py:const:`MODE_*` constants. .. py:method:: Context.set_passwd_cb(callback[, userdata]) Set the passphrase callback to *callback*. This function will be called when a private key with a passphrase is loaded. *callback* must accept three positional arguments. First, an integer giving the maximum length of the passphrase it may return. If the returned passphrase is longer than this, it will be truncated. Second, a boolean value which will be true if the user should be prompted for the passphrase twice and the callback should verify that the two values supplied are equal. Third, the value given as the *userdata* parameter to :py:meth:`set_passwd_cb`. If an error occurs, *callback* should return a false value (e.g. an empty string). .. py:method:: Context.set_session_cache_mode(mode) Set the behavior of the session cache used by all connections using this Context. The previously set mode is returned. See :py:const:`SESS_CACHE_*` for details about particular modes. .. versionadded:: 0.14 .. py:method:: Context.get_session_cache_mode() Get the current session cache mode. .. versionadded:: 0.14 .. py:method:: Context.set_session_id(name) Set the context *name* within which a session can be reused for this Context object. This is needed when doing session resumption, because there is no way for a stored session to know which Context object it is associated with. *name* may be any binary data. .. py:method:: Context.set_timeout(timeout) Set the timeout for newly created sessions for this Context object to *timeout*. *timeout* must be given in (whole) seconds. The default value is 300 seconds. See the OpenSSL manual for more information (e.g. :manpage:`SSL_CTX_set_timeout(3)`). .. py:method:: Context.set_verify(mode, callback) Set the verification flags for this Context object to *mode* and specify that *callback* should be used for verification callbacks. *mode* should be one of :py:const:`VERIFY_NONE` and :py:const:`VERIFY_PEER`. If :py:const:`VERIFY_PEER` is used, *mode* can be OR:ed with :py:const:`VERIFY_FAIL_IF_NO_PEER_CERT` and :py:const:`VERIFY_CLIENT_ONCE` to further control the behaviour. *callback* should take five arguments: A Connection object, an X509 object, and three integer variables, which are in turn potential error number, error depth and return code. *callback* should return true if verification passes and false otherwise. .. py:method:: Context.set_verify_depth(depth) Set the maximum depth for the certificate chain verification that shall be allowed for this Context object. .. py:method:: Context.use_certificate(cert) Use the certificate *cert* which has to be a X509 object. .. py:method:: Context.add_extra_chain_cert(cert) Adds the certificate *cert*, which has to be a X509 object, to the certificate chain presented together with the certificate. .. py:method:: Context.use_certificate_chain_file(file) Load a certificate chain from *file* which must be PEM encoded. .. py:method:: Context.use_privatekey(pkey) Use the private key *pkey* which has to be a PKey object. .. py:method:: Context.use_certificate_file(file[, format]) Load the first certificate found in *file*. The certificate must be in the format specified by *format*, which is either :py:const:`FILETYPE_PEM` or :py:const:`FILETYPE_ASN1`. The default is :py:const:`FILETYPE_PEM`. .. py:method:: Context.use_privatekey_file(file[, format]) Load the first private key found in *file*. The private key must be in the format specified by *format*, which is either :py:const:`FILETYPE_PEM` or :py:const:`FILETYPE_ASN1`. The default is :py:const:`FILETYPE_PEM`. .. py:method:: Context.set_tlsext_servername_callback(callback) Specify a one-argument callable to use as the TLS extension server name callback. When a connection using the server name extension is made using this context, the callback will be invoked with the :py:class:`Connection` instance. .. versionadded:: 0.13 .. py:method:: Context.set_npn_advertise_callback(callback) Specify a callback function that will be called when offering `Next Protocol Negotiation `_ as a server. *callback* should be the callback function. It will be invoked with one argument, the :py:class:`Connection` instance. It should return a list of bytestrings representing the advertised protocols, like ``[b'http/1.1', b'spdy/2']``. .. versionadded:: 0.15 .. py:method:: Context.set_npn_select_callback(callback): Specify a callback function that will be called when a server offers Next Protocol Negotiation options. *callback* should be the callback function. It will be invoked with two arguments: the :py:class:`Connection`, and a list of offered protocols as bytestrings, e.g. ``[b'http/1.1', b'spdy/2']``. It should return one of those bytestrings, the chosen protocol. .. versionadded:: 0.15 .. py:method:: Context.set_alpn_protos(protos) Specify the protocols that the client is prepared to speak after the TLS connection has been negotiated using Application Layer Protocol Negotiation. *protos* should be a list of protocols that the client is offering, each as a bytestring. For example, ``[b'http/1.1', b'spdy/2']``. .. py:method:: Context.set_alpn_select_callback(callback) Specify a callback function that will be called on the server when a client offers protocols using Application Layer Protocol Negotiation. *callback* should be the callback function. It will be invoked with two arguments: the :py:class:`Connection` and a list of offered protocols as bytestrings, e.g. ``[b'http/1.1', b'spdy/2']``. It should return one of these bytestrings, the chosen protocol. .. _openssl-session: Session objects --------------- Session objects have no methods. .. _openssl-connection: Connection objects ------------------ Connection objects have the following methods: .. py:method:: Connection.accept() Call the :py:meth:`accept` method of the underlying socket and set up SSL on the returned socket, using the Context object supplied to this Connection object at creation. Returns a pair *(conn, address)*. where *conn* is the new Connection object created, and *address* is as returned by the socket's :py:meth:`accept`. .. py:method:: Connection.bind(address) Call the :py:meth:`bind` method of the underlying socket. .. py:method:: Connection.close() Call the :py:meth:`close` method of the underlying socket. Note: If you want correct SSL closure, you need to call the :py:meth:`shutdown` method first. .. py:method:: Connection.connect(address) Call the :py:meth:`connect` method of the underlying socket and set up SSL on the socket, using the Context object supplied to this Connection object at creation. .. py:method:: Connection.connect_ex(address) Call the :py:meth:`connect_ex` method of the underlying socket and set up SSL on the socket, using the Context object supplied to this Connection object at creation. Note that if the :py:meth:`connect_ex` method of the socket doesn't return 0, SSL won't be initialized. .. py:method:: Connection.do_handshake() Perform an SSL handshake (usually called after :py:meth:`renegotiate` or one of :py:meth:`set_accept_state` or :py:meth:`set_accept_state`). This can raise the same exceptions as :py:meth:`send` and :py:meth:`recv`. .. py:method:: Connection.fileno() Retrieve the file descriptor number for the underlying socket. .. py:method:: Connection.listen(backlog) Call the :py:meth:`listen` method of the underlying socket. .. py:method:: Connection.get_app_data() Retrieve application data as set by :py:meth:`set_app_data`. .. py:method:: Connection.get_cipher_list() Retrieve the list of ciphers used by the Connection object. WARNING: This API has changed. It used to take an optional parameter and just return a string, but not it returns the entire list in one go. .. py:method:: Connection.get_client_ca_list() Retrieve the list of preferred client certificate issuers sent by the server as :py:class:`OpenSSL.crypto.X509Name` objects. If this is a client :py:class:`Connection`, the list will be empty until the connection with the server is established. If this is a server :py:class:`Connection`, return the list of certificate authorities that will be sent or has been sent to the client, as controlled by this :py:class:`Connection`'s :py:class:`Context`. .. versionadded:: 0.10 .. py:method:: Connection.get_context() Retrieve the Context object associated with this Connection. .. py:method:: Connection.set_context(context) Specify a replacement Context object for this Connection. .. py:method:: Connection.get_peer_certificate() Retrieve the other side's certificate (if any) .. py:method:: Connection.get_peer_cert_chain() Retrieve the tuple of the other side's certificate chain (if any) .. py:method:: Connection.getpeername() Call the :py:meth:`getpeername` method of the underlying socket. .. py:method:: Connection.getsockname() Call the :py:meth:`getsockname` method of the underlying socket. .. py:method:: Connection.getsockopt(level, optname[, buflen]) Call the :py:meth:`getsockopt` method of the underlying socket. .. py:method:: Connection.pending() Retrieve the number of bytes that can be safely read from the SSL buffer (**not** the underlying transport buffer). .. py:method:: Connection.recv(bufsize) Receive data from the Connection. The return value is a string representing the data received. The maximum amount of data to be received at once, is specified by *bufsize*. .. py:method:: Connection.recv_into(buffer[, nbytes[, flags]]) Receive data from the Connection and copy it directly into the provided buffer. The return value is the number of bytes read from the connection. The maximum amount of data to be received at once is specified by *nbytes*. *flags* is accepted for compatibility with ``socket.recv_into`` but its value is ignored. .. py:method:: Connection.bio_write(bytes) If the Connection was created with a memory BIO, this method can be used to add bytes to the read end of that memory BIO. The Connection can then read the bytes (for example, in response to a call to :py:meth:`recv`). .. py:method:: Connection.renegotiate() Renegotiate the SSL session. Call this if you wish to change cipher suites or anything like that. .. py:method:: Connection.send(string) Send the *string* data to the Connection. .. py:method:: Connection.bio_read(bufsize) If the Connection was created with a memory BIO, this method can be used to read bytes from the write end of that memory BIO. Many Connection methods will add bytes which must be read in this manner or the buffer will eventually fill up and the Connection will be able to take no further actions. .. py:method:: Connection.sendall(string) Send all of the *string* data to the Connection. This calls :py:meth:`send` repeatedly until all data is sent. If an error occurs, it's impossible to tell how much data has been sent. .. py:method:: Connection.set_accept_state() Set the connection to work in server mode. The handshake will be handled automatically by read/write. .. py:method:: Connection.set_app_data(data) Associate *data* with this Connection object. *data* can be retrieved later using the :py:meth:`get_app_data` method. .. py:method:: Connection.set_connect_state() Set the connection to work in client mode. The handshake will be handled automatically by read/write. .. py:method:: Connection.setblocking(flag) Call the :py:meth:`setblocking` method of the underlying socket. .. py:method:: Connection.setsockopt(level, optname, value) Call the :py:meth:`setsockopt` method of the underlying socket. .. py:method:: Connection.shutdown() Send the shutdown message to the Connection. Returns true if the shutdown message exchange is completed and false otherwise (in which case you call :py:meth:`recv` or :py:meth:`send` when the connection becomes readable/writeable. .. py:method:: Connection.get_shutdown() Get the shutdown state of the Connection. Returns a bitvector of either or both of *SENT_SHUTDOWN* and *RECEIVED_SHUTDOWN*. .. py:method:: Connection.set_shutdown(state) Set the shutdown state of the Connection. *state* is a bitvector of either or both of *SENT_SHUTDOWN* and *RECEIVED_SHUTDOWN*. .. py:method:: Connection.sock_shutdown(how) Call the :py:meth:`shutdown` method of the underlying socket. .. py:method:: Connection.bio_shutdown() If the Connection was created with a memory BIO, this method can be used to indicate that *end of file* has been reached on the read end of that memory BIO. .. py:method:: Connection.state_string() Retrieve a verbose string detailing the state of the Connection. .. py:method:: Connection.client_random() Retrieve the random value used with the client hello message. .. py:method:: Connection.server_random() Retrieve the random value used with the server hello message. .. py:method:: Connection.master_key() Retrieve the value of the master key for this session. .. py:method:: Connection.want_read() Checks if more data has to be read from the transport layer to complete an operation. .. py:method:: Connection.want_write() Checks if there is data to write to the transport layer to complete an operation. .. py:method:: Connection.set_tlsext_host_name(name) Specify the byte string to send as the server name in the client hello message. .. versionadded:: 0.13 .. py:method:: Connection.get_servername() Get the value of the server name received in the client hello message. .. versionadded:: 0.13 .. py:method:: Connection.get_session() Get a :py:class:`Session` instance representing the SSL session in use by the connection, or :py:obj:`None` if there is no session. .. versionadded:: 0.14 .. py:method:: Connection.set_session(session) Set a new SSL session (using a :py:class:`Session` instance) to be used by the connection. .. versionadded:: 0.14 .. py:method:: Connection.get_finished() Obtain latest TLS Finished message that we sent, or :py:obj:`None` if handshake is not completed. .. versionadded:: 0.15 .. py:method:: Connection.get_peer_finished() Obtain latest TLS Finished message that we expected from peer, or :py:obj:`None` if handshake is not completed. .. versionadded:: 0.15 .. py:method:: Connection.get_cipher_name() Obtain the name of the currently used cipher. .. versionadded:: 0.15 .. py:method:: Connection.get_cipher_bits() Obtain the number of secret bits of the currently used cipher. .. versionadded:: 0.15 .. py:method:: Connection.get_cipher_version() Obtain the protocol name of the currently used cipher. .. versionadded:: 0.15 .. py:method:: Connection.get_next_proto_negotiated(): Get the protocol that was negotiated by Next Protocol Negotiation. Returns a bytestring of the protocol name. If no protocol has been negotiated yet, returns an empty string. .. versionadded:: 0.15 .. py:method:: Connection.set_alpn_protos(protos) Specify the protocols that the client is prepared to speak after the TLS connection has been negotiated using Application Layer Protocol Negotiation. *protos* should be a list of protocols that the client is offering, each as a bytestring. For example, ``[b'http/1.1', b'spdy/2']``. .. py:method:: Connection.get_alpn_proto_negotiated() Get the protocol that was negotiated by Application Layer Protocol Negotiation. Returns a bytestring of the protocol name. If no protocol has been negotiated yet, returns an empty string. .. Rubric:: Footnotes .. [#connection-context-socket] Actually, all that is required is an object that **behaves** like a socket, you could even use files, even though it'd be tricky to get the handshakes right! pyOpenSSL-0.15.1/doc/api.rst0000644000076500000240000000056312263743062015754 0ustar hynekstaff00000000000000.. _openssl: :py:mod:`OpenSSL` --- Python interface to OpenSSL ================================================= .. py:module:: OpenSSL :synopsis: Python interface to OpenSSL This package provides a high-level interface to the functions in the OpenSSL library. The following modules are defined: .. toctree:: :maxdepth: 2 api/crypto api/rand api/ssl pyOpenSSL-0.15.1/doc/conf.py0000644000076500000240000001573312513316203015744 0ustar hynekstaff00000000000000# -*- coding: utf-8 -*- # # pyOpenSSL documentation build configuration file, created by # sphinx-quickstart on Sat Jul 16 07:12:22 2011. # # 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. import sys, os DOC_DIR = os.path.abspath(os.path.dirname(__file__)) sys.path.insert(0, os.path.abspath(os.path.join(DOC_DIR, ".."))) # 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 = [] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # 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 = u'pyOpenSSL' copyright = u'2011, Jean-Paul Calderone' # 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. # # The short X.Y version. version = '0.15.1' # The full version, including alpha/beta/rc tags. release = 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' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # 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'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'pyOpenSSLdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'pyOpenSSL.tex', u'pyOpenSSL Documentation', u'Jean-Paul Calderone', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pyopenssl', u'pyOpenSSL Documentation', [u'Jean-Paul Calderone'], 1) ] pyOpenSSL-0.15.1/doc/images/0000755000076500000240000000000012513316322015703 5ustar hynekstaff00000000000000pyOpenSSL-0.15.1/doc/images/pyopenssl-brand.png0000644000076500000240000000706412263743062021547 0ustar hynekstaff00000000000000PNG  IHDRRlbKGD pHYs  tIME 1 $9 IDATxpU/L 4A 2 ƂhdE(Йv:.f-{WA0,nYԩ`w*uYͮK5 "R~ (#A IܳNAs~^3ys_yys@DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDBFoJMӜbFܛvia yjlͶA엗/_ڵk(|#8\.P4a\.B70n+S=?(|#4~Ѧi:#mlHYruCd&@vvC>Yt 8 ܥC4;JŖa?t\'|"!!E4:/\MUUUJJ_p[].Wi.W\.ׅ)6G M%õ.IQ(D"J%@D Q(D"J%@D A= {㢢boUiokk;&66v$(6N{{{w_˗/wk ̡)@D Q(DPH$VtO5j$%%ѧOΞ=˩Shiiݽy%Jp8>}:yyydffb79rCmm-0s|cbb={6ͣO>^[R[[><R+ʕ+IIIݻwSUUŋS%&L :t:W 󩨨 .ο  }fb,{&n zKy7b?{+WXZOcc#ׯiǻ7[4xd9͛-=Zzu ]frQ)AT_1Z w%**˗r[ZZXnǎS' !<Huk]^Skpa***U]]7n pKwC.\)pk6 C7G'>󩜷~իW+5 v8.^bb"wyOe3FSP e+--%66֧2.M6ȧͰ rݔԩSRŋW$=:+ӀfxL*: e˖`uov٣Rfڠϐ#@W>-b٤3-,ѯ=`^56=t. T ,^v(n@8r2G|O?WϾf>/!k̪X߃5;o" >dz5sΙ3Dz+++ٳgO6@F4?1?QF x} )~}CNވ ?lilgͣ7S9Ή;w*E1L>i'"1.]JtD6Yf*H.|hmcakV솅  {.lQQSOu1+ْĿA93?_ X)ҥKV_߾})** >#ͤ~U}WCXqRWܽVAAA8c ihh>?(:BZ ]ʜls S^^e뗴y{K^M,B>]sw}~L!k;rѢE=z ?7<ꬫzoӍ컰{t LD%2==7dSS?8VU["lw_/y[o=NY8ݖ܃0ї-[k򩩩et%jNvSro?".Lر=E*7-7_\9[ŽS;Bz;"*v;!ӞxJJJB9^Mg;v9{pBj~:ͭ?WϦc[;-[,ou^hܹYl߾O?4MSۚi{~#7o^]0 /_ MC'())!...$ۖI~~~65j<@H} PQocV͟??p13ޤsI@ pG xڴi92,ZXXHMM L&`>nl{g |s`! wyZ{mm-k Phaw#;;[Mjj*g˶'APVVn˶t:J?~ps̳BENE^J`j&/U0͌jd|5eY4 ;8J%ڳF]n-Ǻ1q%@0r l#Ofs(b=pII΋"P.u(sy:xFu(Ѯ18p[3IENDB`pyOpenSSL-0.15.1/doc/images/pyopenssl-logo.png0000644000076500000240000000271312263743062021415 0ustar hynekstaff00000000000000PNG  IHDR@@iqbKGDC pHYs  kIDATxHw_w>wuө[n6df^pYWl#Q ILX*Mrbi.v˙zuWdNλs|?| /cՀ\xLMMM{~>"**@VljL& Yti|wb4]x,}jș477looofb,//ț"5So>x|$/*| hD lbYT @޽;~oXҁܜotUu2 _` ڗ34 ƢP(0L\t{0 0h4R__Oyy9Daa!cb*ׯl6SUU5;ot|Lv8D)}7fs"@NN̤n#r;S4[;AHLLwi!i[{u=俕?U8x82%创h%K<&88xflvɎ DFp,ñnF09:a&=mp2ED[.*: #ߏ?24T?rC%L&v6VmYntig4!B "sa6}RNsU'رO7:|KThrx_\BEE6iCCK n?Mӓ&|I Q59XlΞ=K[[۴VG2444N'_-Fj#~\̫jjӞT*n*Dv7 _BM+9T|@@.mZnEWj;"w87ZVۅ,WG*7nc(00\9 _J"ӍB՘id_>:^} Q' ^u5[R}vb \'a)!•oR$LFq:" ۛ_`r*Ww&###G]]]r'|䍷cXlyG}lɱԱD˖-#==cz1:bul쿷ߒHar)|4/L{Ν; ̸*55bRYGxRprr2V8ZͶmۨ2wc1xv:҇G-P(صk tww7.c#nÇcppP/< {1/ट?)$$$$$$$$$$$$$$ SmeIENDB`pyOpenSSL-0.15.1/doc/images/pyopenssl.svg0000644000076500000240000002044612263743062020475 0ustar hynekstaff00000000000000 image/svg+xml pyOpenSSL-0.15.1/doc/index.rst0000644000076500000240000000075612276115027016314 0ustar hynekstaff00000000000000Welcome to pyOpenSSL's documentation! ===================================== .. topic:: Abstract This module is a rather thin wrapper around (a subset of) the OpenSSL library. With thin wrapper I mean that a lot of the object methods do nothing more than calling a corresponding function in the OpenSSL library. Contents: .. toctree:: :maxdepth: 3 introduction api internals Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` pyOpenSSL-0.15.1/doc/internals.rst0000644000076500000240000000616612276115027017205 0ustar hynekstaff00000000000000.. _internals: Internals ========= We ran into three main problems developing this: Exceptions, callbacks and accessing socket methods. This is what this chapter is about. .. _exceptions: Exceptions ---------- We realized early that most of the exceptions would be raised by the I/O functions of OpenSSL, so it felt natural to mimic OpenSSL's error code system, translating them into Python exceptions. This naturally gives us the exceptions :py:exc:`.SSL.ZeroReturnError`, :py:exc:`.SSL.WantReadError`, :py:exc:`.SSL.WantWriteError`, :py:exc:`.SSL.WantX509LookupError` and :py:exc:`.SSL.SysCallError`. For more information about this, see section :ref:`openssl-ssl`. .. _callbacks: Callbacks --------- Callbacks were more of a problem when pyOpenSSL was written in C. Having switched to being written in Python using cffi, callbacks are now straightforward. The problems that originally existed no longer do (if you are interested in the details you can find descriptions of those problems in the version control history for this document). .. _socket-methods: Accessing Socket Methods ------------------------ We quickly saw the benefit of wrapping socket methods in the :py:class:`.SSL.Connection` class, for an easy transition into using SSL. The problem here is that the :py:mod:`socket` module lacks a C API, and all the methods are declared static. One approach would be to have :py:mod:`.OpenSSL` as a submodule to the :py:mod:`socket` module, placing all the code in ``socketmodule.c``, but this is obviously not a good solution, since you might not want to import tonnes of extra stuff you're not going to use when importing the :py:mod:`socket` module. The other approach is to somehow get a pointer to the method to be called, either the C function, or a callable Python object. This is not really a good solution either, since there's a lot of lookups involved. The way it works is that you have to supply a :py:class:`socket`- **like** transport object to the :py:class:`.SSL.Connection`. The only requirement of this object is that it has a :py:meth:`fileno()` method that returns a file descriptor that's valid at the C level (i.e. you can use the system calls read and write). If you want to use the :py:meth:`connect()` or :py:meth:`accept()` methods of the :py:class:`.SSL.Connection` object, the transport object has to supply such methods too. Apart from them, any method lookups in the :py:class:`.SSL.Connection` object that fail are passed on to the underlying transport object. Future changes might be to allow Python-level transport objects, that instead of having :py:meth:`fileno()` methods, have :py:meth:`read()` and :py:meth:`write()` methods, so more advanced features of Python can be used. This would probably entail some sort of OpenSSL **BIOs**, but converting Python strings back and forth is expensive, so this shouldn't be used unless necessary. Other nice things would be to be able to pass in different transport objects for reading and writing, but then the :py:meth:`fileno()` method of :py:class:`.SSL.Connection` becomes virtually useless. Also, should the method resolution be used on the read-transport or the write-transport? pyOpenSSL-0.15.1/doc/introduction.rst0000644000076500000240000000145712263743062017727 0ustar hynekstaff00000000000000.. _intro: Introduction ============ The reason pyOpenSSL was created is that the SSL support in the socket module in Python 2.1 (the contemporary version of Python when the pyOpenSSL project was begun) was severely limited. Other OpenSSL wrappers for Python at the time were also limited, though in different ways. Unfortunately, Python's standard library SSL support has remained weak, although other packages (such as `M2Crypto `_) have made great advances and now equal or exceed pyOpenSSL's functionality. The reason pyOpenSSL continues to be maintained is that there is a significant user community around it, as well as a large amount of software which depends on it. It is a great benefit to many people for pyOpenSSL to continue to exist and advance. pyOpenSSL-0.15.1/doc/make.bat0000644000076500000240000001064512263743062016060 0ustar hynekstaff00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) 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. 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\pyOpenSSL.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyOpenSSL.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" == "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 pyOpenSSL-0.15.1/doc/Makefile0000644000076500000240000001077212263743062016114 0ustar hynekstaff00000000000000# 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) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest 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 " 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/pyOpenSSL.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyOpenSSL.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/pyOpenSSL" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyOpenSSL" @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." 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." pyOpenSSL-0.15.1/doc/Quotes0000644000076500000240000000017612263743062015654 0ustar hynekstaff00000000000000< Screwtape> I like how developing against OpenSSL is like a text adventure game with a maze of twisty passages, all alike. % pyOpenSSL-0.15.1/doc/README0000644000076500000240000000020712263743062015324 0ustar hynekstaff00000000000000This is the pyOpenSSL documentation source. It uses Sphinx. To build the documentation, install Sphinx 1.0 and run: $ make html pyOpenSSL-0.15.1/examples/0000755000076500000240000000000012513316322015507 5ustar hynekstaff00000000000000pyOpenSSL-0.15.1/examples/certgen.py0000644000076500000240000000514312263743062017522 0ustar hynekstaff00000000000000# -*- coding: latin-1 -*- # # Copyright (C) AB Strakt # Copyright (C) Jean-Paul Calderone # See LICENSE for details. """ Certificate generation module. """ from OpenSSL import crypto TYPE_RSA = crypto.TYPE_RSA TYPE_DSA = crypto.TYPE_DSA def createKeyPair(type, bits): """ Create a public/private key pair. Arguments: type - Key type, must be one of TYPE_RSA and TYPE_DSA bits - Number of bits to use in the key Returns: The public/private key pair in a PKey object """ pkey = crypto.PKey() pkey.generate_key(type, bits) return pkey def createCertRequest(pkey, digest="md5", **name): """ Create a certificate request. Arguments: pkey - The key to associate with the request digest - Digestion method to use for signing, default is md5 **name - The name of the subject of the request, possible arguments are: C - Country name ST - State or province name L - Locality name O - Organization name OU - Organizational unit name CN - Common name emailAddress - E-mail address Returns: The certificate request in an X509Req object """ req = crypto.X509Req() subj = req.get_subject() for (key,value) in name.items(): setattr(subj, key, value) req.set_pubkey(pkey) req.sign(pkey, digest) return req def createCertificate(req, (issuerCert, issuerKey), serial, (notBefore, notAfter), digest="md5"): """ Generate a certificate given a certificate request. Arguments: req - Certificate reqeust to use issuerCert - The certificate of the issuer issuerKey - The private key of the issuer serial - Serial number for the certificate notBefore - Timestamp (relative to now) when the certificate starts being valid notAfter - Timestamp (relative to now) when the certificate stops being valid digest - Digest method to use for signing, default is md5 Returns: The signed certificate in an X509 object """ cert = crypto.X509() cert.set_serial_number(serial) cert.gmtime_adj_notBefore(notBefore) cert.gmtime_adj_notAfter(notAfter) cert.set_issuer(issuerCert.get_subject()) cert.set_subject(req.get_subject()) cert.set_pubkey(req.get_pubkey()) cert.sign(issuerKey, digest) return cert pyOpenSSL-0.15.1/examples/mk_simple_certs.py0000644000076500000240000000170712263743062021255 0ustar hynekstaff00000000000000""" Create certificates and private keys for the 'simple' example. """ from OpenSSL import crypto from certgen import * # yes yes, I know, I'm lazy cakey = createKeyPair(TYPE_RSA, 1024) careq = createCertRequest(cakey, CN='Certificate Authority') cacert = createCertificate(careq, (careq, cakey), 0, (0, 60*60*24*365*5)) # five years open('simple/CA.pkey', 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, cakey)) open('simple/CA.cert', 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cacert)) for (fname, cname) in [('client', 'Simple Client'), ('server', 'Simple Server')]: pkey = createKeyPair(TYPE_RSA, 1024) req = createCertRequest(pkey, CN=cname) cert = createCertificate(req, (cacert, cakey), 1, (0, 60*60*24*365*5)) # five years open('simple/%s.pkey' % (fname,), 'w').write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey)) open('simple/%s.cert' % (fname,), 'w').write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert)) pyOpenSSL-0.15.1/examples/proxy.py0000644000076500000240000000350712263743062017256 0ustar hynekstaff00000000000000#!/usr/bin/env python # # This script demostrates how one can use pyOpenSSL to speak SSL over an HTTP # proxy # The challenge here is to start talking SSL over an already connected socket # # Author: Mihai Ibanescu # # $Id: proxy.py,v 1.2 2004/07/22 12:01:25 martin Exp $ import sys, socket, string from OpenSSL import SSL def usage(exit_code=0): print "Usage: %s server[:port] proxy[:port]" % sys.argv[0] print " Connects SSL to the specified server (port 443 by default)" print " using the specified proxy (port 8080 by default)" sys.exit(exit_code) def main(): # Command-line processing if len(sys.argv) != 3: usage(-1) server, proxy = sys.argv[1:3] run(split_host(server, 443), split_host(proxy, 8080)) def split_host(hostname, default_port=80): a = string.split(hostname, ':', 1) if len(a) == 1: a.append(default_port) return a[0], int(a[1]) # Connects to the server, through the proxy def run(server, proxy): s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: s.connect(proxy) except socket.error, e: print "Unable to connect to %s:%s %s" % (proxy[0], proxy[1], str(e)) sys.exit(-1) # Use the CONNECT method to get a connection to the actual server s.send("CONNECT %s:%s HTTP/1.0\n\n" % (server[0], server[1])) print "Proxy response: %s" % string.strip(s.recv(1024)) ctx = SSL.Context(SSL.SSLv23_METHOD) conn = SSL.Connection(ctx, s) # Go to client mode conn.set_connect_state() # start using HTTP conn.send("HEAD / HTTP/1.0\n\n") print "Sever response:" print "-" * 40 while 1: try: buff = conn.recv(4096) except SSL.ZeroReturnError: # we're done break print buff, if __name__ == '__main__': main() pyOpenSSL-0.15.1/examples/README0000644000076500000240000000275212263743062016404 0ustar hynekstaff00000000000000I've finally gotten around to writing some examples :-) They aren't many, but at least it's something. If you write any, feel free to send them to me and I will add themn. certgen.py - Certificate generation module ========================================== Example module with three functions: createKeyPair - Create a public/private key pair createCertRequest - Create a certificate request createCertificate - Create a certificate given a cert request In fact, I created the certificates and keys in the 'simple' directory with the script mk_simple_certs.py simple - Simple client/server example ===================================== Start the server with python server.py PORT and start clients with python client.py HOST PORT The server is a simple echo server, anything a client sends, it sends back. proxy.py - Example of an SSL-enabled proxy ========================================== The proxy example demonstrate how to use set_connect_state to start talking SSL over an already connected socket. Usage: python proxy.py server[:port] proxy[:port] Contributed by Mihai Ibanescu SecureXMLRPCServer.py - SSL-enabled version of SimpleXMLRPCServer ================================================================= This acts exactly like SimpleXMLRPCServer from the standard python library, but uses secure connections. The technique and classes should work for any SocketServer style server. However, the code has not been extensively tested. Contributed by Michal Wallace pyOpenSSL-0.15.1/examples/SecureXMLRPCServer.py0000644000076500000240000000666412263743062021447 0ustar hynekstaff00000000000000""" SecureXMLRPCServer module using pyOpenSSL 0.5 Written 0907.2002 by Michal Wallace http://www.sabren.net/ This acts exactly like SimpleXMLRPCServer from the standard python library, but uses secure connections. The technique and classes should work for any SocketServer style server. However, the code has not been extensively tested. This code is in the public domain. It is provided AS-IS WITH NO WARRANTY WHATSOEVER. """ import SocketServer import os, socket import SimpleXMLRPCServer from OpenSSL import SSL class SSLWrapper: """ This whole class exists just to filter out a parameter passed in to the shutdown() method in SimpleXMLRPC.doPOST() """ def __init__(self, conn): """ Connection is not yet a new-style class, so I'm making a proxy instead of subclassing. """ self.__dict__["conn"] = conn def __getattr__(self,name): return getattr(self.__dict__["conn"], name) def __setattr__(self,name, value): setattr(self.__dict__["conn"], name, value) def shutdown(self, how=1): """ SimpleXMLRpcServer.doPOST calls shutdown(1), and Connection.shutdown() doesn't take an argument. So we just discard the argument. """ self.__dict__["conn"].shutdown() def accept(self): """ This is the other part of the shutdown() workaround. Since servers create new sockets, we have to infect them with our magic. :) """ c, a = self.__dict__["conn"].accept() return (SSLWrapper(c), a) class SecureTCPServer(SocketServer.TCPServer): """ Just like TCPServer, but use a socket. This really ought to let you specify the key and certificate files. """ def __init__(self, server_address, RequestHandlerClass): SocketServer.BaseServer.__init__(self, server_address, RequestHandlerClass) ## Same as normal, but make it secure: ctx = SSL.Context(SSL.SSLv23_METHOD) ctx.set_options(SSL.OP_NO_SSLv2) dir = os.curdir ctx.use_privatekey_file (os.path.join(dir, 'server.pkey')) ctx.use_certificate_file(os.path.join(dir, 'server.cert')) self.socket = SSLWrapper(SSL.Connection(ctx, socket.socket(self.address_family, self.socket_type))) self.server_bind() self.server_activate() class SecureXMLRPCRequestHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler): def setup(self): """ We need to use socket._fileobject Because SSL.Connection doesn't have a 'dup'. Not exactly sure WHY this is, but this is backed up by comments in socket.py and SSL/connection.c """ self.connection = self.request # for doPOST self.rfile = socket._fileobject(self.request, "rb", self.rbufsize) self.wfile = socket._fileobject(self.request, "wb", self.wbufsize) class SecureXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer, SecureTCPServer): def __init__(self, addr, requestHandler=SecureXMLRPCRequestHandler, logRequests=1): """ This is the exact same code as SimpleXMLRPCServer.__init__ except it calls SecureTCPServer.__init__ instead of plain old TCPServer.__init__ """ self.funcs = {} self.logRequests = logRequests self.instance = None SecureTCPServer.__init__(self, addr, requestHandler) pyOpenSSL-0.15.1/examples/simple/0000755000076500000240000000000012513316322017000 5ustar hynekstaff00000000000000pyOpenSSL-0.15.1/examples/simple/client.py0000644000076500000240000000235412263743062020643 0ustar hynekstaff00000000000000# -*- coding: latin-1 -*- # # Copyright (C) AB Strakt # Copyright (C) Jean-Paul Calderone # See LICENSE for details. """ Simple SSL client, using blocking I/O """ from OpenSSL import SSL import sys, os, select, socket def verify_cb(conn, cert, errnum, depth, ok): # This obviously has to be updated print 'Got certificate: %s' % cert.get_subject() return ok if len(sys.argv) < 3: print 'Usage: python[2] client.py HOST PORT' sys.exit(1) dir = os.path.dirname(sys.argv[0]) if dir == '': dir = os.curdir # Initialize context ctx = SSL.Context(SSL.SSLv23_METHOD) ctx.set_verify(SSL.VERIFY_PEER, verify_cb) # Demand a certificate ctx.use_privatekey_file (os.path.join(dir, 'client.pkey')) ctx.use_certificate_file(os.path.join(dir, 'client.cert')) ctx.load_verify_locations(os.path.join(dir, 'CA.cert')) # Set up client sock = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM)) sock.connect((sys.argv[1], int(sys.argv[2]))) while 1: line = sys.stdin.readline() if line == '': break try: sock.send(line) sys.stdout.write(sock.recv(1024)) sys.stdout.flush() except SSL.Error: print 'Connection died unexpectedly' break sock.shutdown() sock.close() pyOpenSSL-0.15.1/examples/simple/README0000644000076500000240000000026412263743062017671 0ustar hynekstaff00000000000000To use this example, first generate keys and certificates for both the client and the server. You can do this with the script in the directory above this one, mk_simple_certs.py. pyOpenSSL-0.15.1/examples/simple/server.py0000644000076500000240000000520112263743062020665 0ustar hynekstaff00000000000000# -*- coding: latin-1 -*- # # Copyright (C) AB Strakt # Copyright (C) Jean-Paul Calderone # See LICENSE for details. """ Simple echo server, using nonblocking I/O """ from OpenSSL import SSL import sys, os, select, socket def verify_cb(conn, cert, errnum, depth, ok): # This obviously has to be updated print 'Got certificate: %s' % cert.get_subject() return ok if len(sys.argv) < 2: print 'Usage: python[2] server.py PORT' sys.exit(1) dir = os.path.dirname(sys.argv[0]) if dir == '': dir = os.curdir # Initialize context ctx = SSL.Context(SSL.SSLv23_METHOD) ctx.set_options(SSL.OP_NO_SSLv2) ctx.set_verify(SSL.VERIFY_PEER|SSL.VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) # Demand a certificate ctx.use_privatekey_file (os.path.join(dir, 'server.pkey')) ctx.use_certificate_file(os.path.join(dir, 'server.cert')) ctx.load_verify_locations(os.path.join(dir, 'CA.cert')) # Set up server server = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM)) server.bind(('', int(sys.argv[1]))) server.listen(3) server.setblocking(0) clients = {} writers = {} def dropClient(cli, errors=None): if errors: print 'Client %s left unexpectedly:' % (clients[cli],) print ' ', errors else: print 'Client %s left politely' % (clients[cli],) del clients[cli] if writers.has_key(cli): del writers[cli] if not errors: cli.shutdown() cli.close() while 1: try: r,w,_ = select.select([server]+clients.keys(), writers.keys(), []) except: break for cli in r: if cli == server: cli,addr = server.accept() print 'Connection from %s' % (addr,) clients[cli] = addr else: try: ret = cli.recv(1024) except (SSL.WantReadError, SSL.WantWriteError, SSL.WantX509LookupError): pass except SSL.ZeroReturnError: dropClient(cli) except SSL.Error, errors: dropClient(cli, errors) else: if not writers.has_key(cli): writers[cli] = '' writers[cli] = writers[cli] + ret for cli in w: try: ret = cli.send(writers[cli]) except (SSL.WantReadError, SSL.WantWriteError, SSL.WantX509LookupError): pass except SSL.ZeroReturnError: dropClient(cli) except SSL.Error, errors: dropClient(cli, errors) else: writers[cli] = writers[cli][ret:] if writers[cli] == '': del writers[cli] for cli in clients.keys(): cli.close() server.close() pyOpenSSL-0.15.1/examples/sni/0000755000076500000240000000000012513316322016300 5ustar hynekstaff00000000000000pyOpenSSL-0.15.1/examples/sni/another.invalid.crt0000644000076500000240000000173112263743062022110 0ustar hynekstaff00000000000000-----BEGIN CERTIFICATE----- MIICqTCCAhICAQEwDQYJKoZIhvcNAQEEBQAwgZwxETAPBgNVBAsTCFNlY3VyaXR5 MRIwEAYDVQQKEwlweU9wZW5TU0wxGDAWBgNVBAMTD2Fub3RoZXIuaW52YWxpZDER MA8GA1UECBMITmV3IFlvcmsxCzAJBgNVBAYTAlVTMSYwJAYJKoZIhvcNAQkBFhdp bnZhbGlkQGFub3RoZXIuaW52YWxpZDERMA8GA1UEBxMITmV3IFlvcmswHhcNMTEw NjA2MTIyMTQyWhcNMTIwNjA1MTIyMTQyWjCBnDERMA8GA1UECxMIU2VjdXJpdHkx EjAQBgNVBAoTCXB5T3BlblNTTDEYMBYGA1UEAxMPYW5vdGhlci5pbnZhbGlkMREw DwYDVQQIEwhOZXcgWW9yazELMAkGA1UEBhMCVVMxJjAkBgkqhkiG9w0BCQEWF2lu dmFsaWRAYW5vdGhlci5pbnZhbGlkMREwDwYDVQQHEwhOZXcgWW9yazCBnzANBgkq hkiG9w0BAQEFAAOBjQAwgYkCgYEA7jUOM0EnH0/bvqyQfrGlZ5ROc29JWEq3wp7/ n96cxQ/oSf5G6rlQ5ZYnDlp44csQOY3DIq5/7cRju/Qf5cZ03YMOjzYSi4ElS0+o 3Av/VgL/ssC6Z0PfQO4+NyXIQTn+cS6P6T65AVBdqn6Z5t0eY0wkU6QznpdJ/1c2 a7gIYnUCAwEAATANBgkqhkiG9w0BAQQFAAOBgQBqyrP1wmpTmfeZnoB7piJd+qIj VHpCDRAZcdsxKUl/8PahjtWPMB0G5VaMwOoIGIlMxZ/LPKf44cA+QNEIXq8rohr2 XFaA4t4X4aP7OmwQ4pa8mh4r86mP+vQU2iRJOqRYP+/gKaAqI2+ZbORZXJ7bewb5 DTvvQRw2PRBf270h8g== -----END CERTIFICATE----- pyOpenSSL-0.15.1/examples/sni/another.invalid.key0000644000076500000240000000157312263743062022114 0ustar hynekstaff00000000000000-----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQDuNQ4zQScfT9u+rJB+saVnlE5zb0lYSrfCnv+f3pzFD+hJ/kbq uVDllicOWnjhyxA5jcMirn/txGO79B/lxnTdgw6PNhKLgSVLT6jcC/9WAv+ywLpn Q99A7j43JchBOf5xLo/pPrkBUF2qfpnm3R5jTCRTpDOel0n/VzZruAhidQIDAQAB AoGBAOGaJBHM8fWI17DVlKA5NVNNNaPEUW2qjjFoDuflmQpWD4UMqzOhQYm/VMwW SYhnnr0zkw1kwUp6Bo87HX6sH37b1GeqIyp+b0Hqc+vLyiXPo0suqV23B9K8jjZ0 6ap8h6hxpa5D1HtYKKDzWLhLJVtmtslxsvimR/CS+rmpUgBBAkEA+lJ2dXMDsUzB xOpX8MLfQsl8XB5tx4ejmXGyNp/hmRFqFi38FFemJXX1YC3wL5jbQ2Ltz9rnbdnG Xb/IWrn25QJBAPOcPua6xiNTWW5519JGaNgWdYnUgbj/ib8waLoElHp5Hl5DLuYX y8U96Xl/wAE4aQnp5R/PS75tYrKZo79z9FECQQDALk1J8IpWNbLSRoRLkKEtulji tG3d8VH1/WcwLuFZzhfffWB6Eay6N+yx8bLkJ/u2qZ4gpVRmbvqvgQ0GMp3NAkBE FFczzeCPgLyOdjiNSCYGtYgVg7DZDXjmWFX8HkmMTIrjFu1lWiMVNS8pSD1VWflo zte8Ywcs6Y7akLtFRtdxAkEA346J1/Zqtibez2TcjzCK+s9Ihwta23ZN2YTjo60o sDZ5AVJwyLa7VFEzO/e9v2ytD7k9fCJjHcxIWIe8zj0dYA== -----END RSA PRIVATE KEY----- pyOpenSSL-0.15.1/examples/sni/client.py0000644000076500000240000000161412263743062020141 0ustar hynekstaff00000000000000# Copyright (C) Jean-Paul Calderone # See LICENSE for details. if __name__ == '__main__': import client raise SystemExit(client.main()) from sys import argv, stdout from socket import socket from OpenSSL.SSL import TLSv1_METHOD, Context, Connection def main(): """ Connect to an SNI-enabled server and request a specific hostname, specified by argv[1], of it. """ if len(argv) < 2: print 'Usage: %s ' % (argv[0],) return 1 client = socket() print 'Connecting...', stdout.flush() client.connect(('127.0.0.1', 8443)) print 'connected', client.getpeername() client_ssl = Connection(Context(TLSv1_METHOD), client) client_ssl.set_connect_state() client_ssl.set_tlsext_host_name(argv[1]) client_ssl.do_handshake() print 'Server subject is', client_ssl.get_peer_certificate().get_subject() client_ssl.close() pyOpenSSL-0.15.1/examples/sni/example.invalid.crt0000644000076500000240000000173112263743062022103 0ustar hynekstaff00000000000000-----BEGIN CERTIFICATE----- MIICqTCCAhICAQEwDQYJKoZIhvcNAQEEBQAwgZwxETAPBgNVBAsTCFNlY3VyaXR5 MRIwEAYDVQQKEwlweU9wZW5TU0wxGDAWBgNVBAMTD2V4YW1wbGUuaW52YWxpZDER MA8GA1UECBMITmV3IFlvcmsxCzAJBgNVBAYTAlVTMSYwJAYJKoZIhvcNAQkBFhdp bnZhbGlkQGV4YW1wbGUuaW52YWxpZDERMA8GA1UEBxMITmV3IFlvcmswHhcNMTEw NjA2MTIyMTMzWhcNMTIwNjA1MTIyMTMzWjCBnDERMA8GA1UECxMIU2VjdXJpdHkx EjAQBgNVBAoTCXB5T3BlblNTTDEYMBYGA1UEAxMPZXhhbXBsZS5pbnZhbGlkMREw DwYDVQQIEwhOZXcgWW9yazELMAkGA1UEBhMCVVMxJjAkBgkqhkiG9w0BCQEWF2lu dmFsaWRAZXhhbXBsZS5pbnZhbGlkMREwDwYDVQQHEwhOZXcgWW9yazCBnzANBgkq hkiG9w0BAQEFAAOBjQAwgYkCgYEAwmLucR0IXvoGTOfzb2WJlHis2s/FFJfmYAKd hq9bs+XzPeAPG0VQqAsy+om1gBOb8KPGtSet2SeNc25FU+QuwAza8uws7EaxD9b9 CcarIh2X5LMcmiI/p34FuVGUSVsfc4QCTYFWGA0Mrw4jz9sGGeSEmTjVRnc3uAix 31orKScCAwEAATANBgkqhkiG9w0BAQQFAAOBgQBxm8Qta5wYFmQ3l3EAne9+HaQ5 gPStgox6STmyOGfRkybSePgOeKftOasaXpKboiNg6PJEkaFEnl9epNwS+8PIjQqv mPiZdlrNIfw+YVWpqgcTAIzkhYFH0K4v6d5Wn2adNgd5KbrxYOjsr2w0ixQEtdW/ +z1x/ngjc08EPqOIPQ== -----END CERTIFICATE----- pyOpenSSL-0.15.1/examples/sni/example.invalid.key0000644000076500000240000000156712263743062022112 0ustar hynekstaff00000000000000-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQDCYu5xHQhe+gZM5/NvZYmUeKzaz8UUl+ZgAp2Gr1uz5fM94A8b RVCoCzL6ibWAE5vwo8a1J63ZJ41zbkVT5C7ADNry7CzsRrEP1v0JxqsiHZfksxya Ij+nfgW5UZRJWx9zhAJNgVYYDQyvDiPP2wYZ5ISZONVGdze4CLHfWispJwIDAQAB AoGBAL8L8qNTUHXgL68ITRZP6g71J5YKm/zoafA0wdOsp2lA+Hb4roAz+Nif4SOh krPlEd9JZ7OF4vRJTlmDqDmSS2qY7hJuZpdrdvhdxaPGeX4uftC43thEzxLxPQHd gCCxugbGJOHChjMPk06oC0w1q70ex3gWmki82Jt/5INV6Z6RAkEA4km0s0RvbVmW AT12PROplCRE86eJNlLCVp2TJNl0LPZe5uWqaZZ8wBvfFd1PXEk/Qcpj4IotMZ5M 1Ai4zw2+6QJBANvo6R5yLRrY8/7YKw9Y/1bbSRLhGYok2Ur4fFz64G28wA1VI3yS uXrJ7NjTVykfrBq59WEfh3a15P9g/TMAPY8CQQDdW3Z9iqtpj6IScnowgwR22wfs RW4PCuP6cMhY2rMvrI3nVrDd+wzrrBgNPmF8iFZt2Drdkq1lBVJodGO8f9jJAj9O K3yyVeOyp2wUKsMjsX8SYOCY1Ws+r9qNy8ZpRsSAPZgHJTx4C6/i9eQ7LuTMuXV0 CqYu4AZHLGE6Zj+a4XsCQQC8Ken471EXuahfPcKTzsphuZnYZkoVUsFUxJFfqG+S 8k2Jo/4c+2NyyvVXhXu2at8kmu45c92BrCTXIvLEwtnn -----END RSA PRIVATE KEY----- pyOpenSSL-0.15.1/examples/sni/README0000644000076500000240000000166312263743062017175 0ustar hynekstaff00000000000000This directory contains client and server examples for the "Server Name Indication" (SNI) feature. Run server.py with no arguments. It will accept one client connection and then exit. It has two certificates it can use, one for "example.invalid" and another for "another.invalid". If a client indicates one of these names to it, it will use the corresponding certificate for that connection (if a client doesn't indicate a name or indicates another name, it won't try to use any certificate). Run client.py with one argument, the server name to indicate. For example: $ python client.py example.invalid Connecting... connected ('127.0.0.1', 8443) Server subject is $ Depending on what hostname is supplied, the server will select a different certificate to use and the client output will be different. pyOpenSSL-0.15.1/examples/sni/server.py0000644000076500000240000000323412263743062020171 0ustar hynekstaff00000000000000# Copyright (C) Jean-Paul Calderone # See LICENSE for details. if __name__ == '__main__': import server raise SystemExit(server.main()) from sys import stdout from socket import SOL_SOCKET, SO_REUSEADDR, socket from OpenSSL.crypto import FILETYPE_PEM, load_privatekey, load_certificate from OpenSSL.SSL import TLSv1_METHOD, Context, Connection def load(domain): crt = open(domain + ".crt") key = open(domain + ".key") result = ( load_privatekey(FILETYPE_PEM, key.read()), load_certificate(FILETYPE_PEM, crt.read())) crt.close() key.close() return result def main(): """ Run an SNI-enabled server which selects between a few certificates in a C{dict} based on the handshake request it receives from a client. """ port = socket() port.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) port.bind(('', 8443)) port.listen(3) print 'Accepting...', stdout.flush() server, addr = port.accept() print 'accepted', addr server_context = Context(TLSv1_METHOD) server_context.set_tlsext_servername_callback(pick_certificate) server_ssl = Connection(server_context, server) server_ssl.set_accept_state() server_ssl.do_handshake() server.close() certificates = { "example.invalid": load("example.invalid"), "another.invalid": load("another.invalid"), } def pick_certificate(connection): try: key, cert = certificates[connection.get_servername()] except KeyError: pass else: new_context = Context(TLSv1_METHOD) new_context.use_privatekey(key) new_context.use_certificate(cert) connection.set_context(new_context) pyOpenSSL-0.15.1/INSTALL.rst0000644000076500000240000000053712513100143015526 0ustar hynekstaff00000000000000Installation ============ To install pyOpenSSL:: $ pip install pyopenssl If you are installing in order to *develop* on pyOpenSSL, move to the root directory of a pyOpenSSL checkout, and run:: $ pip install -e . Documentation ============= The documentation is written in reStructuredText and built using Sphinx:: $ cd doc $ make html pyOpenSSL-0.15.1/LICENSE0000644000076500000240000002613612263743062014715 0ustar hynekstaff00000000000000 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. pyOpenSSL-0.15.1/MANIFEST.in0000644000076500000240000000052112513224551015427 0ustar hynekstaff00000000000000include LICENSE ChangeLog TODO MANIFEST.in OpenSSL/RATIONALE *.rst tox.ini memdbg.py runtests.py OpenSSL/test/README exclude leakcheck recursive-include doc * recursive-include examples * recursive-include rpm * recursive-exclude leakcheck *.py *.pem global-exclude *.pyc prune doc/_build pyOpenSSL-0.15.1/memdbg.py0000644000076500000240000000366112264311746015514 0ustar hynekstaff00000000000000import sys sys.modules['ssl'] = None sys.modules['_hashlib'] = None import traceback from cffi import api as _api _ffi = _api.FFI() _ffi.cdef( """ void *malloc(size_t size); void free(void *ptr); void *realloc(void *ptr, size_t size); int CRYPTO_set_mem_functions(void *(*m)(size_t),void *(*r)(void *,size_t), void (*f)(void *)); int backtrace(void **buffer, int size); char **backtrace_symbols(void *const *buffer, int size); void backtrace_symbols_fd(void *const *buffer, int size, int fd); """) _api = _ffi.verify( """ #include #include #include """, libraries=["crypto"]) C = _ffi.dlopen(None) verbose = False def log(s): if verbose: print(s) def _backtrace(): buf = _ffi.new("void*[]", 64) result = _api.backtrace(buf, len(buf)) strings = _api.backtrace_symbols(buf, result) stack = [_ffi.string(strings[i]) for i in range(result)] C.free(strings) return stack @_ffi.callback("void*(*)(size_t)") def malloc(n): memory = C.malloc(n) python_stack = traceback.extract_stack(limit=3) c_stack = _backtrace() heap[memory] = [(n, python_stack, c_stack)] log("malloc(%d) -> %s" % (n, memory)) return memory @_ffi.callback("void*(*)(void*, size_t)") def realloc(p, n): memory = C.realloc(p, n) old = heap.pop(p) python_stack = traceback.extract_stack(limit=3) c_stack = _backtrace() old.append((n, python_stack, c_stack)) heap[memory] = old log("realloc(0x%x, %d) -> %s" % (int(_ffi.cast("int", p)), n, memory)) return memory @_ffi.callback("void(*)(void*)") def free(p): if p != _ffi.NULL: C.free(p) del heap[p] log("free(0x%x)" % (int(_ffi.cast("int", p)),)) if _api.CRYPTO_set_mem_functions(malloc, realloc, free): log('Enabled memory debugging') heap = {} else: log('Failed to enable memory debugging') heap = None pyOpenSSL-0.15.1/OpenSSL/0000755000076500000240000000000012513316322015154 5ustar hynekstaff00000000000000pyOpenSSL-0.15.1/OpenSSL/__init__.py0000644000076500000240000000040712273461431017273 0ustar hynekstaff00000000000000# Copyright (C) AB Strakt # See LICENSE for details. """ pyOpenSSL - A simple wrapper around the OpenSSL library """ from OpenSSL import rand, crypto, SSL from OpenSSL.version import __version__ __all__ = [ 'rand', 'crypto', 'SSL', 'tsafe', '__version__'] pyOpenSSL-0.15.1/OpenSSL/_util.py0000644000076500000240000000662612513100143016644 0ustar hynekstaff00000000000000from warnings import warn import sys from six import PY3, binary_type, text_type from cryptography.hazmat.bindings.openssl.binding import Binding binding = Binding() ffi = binding.ffi lib = binding.lib def text(charp): """ Get a native string type representing of the given CFFI ``char*`` object. :param charp: A C-style string represented using CFFI. :return: :class:`str` """ if not charp: return "" return native(ffi.string(charp)) def exception_from_error_queue(exception_type): """ Convert an OpenSSL library failure into a Python exception. When a call to the native OpenSSL library fails, this is usually signalled by the return value, and an error code is stored in an error queue associated with the current thread. The err library provides functions to obtain these error codes and textual error messages. """ errors = [] while True: error = lib.ERR_get_error() if error == 0: break errors.append(( text(lib.ERR_lib_error_string(error)), text(lib.ERR_func_error_string(error)), text(lib.ERR_reason_error_string(error)))) raise exception_type(errors) def native(s): """ Convert :py:class:`bytes` or :py:class:`unicode` to the native :py:class:`str` type, using UTF-8 encoding if conversion is necessary. :raise UnicodeError: The input string is not UTF-8 decodeable. :raise TypeError: The input is neither :py:class:`bytes` nor :py:class:`unicode`. """ if not isinstance(s, (binary_type, text_type)): raise TypeError("%r is neither bytes nor unicode" % s) if PY3: if isinstance(s, binary_type): return s.decode("utf-8") else: if isinstance(s, text_type): return s.encode("utf-8") return s def path_string(s): """ Convert a Python string to a :py:class:`bytes` string identifying the same path and which can be passed into an OpenSSL API accepting a filename. :param s: An instance of :py:class:`bytes` or :py:class:`unicode`. :return: An instance of :py:class:`bytes`. """ if isinstance(s, binary_type): return s elif isinstance(s, text_type): return s.encode(sys.getfilesystemencoding()) else: raise TypeError("Path must be represented as bytes or unicode string") if PY3: def byte_string(s): return s.encode("charmap") else: def byte_string(s): return s # A marker object to observe whether some optional arguments are passed any # value or not. UNSPECIFIED = object() _TEXT_WARNING = ( text_type.__name__ + " for {0} is no longer accepted, use bytes" ) def text_to_bytes_and_warn(label, obj): """ If ``obj`` is text, emit a warning that it should be bytes instead and try to convert it to bytes automatically. :param str label: The name of the parameter from which ``obj`` was taken (so a developer can easily find the source of the problem and correct it). :return: If ``obj`` is the text string type, a ``bytes`` object giving the UTF-8 encoding of that text is returned. Otherwise, ``obj`` itself is returned. """ if isinstance(obj, text_type): warn( _TEXT_WARNING.format(label), category=DeprecationWarning, stacklevel=3 ) return obj.encode('utf-8') return obj pyOpenSSL-0.15.1/OpenSSL/crypto.py0000644000076500000240000023756112513100143017054 0ustar hynekstaff00000000000000from time import time from base64 import b16encode from functools import partial from operator import __eq__, __ne__, __lt__, __le__, __gt__, __ge__ from warnings import warn as _warn from six import ( integer_types as _integer_types, text_type as _text_type, PY3 as _PY3) from OpenSSL._util import ( ffi as _ffi, lib as _lib, exception_from_error_queue as _exception_from_error_queue, byte_string as _byte_string, native as _native, UNSPECIFIED as _UNSPECIFIED, text_to_bytes_and_warn as _text_to_bytes_and_warn, ) FILETYPE_PEM = _lib.SSL_FILETYPE_PEM FILETYPE_ASN1 = _lib.SSL_FILETYPE_ASN1 # TODO This was an API mistake. OpenSSL has no such constant. FILETYPE_TEXT = 2 ** 16 - 1 TYPE_RSA = _lib.EVP_PKEY_RSA TYPE_DSA = _lib.EVP_PKEY_DSA class Error(Exception): """ An error occurred in an `OpenSSL.crypto` API. """ _raise_current_error = partial(_exception_from_error_queue, Error) def _untested_error(where): """ An OpenSSL API failed somehow. Additionally, the failure which was encountered isn't one that's exercised by the test suite so future behavior of pyOpenSSL is now somewhat less predictable. """ raise RuntimeError("Unknown %s failure" % (where,)) def _new_mem_buf(buffer=None): """ Allocate a new OpenSSL memory BIO. Arrange for the garbage collector to clean it up automatically. :param buffer: None or some bytes to use to put into the BIO so that they can be read out. """ if buffer is None: bio = _lib.BIO_new(_lib.BIO_s_mem()) free = _lib.BIO_free else: data = _ffi.new("char[]", buffer) bio = _lib.BIO_new_mem_buf(data, len(buffer)) # Keep the memory alive as long as the bio is alive! def free(bio, ref=data): return _lib.BIO_free(bio) if bio == _ffi.NULL: # TODO: This is untested. _raise_current_error() bio = _ffi.gc(bio, free) return bio def _bio_to_string(bio): """ Copy the contents of an OpenSSL BIO object into a Python byte string. """ result_buffer = _ffi.new('char**') buffer_length = _lib.BIO_get_mem_data(bio, result_buffer) return _ffi.buffer(result_buffer[0], buffer_length)[:] def _set_asn1_time(boundary, when): """ The the time value of an ASN1 time object. @param boundary: An ASN1_GENERALIZEDTIME pointer (or an object safely castable to that type) which will have its value set. @param when: A string representation of the desired time value. @raise TypeError: If C{when} is not a L{bytes} string. @raise ValueError: If C{when} does not represent a time in the required format. @raise RuntimeError: If the time value cannot be set for some other (unspecified) reason. """ if not isinstance(when, bytes): raise TypeError("when must be a byte string") set_result = _lib.ASN1_GENERALIZEDTIME_set_string( _ffi.cast('ASN1_GENERALIZEDTIME*', boundary), when) if set_result == 0: dummy = _ffi.gc(_lib.ASN1_STRING_new(), _lib.ASN1_STRING_free) _lib.ASN1_STRING_set(dummy, when, len(when)) check_result = _lib.ASN1_GENERALIZEDTIME_check( _ffi.cast('ASN1_GENERALIZEDTIME*', dummy)) if not check_result: raise ValueError("Invalid string") else: _untested_error() def _get_asn1_time(timestamp): """ Retrieve the time value of an ASN1 time object. @param timestamp: An ASN1_GENERALIZEDTIME* (or an object safely castable to that type) from which the time value will be retrieved. @return: The time value from C{timestamp} as a L{bytes} string in a certain format. Or C{None} if the object contains no time value. """ string_timestamp = _ffi.cast('ASN1_STRING*', timestamp) if _lib.ASN1_STRING_length(string_timestamp) == 0: return None elif _lib.ASN1_STRING_type(string_timestamp) == _lib.V_ASN1_GENERALIZEDTIME: return _ffi.string(_lib.ASN1_STRING_data(string_timestamp)) else: generalized_timestamp = _ffi.new("ASN1_GENERALIZEDTIME**") _lib.ASN1_TIME_to_generalizedtime(timestamp, generalized_timestamp) if generalized_timestamp[0] == _ffi.NULL: # This may happen: # - if timestamp was not an ASN1_TIME # - if allocating memory for the ASN1_GENERALIZEDTIME failed # - if a copy of the time data from timestamp cannot be made for # the newly allocated ASN1_GENERALIZEDTIME # # These are difficult to test. cffi enforces the ASN1_TIME type. # Memory allocation failures are a pain to trigger # deterministically. _untested_error("ASN1_TIME_to_generalizedtime") else: string_timestamp = _ffi.cast( "ASN1_STRING*", generalized_timestamp[0]) string_data = _lib.ASN1_STRING_data(string_timestamp) string_result = _ffi.string(string_data) _lib.ASN1_GENERALIZEDTIME_free(generalized_timestamp[0]) return string_result class PKey(object): _only_public = False _initialized = True def __init__(self): pkey = _lib.EVP_PKEY_new() self._pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free) self._initialized = False def generate_key(self, type, bits): """ Generate a key of a given type, with a given number of a bits :param type: The key type (TYPE_RSA or TYPE_DSA) :param bits: The number of bits :return: None """ if not isinstance(type, int): raise TypeError("type must be an integer") if not isinstance(bits, int): raise TypeError("bits must be an integer") # TODO Check error return exponent = _lib.BN_new() exponent = _ffi.gc(exponent, _lib.BN_free) _lib.BN_set_word(exponent, _lib.RSA_F4) if type == TYPE_RSA: if bits <= 0: raise ValueError("Invalid number of bits") rsa = _lib.RSA_new() result = _lib.RSA_generate_key_ex(rsa, bits, exponent, _ffi.NULL) if result == 0: # TODO: The test for this case is commented out. Different # builds of OpenSSL appear to have different failure modes that # make it hard to test. Visual inspection of the OpenSSL # source reveals that a return value of 0 signals an error. # Manual testing on a particular build of OpenSSL suggests that # this is probably the appropriate way to handle those errors. _raise_current_error() result = _lib.EVP_PKEY_assign_RSA(self._pkey, rsa) if not result: # TODO: It appears as though this can fail if an engine is in # use which does not support RSA. _raise_current_error() elif type == TYPE_DSA: dsa = _lib.DSA_generate_parameters( bits, _ffi.NULL, 0, _ffi.NULL, _ffi.NULL, _ffi.NULL, _ffi.NULL) if dsa == _ffi.NULL: # TODO: This is untested. _raise_current_error() if not _lib.DSA_generate_key(dsa): # TODO: This is untested. _raise_current_error() if not _lib.EVP_PKEY_assign_DSA(self._pkey, dsa): # TODO: This is untested. _raise_current_error() else: raise Error("No such key type") self._initialized = True def check(self): """ Check the consistency of an RSA private key. :return: True if key is consistent. :raise Error: if the key is inconsistent. :raise TypeError: if the key is of a type which cannot be checked. Only RSA keys can currently be checked. """ if self._only_public: raise TypeError("public key only") if _lib.EVP_PKEY_type(self._pkey.type) != _lib.EVP_PKEY_RSA: raise TypeError("key type unsupported") rsa = _lib.EVP_PKEY_get1_RSA(self._pkey) rsa = _ffi.gc(rsa, _lib.RSA_free) result = _lib.RSA_check_key(rsa) if result: return True _raise_current_error() def type(self): """ Returns the type of the key :return: The type of the key. """ return self._pkey.type def bits(self): """ Returns the number of bits of the key :return: The number of bits of the key. """ return _lib.EVP_PKEY_bits(self._pkey) PKeyType = PKey class _EllipticCurve(object): """ A representation of a supported elliptic curve. @cvar _curves: :py:obj:`None` until an attempt is made to load the curves. Thereafter, a :py:type:`set` containing :py:type:`_EllipticCurve` instances each of which represents one curve supported by the system. @type _curves: :py:type:`NoneType` or :py:type:`set` """ _curves = None if _PY3: # This only necessary on Python 3. Morever, it is broken on Python 2. def __ne__(self, other): """ Implement cooperation with the right-hand side argument of ``!=``. Python 3 seems to have dropped this cooperation in this very narrow circumstance. """ if isinstance(other, _EllipticCurve): return super(_EllipticCurve, self).__ne__(other) return NotImplemented @classmethod def _load_elliptic_curves(cls, lib): """ Get the curves supported by OpenSSL. :param lib: The OpenSSL library binding object. :return: A :py:type:`set` of ``cls`` instances giving the names of the elliptic curves the underlying library supports. """ if lib.Cryptography_HAS_EC: num_curves = lib.EC_get_builtin_curves(_ffi.NULL, 0) builtin_curves = _ffi.new('EC_builtin_curve[]', num_curves) # The return value on this call should be num_curves again. We could # check it to make sure but if it *isn't* then.. what could we do? # Abort the whole process, I suppose...? -exarkun lib.EC_get_builtin_curves(builtin_curves, num_curves) return set( cls.from_nid(lib, c.nid) for c in builtin_curves) return set() @classmethod def _get_elliptic_curves(cls, lib): """ Get, cache, and return the curves supported by OpenSSL. :param lib: The OpenSSL library binding object. :return: A :py:type:`set` of ``cls`` instances giving the names of the elliptic curves the underlying library supports. """ if cls._curves is None: cls._curves = cls._load_elliptic_curves(lib) return cls._curves @classmethod def from_nid(cls, lib, nid): """ Instantiate a new :py:class:`_EllipticCurve` associated with the given OpenSSL NID. :param lib: The OpenSSL library binding object. :param nid: The OpenSSL NID the resulting curve object will represent. This must be a curve NID (and not, for example, a hash NID) or subsequent operations will fail in unpredictable ways. :type nid: :py:class:`int` :return: The curve object. """ return cls(lib, nid, _ffi.string(lib.OBJ_nid2sn(nid)).decode("ascii")) def __init__(self, lib, nid, name): """ :param _lib: The :py:mod:`cryptography` binding instance used to interface with OpenSSL. :param _nid: The OpenSSL NID identifying the curve this object represents. :type _nid: :py:class:`int` :param name: The OpenSSL short name identifying the curve this object represents. :type name: :py:class:`unicode` """ self._lib = lib self._nid = nid self.name = name def __repr__(self): return "" % (self.name,) def _to_EC_KEY(self): """ Create a new OpenSSL EC_KEY structure initialized to use this curve. The structure is automatically garbage collected when the Python object is garbage collected. """ key = self._lib.EC_KEY_new_by_curve_name(self._nid) return _ffi.gc(key, _lib.EC_KEY_free) def get_elliptic_curves(): """ Return a set of objects representing the elliptic curves supported in the OpenSSL build in use. The curve objects have a :py:class:`unicode` ``name`` attribute by which they identify themselves. The curve objects are useful as values for the argument accepted by :py:meth:`Context.set_tmp_ecdh` to specify which elliptical curve should be used for ECDHE key exchange. """ return _EllipticCurve._get_elliptic_curves(_lib) def get_elliptic_curve(name): """ Return a single curve object selected by name. See :py:func:`get_elliptic_curves` for information about curve objects. :param name: The OpenSSL short name identifying the curve object to retrieve. :type name: :py:class:`unicode` If the named curve is not supported then :py:class:`ValueError` is raised. """ for curve in get_elliptic_curves(): if curve.name == name: return curve raise ValueError("unknown curve name", name) class X509Name(object): def __init__(self, name): """ Create a new X509Name, copying the given X509Name instance. :param name: An X509Name object to copy """ name = _lib.X509_NAME_dup(name._name) self._name = _ffi.gc(name, _lib.X509_NAME_free) def __setattr__(self, name, value): if name.startswith('_'): return super(X509Name, self).__setattr__(name, value) # Note: we really do not want str subclasses here, so we do not use # isinstance. if type(name) is not str: raise TypeError("attribute name must be string, not '%.200s'" % ( type(value).__name__,)) nid = _lib.OBJ_txt2nid(_byte_string(name)) if nid == _lib.NID_undef: try: _raise_current_error() except Error: pass raise AttributeError("No such attribute") # If there's an old entry for this NID, remove it for i in range(_lib.X509_NAME_entry_count(self._name)): ent = _lib.X509_NAME_get_entry(self._name, i) ent_obj = _lib.X509_NAME_ENTRY_get_object(ent) ent_nid = _lib.OBJ_obj2nid(ent_obj) if nid == ent_nid: ent = _lib.X509_NAME_delete_entry(self._name, i) _lib.X509_NAME_ENTRY_free(ent) break if isinstance(value, _text_type): value = value.encode('utf-8') add_result = _lib.X509_NAME_add_entry_by_NID( self._name, nid, _lib.MBSTRING_UTF8, value, -1, -1, 0) if not add_result: _raise_current_error() def __getattr__(self, name): """ Find attribute. An X509Name object has the following attributes: countryName (alias C), stateOrProvince (alias ST), locality (alias L), organization (alias O), organizationalUnit (alias OU), commonName (alias CN) and more... """ nid = _lib.OBJ_txt2nid(_byte_string(name)) if nid == _lib.NID_undef: # This is a bit weird. OBJ_txt2nid indicated failure, but it seems # a lower level function, a2d_ASN1_OBJECT, also feels the need to # push something onto the error queue. If we don't clean that up # now, someone else will bump into it later and be quite confused. # See lp#314814. try: _raise_current_error() except Error: pass return super(X509Name, self).__getattr__(name) entry_index = _lib.X509_NAME_get_index_by_NID(self._name, nid, -1) if entry_index == -1: return None entry = _lib.X509_NAME_get_entry(self._name, entry_index) data = _lib.X509_NAME_ENTRY_get_data(entry) result_buffer = _ffi.new("unsigned char**") data_length = _lib.ASN1_STRING_to_UTF8(result_buffer, data) if data_length < 0: # TODO: This is untested. _raise_current_error() try: result = _ffi.buffer(result_buffer[0], data_length)[:].decode('utf-8') finally: # XXX untested _lib.OPENSSL_free(result_buffer[0]) return result def _cmp(op): def f(self, other): if not isinstance(other, X509Name): return NotImplemented result = _lib.X509_NAME_cmp(self._name, other._name) return op(result, 0) return f __eq__ = _cmp(__eq__) __ne__ = _cmp(__ne__) __lt__ = _cmp(__lt__) __le__ = _cmp(__le__) __gt__ = _cmp(__gt__) __ge__ = _cmp(__ge__) def __repr__(self): """ String representation of an X509Name """ result_buffer = _ffi.new("char[]", 512); format_result = _lib.X509_NAME_oneline( self._name, result_buffer, len(result_buffer)) if format_result == _ffi.NULL: # TODO: This is untested. _raise_current_error() return "" % ( _native(_ffi.string(result_buffer)),) def hash(self): """ Return the hash value of this name :return: None """ return _lib.X509_NAME_hash(self._name) def der(self): """ Return the DER encoding of this name :return: A :py:class:`bytes` instance giving the DER encoded form of this name. """ result_buffer = _ffi.new('unsigned char**') encode_result = _lib.i2d_X509_NAME(self._name, result_buffer) if encode_result < 0: # TODO: This is untested. _raise_current_error() string_result = _ffi.buffer(result_buffer[0], encode_result)[:] _lib.OPENSSL_free(result_buffer[0]) return string_result def get_components(self): """ Returns the split-up components of this name. :return: List of tuples (name, value). """ result = [] for i in range(_lib.X509_NAME_entry_count(self._name)): ent = _lib.X509_NAME_get_entry(self._name, i) fname = _lib.X509_NAME_ENTRY_get_object(ent) fval = _lib.X509_NAME_ENTRY_get_data(ent) nid = _lib.OBJ_obj2nid(fname) name = _lib.OBJ_nid2sn(nid) result.append(( _ffi.string(name), _ffi.string( _lib.ASN1_STRING_data(fval), _lib.ASN1_STRING_length(fval)))) return result X509NameType = X509Name class X509Extension(object): def __init__(self, type_name, critical, value, subject=None, issuer=None): """ :param typename: The name of the extension to create. :type typename: :py:data:`str` :param critical: A flag indicating whether this is a critical extension. :param value: The value of the extension. :type value: :py:data:`str` :param subject: Optional X509 cert to use as subject. :type subject: :py:class:`X509` :param issuer: Optional X509 cert to use as issuer. :type issuer: :py:class:`X509` :return: The X509Extension object """ ctx = _ffi.new("X509V3_CTX*") # A context is necessary for any extension which uses the r2i conversion # method. That is, X509V3_EXT_nconf may segfault if passed a NULL ctx. # Start off by initializing most of the fields to NULL. _lib.X509V3_set_ctx(ctx, _ffi.NULL, _ffi.NULL, _ffi.NULL, _ffi.NULL, 0) # We have no configuration database - but perhaps we should (some # extensions may require it). _lib.X509V3_set_ctx_nodb(ctx) # Initialize the subject and issuer, if appropriate. ctx is a local, # and as far as I can tell none of the X509V3_* APIs invoked here steal # any references, so no need to mess with reference counts or duplicates. if issuer is not None: if not isinstance(issuer, X509): raise TypeError("issuer must be an X509 instance") ctx.issuer_cert = issuer._x509 if subject is not None: if not isinstance(subject, X509): raise TypeError("subject must be an X509 instance") ctx.subject_cert = subject._x509 if critical: # There are other OpenSSL APIs which would let us pass in critical # separately, but they're harder to use, and since value is already # a pile of crappy junk smuggling a ton of utterly important # structured data, what's the point of trying to avoid nasty stuff # with strings? (However, X509V3_EXT_i2d in particular seems like it # would be a better API to invoke. I do not know where to get the # ext_struc it desires for its last parameter, though.) value = b"critical," + value extension = _lib.X509V3_EXT_nconf(_ffi.NULL, ctx, type_name, value) if extension == _ffi.NULL: _raise_current_error() self._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) @property def _nid(self): return _lib.OBJ_obj2nid(self._extension.object) _prefixes = { _lib.GEN_EMAIL: "email", _lib.GEN_DNS: "DNS", _lib.GEN_URI: "URI", } def _subjectAltNameString(self): method = _lib.X509V3_EXT_get(self._extension) if method == _ffi.NULL: # TODO: This is untested. _raise_current_error() payload = self._extension.value.data length = self._extension.value.length payloadptr = _ffi.new("unsigned char**") payloadptr[0] = payload if method.it != _ffi.NULL: ptr = _lib.ASN1_ITEM_ptr(method.it) data = _lib.ASN1_item_d2i(_ffi.NULL, payloadptr, length, ptr) names = _ffi.cast("GENERAL_NAMES*", data) else: names = _ffi.cast( "GENERAL_NAMES*", method.d2i(_ffi.NULL, payloadptr, length)) parts = [] for i in range(_lib.sk_GENERAL_NAME_num(names)): name = _lib.sk_GENERAL_NAME_value(names, i) try: label = self._prefixes[name.type] except KeyError: bio = _new_mem_buf() _lib.GENERAL_NAME_print(bio, name) parts.append(_native(_bio_to_string(bio))) else: value = _native( _ffi.buffer(name.d.ia5.data, name.d.ia5.length)[:]) parts.append(label + ":" + value) return ", ".join(parts) def __str__(self): """ :return: a nice text representation of the extension """ if _lib.NID_subject_alt_name == self._nid: return self._subjectAltNameString() bio = _new_mem_buf() print_result = _lib.X509V3_EXT_print(bio, self._extension, 0, 0) if not print_result: # TODO: This is untested. _raise_current_error() return _native(_bio_to_string(bio)) def get_critical(self): """ Returns the critical field of the X509Extension :return: The critical field. """ return _lib.X509_EXTENSION_get_critical(self._extension) def get_short_name(self): """ Returns the short version of the type name of the X509Extension :return: The short type name. """ obj = _lib.X509_EXTENSION_get_object(self._extension) nid = _lib.OBJ_obj2nid(obj) return _ffi.string(_lib.OBJ_nid2sn(nid)) def get_data(self): """ Returns the data of the X509Extension :return: A :py:data:`str` giving the X509Extension's ASN.1 encoded data. """ octet_result = _lib.X509_EXTENSION_get_data(self._extension) string_result = _ffi.cast('ASN1_STRING*', octet_result) char_result = _lib.ASN1_STRING_data(string_result) result_length = _lib.ASN1_STRING_length(string_result) return _ffi.buffer(char_result, result_length)[:] X509ExtensionType = X509Extension class X509Req(object): def __init__(self): req = _lib.X509_REQ_new() self._req = _ffi.gc(req, _lib.X509_REQ_free) def set_pubkey(self, pkey): """ Set the public key of the certificate request :param pkey: The public key to use :return: None """ set_result = _lib.X509_REQ_set_pubkey(self._req, pkey._pkey) if not set_result: # TODO: This is untested. _raise_current_error() def get_pubkey(self): """ Get the public key from the certificate request :return: The public key """ pkey = PKey.__new__(PKey) pkey._pkey = _lib.X509_REQ_get_pubkey(self._req) if pkey._pkey == _ffi.NULL: # TODO: This is untested. _raise_current_error() pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) pkey._only_public = True return pkey def set_version(self, version): """ Set the version subfield (RFC 2459, section 4.1.2.1) of the certificate request. :param version: The version number :return: None """ set_result = _lib.X509_REQ_set_version(self._req, version) if not set_result: _raise_current_error() def get_version(self): """ Get the version subfield (RFC 2459, section 4.1.2.1) of the certificate request. :return: an integer giving the value of the version subfield """ return _lib.X509_REQ_get_version(self._req) def get_subject(self): """ Create an X509Name object for the subject of the certificate request :return: An X509Name object """ name = X509Name.__new__(X509Name) name._name = _lib.X509_REQ_get_subject_name(self._req) if name._name == _ffi.NULL: # TODO: This is untested. _raise_current_error() # The name is owned by the X509Req structure. As long as the X509Name # Python object is alive, keep the X509Req Python object alive. name._owner = self return name def add_extensions(self, extensions): """ Add extensions to the request. :param extensions: a sequence of X509Extension objects :return: None """ stack = _lib.sk_X509_EXTENSION_new_null() if stack == _ffi.NULL: # TODO: This is untested. _raise_current_error() stack = _ffi.gc(stack, _lib.sk_X509_EXTENSION_free) for ext in extensions: if not isinstance(ext, X509Extension): raise ValueError("One of the elements is not an X509Extension") # TODO push can fail (here and elsewhere) _lib.sk_X509_EXTENSION_push(stack, ext._extension) add_result = _lib.X509_REQ_add_extensions(self._req, stack) if not add_result: # TODO: This is untested. _raise_current_error() def get_extensions(self): """ Get extensions to the request. :return: A :py:class:`list` of :py:class:`X509Extension` objects. """ exts = [] native_exts_obj = _lib.X509_REQ_get_extensions(self._req) for i in range(_lib.sk_X509_EXTENSION_num(native_exts_obj)): ext = X509Extension.__new__(X509Extension) ext._extension = _lib.sk_X509_EXTENSION_value(native_exts_obj, i) exts.append(ext) return exts def sign(self, pkey, digest): """ Sign the certificate request using the supplied key and digest :param pkey: The key to sign with :param digest: The message digest to use :return: None """ if pkey._only_public: raise ValueError("Key has only public part") if not pkey._initialized: raise ValueError("Key is uninitialized") digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) if digest_obj == _ffi.NULL: raise ValueError("No such digest method") sign_result = _lib.X509_REQ_sign(self._req, pkey._pkey, digest_obj) if not sign_result: # TODO: This is untested. _raise_current_error() def verify(self, pkey): """ Verifies a certificate request using the supplied public key :param key: a public key :return: True if the signature is correct. :raise OpenSSL.crypto.Error: If the signature is invalid or there is a problem verifying the signature. """ if not isinstance(pkey, PKey): raise TypeError("pkey must be a PKey instance") result = _lib.X509_REQ_verify(self._req, pkey._pkey) if result <= 0: _raise_current_error() return result X509ReqType = X509Req class X509(object): def __init__(self): # TODO Allocation failure? And why not __new__ instead of __init__? x509 = _lib.X509_new() self._x509 = _ffi.gc(x509, _lib.X509_free) def set_version(self, version): """ Set version number of the certificate :param version: The version number :type version: :py:class:`int` :return: None """ if not isinstance(version, int): raise TypeError("version must be an integer") _lib.X509_set_version(self._x509, version) def get_version(self): """ Return version number of the certificate :return: Version number as a Python integer """ return _lib.X509_get_version(self._x509) def get_pubkey(self): """ Get the public key of the certificate :return: The public key """ pkey = PKey.__new__(PKey) pkey._pkey = _lib.X509_get_pubkey(self._x509) if pkey._pkey == _ffi.NULL: _raise_current_error() pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) pkey._only_public = True return pkey def set_pubkey(self, pkey): """ Set the public key of the certificate :param pkey: The public key :return: None """ if not isinstance(pkey, PKey): raise TypeError("pkey must be a PKey instance") set_result = _lib.X509_set_pubkey(self._x509, pkey._pkey) if not set_result: _raise_current_error() def sign(self, pkey, digest): """ Sign the certificate using the supplied key and digest :param pkey: The key to sign with :param digest: The message digest to use :return: None """ if not isinstance(pkey, PKey): raise TypeError("pkey must be a PKey instance") if pkey._only_public: raise ValueError("Key only has public part") if not pkey._initialized: raise ValueError("Key is uninitialized") evp_md = _lib.EVP_get_digestbyname(_byte_string(digest)) if evp_md == _ffi.NULL: raise ValueError("No such digest method") sign_result = _lib.X509_sign(self._x509, pkey._pkey, evp_md) if not sign_result: _raise_current_error() def get_signature_algorithm(self): """ Retrieve the signature algorithm used in the certificate :return: A byte string giving the name of the signature algorithm used in the certificate. :raise ValueError: If the signature algorithm is undefined. """ alg = self._x509.cert_info.signature.algorithm nid = _lib.OBJ_obj2nid(alg) if nid == _lib.NID_undef: raise ValueError("Undefined signature algorithm") return _ffi.string(_lib.OBJ_nid2ln(nid)) def digest(self, digest_name): """ Return the digest of the X509 object. :param digest_name: The name of the digest algorithm to use. :type digest_name: :py:class:`bytes` :return: The digest of the object """ digest = _lib.EVP_get_digestbyname(_byte_string(digest_name)) if digest == _ffi.NULL: raise ValueError("No such digest method") result_buffer = _ffi.new("char[]", _lib.EVP_MAX_MD_SIZE) result_length = _ffi.new("unsigned int[]", 1) result_length[0] = len(result_buffer) digest_result = _lib.X509_digest( self._x509, digest, result_buffer, result_length) if not digest_result: # TODO: This is untested. _raise_current_error() return b":".join([ b16encode(ch).upper() for ch in _ffi.buffer(result_buffer, result_length[0])]) def subject_name_hash(self): """ Return the hash of the X509 subject. :return: The hash of the subject. """ return _lib.X509_subject_name_hash(self._x509) def set_serial_number(self, serial): """ Set serial number of the certificate :param serial: The serial number :type serial: :py:class:`int` :return: None """ if not isinstance(serial, _integer_types): raise TypeError("serial must be an integer") hex_serial = hex(serial)[2:] if not isinstance(hex_serial, bytes): hex_serial = hex_serial.encode('ascii') bignum_serial = _ffi.new("BIGNUM**") # BN_hex2bn stores the result in &bignum. Unless it doesn't feel like # it. If bignum is still NULL after this call, then the return value is # actually the result. I hope. -exarkun small_serial = _lib.BN_hex2bn(bignum_serial, hex_serial) if bignum_serial[0] == _ffi.NULL: set_result = _lib.ASN1_INTEGER_set( _lib.X509_get_serialNumber(self._x509), small_serial) if set_result: # TODO Not tested _raise_current_error() else: asn1_serial = _lib.BN_to_ASN1_INTEGER(bignum_serial[0], _ffi.NULL) _lib.BN_free(bignum_serial[0]) if asn1_serial == _ffi.NULL: # TODO Not tested _raise_current_error() asn1_serial = _ffi.gc(asn1_serial, _lib.ASN1_INTEGER_free) set_result = _lib.X509_set_serialNumber(self._x509, asn1_serial) if not set_result: # TODO Not tested _raise_current_error() def get_serial_number(self): """ Return serial number of the certificate :return: Serial number as a Python integer """ asn1_serial = _lib.X509_get_serialNumber(self._x509) bignum_serial = _lib.ASN1_INTEGER_to_BN(asn1_serial, _ffi.NULL) try: hex_serial = _lib.BN_bn2hex(bignum_serial) try: hexstring_serial = _ffi.string(hex_serial) serial = int(hexstring_serial, 16) return serial finally: _lib.OPENSSL_free(hex_serial) finally: _lib.BN_free(bignum_serial) def gmtime_adj_notAfter(self, amount): """ Adjust the time stamp for when the certificate stops being valid :param amount: The number of seconds by which to adjust the ending validity time. :type amount: :py:class:`int` :return: None """ if not isinstance(amount, int): raise TypeError("amount must be an integer") notAfter = _lib.X509_get_notAfter(self._x509) _lib.X509_gmtime_adj(notAfter, amount) def gmtime_adj_notBefore(self, amount): """ Change the timestamp for when the certificate starts being valid to the current time plus an offset. :param amount: The number of seconds by which to adjust the starting validity time. :return: None """ if not isinstance(amount, int): raise TypeError("amount must be an integer") notBefore = _lib.X509_get_notBefore(self._x509) _lib.X509_gmtime_adj(notBefore, amount) def has_expired(self): """ Check whether the certificate has expired. :return: True if the certificate has expired, false otherwise """ now = int(time()) notAfter = _lib.X509_get_notAfter(self._x509) return _lib.ASN1_UTCTIME_cmp_time_t( _ffi.cast('ASN1_UTCTIME*', notAfter), now) < 0 def _get_boundary_time(self, which): return _get_asn1_time(which(self._x509)) def get_notBefore(self): """ Retrieve the time stamp for when the certificate starts being valid :return: A string giving the timestamp, in the format:: YYYYMMDDhhmmssZ YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm or None if there is no value set. """ return self._get_boundary_time(_lib.X509_get_notBefore) def _set_boundary_time(self, which, when): return _set_asn1_time(which(self._x509), when) def set_notBefore(self, when): """ Set the time stamp for when the certificate starts being valid :param when: A string giving the timestamp, in the format: YYYYMMDDhhmmssZ YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm :type when: :py:class:`bytes` :return: None """ return self._set_boundary_time(_lib.X509_get_notBefore, when) def get_notAfter(self): """ Retrieve the time stamp for when the certificate stops being valid :return: A string giving the timestamp, in the format:: YYYYMMDDhhmmssZ YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm or None if there is no value set. """ return self._get_boundary_time(_lib.X509_get_notAfter) def set_notAfter(self, when): """ Set the time stamp for when the certificate stops being valid :param when: A string giving the timestamp, in the format: YYYYMMDDhhmmssZ YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm :type when: :py:class:`bytes` :return: None """ return self._set_boundary_time(_lib.X509_get_notAfter, when) def _get_name(self, which): name = X509Name.__new__(X509Name) name._name = which(self._x509) if name._name == _ffi.NULL: # TODO: This is untested. _raise_current_error() # The name is owned by the X509 structure. As long as the X509Name # Python object is alive, keep the X509 Python object alive. name._owner = self return name def _set_name(self, which, name): if not isinstance(name, X509Name): raise TypeError("name must be an X509Name") set_result = which(self._x509, name._name) if not set_result: # TODO: This is untested. _raise_current_error() def get_issuer(self): """ Create an X509Name object for the issuer of the certificate :return: An X509Name object """ return self._get_name(_lib.X509_get_issuer_name) def set_issuer(self, issuer): """ Set the issuer of the certificate :param issuer: The issuer name :type issuer: :py:class:`X509Name` :return: None """ return self._set_name(_lib.X509_set_issuer_name, issuer) def get_subject(self): """ Create an X509Name object for the subject of the certificate :return: An X509Name object """ return self._get_name(_lib.X509_get_subject_name) def set_subject(self, subject): """ Set the subject of the certificate :param subject: The subject name :type subject: :py:class:`X509Name` :return: None """ return self._set_name(_lib.X509_set_subject_name, subject) def get_extension_count(self): """ Get the number of extensions on the certificate. :return: The number of extensions as an integer. """ return _lib.X509_get_ext_count(self._x509) def add_extensions(self, extensions): """ Add extensions to the certificate. :param extensions: a sequence of X509Extension objects :return: None """ for ext in extensions: if not isinstance(ext, X509Extension): raise ValueError("One of the elements is not an X509Extension") add_result = _lib.X509_add_ext(self._x509, ext._extension, -1) if not add_result: _raise_current_error() def get_extension(self, index): """ Get a specific extension of the certificate by index. :param index: The index of the extension to retrieve. :return: The X509Extension object at the specified index. """ ext = X509Extension.__new__(X509Extension) ext._extension = _lib.X509_get_ext(self._x509, index) if ext._extension == _ffi.NULL: raise IndexError("extension index out of bounds") extension = _lib.X509_EXTENSION_dup(ext._extension) ext._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) return ext X509Type = X509 class X509Store(object): def __init__(self): store = _lib.X509_STORE_new() self._store = _ffi.gc(store, _lib.X509_STORE_free) def add_cert(self, cert): if not isinstance(cert, X509): raise TypeError() result = _lib.X509_STORE_add_cert(self._store, cert._x509) if not result: _raise_current_error() X509StoreType = X509Store class X509StoreContextError(Exception): """ An error occurred while verifying a certificate using `OpenSSL.X509StoreContext.verify_certificate`. :ivar certificate: The certificate which caused verificate failure. :type cert: :class:`X509` """ def __init__(self, message, certificate): super(X509StoreContextError, self).__init__(message) self.certificate = certificate class X509StoreContext(object): """ An X.509 store context. An :py:class:`X509StoreContext` is used to define some of the criteria for certificate verification. The information encapsulated in this object includes, but is not limited to, a set of trusted certificates, verification parameters, and revoked certificates. Of these, only the set of trusted certificates is currently exposed. :ivar _store_ctx: The underlying X509_STORE_CTX structure used by this instance. It is dynamically allocated and automatically garbage collected. :ivar _store: See the ``store`` ``__init__`` parameter. :ivar _cert: See the ``certificate`` ``__init__`` parameter. """ def __init__(self, store, certificate): """ :param X509Store store: The certificates which will be trusted for the purposes of any verifications. :param X509 certificate: The certificate to be verified. """ store_ctx = _lib.X509_STORE_CTX_new() self._store_ctx = _ffi.gc(store_ctx, _lib.X509_STORE_CTX_free) self._store = store self._cert = certificate # Make the store context available for use after instantiating this # class by initializing it now. Per testing, subsequent calls to # :py:meth:`_init` have no adverse affect. self._init() def _init(self): """ Set up the store context for a subsequent verification operation. """ ret = _lib.X509_STORE_CTX_init(self._store_ctx, self._store._store, self._cert._x509, _ffi.NULL) if ret <= 0: _raise_current_error() def _cleanup(self): """ Internally cleans up the store context. The store context can then be reused with a new call to :py:meth:`_init`. """ _lib.X509_STORE_CTX_cleanup(self._store_ctx) def _exception_from_context(self): """ Convert an OpenSSL native context error failure into a Python exception. When a call to native OpenSSL X509_verify_cert fails, additonal information about the failure can be obtained from the store context. """ errors = [ _lib.X509_STORE_CTX_get_error(self._store_ctx), _lib.X509_STORE_CTX_get_error_depth(self._store_ctx), _native(_ffi.string(_lib.X509_verify_cert_error_string( _lib.X509_STORE_CTX_get_error(self._store_ctx)))), ] # A context error should always be associated with a certificate, so we # expect this call to never return :class:`None`. _x509 = _lib.X509_STORE_CTX_get_current_cert(self._store_ctx) _cert = _lib.X509_dup(_x509) pycert = X509.__new__(X509) pycert._x509 = _ffi.gc(_cert, _lib.X509_free) return X509StoreContextError(errors, pycert) def set_store(self, store): """ Set the context's trust store. :param X509Store store: The certificates which will be trusted for the purposes of any *future* verifications. """ self._store = store def verify_certificate(self): """ Verify a certificate in a context. :param store_ctx: The :py:class:`X509StoreContext` to verify. :raises: Error """ # Always re-initialize the store context in case # :py:meth:`verify_certificate` is called multiple times. self._init() ret = _lib.X509_verify_cert(self._store_ctx) self._cleanup() if ret <= 0: raise self._exception_from_context() def load_certificate(type, buffer): """ Load a certificate from a buffer :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) :param buffer: The buffer the certificate is stored in :type buffer: :py:class:`bytes` :return: The X509 object """ if isinstance(buffer, _text_type): buffer = buffer.encode("ascii") bio = _new_mem_buf(buffer) if type == FILETYPE_PEM: x509 = _lib.PEM_read_bio_X509(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) elif type == FILETYPE_ASN1: x509 = _lib.d2i_X509_bio(bio, _ffi.NULL); else: raise ValueError( "type argument must be FILETYPE_PEM or FILETYPE_ASN1") if x509 == _ffi.NULL: _raise_current_error() cert = X509.__new__(X509) cert._x509 = _ffi.gc(x509, _lib.X509_free) return cert def dump_certificate(type, cert): """ Dump a certificate to a buffer :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT) :param cert: The certificate to dump :return: The buffer with the dumped certificate in """ bio = _new_mem_buf() if type == FILETYPE_PEM: result_code = _lib.PEM_write_bio_X509(bio, cert._x509) elif type == FILETYPE_ASN1: result_code = _lib.i2d_X509_bio(bio, cert._x509) elif type == FILETYPE_TEXT: result_code = _lib.X509_print_ex(bio, cert._x509, 0, 0) else: raise ValueError( "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " "FILETYPE_TEXT") return _bio_to_string(bio) def dump_privatekey(type, pkey, cipher=None, passphrase=None): """ Dump a private key to a buffer :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT) :param pkey: The PKey to dump :param cipher: (optional) if encrypted PEM format, the cipher to use :param passphrase: (optional) if encrypted PEM format, this can be either the passphrase to use, or a callback for providing the passphrase. :return: The buffer with the dumped key in :rtype: :py:data:`str` """ bio = _new_mem_buf() if cipher is not None: if passphrase is None: raise TypeError( "if a value is given for cipher " "one must also be given for passphrase") cipher_obj = _lib.EVP_get_cipherbyname(_byte_string(cipher)) if cipher_obj == _ffi.NULL: raise ValueError("Invalid cipher name") else: cipher_obj = _ffi.NULL helper = _PassphraseHelper(type, passphrase) if type == FILETYPE_PEM: result_code = _lib.PEM_write_bio_PrivateKey( bio, pkey._pkey, cipher_obj, _ffi.NULL, 0, helper.callback, helper.callback_args) helper.raise_if_problem() elif type == FILETYPE_ASN1: result_code = _lib.i2d_PrivateKey_bio(bio, pkey._pkey) elif type == FILETYPE_TEXT: rsa = _lib.EVP_PKEY_get1_RSA(pkey._pkey) result_code = _lib.RSA_print(bio, rsa, 0) # TODO RSA_free(rsa)? else: raise ValueError( "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " "FILETYPE_TEXT") if result_code == 0: _raise_current_error() return _bio_to_string(bio) def _X509_REVOKED_dup(original): copy = _lib.X509_REVOKED_new() if copy == _ffi.NULL: # TODO: This is untested. _raise_current_error() if original.serialNumber != _ffi.NULL: _lib.ASN1_INTEGER_free(copy.serialNumber) copy.serialNumber = _lib.ASN1_INTEGER_dup(original.serialNumber) if original.revocationDate != _ffi.NULL: _lib.ASN1_TIME_free(copy.revocationDate) copy.revocationDate = _lib.M_ASN1_TIME_dup(original.revocationDate) if original.extensions != _ffi.NULL: extension_stack = _lib.sk_X509_EXTENSION_new_null() for i in range(_lib.sk_X509_EXTENSION_num(original.extensions)): original_ext = _lib.sk_X509_EXTENSION_value(original.extensions, i) copy_ext = _lib.X509_EXTENSION_dup(original_ext) _lib.sk_X509_EXTENSION_push(extension_stack, copy_ext) copy.extensions = extension_stack copy.sequence = original.sequence return copy class Revoked(object): # http://www.openssl.org/docs/apps/x509v3_config.html#CRL_distribution_points_ # which differs from crl_reasons of crypto/x509v3/v3_enum.c that matches # OCSP_crl_reason_str. We use the latter, just like the command line # program. _crl_reasons = [ b"unspecified", b"keyCompromise", b"CACompromise", b"affiliationChanged", b"superseded", b"cessationOfOperation", b"certificateHold", # b"removeFromCRL", ] def __init__(self): revoked = _lib.X509_REVOKED_new() self._revoked = _ffi.gc(revoked, _lib.X509_REVOKED_free) def set_serial(self, hex_str): """ Set the serial number of a revoked Revoked structure :param hex_str: The new serial number. :type hex_str: :py:data:`str` :return: None """ bignum_serial = _ffi.gc(_lib.BN_new(), _lib.BN_free) bignum_ptr = _ffi.new("BIGNUM**") bignum_ptr[0] = bignum_serial bn_result = _lib.BN_hex2bn(bignum_ptr, hex_str) if not bn_result: raise ValueError("bad hex string") asn1_serial = _ffi.gc( _lib.BN_to_ASN1_INTEGER(bignum_serial, _ffi.NULL), _lib.ASN1_INTEGER_free) _lib.X509_REVOKED_set_serialNumber(self._revoked, asn1_serial) def get_serial(self): """ Return the serial number of a Revoked structure :return: The serial number as a string """ bio = _new_mem_buf() result = _lib.i2a_ASN1_INTEGER(bio, self._revoked.serialNumber) if result < 0: # TODO: This is untested. _raise_current_error() return _bio_to_string(bio) def _delete_reason(self): stack = self._revoked.extensions for i in range(_lib.sk_X509_EXTENSION_num(stack)): ext = _lib.sk_X509_EXTENSION_value(stack, i) if _lib.OBJ_obj2nid(ext.object) == _lib.NID_crl_reason: _lib.X509_EXTENSION_free(ext) _lib.sk_X509_EXTENSION_delete(stack, i) break def set_reason(self, reason): """ Set the reason of a Revoked object. If :py:data:`reason` is :py:data:`None`, delete the reason instead. :param reason: The reason string. :type reason: :py:class:`str` or :py:class:`NoneType` :return: None """ if reason is None: self._delete_reason() elif not isinstance(reason, bytes): raise TypeError("reason must be None or a byte string") else: reason = reason.lower().replace(b' ', b'') reason_code = [r.lower() for r in self._crl_reasons].index(reason) new_reason_ext = _lib.ASN1_ENUMERATED_new() if new_reason_ext == _ffi.NULL: # TODO: This is untested. _raise_current_error() new_reason_ext = _ffi.gc(new_reason_ext, _lib.ASN1_ENUMERATED_free) set_result = _lib.ASN1_ENUMERATED_set(new_reason_ext, reason_code) if set_result == _ffi.NULL: # TODO: This is untested. _raise_current_error() self._delete_reason() add_result = _lib.X509_REVOKED_add1_ext_i2d( self._revoked, _lib.NID_crl_reason, new_reason_ext, 0, 0) if not add_result: # TODO: This is untested. _raise_current_error() def get_reason(self): """ Return the reason of a Revoked object. :return: The reason as a string """ extensions = self._revoked.extensions for i in range(_lib.sk_X509_EXTENSION_num(extensions)): ext = _lib.sk_X509_EXTENSION_value(extensions, i) if _lib.OBJ_obj2nid(ext.object) == _lib.NID_crl_reason: bio = _new_mem_buf() print_result = _lib.X509V3_EXT_print(bio, ext, 0, 0) if not print_result: print_result = _lib.M_ASN1_OCTET_STRING_print(bio, ext.value) if print_result == 0: # TODO: This is untested. _raise_current_error() return _bio_to_string(bio) def all_reasons(self): """ Return a list of all the supported reason strings. :return: A list of reason strings. """ return self._crl_reasons[:] def set_rev_date(self, when): """ Set the revocation timestamp :param when: A string giving the timestamp, in the format: YYYYMMDDhhmmssZ YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm :return: None """ return _set_asn1_time(self._revoked.revocationDate, when) def get_rev_date(self): """ Retrieve the revocation date :return: A string giving the timestamp, in the format: YYYYMMDDhhmmssZ YYYYMMDDhhmmss+hhmm YYYYMMDDhhmmss-hhmm """ return _get_asn1_time(self._revoked.revocationDate) class CRL(object): def __init__(self): """ Create a new empty CRL object. """ crl = _lib.X509_CRL_new() self._crl = _ffi.gc(crl, _lib.X509_CRL_free) def get_revoked(self): """ Return revoked portion of the CRL structure (by value not reference). :return: A tuple of Revoked objects. """ results = [] revoked_stack = self._crl.crl.revoked for i in range(_lib.sk_X509_REVOKED_num(revoked_stack)): revoked = _lib.sk_X509_REVOKED_value(revoked_stack, i) revoked_copy = _X509_REVOKED_dup(revoked) pyrev = Revoked.__new__(Revoked) pyrev._revoked = _ffi.gc(revoked_copy, _lib.X509_REVOKED_free) results.append(pyrev) if results: return tuple(results) def add_revoked(self, revoked): """ Add a revoked (by value not reference) to the CRL structure :param revoked: The new revoked. :type revoked: :class:`X509` :return: None """ copy = _X509_REVOKED_dup(revoked._revoked) if copy == _ffi.NULL: # TODO: This is untested. _raise_current_error() add_result = _lib.X509_CRL_add0_revoked(self._crl, copy) if add_result == 0: # TODO: This is untested. _raise_current_error() def export(self, cert, key, type=FILETYPE_PEM, days=100, digest=_UNSPECIFIED): """ export a CRL as a string :param cert: Used to sign CRL. :type cert: :class:`X509` :param key: Used to sign CRL. :type key: :class:`PKey` :param type: The export format, either :py:data:`FILETYPE_PEM`, :py:data:`FILETYPE_ASN1`, or :py:data:`FILETYPE_TEXT`. :param int days: The number of days until the next update of this CRL. :param bytes digest: The name of the message digest to use (eg ``b"sha1"``). :return: :py:data:`bytes` """ if not isinstance(cert, X509): raise TypeError("cert must be an X509 instance") if not isinstance(key, PKey): raise TypeError("key must be a PKey instance") if not isinstance(type, int): raise TypeError("type must be an integer") if digest is _UNSPECIFIED: _warn( "The default message digest (md5) is deprecated. " "Pass the name of a message digest explicitly.", category=DeprecationWarning, stacklevel=2, ) digest = b"md5" digest_obj = _lib.EVP_get_digestbyname(digest) if digest_obj == _ffi.NULL: raise ValueError("No such digest method") bio = _lib.BIO_new(_lib.BIO_s_mem()) if bio == _ffi.NULL: # TODO: This is untested. _raise_current_error() # A scratch time object to give different values to different CRL fields sometime = _lib.ASN1_TIME_new() if sometime == _ffi.NULL: # TODO: This is untested. _raise_current_error() _lib.X509_gmtime_adj(sometime, 0) _lib.X509_CRL_set_lastUpdate(self._crl, sometime) _lib.X509_gmtime_adj(sometime, days * 24 * 60 * 60) _lib.X509_CRL_set_nextUpdate(self._crl, sometime) _lib.X509_CRL_set_issuer_name(self._crl, _lib.X509_get_subject_name(cert._x509)) sign_result = _lib.X509_CRL_sign(self._crl, key._pkey, digest_obj) if not sign_result: _raise_current_error() if type == FILETYPE_PEM: ret = _lib.PEM_write_bio_X509_CRL(bio, self._crl) elif type == FILETYPE_ASN1: ret = _lib.i2d_X509_CRL_bio(bio, self._crl) elif type == FILETYPE_TEXT: ret = _lib.X509_CRL_print(bio, self._crl) else: raise ValueError( "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT") if not ret: # TODO: This is untested. _raise_current_error() return _bio_to_string(bio) CRLType = CRL class PKCS7(object): def type_is_signed(self): """ Check if this NID_pkcs7_signed object :return: True if the PKCS7 is of type signed """ if _lib.PKCS7_type_is_signed(self._pkcs7): return True return False def type_is_enveloped(self): """ Check if this NID_pkcs7_enveloped object :returns: True if the PKCS7 is of type enveloped """ if _lib.PKCS7_type_is_enveloped(self._pkcs7): return True return False def type_is_signedAndEnveloped(self): """ Check if this NID_pkcs7_signedAndEnveloped object :returns: True if the PKCS7 is of type signedAndEnveloped """ if _lib.PKCS7_type_is_signedAndEnveloped(self._pkcs7): return True return False def type_is_data(self): """ Check if this NID_pkcs7_data object :return: True if the PKCS7 is of type data """ if _lib.PKCS7_type_is_data(self._pkcs7): return True return False def get_type_name(self): """ Returns the type name of the PKCS7 structure :return: A string with the typename """ nid = _lib.OBJ_obj2nid(self._pkcs7.type) string_type = _lib.OBJ_nid2sn(nid) return _ffi.string(string_type) PKCS7Type = PKCS7 class PKCS12(object): def __init__(self): self._pkey = None self._cert = None self._cacerts = None self._friendlyname = None def get_certificate(self): """ Return certificate portion of the PKCS12 structure :return: X509 object containing the certificate """ return self._cert def set_certificate(self, cert): """ Replace the certificate portion of the PKCS12 structure :param cert: The new certificate. :type cert: :py:class:`X509` or :py:data:`None` :return: None """ if not isinstance(cert, X509): raise TypeError("cert must be an X509 instance") self._cert = cert def get_privatekey(self): """ Return private key portion of the PKCS12 structure :returns: PKey object containing the private key """ return self._pkey def set_privatekey(self, pkey): """ Replace or set the certificate portion of the PKCS12 structure :param pkey: The new private key. :type pkey: :py:class:`PKey` :return: None """ if not isinstance(pkey, PKey): raise TypeError("pkey must be a PKey instance") self._pkey = pkey def get_ca_certificates(self): """ Return CA certificates within of the PKCS12 object :return: A newly created tuple containing the CA certificates in the chain, if any are present, or None if no CA certificates are present. """ if self._cacerts is not None: return tuple(self._cacerts) def set_ca_certificates(self, cacerts): """ Replace or set the CA certificates within the PKCS12 object. :param cacerts: The new CA certificates. :type cacerts: :py:data:`None` or an iterable of :py:class:`X509` :return: None """ if cacerts is None: self._cacerts = None else: cacerts = list(cacerts) for cert in cacerts: if not isinstance(cert, X509): raise TypeError("iterable must only contain X509 instances") self._cacerts = cacerts def set_friendlyname(self, name): """ Replace or set the certificate portion of the PKCS12 structure :param name: The new friendly name. :type name: :py:class:`bytes` :return: None """ if name is None: self._friendlyname = None elif not isinstance(name, bytes): raise TypeError("name must be a byte string or None (not %r)" % (name,)) self._friendlyname = name def get_friendlyname(self): """ Return friendly name portion of the PKCS12 structure :returns: String containing the friendlyname """ return self._friendlyname def export(self, passphrase=None, iter=2048, maciter=1): """ Dump a PKCS12 object as a string. See also "man PKCS12_create". :param passphrase: used to encrypt the PKCS12 :type passphrase: :py:data:`bytes` :param iter: How many times to repeat the encryption :type iter: :py:data:`int` :param maciter: How many times to repeat the MAC :type maciter: :py:data:`int` :return: The string containing the PKCS12 """ passphrase = _text_to_bytes_and_warn("passphrase", passphrase) if self._cacerts is None: cacerts = _ffi.NULL else: cacerts = _lib.sk_X509_new_null() cacerts = _ffi.gc(cacerts, _lib.sk_X509_free) for cert in self._cacerts: _lib.sk_X509_push(cacerts, cert._x509) if passphrase is None: passphrase = _ffi.NULL friendlyname = self._friendlyname if friendlyname is None: friendlyname = _ffi.NULL if self._pkey is None: pkey = _ffi.NULL else: pkey = self._pkey._pkey if self._cert is None: cert = _ffi.NULL else: cert = self._cert._x509 pkcs12 = _lib.PKCS12_create( passphrase, friendlyname, pkey, cert, cacerts, _lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC, _lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC, iter, maciter, 0) if pkcs12 == _ffi.NULL: _raise_current_error() pkcs12 = _ffi.gc(pkcs12, _lib.PKCS12_free) bio = _new_mem_buf() _lib.i2d_PKCS12_bio(bio, pkcs12) return _bio_to_string(bio) PKCS12Type = PKCS12 class NetscapeSPKI(object): def __init__(self): spki = _lib.NETSCAPE_SPKI_new() self._spki = _ffi.gc(spki, _lib.NETSCAPE_SPKI_free) def sign(self, pkey, digest): """ Sign the certificate request using the supplied key and digest :param pkey: The key to sign with :param digest: The message digest to use :return: None """ if pkey._only_public: raise ValueError("Key has only public part") if not pkey._initialized: raise ValueError("Key is uninitialized") digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) if digest_obj == _ffi.NULL: raise ValueError("No such digest method") sign_result = _lib.NETSCAPE_SPKI_sign(self._spki, pkey._pkey, digest_obj) if not sign_result: # TODO: This is untested. _raise_current_error() def verify(self, key): """ Verifies a certificate request using the supplied public key :param key: a public key :return: True if the signature is correct. :raise OpenSSL.crypto.Error: If the signature is invalid or there is a problem verifying the signature. """ answer = _lib.NETSCAPE_SPKI_verify(self._spki, key._pkey) if answer <= 0: _raise_current_error() return True def b64_encode(self): """ Generate a base64 encoded string from an SPKI :return: The base64 encoded string """ encoded = _lib.NETSCAPE_SPKI_b64_encode(self._spki) result = _ffi.string(encoded) _lib.CRYPTO_free(encoded) return result def get_pubkey(self): """ Get the public key of the certificate :return: The public key """ pkey = PKey.__new__(PKey) pkey._pkey = _lib.NETSCAPE_SPKI_get_pubkey(self._spki) if pkey._pkey == _ffi.NULL: # TODO: This is untested. _raise_current_error() pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) pkey._only_public = True return pkey def set_pubkey(self, pkey): """ Set the public key of the certificate :param pkey: The public key :return: None """ set_result = _lib.NETSCAPE_SPKI_set_pubkey(self._spki, pkey._pkey) if not set_result: # TODO: This is untested. _raise_current_error() NetscapeSPKIType = NetscapeSPKI class _PassphraseHelper(object): def __init__(self, type, passphrase, more_args=False, truncate=False): if type != FILETYPE_PEM and passphrase is not None: raise ValueError("only FILETYPE_PEM key format supports encryption") self._passphrase = passphrase self._more_args = more_args self._truncate = truncate self._problems = [] @property def callback(self): if self._passphrase is None: return _ffi.NULL elif isinstance(self._passphrase, bytes): return _ffi.NULL elif callable(self._passphrase): return _ffi.callback("pem_password_cb", self._read_passphrase) else: raise TypeError("Last argument must be string or callable") @property def callback_args(self): if self._passphrase is None: return _ffi.NULL elif isinstance(self._passphrase, bytes): return self._passphrase elif callable(self._passphrase): return _ffi.NULL else: raise TypeError("Last argument must be string or callable") def raise_if_problem(self, exceptionType=Error): try: _exception_from_error_queue(exceptionType) except exceptionType as e: from_queue = e if self._problems: raise self._problems[0] return from_queue def _read_passphrase(self, buf, size, rwflag, userdata): try: if self._more_args: result = self._passphrase(size, rwflag, userdata) else: result = self._passphrase(rwflag) if not isinstance(result, bytes): raise ValueError("String expected") if len(result) > size: if self._truncate: result = result[:size] else: raise ValueError("passphrase returned by callback is too long") for i in range(len(result)): buf[i] = result[i:i + 1] return len(result) except Exception as e: self._problems.append(e) return 0 def load_privatekey(type, buffer, passphrase=None): """ Load a private key from a buffer :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) :param buffer: The buffer the key is stored in :param passphrase: (optional) if encrypted PEM format, this can be either the passphrase to use, or a callback for providing the passphrase. :return: The PKey object """ if isinstance(buffer, _text_type): buffer = buffer.encode("ascii") bio = _new_mem_buf(buffer) helper = _PassphraseHelper(type, passphrase) if type == FILETYPE_PEM: evp_pkey = _lib.PEM_read_bio_PrivateKey( bio, _ffi.NULL, helper.callback, helper.callback_args) helper.raise_if_problem() elif type == FILETYPE_ASN1: evp_pkey = _lib.d2i_PrivateKey_bio(bio, _ffi.NULL) else: raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") if evp_pkey == _ffi.NULL: _raise_current_error() pkey = PKey.__new__(PKey) pkey._pkey = _ffi.gc(evp_pkey, _lib.EVP_PKEY_free) return pkey def dump_certificate_request(type, req): """ Dump a certificate request to a buffer :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) :param req: The certificate request to dump :return: The buffer with the dumped certificate request in """ bio = _new_mem_buf() if type == FILETYPE_PEM: result_code = _lib.PEM_write_bio_X509_REQ(bio, req._req) elif type == FILETYPE_ASN1: result_code = _lib.i2d_X509_REQ_bio(bio, req._req) elif type == FILETYPE_TEXT: result_code = _lib.X509_REQ_print_ex(bio, req._req, 0, 0) else: raise ValueError("type argument must be FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT") if result_code == 0: # TODO: This is untested. _raise_current_error() return _bio_to_string(bio) def load_certificate_request(type, buffer): """ Load a certificate request from a buffer :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) :param buffer: The buffer the certificate request is stored in :return: The X509Req object """ if isinstance(buffer, _text_type): buffer = buffer.encode("ascii") bio = _new_mem_buf(buffer) if type == FILETYPE_PEM: req = _lib.PEM_read_bio_X509_REQ(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) elif type == FILETYPE_ASN1: req = _lib.d2i_X509_REQ_bio(bio, _ffi.NULL) else: raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") if req == _ffi.NULL: # TODO: This is untested. _raise_current_error() x509req = X509Req.__new__(X509Req) x509req._req = _ffi.gc(req, _lib.X509_REQ_free) return x509req def sign(pkey, data, digest): """ Sign data with a digest :param pkey: Pkey to sign with :param data: data to be signed :param digest: message digest to use :return: signature """ data = _text_to_bytes_and_warn("data", data) digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) if digest_obj == _ffi.NULL: raise ValueError("No such digest method") md_ctx = _ffi.new("EVP_MD_CTX*") md_ctx = _ffi.gc(md_ctx, _lib.EVP_MD_CTX_cleanup) _lib.EVP_SignInit(md_ctx, digest_obj) _lib.EVP_SignUpdate(md_ctx, data, len(data)) signature_buffer = _ffi.new("unsigned char[]", 512) signature_length = _ffi.new("unsigned int*") signature_length[0] = len(signature_buffer) final_result = _lib.EVP_SignFinal( md_ctx, signature_buffer, signature_length, pkey._pkey) if final_result != 1: # TODO: This is untested. _raise_current_error() return _ffi.buffer(signature_buffer, signature_length[0])[:] def verify(cert, signature, data, digest): """ Verify a signature :param cert: signing certificate (X509 object) :param signature: signature returned by sign function :param data: data to be verified :param digest: message digest to use :return: None if the signature is correct, raise exception otherwise """ data = _text_to_bytes_and_warn("data", data) digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) if digest_obj == _ffi.NULL: raise ValueError("No such digest method") pkey = _lib.X509_get_pubkey(cert._x509) if pkey == _ffi.NULL: # TODO: This is untested. _raise_current_error() pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free) md_ctx = _ffi.new("EVP_MD_CTX*") md_ctx = _ffi.gc(md_ctx, _lib.EVP_MD_CTX_cleanup) _lib.EVP_VerifyInit(md_ctx, digest_obj) _lib.EVP_VerifyUpdate(md_ctx, data, len(data)) verify_result = _lib.EVP_VerifyFinal(md_ctx, signature, len(signature), pkey) if verify_result != 1: _raise_current_error() def load_crl(type, buffer): """ Load a certificate revocation list from a buffer :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) :param buffer: The buffer the CRL is stored in :return: The PKey object """ if isinstance(buffer, _text_type): buffer = buffer.encode("ascii") bio = _new_mem_buf(buffer) if type == FILETYPE_PEM: crl = _lib.PEM_read_bio_X509_CRL(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) elif type == FILETYPE_ASN1: crl = _lib.d2i_X509_CRL_bio(bio, _ffi.NULL) else: raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") if crl == _ffi.NULL: _raise_current_error() result = CRL.__new__(CRL) result._crl = crl return result def load_pkcs7_data(type, buffer): """ Load pkcs7 data from a buffer :param type: The file type (one of FILETYPE_PEM or FILETYPE_ASN1) :param buffer: The buffer with the pkcs7 data. :return: The PKCS7 object """ if isinstance(buffer, _text_type): buffer = buffer.encode("ascii") bio = _new_mem_buf(buffer) if type == FILETYPE_PEM: pkcs7 = _lib.PEM_read_bio_PKCS7(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) elif type == FILETYPE_ASN1: pkcs7 = _lib.d2i_PKCS7_bio(bio, _ffi.NULL) else: # TODO: This is untested. _raise_current_error() raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") if pkcs7 == _ffi.NULL: _raise_current_error() pypkcs7 = PKCS7.__new__(PKCS7) pypkcs7._pkcs7 = _ffi.gc(pkcs7, _lib.PKCS7_free) return pypkcs7 def load_pkcs12(buffer, passphrase=None): """ Load a PKCS12 object from a buffer :param buffer: The buffer the certificate is stored in :param passphrase: (Optional) The password to decrypt the PKCS12 lump :returns: The PKCS12 object """ passphrase = _text_to_bytes_and_warn("passphrase", passphrase) if isinstance(buffer, _text_type): buffer = buffer.encode("ascii") bio = _new_mem_buf(buffer) # Use null passphrase if passphrase is None or empty string. With PKCS#12 # password based encryption no password and a zero length password are two # different things, but OpenSSL implementation will try both to figure out # which one works. if not passphrase: passphrase = _ffi.NULL p12 = _lib.d2i_PKCS12_bio(bio, _ffi.NULL) if p12 == _ffi.NULL: _raise_current_error() p12 = _ffi.gc(p12, _lib.PKCS12_free) pkey = _ffi.new("EVP_PKEY**") cert = _ffi.new("X509**") cacerts = _ffi.new("Cryptography_STACK_OF_X509**") parse_result = _lib.PKCS12_parse(p12, passphrase, pkey, cert, cacerts) if not parse_result: _raise_current_error() cacerts = _ffi.gc(cacerts[0], _lib.sk_X509_free) # openssl 1.0.0 sometimes leaves an X509_check_private_key error in the # queue for no particular reason. This error isn't interesting to anyone # outside this function. It's not even interesting to us. Get rid of it. try: _raise_current_error() except Error: pass if pkey[0] == _ffi.NULL: pykey = None else: pykey = PKey.__new__(PKey) pykey._pkey = _ffi.gc(pkey[0], _lib.EVP_PKEY_free) if cert[0] == _ffi.NULL: pycert = None friendlyname = None else: pycert = X509.__new__(X509) pycert._x509 = _ffi.gc(cert[0], _lib.X509_free) friendlyname_length = _ffi.new("int*") friendlyname_buffer = _lib.X509_alias_get0(cert[0], friendlyname_length) friendlyname = _ffi.buffer(friendlyname_buffer, friendlyname_length[0])[:] if friendlyname_buffer == _ffi.NULL: friendlyname = None pycacerts = [] for i in range(_lib.sk_X509_num(cacerts)): pycacert = X509.__new__(X509) pycacert._x509 = _lib.sk_X509_value(cacerts, i) pycacerts.append(pycacert) if not pycacerts: pycacerts = None pkcs12 = PKCS12.__new__(PKCS12) pkcs12._pkey = pykey pkcs12._cert = pycert pkcs12._cacerts = pycacerts pkcs12._friendlyname = friendlyname return pkcs12 def _initialize_openssl_threads(get_ident, Lock): import _ssl return locks = list(Lock() for n in range(_lib.CRYPTO_num_locks())) def locking_function(mode, index, filename, line): if mode & _lib.CRYPTO_LOCK: locks[index].acquire() else: locks[index].release() _lib.CRYPTO_set_id_callback( _ffi.callback("unsigned long (*)(void)", get_ident)) _lib.CRYPTO_set_locking_callback( _ffi.callback( "void (*)(int, int, const char*, int)", locking_function)) try: from thread import get_ident from threading import Lock except ImportError: pass else: _initialize_openssl_threads(get_ident, Lock) del get_ident, Lock # There are no direct unit tests for this initialization. It is tested # indirectly since it is necessary for functions like dump_privatekey when # using encryption. # # Thus OpenSSL.test.test_crypto.FunctionTests.test_dump_privatekey_passphrase # and some other similar tests may fail without this (though they may not if # the Python runtime has already done some initialization of the underlying # OpenSSL library (and is linked against the same one that cryptography is # using)). _lib.OpenSSL_add_all_algorithms() # This is similar but exercised mainly by exception_from_error_queue. It calls # both ERR_load_crypto_strings() and ERR_load_SSL_strings(). _lib.SSL_load_error_strings() pyOpenSSL-0.15.1/OpenSSL/rand.py0000644000076500000240000001066012513035756016466 0ustar hynekstaff00000000000000""" PRNG management routines, thin wrappers. See the file RATIONALE for a short explanation of why this module was written. """ from functools import partial from six import integer_types as _integer_types from OpenSSL._util import ( ffi as _ffi, lib as _lib, exception_from_error_queue as _exception_from_error_queue, path_string as _path_string) class Error(Exception): """ An error occurred in an `OpenSSL.rand` API. """ _raise_current_error = partial(_exception_from_error_queue, Error) _unspecified = object() _builtin_bytes = bytes def bytes(num_bytes): """ Get some random bytes as a string. :param num_bytes: The number of bytes to fetch :return: A string of random bytes """ if not isinstance(num_bytes, _integer_types): raise TypeError("num_bytes must be an integer") if num_bytes < 0: raise ValueError("num_bytes must not be negative") result_buffer = _ffi.new("char[]", num_bytes) result_code = _lib.RAND_bytes(result_buffer, num_bytes) if result_code == -1: # TODO: No tests for this code path. Triggering a RAND_bytes failure # might involve supplying a custom ENGINE? That's hard. _raise_current_error() return _ffi.buffer(result_buffer)[:] def add(buffer, entropy): """ Add data with a given entropy to the PRNG :param buffer: Buffer with random data :param entropy: The entropy (in bytes) measurement of the buffer :return: None """ if not isinstance(buffer, _builtin_bytes): raise TypeError("buffer must be a byte string") if not isinstance(entropy, int): raise TypeError("entropy must be an integer") # TODO Nothing tests this call actually being made, or made properly. _lib.RAND_add(buffer, len(buffer), entropy) def seed(buffer): """ Alias for rand_add, with entropy equal to length :param buffer: Buffer with random data :return: None """ if not isinstance(buffer, _builtin_bytes): raise TypeError("buffer must be a byte string") # TODO Nothing tests this call actually being made, or made properly. _lib.RAND_seed(buffer, len(buffer)) def status(): """ Retrieve the status of the PRNG :return: True if the PRNG is seeded enough, false otherwise """ return _lib.RAND_status() def egd(path, bytes=_unspecified): """ Query an entropy gathering daemon (EGD) for random data and add it to the PRNG. I haven't found any problems when the socket is missing, the function just returns 0. :param path: The path to the EGD socket :param bytes: (optional) The number of bytes to read, default is 255 :returns: The number of bytes read (NB: a value of 0 isn't necessarily an error, check rand.status()) """ if not isinstance(path, _builtin_bytes): raise TypeError("path must be a byte string") if bytes is _unspecified: bytes = 255 elif not isinstance(bytes, int): raise TypeError("bytes must be an integer") return _lib.RAND_egd_bytes(path, bytes) def cleanup(): """ Erase the memory used by the PRNG. :return: None """ # TODO Nothing tests this call actually being made, or made properly. _lib.RAND_cleanup() def load_file(filename, maxbytes=_unspecified): """ Seed the PRNG with data from a file :param filename: The file to read data from (``bytes`` or ``unicode``). :param maxbytes: (optional) The number of bytes to read, default is to read the entire file :return: The number of bytes read """ filename = _path_string(filename) if maxbytes is _unspecified: maxbytes = -1 elif not isinstance(maxbytes, int): raise TypeError("maxbytes must be an integer") return _lib.RAND_load_file(filename, maxbytes) def write_file(filename): """ Save PRNG state to a file :param filename: The file to write data to (``bytes`` or ``unicode``). :return: The number of bytes written """ filename = _path_string(filename) return _lib.RAND_write_file(filename) # TODO There are no tests for screen at all def screen(): """ Add the current contents of the screen to the PRNG state. Availability: Windows. :return: None """ _lib.RAND_screen() if getattr(_lib, 'RAND_screen', None) is None: del screen # TODO There are no tests for the RAND strings being loaded, whatever that # means. _lib.ERR_load_RAND_strings() pyOpenSSL-0.15.1/OpenSSL/SSL.py0000644000076500000240000017302512513314057016202 0ustar hynekstaff00000000000000from sys import platform from functools import wraps, partial from itertools import count, chain from weakref import WeakValueDictionary from errno import errorcode from six import text_type as _text_type from six import binary_type as _binary_type from six import integer_types as integer_types from six import int2byte, indexbytes from OpenSSL._util import ( ffi as _ffi, lib as _lib, exception_from_error_queue as _exception_from_error_queue, native as _native, text_to_bytes_and_warn as _text_to_bytes_and_warn, path_string as _path_string, UNSPECIFIED as _UNSPECIFIED, ) from OpenSSL.crypto import ( FILETYPE_PEM, _PassphraseHelper, PKey, X509Name, X509, X509Store) try: _memoryview = memoryview except NameError: class _memoryview(object): pass try: _buffer = buffer except NameError: class _buffer(object): pass OPENSSL_VERSION_NUMBER = _lib.OPENSSL_VERSION_NUMBER SSLEAY_VERSION = _lib.SSLEAY_VERSION SSLEAY_CFLAGS = _lib.SSLEAY_CFLAGS SSLEAY_PLATFORM = _lib.SSLEAY_PLATFORM SSLEAY_DIR = _lib.SSLEAY_DIR SSLEAY_BUILT_ON = _lib.SSLEAY_BUILT_ON SENT_SHUTDOWN = _lib.SSL_SENT_SHUTDOWN RECEIVED_SHUTDOWN = _lib.SSL_RECEIVED_SHUTDOWN SSLv2_METHOD = 1 SSLv3_METHOD = 2 SSLv23_METHOD = 3 TLSv1_METHOD = 4 TLSv1_1_METHOD = 5 TLSv1_2_METHOD = 6 OP_NO_SSLv2 = _lib.SSL_OP_NO_SSLv2 OP_NO_SSLv3 = _lib.SSL_OP_NO_SSLv3 OP_NO_TLSv1 = _lib.SSL_OP_NO_TLSv1 OP_NO_TLSv1_1 = getattr(_lib, "SSL_OP_NO_TLSv1_1", 0) OP_NO_TLSv1_2 = getattr(_lib, "SSL_OP_NO_TLSv1_2", 0) try: MODE_RELEASE_BUFFERS = _lib.SSL_MODE_RELEASE_BUFFERS except AttributeError: pass OP_SINGLE_DH_USE = _lib.SSL_OP_SINGLE_DH_USE OP_EPHEMERAL_RSA = _lib.SSL_OP_EPHEMERAL_RSA OP_MICROSOFT_SESS_ID_BUG = _lib.SSL_OP_MICROSOFT_SESS_ID_BUG OP_NETSCAPE_CHALLENGE_BUG = _lib.SSL_OP_NETSCAPE_CHALLENGE_BUG OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG = _lib.SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG OP_SSLREF2_REUSE_CERT_TYPE_BUG = _lib.SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG OP_MICROSOFT_BIG_SSLV3_BUFFER = _lib.SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER try: OP_MSIE_SSLV2_RSA_PADDING = _lib.SSL_OP_MSIE_SSLV2_RSA_PADDING except AttributeError: pass OP_SSLEAY_080_CLIENT_DH_BUG = _lib.SSL_OP_SSLEAY_080_CLIENT_DH_BUG OP_TLS_D5_BUG = _lib.SSL_OP_TLS_D5_BUG OP_TLS_BLOCK_PADDING_BUG = _lib.SSL_OP_TLS_BLOCK_PADDING_BUG OP_DONT_INSERT_EMPTY_FRAGMENTS = _lib.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS OP_CIPHER_SERVER_PREFERENCE = _lib.SSL_OP_CIPHER_SERVER_PREFERENCE OP_TLS_ROLLBACK_BUG = _lib.SSL_OP_TLS_ROLLBACK_BUG OP_PKCS1_CHECK_1 = _lib.SSL_OP_PKCS1_CHECK_1 OP_PKCS1_CHECK_2 = _lib.SSL_OP_PKCS1_CHECK_2 OP_NETSCAPE_CA_DN_BUG = _lib.SSL_OP_NETSCAPE_CA_DN_BUG OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG= _lib.SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG try: OP_NO_COMPRESSION = _lib.SSL_OP_NO_COMPRESSION except AttributeError: pass OP_NO_QUERY_MTU = _lib.SSL_OP_NO_QUERY_MTU OP_COOKIE_EXCHANGE = _lib.SSL_OP_COOKIE_EXCHANGE try: OP_NO_TICKET = _lib.SSL_OP_NO_TICKET except AttributeError: pass OP_ALL = _lib.SSL_OP_ALL VERIFY_PEER = _lib.SSL_VERIFY_PEER VERIFY_FAIL_IF_NO_PEER_CERT = _lib.SSL_VERIFY_FAIL_IF_NO_PEER_CERT VERIFY_CLIENT_ONCE = _lib.SSL_VERIFY_CLIENT_ONCE VERIFY_NONE = _lib.SSL_VERIFY_NONE SESS_CACHE_OFF = _lib.SSL_SESS_CACHE_OFF SESS_CACHE_CLIENT = _lib.SSL_SESS_CACHE_CLIENT SESS_CACHE_SERVER = _lib.SSL_SESS_CACHE_SERVER SESS_CACHE_BOTH = _lib.SSL_SESS_CACHE_BOTH SESS_CACHE_NO_AUTO_CLEAR = _lib.SSL_SESS_CACHE_NO_AUTO_CLEAR SESS_CACHE_NO_INTERNAL_LOOKUP = _lib.SSL_SESS_CACHE_NO_INTERNAL_LOOKUP SESS_CACHE_NO_INTERNAL_STORE = _lib.SSL_SESS_CACHE_NO_INTERNAL_STORE SESS_CACHE_NO_INTERNAL = _lib.SSL_SESS_CACHE_NO_INTERNAL SSL_ST_CONNECT = _lib.SSL_ST_CONNECT SSL_ST_ACCEPT = _lib.SSL_ST_ACCEPT SSL_ST_MASK = _lib.SSL_ST_MASK SSL_ST_INIT = _lib.SSL_ST_INIT SSL_ST_BEFORE = _lib.SSL_ST_BEFORE SSL_ST_OK = _lib.SSL_ST_OK SSL_ST_RENEGOTIATE = _lib.SSL_ST_RENEGOTIATE SSL_CB_LOOP = _lib.SSL_CB_LOOP SSL_CB_EXIT = _lib.SSL_CB_EXIT SSL_CB_READ = _lib.SSL_CB_READ SSL_CB_WRITE = _lib.SSL_CB_WRITE SSL_CB_ALERT = _lib.SSL_CB_ALERT SSL_CB_READ_ALERT = _lib.SSL_CB_READ_ALERT SSL_CB_WRITE_ALERT = _lib.SSL_CB_WRITE_ALERT SSL_CB_ACCEPT_LOOP = _lib.SSL_CB_ACCEPT_LOOP SSL_CB_ACCEPT_EXIT = _lib.SSL_CB_ACCEPT_EXIT SSL_CB_CONNECT_LOOP = _lib.SSL_CB_CONNECT_LOOP SSL_CB_CONNECT_EXIT = _lib.SSL_CB_CONNECT_EXIT SSL_CB_HANDSHAKE_START = _lib.SSL_CB_HANDSHAKE_START SSL_CB_HANDSHAKE_DONE = _lib.SSL_CB_HANDSHAKE_DONE class Error(Exception): """ An error occurred in an `OpenSSL.SSL` API. """ _raise_current_error = partial(_exception_from_error_queue, Error) class WantReadError(Error): pass class WantWriteError(Error): pass class WantX509LookupError(Error): pass class ZeroReturnError(Error): pass class SysCallError(Error): pass class _CallbackExceptionHelper(object): """ A base class for wrapper classes that allow for intelligent exception handling in OpenSSL callbacks. :ivar list _problems: Any exceptions that occurred while executing in a context where they could not be raised in the normal way. Typically this is because OpenSSL has called into some Python code and requires a return value. The exceptions are saved to be raised later when it is possible to do so. """ def __init__(self): self._problems = [] def raise_if_problem(self): """ Raise an exception from the OpenSSL error queue or that was previously captured whe running a callback. """ if self._problems: try: _raise_current_error() except Error: pass raise self._problems.pop(0) class _VerifyHelper(_CallbackExceptionHelper): """ Wrap a callback such that it can be used as a certificate verification callback. """ def __init__(self, callback): _CallbackExceptionHelper.__init__(self) @wraps(callback) def wrapper(ok, store_ctx): cert = X509.__new__(X509) cert._x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx) error_number = _lib.X509_STORE_CTX_get_error(store_ctx) error_depth = _lib.X509_STORE_CTX_get_error_depth(store_ctx) index = _lib.SSL_get_ex_data_X509_STORE_CTX_idx() ssl = _lib.X509_STORE_CTX_get_ex_data(store_ctx, index) connection = Connection._reverse_mapping[ssl] try: result = callback(connection, cert, error_number, error_depth, ok) except Exception as e: self._problems.append(e) return 0 else: if result: _lib.X509_STORE_CTX_set_error(store_ctx, _lib.X509_V_OK) return 1 else: return 0 self.callback = _ffi.callback( "int (*)(int, X509_STORE_CTX *)", wrapper) class _NpnAdvertiseHelper(_CallbackExceptionHelper): """ Wrap a callback such that it can be used as an NPN advertisement callback. """ def __init__(self, callback): _CallbackExceptionHelper.__init__(self) @wraps(callback) def wrapper(ssl, out, outlen, arg): try: conn = Connection._reverse_mapping[ssl] protos = callback(conn) # Join the protocols into a Python bytestring, length-prefixing # each element. protostr = b''.join( chain.from_iterable((int2byte(len(p)), p) for p in protos) ) # Save our callback arguments on the connection object. This is # done to make sure that they don't get freed before OpenSSL # uses them. Then, return them appropriately in the output # parameters. conn._npn_advertise_callback_args = [ _ffi.new("unsigned int *", len(protostr)), _ffi.new("unsigned char[]", protostr), ] outlen[0] = conn._npn_advertise_callback_args[0][0] out[0] = conn._npn_advertise_callback_args[1] return 0 except Exception as e: self._problems.append(e) return 2 # SSL_TLSEXT_ERR_ALERT_FATAL self.callback = _ffi.callback( "int (*)(SSL *, const unsigned char **, unsigned int *, void *)", wrapper ) class _NpnSelectHelper(_CallbackExceptionHelper): """ Wrap a callback such that it can be used as an NPN selection callback. """ def __init__(self, callback): _CallbackExceptionHelper.__init__(self) @wraps(callback) def wrapper(ssl, out, outlen, in_, inlen, arg): try: conn = Connection._reverse_mapping[ssl] # The string passed to us is actually made up of multiple # length-prefixed bytestrings. We need to split that into a # list. instr = _ffi.buffer(in_, inlen)[:] protolist = [] while instr: l = indexbytes(instr, 0) proto = instr[1:l+1] protolist.append(proto) instr = instr[l+1:] # Call the callback outstr = callback(conn, protolist) # Save our callback arguments on the connection object. This is # done to make sure that they don't get freed before OpenSSL # uses them. Then, return them appropriately in the output # parameters. conn._npn_select_callback_args = [ _ffi.new("unsigned char *", len(outstr)), _ffi.new("unsigned char[]", outstr), ] outlen[0] = conn._npn_select_callback_args[0][0] out[0] = conn._npn_select_callback_args[1] return 0 except Exception as e: self._problems.append(e) return 2 # SSL_TLSEXT_ERR_ALERT_FATAL self.callback = _ffi.callback( "int (*)(SSL *, unsigned char **, unsigned char *, " "const unsigned char *, unsigned int, void *)", wrapper ) class _ALPNSelectHelper(_CallbackExceptionHelper): """ Wrap a callback such that it can be used as an ALPN selection callback. """ def __init__(self, callback): _CallbackExceptionHelper.__init__(self) @wraps(callback) def wrapper(ssl, out, outlen, in_, inlen, arg): try: conn = Connection._reverse_mapping[ssl] # The string passed to us is made up of multiple # length-prefixed bytestrings. We need to split that into a # list. instr = _ffi.buffer(in_, inlen)[:] protolist = [] while instr: encoded_len = indexbytes(instr, 0) proto = instr[1:encoded_len + 1] protolist.append(proto) instr = instr[encoded_len + 1:] # Call the callback outstr = callback(conn, protolist) if not isinstance(outstr, _binary_type): raise TypeError("ALPN callback must return a bytestring.") # Save our callback arguments on the connection object to make # sure that they don't get freed before OpenSSL can use them. # Then, return them in the appropriate output parameters. conn._alpn_select_callback_args = [ _ffi.new("unsigned char *", len(outstr)), _ffi.new("unsigned char[]", outstr), ] outlen[0] = conn._alpn_select_callback_args[0][0] out[0] = conn._alpn_select_callback_args[1] return 0 except Exception as e: self._problems.append(e) return 2 # SSL_TLSEXT_ERR_ALERT_FATAL self.callback = _ffi.callback( "int (*)(SSL *, unsigned char **, unsigned char *, " "const unsigned char *, unsigned int, void *)", wrapper ) def _asFileDescriptor(obj): fd = None if not isinstance(obj, integer_types): meth = getattr(obj, "fileno", None) if meth is not None: obj = meth() if isinstance(obj, integer_types): fd = obj if not isinstance(fd, integer_types): raise TypeError("argument must be an int, or have a fileno() method.") elif fd < 0: raise ValueError( "file descriptor cannot be a negative integer (%i)" % (fd,)) return fd def SSLeay_version(type): """ Return a string describing the version of OpenSSL in use. :param type: One of the SSLEAY_ constants defined in this module. """ return _ffi.string(_lib.SSLeay_version(type)) def _requires_npn(func): """ Wraps any function that requires NPN support in OpenSSL, ensuring that NotImplementedError is raised if NPN is not present. """ @wraps(func) def wrapper(*args, **kwargs): if not _lib.Cryptography_HAS_NEXTPROTONEG: raise NotImplementedError("NPN not available.") return func(*args, **kwargs) return wrapper def _requires_alpn(func): """ Wraps any function that requires ALPN support in OpenSSL, ensuring that NotImplementedError is raised if ALPN support is not present. """ @wraps(func) def wrapper(*args, **kwargs): if not _lib.Cryptography_HAS_ALPN: raise NotImplementedError("ALPN not available.") return func(*args, **kwargs) return wrapper class Session(object): pass class Context(object): """ :py:obj:`OpenSSL.SSL.Context` instances define the parameters for setting up new SSL connections. """ _methods = { SSLv2_METHOD: "SSLv2_method", SSLv3_METHOD: "SSLv3_method", SSLv23_METHOD: "SSLv23_method", TLSv1_METHOD: "TLSv1_method", TLSv1_1_METHOD: "TLSv1_1_method", TLSv1_2_METHOD: "TLSv1_2_method", } _methods = dict( (identifier, getattr(_lib, name)) for (identifier, name) in _methods.items() if getattr(_lib, name, None) is not None) def __init__(self, method): """ :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, or TLSv1_METHOD. """ if not isinstance(method, integer_types): raise TypeError("method must be an integer") try: method_func = self._methods[method] except KeyError: raise ValueError("No such protocol") method_obj = method_func() if method_obj == _ffi.NULL: # TODO: This is untested. _raise_current_error() context = _lib.SSL_CTX_new(method_obj) if context == _ffi.NULL: # TODO: This is untested. _raise_current_error() context = _ffi.gc(context, _lib.SSL_CTX_free) self._context = context self._passphrase_helper = None self._passphrase_callback = None self._passphrase_userdata = None self._verify_helper = None self._verify_callback = None self._info_callback = None self._tlsext_servername_callback = None self._app_data = None self._npn_advertise_helper = None self._npn_advertise_callback = None self._npn_select_helper = None self._npn_select_callback = None self._alpn_select_helper = None self._alpn_select_callback = None # SSL_CTX_set_app_data(self->ctx, self); # SSL_CTX_set_mode(self->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | # SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | # SSL_MODE_AUTO_RETRY); self.set_mode(_lib.SSL_MODE_ENABLE_PARTIAL_WRITE) def load_verify_locations(self, cafile, capath=None): """ Let SSL know where we can find trusted certificates for the certificate chain :param cafile: In which file we can find the certificates (``bytes`` or ``unicode``). :param capath: In which directory we can find the certificates (``bytes`` or ``unicode``). :return: None """ if cafile is None: cafile = _ffi.NULL else: cafile = _path_string(cafile) if capath is None: capath = _ffi.NULL else: capath = _path_string(capath) load_result = _lib.SSL_CTX_load_verify_locations(self._context, cafile, capath) if not load_result: _raise_current_error() def _wrap_callback(self, callback): @wraps(callback) def wrapper(size, verify, userdata): return callback(size, verify, self._passphrase_userdata) return _PassphraseHelper( FILETYPE_PEM, wrapper, more_args=True, truncate=True) def set_passwd_cb(self, callback, userdata=None): """ Set the passphrase callback :param callback: The Python callback to use :param userdata: (optional) A Python object which will be given as argument to the callback :return: None """ if not callable(callback): raise TypeError("callback must be callable") self._passphrase_helper = self._wrap_callback(callback) self._passphrase_callback = self._passphrase_helper.callback _lib.SSL_CTX_set_default_passwd_cb( self._context, self._passphrase_callback) self._passphrase_userdata = userdata def set_default_verify_paths(self): """ Use the platform-specific CA certificate locations :return: None """ set_result = _lib.SSL_CTX_set_default_verify_paths(self._context) if not set_result: # TODO: This is untested. _raise_current_error() def use_certificate_chain_file(self, certfile): """ Load a certificate chain from a file :param certfile: The name of the certificate chain file (``bytes`` or ``unicode``). :return: None """ certfile = _path_string(certfile) result = _lib.SSL_CTX_use_certificate_chain_file(self._context, certfile) if not result: _raise_current_error() def use_certificate_file(self, certfile, filetype=FILETYPE_PEM): """ Load a certificate from a file :param certfile: The name of the certificate file (``bytes`` or ``unicode``). :param filetype: (optional) The encoding of the file, default is PEM :return: None """ certfile = _path_string(certfile) if not isinstance(filetype, integer_types): raise TypeError("filetype must be an integer") use_result = _lib.SSL_CTX_use_certificate_file(self._context, certfile, filetype) if not use_result: _raise_current_error() def use_certificate(self, cert): """ Load a certificate from a X509 object :param cert: The X509 object :return: None """ if not isinstance(cert, X509): raise TypeError("cert must be an X509 instance") use_result = _lib.SSL_CTX_use_certificate(self._context, cert._x509) if not use_result: _raise_current_error() def add_extra_chain_cert(self, certobj): """ Add certificate to chain :param certobj: The X509 certificate object to add to the chain :return: None """ if not isinstance(certobj, X509): raise TypeError("certobj must be an X509 instance") copy = _lib.X509_dup(certobj._x509) add_result = _lib.SSL_CTX_add_extra_chain_cert(self._context, copy) if not add_result: # TODO: This is untested. _lib.X509_free(copy) _raise_current_error() def _raise_passphrase_exception(self): if self._passphrase_helper is None: _raise_current_error() exception = self._passphrase_helper.raise_if_problem(Error) if exception is not None: raise exception def use_privatekey_file(self, keyfile, filetype=_UNSPECIFIED): """ Load a private key from a file :param keyfile: The name of the key file (``bytes`` or ``unicode``) :param filetype: (optional) The encoding of the file, default is PEM :return: None """ keyfile = _path_string(keyfile) if filetype is _UNSPECIFIED: filetype = FILETYPE_PEM elif not isinstance(filetype, integer_types): raise TypeError("filetype must be an integer") use_result = _lib.SSL_CTX_use_PrivateKey_file( self._context, keyfile, filetype) if not use_result: self._raise_passphrase_exception() def use_privatekey(self, pkey): """ Load a private key from a PKey object :param pkey: The PKey object :return: None """ if not isinstance(pkey, PKey): raise TypeError("pkey must be a PKey instance") use_result = _lib.SSL_CTX_use_PrivateKey(self._context, pkey._pkey) if not use_result: self._raise_passphrase_exception() def check_privatekey(self): """ Check that the private key and certificate match up :return: None (raises an exception if something's wrong) """ if not _lib.SSL_CTX_check_private_key(self._context): _raise_current_error() def load_client_ca(self, cafile): """ Load the trusted certificates that will be sent to the client (basically telling the client "These are the guys I trust"). Does not actually imply any of the certificates are trusted; that must be configured separately. :param cafile: The name of the certificates file :return: None """ def set_session_id(self, buf): """ Set the session identifier. This is needed if you want to do session resumption. :param buf: A Python object that can be safely converted to a string :returns: None """ def set_session_cache_mode(self, mode): """ Enable/disable session caching and specify the mode used. :param mode: One or more of the SESS_CACHE_* flags (combine using bitwise or) :returns: The previously set caching mode. """ if not isinstance(mode, integer_types): raise TypeError("mode must be an integer") return _lib.SSL_CTX_set_session_cache_mode(self._context, mode) def get_session_cache_mode(self): """ :returns: The currently used cache mode. """ return _lib.SSL_CTX_get_session_cache_mode(self._context) def set_verify(self, mode, callback): """ Set the verify mode and verify callback :param mode: The verify mode, this is either VERIFY_NONE or VERIFY_PEER combined with possible other flags :param callback: The Python callback to use :return: None See SSL_CTX_set_verify(3SSL) for further details. """ if not isinstance(mode, integer_types): raise TypeError("mode must be an integer") if not callable(callback): raise TypeError("callback must be callable") self._verify_helper = _VerifyHelper(callback) self._verify_callback = self._verify_helper.callback _lib.SSL_CTX_set_verify(self._context, mode, self._verify_callback) def set_verify_depth(self, depth): """ Set the verify depth :param depth: An integer specifying the verify depth :return: None """ if not isinstance(depth, integer_types): raise TypeError("depth must be an integer") _lib.SSL_CTX_set_verify_depth(self._context, depth) def get_verify_mode(self): """ Get the verify mode :return: The verify mode """ return _lib.SSL_CTX_get_verify_mode(self._context) def get_verify_depth(self): """ Get the verify depth :return: The verify depth """ return _lib.SSL_CTX_get_verify_depth(self._context) def load_tmp_dh(self, dhfile): """ Load parameters for Ephemeral Diffie-Hellman :param dhfile: The file to load EDH parameters from (``bytes`` or ``unicode``). :return: None """ dhfile = _path_string(dhfile) bio = _lib.BIO_new_file(dhfile, b"r") if bio == _ffi.NULL: _raise_current_error() bio = _ffi.gc(bio, _lib.BIO_free) dh = _lib.PEM_read_bio_DHparams(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) dh = _ffi.gc(dh, _lib.DH_free) _lib.SSL_CTX_set_tmp_dh(self._context, dh) def set_tmp_ecdh(self, curve): """ Select a curve to use for ECDHE key exchange. :param curve: A curve object to use as returned by either :py:meth:`OpenSSL.crypto.get_elliptic_curve` or :py:meth:`OpenSSL.crypto.get_elliptic_curves`. :return: None """ _lib.SSL_CTX_set_tmp_ecdh(self._context, curve._to_EC_KEY()) def set_cipher_list(self, cipher_list): """ Change the cipher list :param cipher_list: A cipher list, see ciphers(1) :return: None """ if isinstance(cipher_list, _text_type): cipher_list = cipher_list.encode("ascii") if not isinstance(cipher_list, bytes): raise TypeError("cipher_list must be bytes or unicode") result = _lib.SSL_CTX_set_cipher_list(self._context, cipher_list) if not result: _raise_current_error() def set_client_ca_list(self, certificate_authorities): """ Set the list of preferred client certificate signers for this server context. This list of certificate authorities will be sent to the client when the server requests a client certificate. :param certificate_authorities: a sequence of X509Names. :return: None """ name_stack = _lib.sk_X509_NAME_new_null() if name_stack == _ffi.NULL: # TODO: This is untested. _raise_current_error() try: for ca_name in certificate_authorities: if not isinstance(ca_name, X509Name): raise TypeError( "client CAs must be X509Name objects, not %s objects" % ( type(ca_name).__name__,)) copy = _lib.X509_NAME_dup(ca_name._name) if copy == _ffi.NULL: # TODO: This is untested. _raise_current_error() push_result = _lib.sk_X509_NAME_push(name_stack, copy) if not push_result: _lib.X509_NAME_free(copy) _raise_current_error() except: _lib.sk_X509_NAME_free(name_stack) raise _lib.SSL_CTX_set_client_CA_list(self._context, name_stack) def add_client_ca(self, certificate_authority): """ Add the CA certificate to the list of preferred signers for this context. The list of certificate authorities will be sent to the client when the server requests a client certificate. :param certificate_authority: certificate authority's X509 certificate. :return: None """ if not isinstance(certificate_authority, X509): raise TypeError("certificate_authority must be an X509 instance") add_result = _lib.SSL_CTX_add_client_CA( self._context, certificate_authority._x509) if not add_result: # TODO: This is untested. _raise_current_error() def set_timeout(self, timeout): """ Set session timeout :param timeout: The timeout in seconds :return: The previous session timeout """ if not isinstance(timeout, integer_types): raise TypeError("timeout must be an integer") return _lib.SSL_CTX_set_timeout(self._context, timeout) def get_timeout(self): """ Get the session timeout :return: The session timeout """ return _lib.SSL_CTX_get_timeout(self._context) def set_info_callback(self, callback): """ Set the info callback :param callback: The Python callback to use :return: None """ @wraps(callback) def wrapper(ssl, where, return_code): callback(Connection._reverse_mapping[ssl], where, return_code) self._info_callback = _ffi.callback( "void (*)(const SSL *, int, int)", wrapper) _lib.SSL_CTX_set_info_callback(self._context, self._info_callback) def get_app_data(self): """ Get the application data (supplied via set_app_data()) :return: The application data """ return self._app_data def set_app_data(self, data): """ Set the application data (will be returned from get_app_data()) :param data: Any Python object :return: None """ self._app_data = data def get_cert_store(self): """ Get the certificate store for the context. :return: A X509Store object or None if it does not have one. """ store = _lib.SSL_CTX_get_cert_store(self._context) if store == _ffi.NULL: # TODO: This is untested. return None pystore = X509Store.__new__(X509Store) pystore._store = store return pystore def set_options(self, options): """ Add options. Options set before are not cleared! :param options: The options to add. :return: The new option bitmask. """ if not isinstance(options, integer_types): raise TypeError("options must be an integer") return _lib.SSL_CTX_set_options(self._context, options) def set_mode(self, mode): """ Add modes via bitmask. Modes set before are not cleared! :param mode: The mode to add. :return: The new mode bitmask. """ if not isinstance(mode, integer_types): raise TypeError("mode must be an integer") return _lib.SSL_CTX_set_mode(self._context, mode) def set_tlsext_servername_callback(self, callback): """ Specify a callback function to be called when clients specify a server name. :param callback: The callback function. It will be invoked with one argument, the Connection instance. """ @wraps(callback) def wrapper(ssl, alert, arg): callback(Connection._reverse_mapping[ssl]) return 0 self._tlsext_servername_callback = _ffi.callback( "int (*)(const SSL *, int *, void *)", wrapper) _lib.SSL_CTX_set_tlsext_servername_callback( self._context, self._tlsext_servername_callback) @_requires_npn def set_npn_advertise_callback(self, callback): """ Specify a callback function that will be called when offering `Next Protocol Negotiation `_ as a server. :param callback: The callback function. It will be invoked with one argument, the Connection instance. It should return a list of bytestrings representing the advertised protocols, like ``[b'http/1.1', b'spdy/2']``. """ self._npn_advertise_helper = _NpnAdvertiseHelper(callback) self._npn_advertise_callback = self._npn_advertise_helper.callback _lib.SSL_CTX_set_next_protos_advertised_cb( self._context, self._npn_advertise_callback, _ffi.NULL) @_requires_npn def set_npn_select_callback(self, callback): """ Specify a callback function that will be called when a server offers Next Protocol Negotiation options. :param callback: The callback function. It will be invoked with two arguments: the Connection, and a list of offered protocols as bytestrings, e.g. ``[b'http/1.1', b'spdy/2']``. It should return one of those bytestrings, the chosen protocol. """ self._npn_select_helper = _NpnSelectHelper(callback) self._npn_select_callback = self._npn_select_helper.callback _lib.SSL_CTX_set_next_proto_select_cb( self._context, self._npn_select_callback, _ffi.NULL) @_requires_alpn def set_alpn_protos(self, protos): """ Specify the clients ALPN protocol list. These protocols are offered to the server during protocol negotiation. :param protos: A list of the protocols to be offered to the server. This list should be a Python list of bytestrings representing the protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``. """ # Take the list of protocols and join them together, prefixing them # with their lengths. protostr = b''.join( chain.from_iterable((int2byte(len(p)), p) for p in protos) ) # Build a C string from the list. We don't need to save this off # because OpenSSL immediately copies the data out. input_str = _ffi.new("unsigned char[]", protostr) input_str_len = _ffi.cast("unsigned", len(protostr)) _lib.SSL_CTX_set_alpn_protos(self._context, input_str, input_str_len) @_requires_alpn def set_alpn_select_callback(self, callback): """ Set the callback to handle ALPN protocol choice. :param callback: The callback function. It will be invoked with two arguments: the Connection, and a list of offered protocols as bytestrings, e.g ``[b'http/1.1', b'spdy/2']``. It should return one of those bytestrings, the chosen protocol. """ self._alpn_select_helper = _ALPNSelectHelper(callback) self._alpn_select_callback = self._alpn_select_helper.callback _lib.SSL_CTX_set_alpn_select_cb( self._context, self._alpn_select_callback, _ffi.NULL) ContextType = Context class Connection(object): """ """ _reverse_mapping = WeakValueDictionary() def __init__(self, context, socket=None): """ Create a new Connection object, using the given OpenSSL.SSL.Context instance and socket. :param context: An SSL Context to use for this connection :param socket: The socket to use for transport layer """ if not isinstance(context, Context): raise TypeError("context must be a Context instance") ssl = _lib.SSL_new(context._context) self._ssl = _ffi.gc(ssl, _lib.SSL_free) self._context = context # References to strings used for Next Protocol Negotiation. OpenSSL's # header files suggest that these might get copied at some point, but # doesn't specify when, so we store them here to make sure they don't # get freed before OpenSSL uses them. self._npn_advertise_callback_args = None self._npn_select_callback_args = None # References to strings used for Application Layer Protocol # Negotiation. These strings get copied at some point but it's well # after the callback returns, so we have to hang them somewhere to # avoid them getting freed. self._alpn_select_callback_args = None self._reverse_mapping[self._ssl] = self if socket is None: self._socket = None # Don't set up any gc for these, SSL_free will take care of them. self._into_ssl = _lib.BIO_new(_lib.BIO_s_mem()) self._from_ssl = _lib.BIO_new(_lib.BIO_s_mem()) if self._into_ssl == _ffi.NULL or self._from_ssl == _ffi.NULL: # TODO: This is untested. _raise_current_error() _lib.SSL_set_bio(self._ssl, self._into_ssl, self._from_ssl) else: self._into_ssl = None self._from_ssl = None self._socket = socket set_result = _lib.SSL_set_fd(self._ssl, _asFileDescriptor(self._socket)) if not set_result: # TODO: This is untested. _raise_current_error() def __getattr__(self, name): """ Look up attributes on the wrapped socket object if they are not found on the Connection object. """ return getattr(self._socket, name) def _raise_ssl_error(self, ssl, result): if self._context._verify_helper is not None: self._context._verify_helper.raise_if_problem() if self._context._npn_advertise_helper is not None: self._context._npn_advertise_helper.raise_if_problem() if self._context._npn_select_helper is not None: self._context._npn_select_helper.raise_if_problem() if self._context._alpn_select_helper is not None: self._context._alpn_select_helper.raise_if_problem() error = _lib.SSL_get_error(ssl, result) if error == _lib.SSL_ERROR_WANT_READ: raise WantReadError() elif error == _lib.SSL_ERROR_WANT_WRITE: raise WantWriteError() elif error == _lib.SSL_ERROR_ZERO_RETURN: raise ZeroReturnError() elif error == _lib.SSL_ERROR_WANT_X509_LOOKUP: # TODO: This is untested. raise WantX509LookupError() elif error == _lib.SSL_ERROR_SYSCALL: if _lib.ERR_peek_error() == 0: if result < 0: if platform == "win32": errno = _ffi.getwinerror()[0] else: errno = _ffi.errno raise SysCallError(errno, errorcode.get(errno)) else: raise SysCallError(-1, "Unexpected EOF") else: # TODO: This is untested. _raise_current_error() elif error == _lib.SSL_ERROR_NONE: pass else: _raise_current_error() def get_context(self): """ Get session context """ return self._context def set_context(self, context): """ Switch this connection to a new session context :param context: A :py:class:`Context` instance giving the new session context to use. """ if not isinstance(context, Context): raise TypeError("context must be a Context instance") _lib.SSL_set_SSL_CTX(self._ssl, context._context) self._context = context def get_servername(self): """ Retrieve the servername extension value if provided in the client hello message, or None if there wasn't one. :return: A byte string giving the server name or :py:data:`None`. """ name = _lib.SSL_get_servername(self._ssl, _lib.TLSEXT_NAMETYPE_host_name) if name == _ffi.NULL: return None return _ffi.string(name) def set_tlsext_host_name(self, name): """ Set the value of the servername extension to send in the client hello. :param name: A byte string giving the name. """ if not isinstance(name, bytes): raise TypeError("name must be a byte string") elif b"\0" in name: raise TypeError("name must not contain NUL byte") # XXX I guess this can fail sometimes? _lib.SSL_set_tlsext_host_name(self._ssl, name) def pending(self): """ Get the number of bytes that can be safely read from the connection :return: The number of bytes available in the receive buffer. """ return _lib.SSL_pending(self._ssl) def send(self, buf, flags=0): """ Send data on the connection. NOTE: If you get one of the WantRead, WantWrite or WantX509Lookup exceptions on this, you have to call the method again with the SAME buffer. :param buf: The string, buffer or memoryview to send :param flags: (optional) Included for compatibility with the socket API, the value is ignored :return: The number of bytes written """ # Backward compatibility buf = _text_to_bytes_and_warn("buf", buf) if isinstance(buf, _memoryview): buf = buf.tobytes() if isinstance(buf, _buffer): buf = str(buf) if not isinstance(buf, bytes): raise TypeError("data must be a memoryview, buffer or byte string") result = _lib.SSL_write(self._ssl, buf, len(buf)) self._raise_ssl_error(self._ssl, result) return result write = send def sendall(self, buf, flags=0): """ Send "all" data on the connection. This calls send() repeatedly until all data is sent. If an error occurs, it's impossible to tell how much data has been sent. :param buf: The string, buffer or memoryview to send :param flags: (optional) Included for compatibility with the socket API, the value is ignored :return: The number of bytes written """ buf = _text_to_bytes_and_warn("buf", buf) if isinstance(buf, _memoryview): buf = buf.tobytes() if isinstance(buf, _buffer): buf = str(buf) if not isinstance(buf, bytes): raise TypeError("buf must be a memoryview, buffer or byte string") left_to_send = len(buf) total_sent = 0 data = _ffi.new("char[]", buf) while left_to_send: result = _lib.SSL_write(self._ssl, data + total_sent, left_to_send) self._raise_ssl_error(self._ssl, result) total_sent += result left_to_send -= result def recv(self, bufsiz, flags=None): """ Receive data on the connection. NOTE: If you get one of the WantRead, WantWrite or WantX509Lookup exceptions on this, you have to call the method again with the SAME buffer. :param bufsiz: The maximum number of bytes to read :param flags: (optional) Included for compatibility with the socket API, the value is ignored :return: The string read from the Connection """ buf = _ffi.new("char[]", bufsiz) result = _lib.SSL_read(self._ssl, buf, bufsiz) self._raise_ssl_error(self._ssl, result) return _ffi.buffer(buf, result)[:] read = recv def recv_into(self, buffer, nbytes=None, flags=None): """ Receive data on the connection and store the data into a buffer rather than creating a new string. :param buffer: The buffer to copy into. :param nbytes: (optional) The maximum number of bytes to read into the buffer. If not present, defaults to the size of the buffer. If larger than the size of the buffer, is reduced to the size of the buffer. :param flags: (optional) Included for compatibility with the socket API, the value is ignored. :return: The number of bytes read into the buffer. """ if nbytes is None: nbytes = len(buffer) else: nbytes = min(nbytes, len(buffer)) # We need to create a temporary buffer. This is annoying, it would be # better if we could pass memoryviews straight into the SSL_read call, # but right now we can't. Revisit this if CFFI gets that ability. buf = _ffi.new("char[]", nbytes) result = _lib.SSL_read(self._ssl, buf, nbytes) self._raise_ssl_error(self._ssl, result) # This strange line is all to avoid a memory copy. The buffer protocol # should allow us to assign a CFFI buffer to the LHS of this line, but # on CPython 3.3+ that segfaults. As a workaround, we can temporarily # wrap it in a memoryview, except on Python 2.6 which doesn't have a # memoryview type. try: buffer[:result] = memoryview(_ffi.buffer(buf, result)) except NameError: buffer[:result] = _ffi.buffer(buf, result) return result def _handle_bio_errors(self, bio, result): if _lib.BIO_should_retry(bio): if _lib.BIO_should_read(bio): raise WantReadError() elif _lib.BIO_should_write(bio): # TODO: This is untested. raise WantWriteError() elif _lib.BIO_should_io_special(bio): # TODO: This is untested. I think io_special means the socket # BIO has a not-yet connected socket. raise ValueError("BIO_should_io_special") else: # TODO: This is untested. raise ValueError("unknown bio failure") else: # TODO: This is untested. _raise_current_error() def bio_read(self, bufsiz): """ When using non-socket connections this function reads the "dirty" data that would have traveled away on the network. :param bufsiz: The maximum number of bytes to read :return: The string read. """ if self._from_ssl is None: raise TypeError("Connection sock was not None") if not isinstance(bufsiz, integer_types): raise TypeError("bufsiz must be an integer") buf = _ffi.new("char[]", bufsiz) result = _lib.BIO_read(self._from_ssl, buf, bufsiz) if result <= 0: self._handle_bio_errors(self._from_ssl, result) return _ffi.buffer(buf, result)[:] def bio_write(self, buf): """ When using non-socket connections this function sends "dirty" data that would have traveled in on the network. :param buf: The string to put into the memory BIO. :return: The number of bytes written """ buf = _text_to_bytes_and_warn("buf", buf) if self._into_ssl is None: raise TypeError("Connection sock was not None") if not isinstance(buf, bytes): raise TypeError("buf must be a byte string") result = _lib.BIO_write(self._into_ssl, buf, len(buf)) if result <= 0: self._handle_bio_errors(self._into_ssl, result) return result def renegotiate(self): """ Renegotiate the session :return: True if the renegotiation can be started, false otherwise """ def do_handshake(self): """ Perform an SSL handshake (usually called after renegotiate() or one of set_*_state()). This can raise the same exceptions as send and recv. :return: None. """ result = _lib.SSL_do_handshake(self._ssl) self._raise_ssl_error(self._ssl, result) def renegotiate_pending(self): """ Check if there's a renegotiation in progress, it will return false once a renegotiation is finished. :return: Whether there's a renegotiation in progress """ def total_renegotiations(self): """ Find out the total number of renegotiations. :return: The number of renegotiations. """ return _lib.SSL_total_renegotiations(self._ssl) def connect(self, addr): """ Connect to remote host and set up client-side SSL :param addr: A remote address :return: What the socket's connect method returns """ _lib.SSL_set_connect_state(self._ssl) return self._socket.connect(addr) def connect_ex(self, addr): """ Connect to remote host and set up client-side SSL. Note that if the socket's connect_ex method doesn't return 0, SSL won't be initialized. :param addr: A remove address :return: What the socket's connect_ex method returns """ connect_ex = self._socket.connect_ex self.set_connect_state() return connect_ex(addr) def accept(self): """ Accept incoming connection and set up SSL on it :return: A (conn,addr) pair where conn is a Connection and addr is an address """ client, addr = self._socket.accept() conn = Connection(self._context, client) conn.set_accept_state() return (conn, addr) def bio_shutdown(self): """ When using non-socket connections this function signals end of data on the input for this connection. :return: None """ if self._from_ssl is None: raise TypeError("Connection sock was not None") _lib.BIO_set_mem_eof_return(self._into_ssl, 0) def shutdown(self): """ Send closure alert :return: True if the shutdown completed successfully (i.e. both sides have sent closure alerts), false otherwise (i.e. you have to wait for a ZeroReturnError on a recv() method call """ result = _lib.SSL_shutdown(self._ssl) if result < 0: self._raise_ssl_error(self._ssl, result) elif result > 0: return True else: return False def get_cipher_list(self): """ Get the session cipher list :return: A list of cipher strings """ ciphers = [] for i in count(): result = _lib.SSL_get_cipher_list(self._ssl, i) if result == _ffi.NULL: break ciphers.append(_native(_ffi.string(result))) return ciphers def get_client_ca_list(self): """ Get CAs whose certificates are suggested for client authentication. :return: If this is a server connection, a list of X509Names representing the acceptable CAs as set by :py:meth:`OpenSSL.SSL.Context.set_client_ca_list` or :py:meth:`OpenSSL.SSL.Context.add_client_ca`. If this is a client connection, the list of such X509Names sent by the server, or an empty list if that has not yet happened. """ ca_names = _lib.SSL_get_client_CA_list(self._ssl) if ca_names == _ffi.NULL: # TODO: This is untested. return [] result = [] for i in range(_lib.sk_X509_NAME_num(ca_names)): name = _lib.sk_X509_NAME_value(ca_names, i) copy = _lib.X509_NAME_dup(name) if copy == _ffi.NULL: # TODO: This is untested. _raise_current_error() pyname = X509Name.__new__(X509Name) pyname._name = _ffi.gc(copy, _lib.X509_NAME_free) result.append(pyname) return result def makefile(self): """ The makefile() method is not implemented, since there is no dup semantics for SSL connections :raise: NotImplementedError """ raise NotImplementedError("Cannot make file object of OpenSSL.SSL.Connection") def get_app_data(self): """ Get application data :return: The application data """ return self._app_data def set_app_data(self, data): """ Set application data :param data - The application data :return: None """ self._app_data = data def get_shutdown(self): """ Get shutdown state :return: The shutdown state, a bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN. """ return _lib.SSL_get_shutdown(self._ssl) def set_shutdown(self, state): """ Set shutdown state :param state - bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN. :return: None """ if not isinstance(state, integer_types): raise TypeError("state must be an integer") _lib.SSL_set_shutdown(self._ssl, state) def state_string(self): """ Get a verbose state description :return: A string representing the state """ def server_random(self): """ Get a copy of the server hello nonce. :return: A string representing the state """ if self._ssl.session == _ffi.NULL: return None return _ffi.buffer( self._ssl.s3.server_random, _lib.SSL3_RANDOM_SIZE)[:] def client_random(self): """ Get a copy of the client hello nonce. :return: A string representing the state """ if self._ssl.session == _ffi.NULL: return None return _ffi.buffer( self._ssl.s3.client_random, _lib.SSL3_RANDOM_SIZE)[:] def master_key(self): """ Get a copy of the master key. :return: A string representing the state """ if self._ssl.session == _ffi.NULL: return None return _ffi.buffer( self._ssl.session.master_key, self._ssl.session.master_key_length)[:] def sock_shutdown(self, *args, **kwargs): """ See shutdown(2) :return: What the socket's shutdown() method returns """ return self._socket.shutdown(*args, **kwargs) def get_peer_certificate(self): """ Retrieve the other side's certificate (if any) :return: The peer's certificate """ cert = _lib.SSL_get_peer_certificate(self._ssl) if cert != _ffi.NULL: pycert = X509.__new__(X509) pycert._x509 = _ffi.gc(cert, _lib.X509_free) return pycert return None def get_peer_cert_chain(self): """ Retrieve the other side's certificate (if any) :return: A list of X509 instances giving the peer's certificate chain, or None if it does not have one. """ cert_stack = _lib.SSL_get_peer_cert_chain(self._ssl) if cert_stack == _ffi.NULL: return None result = [] for i in range(_lib.sk_X509_num(cert_stack)): # TODO could incref instead of dup here cert = _lib.X509_dup(_lib.sk_X509_value(cert_stack, i)) pycert = X509.__new__(X509) pycert._x509 = _ffi.gc(cert, _lib.X509_free) result.append(pycert) return result def want_read(self): """ Checks if more data has to be read from the transport layer to complete an operation. :return: True iff more data has to be read """ return _lib.SSL_want_read(self._ssl) def want_write(self): """ Checks if there is data to write to the transport layer to complete an operation. :return: True iff there is data to write """ return _lib.SSL_want_write(self._ssl) def set_accept_state(self): """ Set the connection to work in server mode. The handshake will be handled automatically by read/write. :return: None """ _lib.SSL_set_accept_state(self._ssl) def set_connect_state(self): """ Set the connection to work in client mode. The handshake will be handled automatically by read/write. :return: None """ _lib.SSL_set_connect_state(self._ssl) def get_session(self): """ Returns the Session currently used. @return: An instance of :py:class:`OpenSSL.SSL.Session` or :py:obj:`None` if no session exists. """ session = _lib.SSL_get1_session(self._ssl) if session == _ffi.NULL: return None pysession = Session.__new__(Session) pysession._session = _ffi.gc(session, _lib.SSL_SESSION_free) return pysession def set_session(self, session): """ Set the session to be used when the TLS/SSL connection is established. :param session: A Session instance representing the session to use. :returns: None """ if not isinstance(session, Session): raise TypeError("session must be a Session instance") result = _lib.SSL_set_session(self._ssl, session._session) if not result: _raise_current_error() def _get_finished_message(self, function): """ Helper to implement :py:meth:`get_finished` and :py:meth:`get_peer_finished`. :param function: Either :py:data:`SSL_get_finished`: or :py:data:`SSL_get_peer_finished`. :return: :py:data:`None` if the desired message has not yet been received, otherwise the contents of the message. :rtype: :py:class:`bytes` or :py:class:`NoneType` """ # The OpenSSL documentation says nothing about what might happen if the # count argument given is zero. Specifically, it doesn't say whether # the output buffer may be NULL in that case or not. Inspection of the # implementation reveals that it calls memcpy() unconditionally. # Section 7.1.4, paragraph 1 of the C standard suggests that # memcpy(NULL, source, 0) is not guaranteed to produce defined (let # alone desirable) behavior (though it probably does on just about # every implementation...) # # Allocate a tiny buffer to pass in (instead of just passing NULL as # one might expect) for the initial call so as to be safe against this # potentially undefined behavior. empty = _ffi.new("char[]", 0) size = function(self._ssl, empty, 0) if size == 0: # No Finished message so far. return None buf = _ffi.new("char[]", size) function(self._ssl, buf, size) return _ffi.buffer(buf, size)[:] def get_finished(self): """ Obtain the latest `handshake finished` message sent to the peer. :return: The contents of the message or :py:obj:`None` if the TLS handshake has not yet completed. :rtype: :py:class:`bytes` or :py:class:`NoneType` """ return self._get_finished_message(_lib.SSL_get_finished) def get_peer_finished(self): """ Obtain the latest `handshake finished` message received from the peer. :return: The contents of the message or :py:obj:`None` if the TLS handshake has not yet completed. :rtype: :py:class:`bytes` or :py:class:`NoneType` """ return self._get_finished_message(_lib.SSL_get_peer_finished) def get_cipher_name(self): """ Obtain the name of the currently used cipher. :returns: The name of the currently used cipher or :py:obj:`None` if no connection has been established. :rtype: :py:class:`unicode` or :py:class:`NoneType` """ cipher = _lib.SSL_get_current_cipher(self._ssl) if cipher == _ffi.NULL: return None else: name = _ffi.string(_lib.SSL_CIPHER_get_name(cipher)) return name.decode("utf-8") def get_cipher_bits(self): """ Obtain the number of secret bits of the currently used cipher. :returns: The number of secret bits of the currently used cipher or :py:obj:`None` if no connection has been established. :rtype: :py:class:`int` or :py:class:`NoneType` """ cipher = _lib.SSL_get_current_cipher(self._ssl) if cipher == _ffi.NULL: return None else: return _lib.SSL_CIPHER_get_bits(cipher, _ffi.NULL) def get_cipher_version(self): """ Obtain the protocol version of the currently used cipher. :returns: The protocol name of the currently used cipher or :py:obj:`None` if no connection has been established. :rtype: :py:class:`unicode` or :py:class:`NoneType` """ cipher = _lib.SSL_get_current_cipher(self._ssl) if cipher == _ffi.NULL: return None else: version =_ffi.string(_lib.SSL_CIPHER_get_version(cipher)) return version.decode("utf-8") @_requires_npn def get_next_proto_negotiated(self): """ Get the protocol that was negotiated by NPN. """ data = _ffi.new("unsigned char **") data_len = _ffi.new("unsigned int *") _lib.SSL_get0_next_proto_negotiated(self._ssl, data, data_len) return _ffi.buffer(data[0], data_len[0])[:] @_requires_alpn def set_alpn_protos(self, protos): """ Specify the client's ALPN protocol list. These protocols are offered to the server during protocol negotiation. :param protos: A list of the protocols to be offered to the server. This list should be a Python list of bytestrings representing the protocols to offer, e.g. ``[b'http/1.1', b'spdy/2']``. """ # Take the list of protocols and join them together, prefixing them # with their lengths. protostr = b''.join( chain.from_iterable((int2byte(len(p)), p) for p in protos) ) # Build a C string from the list. We don't need to save this off # because OpenSSL immediately copies the data out. input_str = _ffi.new("unsigned char[]", protostr) input_str_len = _ffi.cast("unsigned", len(protostr)) _lib.SSL_set_alpn_protos(self._ssl, input_str, input_str_len) def get_alpn_proto_negotiated(self): """ Get the protocol that was negotiated by ALPN. """ if not _lib.Cryptography_HAS_ALPN: raise NotImplementedError("ALPN not available") data = _ffi.new("unsigned char **") data_len = _ffi.new("unsigned int *") _lib.SSL_get0_alpn_selected(self._ssl, data, data_len) if not data_len: return b'' return _ffi.buffer(data[0], data_len[0])[:] ConnectionType = Connection # This is similar to the initialization calls at the end of OpenSSL/crypto.py # but is exercised mostly by the Context initializer. _lib.SSL_library_init() pyOpenSSL-0.15.1/OpenSSL/test/0000755000076500000240000000000012513316322016133 5ustar hynekstaff00000000000000pyOpenSSL-0.15.1/OpenSSL/test/__init__.py0000644000076500000240000000017512263743062020256 0ustar hynekstaff00000000000000# Copyright (C) Jean-Paul Calderone # See LICENSE for details. """ Package containing unit tests for :py:mod:`OpenSSL`. """ pyOpenSSL-0.15.1/OpenSSL/test/README0000644000076500000240000000026512263743062017025 0ustar hynekstaff00000000000000These tests are meant to be run using twisted's "trial" command. See http://twistedmatrix.com/trac/wiki/TwistedTrial For example... $ sudo python ./setup install $ trial OpenSSL pyOpenSSL-0.15.1/OpenSSL/test/test_crypto.py0000644000076500000240000042271112513100143021063 0ustar hynekstaff00000000000000# Copyright (c) Jean-Paul Calderone # See LICENSE file for details. """ Unit tests for :py:mod:`OpenSSL.crypto`. """ from unittest import main from warnings import catch_warnings, simplefilter import base64 import os import re from subprocess import PIPE, Popen from datetime import datetime, timedelta from six import u, b, binary_type, PY3 from warnings import simplefilter from warnings import catch_warnings from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType from OpenSSL.crypto import X509Store, X509StoreType, X509StoreContext, X509StoreContextError from OpenSSL.crypto import X509Req, X509ReqType from OpenSSL.crypto import X509Extension, X509ExtensionType from OpenSSL.crypto import load_certificate, load_privatekey from OpenSSL.crypto import FILETYPE_PEM, FILETYPE_ASN1, FILETYPE_TEXT from OpenSSL.crypto import dump_certificate, load_certificate_request from OpenSSL.crypto import dump_certificate_request, dump_privatekey from OpenSSL.crypto import PKCS7Type, load_pkcs7_data from OpenSSL.crypto import PKCS12, PKCS12Type, load_pkcs12 from OpenSSL.crypto import CRL, Revoked, load_crl from OpenSSL.crypto import NetscapeSPKI, NetscapeSPKIType from OpenSSL.crypto import ( sign, verify, get_elliptic_curve, get_elliptic_curves) from OpenSSL.test.util import ( EqualityTestsMixin, TestCase, WARNING_TYPE_EXPECTED ) from OpenSSL._util import native, lib def normalize_certificate_pem(pem): return dump_certificate(FILETYPE_PEM, load_certificate(FILETYPE_PEM, pem)) def normalize_privatekey_pem(pem): return dump_privatekey(FILETYPE_PEM, load_privatekey(FILETYPE_PEM, pem)) GOOD_CIPHER = "blowfish" BAD_CIPHER = "zippers" GOOD_DIGEST = "MD5" BAD_DIGEST = "monkeys" root_cert_pem = b("""-----BEGIN CERTIFICATE----- MIIC7TCCAlagAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwIhgPMjAwOTAzMjUxMjM2 NThaGA8yMDE3MDYxMTEyMzY1OFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklM MRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9U ZXN0aW5nIFJvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPmaQumL urpE527uSEHdL1pqcDRmWzu+98Y6YHzT/J7KWEamyMCNZ6fRW1JCR782UQ8a07fy 2xXsKy4WdKaxyG8CcatwmXvpvRQ44dSANMihHELpANTdyVp6DCysED6wkQFurHlF 1dshEaJw8b/ypDhmbVIo6Ci1xvCJqivbLFnbAgMBAAGjgbswgbgwHQYDVR0OBBYE FINVdy1eIfFJDAkk51QJEo3IfgSuMIGIBgNVHSMEgYAwfoAUg1V3LV4h8UkMCSTn VAkSjch+BK6hXKRaMFgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UE BxMHQ2hpY2FnbzEQMA4GA1UEChMHVGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBS b290IENBggg9DMTgxt659DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GB AGGCDazMJGoWNBpc03u6+smc95dEead2KlZXBATOdFT1VesY3+nUOqZhEhTGlDMi hkgaZnzoIq/Uamidegk4hirsCT/R+6vsKAAxNTcBjUeZjlykCJWy5ojShGftXIKY w/njVbKMXrvc83qmTdGl3TAM0fxQIpqgcglFLveEBgzn -----END CERTIFICATE----- """) root_key_pem = b("""-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQD5mkLpi7q6ROdu7khB3S9aanA0Zls7vvfGOmB80/yeylhGpsjA jWen0VtSQke/NlEPGtO38tsV7CsuFnSmschvAnGrcJl76b0UOOHUgDTIoRxC6QDU 3claegwsrBA+sJEBbqx5RdXbIRGicPG/8qQ4Zm1SKOgotcbwiaor2yxZ2wIDAQAB AoGBAPCgMpmLxzwDaUmcFbTJUvlLW1hoxNNYSu2jIZm1k/hRAcE60JYwvBkgz3UB yMEh0AtLxYe0bFk6EHah11tMUPgscbCq73snJ++8koUw+csk22G65hOs51bVb7Aa 6JBe67oLzdtvgCUFAA2qfrKzWRZzAdhUirQUZgySZk+Xq1pBAkEA/kZG0A6roTSM BVnx7LnPfsycKUsTumorpXiylZJjTi9XtmzxhrYN6wgZlDOOwOLgSQhszGpxVoMD u3gByT1b2QJBAPtL3mSKdvwRu/+40zaZLwvSJRxaj0mcE4BJOS6Oqs/hS1xRlrNk PpQ7WJ4yM6ZOLnXzm2mKyxm50Mv64109FtMCQQDOqS2KkjHaLowTGVxwC0DijMfr I9Lf8sSQk32J5VWCySWf5gGTfEnpmUa41gKTMJIbqZZLucNuDcOtzUaeWZlZAkA8 ttXigLnCqR486JDPTi9ZscoZkZ+w7y6e/hH8t6d5Vjt48JVyfjPIaJY+km58LcN3 6AWSeGAdtRFHVzR7oHjVAkB4hutvxiOeiIVQNBhM6RSI9aBPMI21DoX2JRoxvNW2 cbvAhow217X9V0dVerEOKxnNYspXRrh36h7k4mQA+sDq -----END RSA PRIVATE KEY----- """) intermediate_cert_pem = b("""-----BEGIN CERTIFICATE----- MIICVzCCAcCgAwIBAgIRAMPzhm6//0Y/g2pmnHR2C4cwDQYJKoZIhvcNAQENBQAw WDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAw DgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwHhcNMTQw ODI4MDIwNDA4WhcNMjQwODI1MDIwNDA4WjBmMRUwEwYDVQQDEwxpbnRlcm1lZGlh dGUxDDAKBgNVBAoTA29yZzERMA8GA1UECxMIb3JnLXVuaXQxCzAJBgNVBAYTAlVT MQswCQYDVQQIEwJDQTESMBAGA1UEBxMJU2FuIERpZWdvMIGfMA0GCSqGSIb3DQEB AQUAA4GNADCBiQKBgQDYcEQw5lfbEQRjr5Yy4yxAHGV0b9Al+Lmu7wLHMkZ/ZMmK FGIbljbviiD1Nz97Oh2cpB91YwOXOTN2vXHq26S+A5xe8z/QJbBsyghMur88CjdT 21H2qwMa+r5dCQwEhuGIiZ3KbzB/n4DTMYI5zy4IYPv0pjxShZn4aZTCCK2IUwID AQABoxMwETAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAPIWSkLX QRMApOjjyC+tMxumT5e2pMqChHmxobQK4NMdrf2VCx+cRT6EmY8sK3/Xl/X8UBQ+ 9n5zXb1ZwhW/sTWgUvmOceJ4/XVs9FkdWOOn1J0XBch9ZIiFe/s5ASIgG7fUdcUF 9mAWS6FK2ca3xIh5kIupCXOFa0dPvlw/YUFT -----END CERTIFICATE----- """) intermediate_key_pem = b("""-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQDYcEQw5lfbEQRjr5Yy4yxAHGV0b9Al+Lmu7wLHMkZ/ZMmKFGIb ljbviiD1Nz97Oh2cpB91YwOXOTN2vXHq26S+A5xe8z/QJbBsyghMur88CjdT21H2 qwMa+r5dCQwEhuGIiZ3KbzB/n4DTMYI5zy4IYPv0pjxShZn4aZTCCK2IUwIDAQAB AoGAfSZVV80pSeOKHTYfbGdNY/jHdU9eFUa/33YWriXU+77EhpIItJjkRRgivIfo rhFJpBSGmDLblaqepm8emsXMeH4+2QzOYIf0QGGP6E6scjTt1PLqdqKfVJ1a2REN 147cujNcmFJb/5VQHHMpaPTgttEjlzuww4+BCDPsVRABWrkCQQD3loH36nLoQTtf +kQq0T6Bs9/UWkTAGo0ND81ALj0F8Ie1oeZg6RNT96RxZ3aVuFTESTv6/TbjWywO wdzlmV1vAkEA38rTJ6PTwaJlw5OttdDzAXGPB9tDmzh9oSi7cHwQQXizYd8MBYx4 sjHUKD3dCQnb1dxJFhd3BT5HsnkRMbVZXQJAbXduH17ZTzcIOXc9jHDXYiFVZV5D 52vV0WCbLzVCZc3jMrtSUKa8lPN5EWrdU3UchWybyG0MR5mX8S5lrF4SoQJAIyUD DBKaSqpqONCUUx1BTFS9FYrFjzbL4+c1qHCTTPTblt8kUCrDOZjBrKAqeiTmNSum /qUot9YUBF8m6BuGsQJATHHmdFy/fG1VLkyBp49CAa8tN3Z5r/CgTznI4DfMTf4C NbRHn2UmYlwQBa+L5lg9phewNe8aEwpPyPLoV85U8Q== -----END RSA PRIVATE KEY----- """) server_cert_pem = b("""-----BEGIN CERTIFICATE----- MIICKDCCAZGgAwIBAgIJAJn/HpR21r/8MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UEBxMHQ2hpY2FnbzEQMA4GA1UEChMH VGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBSb290IENBMCIYDzIwMDkwMzI1MTIz NzUzWhgPMjAxNzA2MTExMjM3NTNaMBgxFjAUBgNVBAMTDWxvdmVseSBzZXJ2ZXIw gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAL6m+G653V0tpBC/OKl22VxOi2Cv lK4TYu9LHSDP9uDVTe7V5D5Tl6qzFoRRx5pfmnkqT5B+W9byp2NU3FC5hLm5zSAr b45meUhjEJ/ifkZgbNUjHdBIGP9MAQUHZa5WKdkGIJvGAvs8UzUqlr4TBWQIB24+ lJ+Ukk/CRgasrYwdAgMBAAGjNjA0MB0GA1UdDgQWBBS4kC7Ij0W1TZXZqXQFAM2e gKEG2DATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQUFAAOBgQBh30Li dJ+NlxIOx5343WqIBka3UbsOb2kxWrbkVCrvRapCMLCASO4FqiKWM+L0VDBprqIp 2mgpFQ6FHpoIENGvJhdEKpptQ5i7KaGhnDNTfdy3x1+h852G99f1iyj0RmbuFcM8 uzujnS8YXWvM7DM1Ilozk4MzPug8jzFp5uhKCQ== -----END CERTIFICATE----- """) server_key_pem = normalize_privatekey_pem(b("""-----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBgQC+pvhuud1dLaQQvzipdtlcTotgr5SuE2LvSx0gz/bg1U3u1eQ+ U5eqsxaEUceaX5p5Kk+QflvW8qdjVNxQuYS5uc0gK2+OZnlIYxCf4n5GYGzVIx3Q SBj/TAEFB2WuVinZBiCbxgL7PFM1Kpa+EwVkCAduPpSflJJPwkYGrK2MHQIDAQAB AoGAbwuZ0AR6JveahBaczjfnSpiFHf+mve2UxoQdpyr6ROJ4zg/PLW5K/KXrC48G j6f3tXMrfKHcpEoZrQWUfYBRCUsGD5DCazEhD8zlxEHahIsqpwA0WWssJA2VOLEN j6DuV2pCFbw67rfTBkTSo32ahfXxEKev5KswZk0JIzH3ooECQQDgzS9AI89h0gs8 Dt+1m11Rzqo3vZML7ZIyGApUzVan+a7hbc33nbGRkAXjHaUBJO31it/H6dTO+uwX msWwNG5ZAkEA2RyFKs5xR5USTFaKLWCgpH/ydV96KPOpBND7TKQx62snDenFNNbn FwwOhpahld+vqhYk+pfuWWUpQciE+Bu7ZQJASjfT4sQv4qbbKK/scePicnDdx9th 4e1EeB9xwb+tXXXUo/6Bor/AcUNwfiQ6Zt9PZOK9sR3lMZSsP7rMi7kzuQJABie6 1sXXjFH7nNJvRG4S39cIxq8YRYTy68II/dlB2QzGpKxV/POCxbJ/zu0CU79tuYK7 NaeNCFfH3aeTrX0LyQJAMBWjWmeKM2G2sCExheeQK0ROnaBC8itCECD4Jsve4nqf r50+LF74iLXFwqysVCebPKMOpDWp/qQ1BbJQIPs7/A== -----END RSA PRIVATE KEY----- """)) intermediate_server_cert_pem = b("""-----BEGIN CERTIFICATE----- MIICWDCCAcGgAwIBAgIRAPQFY9jfskSihdiNSNdt6GswDQYJKoZIhvcNAQENBQAw ZjEVMBMGA1UEAxMMaW50ZXJtZWRpYXRlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh biBEaWVnbzAeFw0xNDA4MjgwMjEwNDhaFw0yNDA4MjUwMjEwNDhaMG4xHTAbBgNV BAMTFGludGVybWVkaWF0ZS1zZXJ2aWNlMQwwCgYDVQQKEwNvcmcxETAPBgNVBAsT CG9yZy11bml0MQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAcTCVNh biBEaWVnbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqpJZygd+w1faLOr1 iOAmbBhx5SZWcTCZ/ZjHQTJM7GuPT624QkqsixFghRKdDROwpwnAP7gMRukLqiy4 +kRuGT5OfyGggL95i2xqA+zehjj08lSTlvGHpePJgCyTavIy5+Ljsj4DKnKyuhxm biXTRrH83NDgixVkObTEmh/OVK0CAwEAATANBgkqhkiG9w0BAQ0FAAOBgQBa0Npw UkzjaYEo1OUE1sTI6Mm4riTIHMak4/nswKh9hYup//WVOlr/RBSBtZ7Q/BwbjobN 3bfAtV7eSAqBsfxYXyof7G1ALANQERkq3+oyLP1iVt08W1WOUlIMPhdCF/QuCwy6 x9MJLhUCGLJPM+O2rAPWVD9wCmvq10ALsiH3yA== -----END CERTIFICATE----- """) intermediate_server_key_pem = b("""-----BEGIN RSA PRIVATE KEY----- MIICXAIBAAKBgQCqklnKB37DV9os6vWI4CZsGHHlJlZxMJn9mMdBMkzsa49PrbhC SqyLEWCFEp0NE7CnCcA/uAxG6QuqLLj6RG4ZPk5/IaCAv3mLbGoD7N6GOPTyVJOW 8Yel48mALJNq8jLn4uOyPgMqcrK6HGZuJdNGsfzc0OCLFWQ5tMSaH85UrQIDAQAB AoGAIQ594j5zna3/9WaPsTgnmhlesVctt4AAx/n827DA4ayyuHFlXUuVhtoWR5Pk 5ezj9mtYW8DyeCegABnsu2vZni/CdvU6uiS1Hv6qM1GyYDm9KWgovIP9rQCDSGaz d57IWVGxx7ODFkm3gN5nxnSBOFVHytuW1J7FBRnEsehRroECQQDXHFOv82JuXDcz z3+4c74IEURdOHcbycxlppmK9kFqm5lsUdydnnGW+mvwDk0APOB7Wg7vyFyr393e dpmBDCzNAkEAyv6tVbTKUYhSjW+QhabJo896/EqQEYUmtMXxk4cQnKeR/Ao84Rkf EqD5IykMUfUI0jJU4DGX+gWZ10a7kNbHYQJAVFCuHNFxS4Cpwo0aqtnzKoZaHY/8 X9ABZfafSHCtw3Op92M+7ikkrOELXdS9KdKyyqbKJAKNEHF3LbOfB44WIQJAA2N4 9UNNVUsXRbElEnYUS529CdUczo4QdVgQjkvk5RiPAUwSdBd9Q0xYnFOlFwEmIowg ipWJWe0aAlP18ZcEQQJBAL+5lekZ/GUdQoZ4HAsN5a9syrzavJ9VvU1KOOPorPZK nMRZbbQgP+aSB7yl6K0gaLaZ8XaK0pjxNBh6ASqg9f4= -----END RSA PRIVATE KEY----- """) client_cert_pem = b("""-----BEGIN CERTIFICATE----- MIICJjCCAY+gAwIBAgIJAKxpFI5lODkjMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV BAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UEBxMHQ2hpY2FnbzEQMA4GA1UEChMH VGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBSb290IENBMCIYDzIwMDkwMzI1MTIz ODA1WhgPMjAxNzA2MTExMjM4MDVaMBYxFDASBgNVBAMTC3VnbHkgY2xpZW50MIGf MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAZh/SRtNm5ntMT4qb6YzEpTroMlq2 rn+GrRHRiZ+xkCw/CGNhbtPir7/QxaUj26BSmQrHw1bGKEbPsWiW7bdXSespl+xK iku4G/KvnnmWdeJHqsiXeUZtqurMELcPQAw9xPHEuhqqUJvvEoMTsnCEqGM+7Dtb oCRajYyHfluARQIDAQABozYwNDAdBgNVHQ4EFgQUNQB+qkaOaEVecf1J3TTUtAff 0fAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADgYEAyv/Jh7gM Q3OHvmsFEEvRI+hsW8y66zK4K5de239Y44iZrFYkt7Q5nBPMEWDj4F2hLYWL/qtI 9Zdr0U4UDCU9SmmGYh4o7R4TZ5pGFvBYvjhHbkSFYFQXZxKUi+WUxplP6I0wr2KJ PSTJCjJOn3xo2NTKRgV1gaoTf2EhL+RG8TQ= -----END CERTIFICATE----- """) client_key_pem = normalize_privatekey_pem(b("""-----BEGIN RSA PRIVATE KEY----- MIICXgIBAAKBgQDAZh/SRtNm5ntMT4qb6YzEpTroMlq2rn+GrRHRiZ+xkCw/CGNh btPir7/QxaUj26BSmQrHw1bGKEbPsWiW7bdXSespl+xKiku4G/KvnnmWdeJHqsiX eUZtqurMELcPQAw9xPHEuhqqUJvvEoMTsnCEqGM+7DtboCRajYyHfluARQIDAQAB AoGATkZ+NceY5Glqyl4mD06SdcKfV65814vg2EL7V9t8+/mi9rYL8KztSXGlQWPX zuHgtRoMl78yQ4ZJYOBVo+nsx8KZNRCEBlE19bamSbQLCeQMenWnpeYyQUZ908gF h6L9qsFVJepgA9RDgAjyDoS5CaWCdCCPCH2lDkdcqC54SVUCQQDseuduc4wi8h4t V8AahUn9fn9gYfhoNuM0gdguTA0nPLVWz4hy1yJiWYQe0H7NLNNTmCKiLQaJpAbb TC6vE8C7AkEA0Ee8CMJUc20BnGEmxwgWcVuqFWaKCo8jTH1X38FlATUsyR3krjW2 dL3yDD9NwHxsYP7nTKp/U8MV7U9IBn4y/wJBAJl7H0/BcLeRmuJk7IqJ7b635iYB D/9beFUw3MUXmQXZUfyYz39xf6CDZsu1GEdEC5haykeln3Of4M9d/4Kj+FcCQQCY si6xwT7GzMDkk/ko684AV3KPc/h6G0yGtFIrMg7J3uExpR/VdH2KgwMkZXisSMvw JJEQjOMCVsEJlRk54WWjAkEAzoZNH6UhDdBK5F38rVt/y4SEHgbSfJHIAmPS32Kq f6GGcfNpip0Uk7q7udTKuX7Q/buZi/C4YW7u3VKAquv9NA== -----END RSA PRIVATE KEY----- """)) cleartextCertificatePEM = b("""-----BEGIN CERTIFICATE----- MIIC7TCCAlagAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwIhgPMjAwOTAzMjUxMjM2 NThaGA8yMDE3MDYxMTEyMzY1OFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklM MRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9U ZXN0aW5nIFJvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPmaQumL urpE527uSEHdL1pqcDRmWzu+98Y6YHzT/J7KWEamyMCNZ6fRW1JCR782UQ8a07fy 2xXsKy4WdKaxyG8CcatwmXvpvRQ44dSANMihHELpANTdyVp6DCysED6wkQFurHlF 1dshEaJw8b/ypDhmbVIo6Ci1xvCJqivbLFnbAgMBAAGjgbswgbgwHQYDVR0OBBYE FINVdy1eIfFJDAkk51QJEo3IfgSuMIGIBgNVHSMEgYAwfoAUg1V3LV4h8UkMCSTn VAkSjch+BK6hXKRaMFgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UE BxMHQ2hpY2FnbzEQMA4GA1UEChMHVGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBS b290IENBggg9DMTgxt659DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GB AGGCDazMJGoWNBpc03u6+smc95dEead2KlZXBATOdFT1VesY3+nUOqZhEhTGlDMi hkgaZnzoIq/Uamidegk4hirsCT/R+6vsKAAxNTcBjUeZjlykCJWy5ojShGftXIKY w/njVbKMXrvc83qmTdGl3TAM0fxQIpqgcglFLveEBgzn -----END CERTIFICATE----- """) cleartextPrivateKeyPEM = normalize_privatekey_pem(b("""\ -----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQD5mkLpi7q6ROdu7khB3S9aanA0Zls7vvfGOmB80/yeylhGpsjA jWen0VtSQke/NlEPGtO38tsV7CsuFnSmschvAnGrcJl76b0UOOHUgDTIoRxC6QDU 3claegwsrBA+sJEBbqx5RdXbIRGicPG/8qQ4Zm1SKOgotcbwiaor2yxZ2wIDAQAB AoGBAPCgMpmLxzwDaUmcFbTJUvlLW1hoxNNYSu2jIZm1k/hRAcE60JYwvBkgz3UB yMEh0AtLxYe0bFk6EHah11tMUPgscbCq73snJ++8koUw+csk22G65hOs51bVb7Aa 6JBe67oLzdtvgCUFAA2qfrKzWRZzAdhUirQUZgySZk+Xq1pBAkEA/kZG0A6roTSM BVnx7LnPfsycKUsTumorpXiylZJjTi9XtmzxhrYN6wgZlDOOwOLgSQhszGpxVoMD u3gByT1b2QJBAPtL3mSKdvwRu/+40zaZLwvSJRxaj0mcE4BJOS6Oqs/hS1xRlrNk PpQ7WJ4yM6ZOLnXzm2mKyxm50Mv64109FtMCQQDOqS2KkjHaLowTGVxwC0DijMfr I9Lf8sSQk32J5VWCySWf5gGTfEnpmUa41gKTMJIbqZZLucNuDcOtzUaeWZlZAkA8 ttXigLnCqR486JDPTi9ZscoZkZ+w7y6e/hH8t6d5Vjt48JVyfjPIaJY+km58LcN3 6AWSeGAdtRFHVzR7oHjVAkB4hutvxiOeiIVQNBhM6RSI9aBPMI21DoX2JRoxvNW2 cbvAhow217X9V0dVerEOKxnNYspXRrh36h7k4mQA+sDq -----END RSA PRIVATE KEY----- """)) cleartextCertificateRequestPEM = b("""-----BEGIN CERTIFICATE REQUEST----- MIIBnjCCAQcCAQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQH EwdDaGljYWdvMRcwFQYDVQQKEw5NeSBDb21wYW55IEx0ZDEXMBUGA1UEAxMORnJl ZGVyaWNrIERlYW4wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANp6Y17WzKSw BsUWkXdqg6tnXy8H8hA1msCMWpc+/2KJ4mbv5NyD6UD+/SqagQqulPbF/DFea9nA E0zhmHJELcM8gUTIlXv/cgDWnmK4xj8YkjVUiCdqKRAKeuzLG1pGmwwF5lGeJpXN xQn5ecR0UYSOWj6TTGXB9VyUMQzCClcBAgMBAAGgADANBgkqhkiG9w0BAQUFAAOB gQAAJGuF/R/GGbeC7FbFW+aJgr9ee0Xbl6nlhu7pTe67k+iiKT2dsl2ti68MVTnu Vrb3HUNqOkiwsJf6kCtq5oPn3QVYzTa76Dt2y3Rtzv6boRSlmlfrgS92GNma8JfR oICQk3nAudi6zl1Dix3BCv1pUp5KMtGn3MeDEi6QFGy2rA== -----END CERTIFICATE REQUEST----- """) encryptedPrivateKeyPEM = b("""-----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: DES-EDE3-CBC,9573604A18579E9E SHOho56WxDkT0ht10UTeKc0F5u8cqIa01kzFAmETw0MAs8ezYtK15NPdCXUm3X/2 a17G7LSF5bkxOgZ7vpXyMzun/owrj7CzvLxyncyEFZWvtvzaAhPhvTJtTIB3kf8B 8+qRcpTGK7NgXEgYBW5bj1y4qZkD4zCL9o9NQzsKI3Ie8i0239jsDOWR38AxjXBH mGwAQ4Z6ZN5dnmM4fhMIWsmFf19sNyAML4gHenQCHhmXbjXeVq47aC2ProInJbrm +00TcisbAQ40V9aehVbcDKtS4ZbMVDwncAjpXpcncC54G76N6j7F7wL7L/FuXa3A fvSVy9n2VfF/pJ3kYSflLHH2G/DFxjF7dl0GxhKPxJjp3IJi9VtuvmN9R2jZWLQF tfC8dXgy/P9CfFQhlinqBTEwgH0oZ/d4k4NVFDSdEMaSdmBAjlHpc+Vfdty3HVnV rKXj//wslsFNm9kIwJGIgKUa/n2jsOiydrsk1mgH7SmNCb3YHgZhbbnq0qLat/HC gHDt3FHpNQ31QzzL3yrenFB2L9osIsnRsDTPFNi4RX4SpDgNroxOQmyzCCV6H+d4 o1mcnNiZSdxLZxVKccq0AfRpHqpPAFnJcQHP6xyT9MZp6fBa0XkxDnt9kNU8H3Qw 7SJWZ69VXjBUzMlQViLuaWMgTnL+ZVyFZf9hTF7U/ef4HMLMAVNdiaGG+G+AjCV/ MbzjS007Oe4qqBnCWaFPSnJX6uLApeTbqAxAeyCql56ULW5x6vDMNC3dwjvS/CEh 11n8RkgFIQA0AhuKSIg3CbuartRsJnWOLwgLTzsrKYL4yRog1RJrtw== -----END RSA PRIVATE KEY----- """) encryptedPrivateKeyPEMPassphrase = b("foobar") # Some PKCS#7 stuff. Generated with the openssl command line: # # openssl crl2pkcs7 -inform pem -outform pem -certfile s.pem -nocrl # # with a certificate and key (but the key should be irrelevant) in s.pem pkcs7Data = b("""\ -----BEGIN PKCS7----- MIIDNwYJKoZIhvcNAQcCoIIDKDCCAyQCAQExADALBgkqhkiG9w0BBwGgggMKMIID BjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzERMA8G A1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtN MkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNA cG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzELMAkG A1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhvc3Qx HTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEBBQAD SwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh5kwI zOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQCMAAw LAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G A1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7hyNp 65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0y Q3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8g Q2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNv bYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6BoJu VwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++7QGG /g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JEWUQ9 Ho4EzbYCOaEAMQA= -----END PKCS7----- """) pkcs7DataASN1 = base64.b64decode(b""" MIIDNwYJKoZIhvcNAQcCoIIDKDCCAyQCAQExADALBgkqhkiG9w0BBwGgggMKMIID BjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzERMA8G A1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtN MkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNA cG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzELMAkG A1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhvc3Qx HTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEBBQAD SwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh5kwI zOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQCMAAw LAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G A1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7hyNp 65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0y Q3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8g Q2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNv bYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6BoJu VwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++7QGG /g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JEWUQ9 Ho4EzbYCOaEAMQA= """) crlData = b("""\ -----BEGIN X509 CRL----- MIIBWzCBxTANBgkqhkiG9w0BAQQFADBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMC SUwxEDAOBgNVBAcTB0NoaWNhZ28xEDAOBgNVBAoTB1Rlc3RpbmcxGDAWBgNVBAMT D1Rlc3RpbmcgUm9vdCBDQRcNMDkwNzI2MDQzNDU2WhcNMTIwOTI3MDI0MTUyWjA8 MBUCAgOrGA8yMDA5MDcyNTIzMzQ1NlowIwICAQAYDzIwMDkwNzI1MjMzNDU2WjAM MAoGA1UdFQQDCgEEMA0GCSqGSIb3DQEBBAUAA4GBAEBt7xTs2htdD3d4ErrcGAw1 4dKcVnIWTutoI7xxen26Wwvh8VCsT7i/UeP+rBl9rC/kfjWjzQk3/zleaarGTpBT 0yp4HXRFFoRhhSE/hP+eteaPXRgrsNRLHe9ZDd69wmh7J1wMDb0m81RG7kqcbsid vrzEeLDRiiPl92dyyWmu -----END X509 CRL----- """) # A broken RSA private key which can be used to test the error path through # PKey.check. inconsistentPrivateKeyPEM = b("""-----BEGIN RSA PRIVATE KEY----- MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh 5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEaAQJBAIqm/bz4NA1H++Vx5Ewx OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT zIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4 nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2 HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNeH+vRWsAYU/gbx+OQB+7VOcBAiEA oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w= -----END RSA PRIVATE KEY----- """) # certificate with NULL bytes in subjectAltName and common name nulbyteSubjectAltNamePEM = b("""-----BEGIN CERTIFICATE----- MIIE2DCCA8CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBxTELMAkGA1UEBhMCVVMx DzANBgNVBAgMBk9yZWdvbjESMBAGA1UEBwwJQmVhdmVydG9uMSMwIQYDVQQKDBpQ eXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEgMB4GA1UECwwXUHl0aG9uIENvcmUg RGV2ZWxvcG1lbnQxJDAiBgNVBAMMG251bGwucHl0aG9uLm9yZwBleGFtcGxlLm9y ZzEkMCIGCSqGSIb3DQEJARYVcHl0aG9uLWRldkBweXRob24ub3JnMB4XDTEzMDgw NzEzMTE1MloXDTEzMDgwNzEzMTI1MlowgcUxCzAJBgNVBAYTAlVTMQ8wDQYDVQQI DAZPcmVnb24xEjAQBgNVBAcMCUJlYXZlcnRvbjEjMCEGA1UECgwaUHl0aG9uIFNv ZnR3YXJlIEZvdW5kYXRpb24xIDAeBgNVBAsMF1B5dGhvbiBDb3JlIERldmVsb3Bt ZW50MSQwIgYDVQQDDBtudWxsLnB5dGhvbi5vcmcAZXhhbXBsZS5vcmcxJDAiBgkq hkiG9w0BCQEWFXB5dGhvbi1kZXZAcHl0aG9uLm9yZzCCASIwDQYJKoZIhvcNAQEB BQADggEPADCCAQoCggEBALXq7cn7Rn1vO3aA3TrzA5QLp6bb7B3f/yN0CJ2XFj+j pHs+Gw6WWSUDpybiiKnPec33BFawq3kyblnBMjBU61ioy5HwQqVkJ8vUVjGIUq3P vX/wBmQfzCe4o4uM89gpHyUL9UYGG8oCRa17dgqcv7u5rg0Wq2B1rgY+nHwx3JIv KRrgSwyRkGzpN8WQ1yrXlxWjgI9de0mPVDDUlywcWze1q2kwaEPTM3hLAmD1PESA oY/n8A/RXoeeRs9i/Pm/DGUS8ZPINXk/yOzsR/XvvkTVroIeLZqfmFpnZeF0cHzL 08LODkVJJ9zjLdT7SA4vnne4FEbAxDbKAq5qkYzaL4UCAwEAAaOB0DCBzTAMBgNV HRMBAf8EAjAAMB0GA1UdDgQWBBSIWlXAUv9hzVKjNQ/qWpwkOCL3XDALBgNVHQ8E BAMCBeAwgZAGA1UdEQSBiDCBhYIeYWx0bnVsbC5weXRob24ub3JnAGV4YW1wbGUu Y29tgSBudWxsQHB5dGhvbi5vcmcAdXNlckBleGFtcGxlLm9yZ4YpaHR0cDovL251 bGwucHl0aG9uLm9yZwBodHRwOi8vZXhhbXBsZS5vcmeHBMAAAgGHECABDbgAAAAA AAAAAAAAAAEwDQYJKoZIhvcNAQEFBQADggEBAKxPRe99SaghcI6IWT7UNkJw9aO9 i9eo0Fj2MUqxpKbdb9noRDy2CnHWf7EIYZ1gznXPdwzSN4YCjV5d+Q9xtBaowT0j HPERs1ZuytCNNJTmhyqZ8q6uzMLoht4IqH/FBfpvgaeC5tBTnTT0rD5A/olXeimk kX4LxlEx5RAvpGB2zZVRGr6LobD9rVK91xuHYNIxxxfEGE8tCCWjp0+3ksri9SXx VHWBnbM9YaL32u3hxm8sYB/Yb8WSBavJCWJJqRStVRHM1koZlJmXNx2BX4vPo6iW RFEIPQsFZRLrtnCAiEhyT8bC2s/Njlu6ly9gtJZWSV46Q3ZjBL4q9sHKqZQ= -----END CERTIFICATE-----""") class X509ExtTests(TestCase): """ Tests for :py:class:`OpenSSL.crypto.X509Extension`. """ def setUp(self): """ Create a new private key and start a certificate request (for a test method to finish in one way or another). """ super(X509ExtTests, self).setUp() # Basic setup stuff to generate a certificate self.pkey = PKey() self.pkey.generate_key(TYPE_RSA, 384) self.req = X509Req() self.req.set_pubkey(self.pkey) # Authority good you have. self.req.get_subject().commonName = "Yoda root CA" self.x509 = X509() self.subject = self.x509.get_subject() self.subject.commonName = self.req.get_subject().commonName self.x509.set_issuer(self.subject) self.x509.set_pubkey(self.pkey) now = b(datetime.now().strftime("%Y%m%d%H%M%SZ")) expire = b((datetime.now() + timedelta(days=100)).strftime("%Y%m%d%H%M%SZ")) self.x509.set_notBefore(now) self.x509.set_notAfter(expire) def tearDown(self): """ Forget all of the pyOpenSSL objects so they can be garbage collected, their memory released, and not interfere with the leak detection code. """ self.pkey = self.req = self.x509 = self.subject = None super(X509ExtTests, self).tearDown() def test_str(self): """ The string representation of :py:class:`X509Extension` instances as returned by :py:data:`str` includes stuff. """ # This isn't necessarily the best string representation. Perhaps it # will be changed/improved in the future. self.assertEquals( str(X509Extension(b('basicConstraints'), True, b('CA:false'))), 'CA:FALSE') def test_type(self): """ :py:class:`X509Extension` and :py:class:`X509ExtensionType` refer to the same type object and can be used to create instances of that type. """ self.assertIdentical(X509Extension, X509ExtensionType) self.assertConsistentType( X509Extension, 'X509Extension', b('basicConstraints'), True, b('CA:true')) def test_construction(self): """ :py:class:`X509Extension` accepts an extension type name, a critical flag, and an extension value and returns an :py:class:`X509ExtensionType` instance. """ basic = X509Extension(b('basicConstraints'), True, b('CA:true')) self.assertTrue( isinstance(basic, X509ExtensionType), "%r is of type %r, should be %r" % ( basic, type(basic), X509ExtensionType)) comment = X509Extension( b('nsComment'), False, b('pyOpenSSL unit test')) self.assertTrue( isinstance(comment, X509ExtensionType), "%r is of type %r, should be %r" % ( comment, type(comment), X509ExtensionType)) def test_invalid_extension(self): """ :py:class:`X509Extension` raises something if it is passed a bad extension name or value. """ self.assertRaises( Error, X509Extension, b('thisIsMadeUp'), False, b('hi')) self.assertRaises( Error, X509Extension, b('basicConstraints'), False, b('blah blah')) # Exercise a weird one (an extension which uses the r2i method). This # exercises the codepath that requires a non-NULL ctx to be passed to # X509V3_EXT_nconf. It can't work now because we provide no # configuration database. It might be made to work in the future. self.assertRaises( Error, X509Extension, b('proxyCertInfo'), True, b('language:id-ppl-anyLanguage,pathlen:1,policy:text:AB')) def test_get_critical(self): """ :py:meth:`X509ExtensionType.get_critical` returns the value of the extension's critical flag. """ ext = X509Extension(b('basicConstraints'), True, b('CA:true')) self.assertTrue(ext.get_critical()) ext = X509Extension(b('basicConstraints'), False, b('CA:true')) self.assertFalse(ext.get_critical()) def test_get_short_name(self): """ :py:meth:`X509ExtensionType.get_short_name` returns a string giving the short type name of the extension. """ ext = X509Extension(b('basicConstraints'), True, b('CA:true')) self.assertEqual(ext.get_short_name(), b('basicConstraints')) ext = X509Extension(b('nsComment'), True, b('foo bar')) self.assertEqual(ext.get_short_name(), b('nsComment')) def test_get_data(self): """ :py:meth:`X509Extension.get_data` returns a string giving the data of the extension. """ ext = X509Extension(b('basicConstraints'), True, b('CA:true')) # Expect to get back the DER encoded form of CA:true. self.assertEqual(ext.get_data(), b('0\x03\x01\x01\xff')) def test_get_data_wrong_args(self): """ :py:meth:`X509Extension.get_data` raises :py:exc:`TypeError` if passed any arguments. """ ext = X509Extension(b('basicConstraints'), True, b('CA:true')) self.assertRaises(TypeError, ext.get_data, None) self.assertRaises(TypeError, ext.get_data, "foo") self.assertRaises(TypeError, ext.get_data, 7) def test_unused_subject(self): """ The :py:data:`subject` parameter to :py:class:`X509Extension` may be provided for an extension which does not use it and is ignored in this case. """ ext1 = X509Extension( b('basicConstraints'), False, b('CA:TRUE'), subject=self.x509) self.x509.add_extensions([ext1]) self.x509.sign(self.pkey, 'sha1') # This is a little lame. Can we think of a better way? text = dump_certificate(FILETYPE_TEXT, self.x509) self.assertTrue(b('X509v3 Basic Constraints:') in text) self.assertTrue(b('CA:TRUE') in text) def test_subject(self): """ If an extension requires a subject, the :py:data:`subject` parameter to :py:class:`X509Extension` provides its value. """ ext3 = X509Extension( b('subjectKeyIdentifier'), False, b('hash'), subject=self.x509) self.x509.add_extensions([ext3]) self.x509.sign(self.pkey, 'sha1') text = dump_certificate(FILETYPE_TEXT, self.x509) self.assertTrue(b('X509v3 Subject Key Identifier:') in text) def test_missing_subject(self): """ If an extension requires a subject and the :py:data:`subject` parameter is given no value, something happens. """ self.assertRaises( Error, X509Extension, b('subjectKeyIdentifier'), False, b('hash')) def test_invalid_subject(self): """ If the :py:data:`subject` parameter is given a value which is not an :py:class:`X509` instance, :py:exc:`TypeError` is raised. """ for badObj in [True, object(), "hello", [], self]: self.assertRaises( TypeError, X509Extension, 'basicConstraints', False, 'CA:TRUE', subject=badObj) def test_unused_issuer(self): """ The :py:data:`issuer` parameter to :py:class:`X509Extension` may be provided for an extension which does not use it and is ignored in this case. """ ext1 = X509Extension( b('basicConstraints'), False, b('CA:TRUE'), issuer=self.x509) self.x509.add_extensions([ext1]) self.x509.sign(self.pkey, 'sha1') text = dump_certificate(FILETYPE_TEXT, self.x509) self.assertTrue(b('X509v3 Basic Constraints:') in text) self.assertTrue(b('CA:TRUE') in text) def test_issuer(self): """ If an extension requires an issuer, the :py:data:`issuer` parameter to :py:class:`X509Extension` provides its value. """ ext2 = X509Extension( b('authorityKeyIdentifier'), False, b('issuer:always'), issuer=self.x509) self.x509.add_extensions([ext2]) self.x509.sign(self.pkey, 'sha1') text = dump_certificate(FILETYPE_TEXT, self.x509) self.assertTrue(b('X509v3 Authority Key Identifier:') in text) self.assertTrue(b('DirName:/CN=Yoda root CA') in text) def test_missing_issuer(self): """ If an extension requires an issue and the :py:data:`issuer` parameter is given no value, something happens. """ self.assertRaises( Error, X509Extension, b('authorityKeyIdentifier'), False, b('keyid:always,issuer:always')) def test_invalid_issuer(self): """ If the :py:data:`issuer` parameter is given a value which is not an :py:class:`X509` instance, :py:exc:`TypeError` is raised. """ for badObj in [True, object(), "hello", [], self]: self.assertRaises( TypeError, X509Extension, 'authorityKeyIdentifier', False, 'keyid:always,issuer:always', issuer=badObj) class PKeyTests(TestCase): """ Unit tests for :py:class:`OpenSSL.crypto.PKey`. """ def test_type(self): """ :py:class:`PKey` and :py:class:`PKeyType` refer to the same type object and can be used to create instances of that type. """ self.assertIdentical(PKey, PKeyType) self.assertConsistentType(PKey, 'PKey') def test_construction(self): """ :py:class:`PKey` takes no arguments and returns a new :py:class:`PKey` instance. """ self.assertRaises(TypeError, PKey, None) key = PKey() self.assertTrue( isinstance(key, PKeyType), "%r is of type %r, should be %r" % (key, type(key), PKeyType)) def test_pregeneration(self): """ :py:attr:`PKeyType.bits` and :py:attr:`PKeyType.type` return :py:data:`0` before the key is generated. :py:attr:`PKeyType.check` raises :py:exc:`TypeError` before the key is generated. """ key = PKey() self.assertEqual(key.type(), 0) self.assertEqual(key.bits(), 0) self.assertRaises(TypeError, key.check) def test_failedGeneration(self): """ :py:meth:`PKeyType.generate_key` takes two arguments, the first giving the key type as one of :py:data:`TYPE_RSA` or :py:data:`TYPE_DSA` and the second giving the number of bits to generate. If an invalid type is specified or generation fails, :py:exc:`Error` is raised. If an invalid number of bits is specified, :py:exc:`ValueError` or :py:exc:`Error` is raised. """ key = PKey() self.assertRaises(TypeError, key.generate_key) self.assertRaises(TypeError, key.generate_key, 1, 2, 3) self.assertRaises(TypeError, key.generate_key, "foo", "bar") self.assertRaises(Error, key.generate_key, -1, 0) self.assertRaises(ValueError, key.generate_key, TYPE_RSA, -1) self.assertRaises(ValueError, key.generate_key, TYPE_RSA, 0) # XXX RSA generation for small values of bits is fairly buggy in a wide # range of OpenSSL versions. I need to figure out what the safe lower # bound for a reasonable number of OpenSSL versions is and explicitly # check for that in the wrapper. The failure behavior is typically an # infinite loop inside OpenSSL. # self.assertRaises(Error, key.generate_key, TYPE_RSA, 2) # XXX DSA generation seems happy with any number of bits. The DSS # says bits must be between 512 and 1024 inclusive. OpenSSL's DSA # generator doesn't seem to care about the upper limit at all. For # the lower limit, it uses 512 if anything smaller is specified. # So, it doesn't seem possible to make generate_key fail for # TYPE_DSA with a bits argument which is at least an int. # self.assertRaises(Error, key.generate_key, TYPE_DSA, -7) def test_rsaGeneration(self): """ :py:meth:`PKeyType.generate_key` generates an RSA key when passed :py:data:`TYPE_RSA` as a type and a reasonable number of bits. """ bits = 128 key = PKey() key.generate_key(TYPE_RSA, bits) self.assertEqual(key.type(), TYPE_RSA) self.assertEqual(key.bits(), bits) self.assertTrue(key.check()) def test_dsaGeneration(self): """ :py:meth:`PKeyType.generate_key` generates a DSA key when passed :py:data:`TYPE_DSA` as a type and a reasonable number of bits. """ # 512 is a magic number. The DSS (Digital Signature Standard) # allows a minimum of 512 bits for DSA. DSA_generate_parameters # will silently promote any value below 512 to 512. bits = 512 key = PKey() key.generate_key(TYPE_DSA, bits) # self.assertEqual(key.type(), TYPE_DSA) # self.assertEqual(key.bits(), bits) # self.assertRaises(TypeError, key.check) def test_regeneration(self): """ :py:meth:`PKeyType.generate_key` can be called multiple times on the same key to generate new keys. """ key = PKey() for type, bits in [(TYPE_RSA, 512), (TYPE_DSA, 576)]: key.generate_key(type, bits) self.assertEqual(key.type(), type) self.assertEqual(key.bits(), bits) def test_inconsistentKey(self): """ :py:`PKeyType.check` returns :py:exc:`Error` if the key is not consistent. """ key = load_privatekey(FILETYPE_PEM, inconsistentPrivateKeyPEM) self.assertRaises(Error, key.check) def test_check_wrong_args(self): """ :py:meth:`PKeyType.check` raises :py:exc:`TypeError` if called with any arguments. """ self.assertRaises(TypeError, PKey().check, None) self.assertRaises(TypeError, PKey().check, object()) self.assertRaises(TypeError, PKey().check, 1) def test_check_public_key(self): """ :py:meth:`PKeyType.check` raises :py:exc:`TypeError` if only the public part of the key is available. """ # A trick to get a public-only key key = PKey() key.generate_key(TYPE_RSA, 512) cert = X509() cert.set_pubkey(key) pub = cert.get_pubkey() self.assertRaises(TypeError, pub.check) class X509NameTests(TestCase): """ Unit tests for :py:class:`OpenSSL.crypto.X509Name`. """ def _x509name(self, **attrs): # XXX There's no other way to get a new X509Name yet. name = X509().get_subject() attrs = list(attrs.items()) # Make the order stable - order matters! def key(attr): return attr[1] attrs.sort(key=key) for k, v in attrs: setattr(name, k, v) return name def test_type(self): """ The type of X509Name objects is :py:class:`X509NameType`. """ self.assertIdentical(X509Name, X509NameType) self.assertEqual(X509NameType.__name__, 'X509Name') self.assertTrue(isinstance(X509NameType, type)) name = self._x509name() self.assertTrue( isinstance(name, X509NameType), "%r is of type %r, should be %r" % ( name, type(name), X509NameType)) def test_onlyStringAttributes(self): """ Attempting to set a non-:py:data:`str` attribute name on an :py:class:`X509NameType` instance causes :py:exc:`TypeError` to be raised. """ name = self._x509name() # Beyond these cases, you may also think that unicode should be # rejected. Sorry, you're wrong. unicode is automatically converted to # str outside of the control of X509Name, so there's no way to reject # it. # Also, this used to test str subclasses, but that test is less relevant # now that the implementation is in Python instead of C. Also PyPy # automatically converts str subclasses to str when they are passed to # setattr, so we can't test it on PyPy. Apparently CPython does this # sometimes as well. self.assertRaises(TypeError, setattr, name, None, "hello") self.assertRaises(TypeError, setattr, name, 30, "hello") def test_setInvalidAttribute(self): """ Attempting to set any attribute name on an :py:class:`X509NameType` instance for which no corresponding NID is defined causes :py:exc:`AttributeError` to be raised. """ name = self._x509name() self.assertRaises(AttributeError, setattr, name, "no such thing", None) def test_attributes(self): """ :py:class:`X509NameType` instances have attributes for each standard (?) X509Name field. """ name = self._x509name() name.commonName = "foo" self.assertEqual(name.commonName, "foo") self.assertEqual(name.CN, "foo") name.CN = "baz" self.assertEqual(name.commonName, "baz") self.assertEqual(name.CN, "baz") name.commonName = "bar" self.assertEqual(name.commonName, "bar") self.assertEqual(name.CN, "bar") name.CN = "quux" self.assertEqual(name.commonName, "quux") self.assertEqual(name.CN, "quux") def test_copy(self): """ :py:class:`X509Name` creates a new :py:class:`X509NameType` instance with all the same attributes as an existing :py:class:`X509NameType` instance when called with one. """ name = self._x509name(commonName="foo", emailAddress="bar@example.com") copy = X509Name(name) self.assertEqual(copy.commonName, "foo") self.assertEqual(copy.emailAddress, "bar@example.com") # Mutate the copy and ensure the original is unmodified. copy.commonName = "baz" self.assertEqual(name.commonName, "foo") # Mutate the original and ensure the copy is unmodified. name.emailAddress = "quux@example.com" self.assertEqual(copy.emailAddress, "bar@example.com") def test_repr(self): """ :py:func:`repr` passed an :py:class:`X509NameType` instance should return a string containing a description of the type and the NIDs which have been set on it. """ name = self._x509name(commonName="foo", emailAddress="bar") self.assertEqual( repr(name), "") def test_comparison(self): """ :py:class:`X509NameType` instances should compare based on their NIDs. """ def _equality(a, b, assertTrue, assertFalse): assertTrue(a == b, "(%r == %r) --> False" % (a, b)) assertFalse(a != b) assertTrue(b == a) assertFalse(b != a) def assertEqual(a, b): _equality(a, b, self.assertTrue, self.assertFalse) # Instances compare equal to themselves. name = self._x509name() assertEqual(name, name) # Empty instances should compare equal to each other. assertEqual(self._x509name(), self._x509name()) # Instances with equal NIDs should compare equal to each other. assertEqual(self._x509name(commonName="foo"), self._x509name(commonName="foo")) # Instance with equal NIDs set using different aliases should compare # equal to each other. assertEqual(self._x509name(commonName="foo"), self._x509name(CN="foo")) # Instances with more than one NID with the same values should compare # equal to each other. assertEqual(self._x509name(CN="foo", organizationalUnitName="bar"), self._x509name(commonName="foo", OU="bar")) def assertNotEqual(a, b): _equality(a, b, self.assertFalse, self.assertTrue) # Instances with different values for the same NID should not compare # equal to each other. assertNotEqual(self._x509name(CN="foo"), self._x509name(CN="bar")) # Instances with different NIDs should not compare equal to each other. assertNotEqual(self._x509name(CN="foo"), self._x509name(OU="foo")) def _inequality(a, b, assertTrue, assertFalse): assertTrue(a < b) assertTrue(a <= b) assertTrue(b > a) assertTrue(b >= a) assertFalse(a > b) assertFalse(a >= b) assertFalse(b < a) assertFalse(b <= a) def assertLessThan(a, b): _inequality(a, b, self.assertTrue, self.assertFalse) # An X509Name with a NID with a value which sorts less than the value # of the same NID on another X509Name compares less than the other # X509Name. assertLessThan(self._x509name(CN="abc"), self._x509name(CN="def")) def assertGreaterThan(a, b): _inequality(a, b, self.assertFalse, self.assertTrue) # An X509Name with a NID with a value which sorts greater than the # value of the same NID on another X509Name compares greater than the # other X509Name. assertGreaterThan(self._x509name(CN="def"), self._x509name(CN="abc")) def test_hash(self): """ :py:meth:`X509Name.hash` returns an integer hash based on the value of the name. """ a = self._x509name(CN="foo") b = self._x509name(CN="foo") self.assertEqual(a.hash(), b.hash()) a.CN = "bar" self.assertNotEqual(a.hash(), b.hash()) def test_der(self): """ :py:meth:`X509Name.der` returns the DER encoded form of the name. """ a = self._x509name(CN="foo", C="US") self.assertEqual( a.der(), b('0\x1b1\x0b0\t\x06\x03U\x04\x06\x13\x02US' '1\x0c0\n\x06\x03U\x04\x03\x13\x03foo')) def test_get_components(self): """ :py:meth:`X509Name.get_components` returns a :py:data:`list` of two-tuples of :py:data:`str` giving the NIDs and associated values which make up the name. """ a = self._x509name() self.assertEqual(a.get_components(), []) a.CN = "foo" self.assertEqual(a.get_components(), [(b("CN"), b("foo"))]) a.organizationalUnitName = "bar" self.assertEqual( a.get_components(), [(b("CN"), b("foo")), (b("OU"), b("bar"))]) def test_load_nul_byte_attribute(self): """ An :py:class:`OpenSSL.crypto.X509Name` from an :py:class:`OpenSSL.crypto.X509` instance loaded from a file can have a NUL byte in the value of one of its attributes. """ cert = load_certificate(FILETYPE_PEM, nulbyteSubjectAltNamePEM) subject = cert.get_subject() self.assertEqual( "null.python.org\x00example.org", subject.commonName) def test_setAttributeFailure(self): """ If the value of an attribute cannot be set for some reason then :py:class:`OpenSSL.crypto.Error` is raised. """ name = self._x509name() # This value is too long self.assertRaises(Error, setattr, name, "O", b"x" * 512) class _PKeyInteractionTestsMixin: """ Tests which involve another thing and a PKey. """ def signable(self): """ Return something with a :py:meth:`set_pubkey`, :py:meth:`set_pubkey`, and :py:meth:`sign` method. """ raise NotImplementedError() def test_signWithUngenerated(self): """ :py:meth:`X509Req.sign` raises :py:exc:`ValueError` when pass a :py:class:`PKey` with no parts. """ request = self.signable() key = PKey() self.assertRaises(ValueError, request.sign, key, GOOD_DIGEST) def test_signWithPublicKey(self): """ :py:meth:`X509Req.sign` raises :py:exc:`ValueError` when pass a :py:class:`PKey` with no private part as the signing key. """ request = self.signable() key = PKey() key.generate_key(TYPE_RSA, 512) request.set_pubkey(key) pub = request.get_pubkey() self.assertRaises(ValueError, request.sign, pub, GOOD_DIGEST) def test_signWithUnknownDigest(self): """ :py:meth:`X509Req.sign` raises :py:exc:`ValueError` when passed a digest name which is not known. """ request = self.signable() key = PKey() key.generate_key(TYPE_RSA, 512) self.assertRaises(ValueError, request.sign, key, BAD_DIGEST) def test_sign(self): """ :py:meth:`X509Req.sign` succeeds when passed a private key object and a valid digest function. :py:meth:`X509Req.verify` can be used to check the signature. """ request = self.signable() key = PKey() key.generate_key(TYPE_RSA, 512) request.set_pubkey(key) request.sign(key, GOOD_DIGEST) # If the type has a verify method, cover that too. if getattr(request, 'verify', None) is not None: pub = request.get_pubkey() self.assertTrue(request.verify(pub)) # Make another key that won't verify. key = PKey() key.generate_key(TYPE_RSA, 512) self.assertRaises(Error, request.verify, key) class X509ReqTests(TestCase, _PKeyInteractionTestsMixin): """ Tests for :py:class:`OpenSSL.crypto.X509Req`. """ def signable(self): """ Create and return a new :py:class:`X509Req`. """ return X509Req() def test_type(self): """ :py:obj:`X509Req` and :py:obj:`X509ReqType` refer to the same type object and can be used to create instances of that type. """ self.assertIdentical(X509Req, X509ReqType) self.assertConsistentType(X509Req, 'X509Req') def test_construction(self): """ :py:obj:`X509Req` takes no arguments and returns an :py:obj:`X509ReqType` instance. """ request = X509Req() self.assertTrue( isinstance(request, X509ReqType), "%r is of type %r, should be %r" % (request, type(request), X509ReqType)) def test_version(self): """ :py:obj:`X509ReqType.set_version` sets the X.509 version of the certificate request. :py:obj:`X509ReqType.get_version` returns the X.509 version of the certificate request. The initial value of the version is 0. """ request = X509Req() self.assertEqual(request.get_version(), 0) request.set_version(1) self.assertEqual(request.get_version(), 1) request.set_version(3) self.assertEqual(request.get_version(), 3) def test_version_wrong_args(self): """ :py:obj:`X509ReqType.set_version` raises :py:obj:`TypeError` if called with the wrong number of arguments or with a non-:py:obj:`int` argument. :py:obj:`X509ReqType.get_version` raises :py:obj:`TypeError` if called with any arguments. """ request = X509Req() self.assertRaises(TypeError, request.set_version) self.assertRaises(TypeError, request.set_version, "foo") self.assertRaises(TypeError, request.set_version, 1, 2) self.assertRaises(TypeError, request.get_version, None) def test_get_subject(self): """ :py:obj:`X509ReqType.get_subject` returns an :py:obj:`X509Name` for the subject of the request and which is valid even after the request object is otherwise dead. """ request = X509Req() subject = request.get_subject() self.assertTrue( isinstance(subject, X509NameType), "%r is of type %r, should be %r" % (subject, type(subject), X509NameType)) subject.commonName = "foo" self.assertEqual(request.get_subject().commonName, "foo") del request subject.commonName = "bar" self.assertEqual(subject.commonName, "bar") def test_get_subject_wrong_args(self): """ :py:obj:`X509ReqType.get_subject` raises :py:obj:`TypeError` if called with any arguments. """ request = X509Req() self.assertRaises(TypeError, request.get_subject, None) def test_add_extensions(self): """ :py:obj:`X509Req.add_extensions` accepts a :py:obj:`list` of :py:obj:`X509Extension` instances and adds them to the X509 request. """ request = X509Req() request.add_extensions([ X509Extension(b('basicConstraints'), True, b('CA:false'))]) exts = request.get_extensions() self.assertEqual(len(exts), 1) self.assertEqual(exts[0].get_short_name(), b('basicConstraints')) self.assertEqual(exts[0].get_critical(), 1) self.assertEqual(exts[0].get_data(), b('0\x00')) def test_get_extensions(self): """ :py:obj:`X509Req.get_extensions` returns a :py:obj:`list` of extensions added to this X509 request. """ request = X509Req() exts = request.get_extensions() self.assertEqual(exts, []) request.add_extensions([ X509Extension(b('basicConstraints'), True, b('CA:true')), X509Extension(b('keyUsage'), False, b('digitalSignature'))]) exts = request.get_extensions() self.assertEqual(len(exts), 2) self.assertEqual(exts[0].get_short_name(), b('basicConstraints')) self.assertEqual(exts[0].get_critical(), 1) self.assertEqual(exts[0].get_data(), b('0\x03\x01\x01\xff')) self.assertEqual(exts[1].get_short_name(), b('keyUsage')) self.assertEqual(exts[1].get_critical(), 0) self.assertEqual(exts[1].get_data(), b('\x03\x02\x07\x80')) def test_add_extensions_wrong_args(self): """ :py:obj:`X509Req.add_extensions` raises :py:obj:`TypeError` if called with the wrong number of arguments or with a non-:py:obj:`list`. Or it raises :py:obj:`ValueError` if called with a :py:obj:`list` containing objects other than :py:obj:`X509Extension` instances. """ request = X509Req() self.assertRaises(TypeError, request.add_extensions) self.assertRaises(TypeError, request.add_extensions, object()) self.assertRaises(ValueError, request.add_extensions, [object()]) self.assertRaises(TypeError, request.add_extensions, [], None) def test_verify_wrong_args(self): """ :py:obj:`X509Req.verify` raises :py:obj:`TypeError` if called with zero arguments or more than one argument or if passed anything other than a :py:obj:`PKey` instance as its single argument. """ request = X509Req() self.assertRaises(TypeError, request.verify) self.assertRaises(TypeError, request.verify, object()) self.assertRaises(TypeError, request.verify, PKey(), object()) def test_verify_uninitialized_key(self): """ :py:obj:`X509Req.verify` raises :py:obj:`OpenSSL.crypto.Error` if called with a :py:obj:`OpenSSL.crypto.PKey` which contains no key data. """ request = X509Req() pkey = PKey() self.assertRaises(Error, request.verify, pkey) def test_verify_wrong_key(self): """ :py:obj:`X509Req.verify` raises :py:obj:`OpenSSL.crypto.Error` if called with a :py:obj:`OpenSSL.crypto.PKey` which does not represent the public part of the key which signed the request. """ request = X509Req() pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) request.sign(pkey, GOOD_DIGEST) another_pkey = load_privatekey(FILETYPE_PEM, client_key_pem) self.assertRaises(Error, request.verify, another_pkey) def test_verify_success(self): """ :py:obj:`X509Req.verify` returns :py:obj:`True` if called with a :py:obj:`OpenSSL.crypto.PKey` which represents the public part of the key which signed the request. """ request = X509Req() pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) request.sign(pkey, GOOD_DIGEST) self.assertEqual(True, request.verify(pkey)) class X509Tests(TestCase, _PKeyInteractionTestsMixin): """ Tests for :py:obj:`OpenSSL.crypto.X509`. """ pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM extpem = """ -----BEGIN CERTIFICATE----- MIIC3jCCAkegAwIBAgIJAJHFjlcCgnQzMA0GCSqGSIb3DQEBBQUAMEcxCzAJBgNV BAYTAlNFMRUwEwYDVQQIEwxXZXN0ZXJib3R0b20xEjAQBgNVBAoTCUNhdGFsb2dp eDENMAsGA1UEAxMEUm9vdDAeFw0wODA0MjIxNDQ1MzhaFw0wOTA0MjIxNDQ1Mzha MFQxCzAJBgNVBAYTAlNFMQswCQYDVQQIEwJXQjEUMBIGA1UEChMLT3Blbk1ldGFk aXIxIjAgBgNVBAMTGW5vZGUxLm9tMi5vcGVubWV0YWRpci5vcmcwgZ8wDQYJKoZI hvcNAQEBBQADgY0AMIGJAoGBAPIcQMrwbk2nESF/0JKibj9i1x95XYAOwP+LarwT Op4EQbdlI9SY+uqYqlERhF19w7CS+S6oyqx0DRZSk4Y9dZ9j9/xgm2u/f136YS1u zgYFPvfUs6PqYLPSM8Bw+SjJ+7+2+TN+Tkiof9WP1cMjodQwOmdsiRbR0/J7+b1B hec1AgMBAAGjgcQwgcEwCQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNT TCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFIdHsBcMVVMbAO7j6NCj 03HgLnHaMB8GA1UdIwQYMBaAFL2h9Bf9Mre4vTdOiHTGAt7BRY/8MEYGA1UdEQQ/ MD2CDSouZXhhbXBsZS5vcmeCESoub20yLmV4bWFwbGUuY29thwSC7wgKgRNvbTJA b3Blbm1ldGFkaXIub3JnMA0GCSqGSIb3DQEBBQUAA4GBALd7WdXkp2KvZ7/PuWZA MPlIxyjS+Ly11+BNE0xGQRp9Wz+2lABtpgNqssvU156+HkKd02rGheb2tj7MX9hG uZzbwDAZzJPjzDQDD7d3cWsrVcfIdqVU7epHqIadnOF+X0ghJ39pAm6VVadnSXCt WpOdIpB8KksUTCzV591Nr1wd -----END CERTIFICATE----- """ def signable(self): """ Create and return a new :py:obj:`X509`. """ return X509() def test_type(self): """ :py:obj:`X509` and :py:obj:`X509Type` refer to the same type object and can be used to create instances of that type. """ self.assertIdentical(X509, X509Type) self.assertConsistentType(X509, 'X509') def test_construction(self): """ :py:obj:`X509` takes no arguments and returns an instance of :py:obj:`X509Type`. """ certificate = X509() self.assertTrue( isinstance(certificate, X509Type), "%r is of type %r, should be %r" % (certificate, type(certificate), X509Type)) self.assertEqual(type(X509Type).__name__, 'type') self.assertEqual(type(certificate).__name__, 'X509') self.assertEqual(type(certificate), X509Type) self.assertEqual(type(certificate), X509) def test_get_version_wrong_args(self): """ :py:obj:`X509.get_version` raises :py:obj:`TypeError` if invoked with any arguments. """ cert = X509() self.assertRaises(TypeError, cert.get_version, None) def test_set_version_wrong_args(self): """ :py:obj:`X509.set_version` raises :py:obj:`TypeError` if invoked with the wrong number of arguments or an argument not of type :py:obj:`int`. """ cert = X509() self.assertRaises(TypeError, cert.set_version) self.assertRaises(TypeError, cert.set_version, None) self.assertRaises(TypeError, cert.set_version, 1, None) def test_version(self): """ :py:obj:`X509.set_version` sets the certificate version number. :py:obj:`X509.get_version` retrieves it. """ cert = X509() cert.set_version(1234) self.assertEquals(cert.get_version(), 1234) def test_get_serial_number_wrong_args(self): """ :py:obj:`X509.get_serial_number` raises :py:obj:`TypeError` if invoked with any arguments. """ cert = X509() self.assertRaises(TypeError, cert.get_serial_number, None) def test_serial_number(self): """ The serial number of an :py:obj:`X509Type` can be retrieved and modified with :py:obj:`X509Type.get_serial_number` and :py:obj:`X509Type.set_serial_number`. """ certificate = X509() self.assertRaises(TypeError, certificate.set_serial_number) self.assertRaises(TypeError, certificate.set_serial_number, 1, 2) self.assertRaises(TypeError, certificate.set_serial_number, "1") self.assertRaises(TypeError, certificate.set_serial_number, 5.5) self.assertEqual(certificate.get_serial_number(), 0) certificate.set_serial_number(1) self.assertEqual(certificate.get_serial_number(), 1) certificate.set_serial_number(2 ** 32 + 1) self.assertEqual(certificate.get_serial_number(), 2 ** 32 + 1) certificate.set_serial_number(2 ** 64 + 1) self.assertEqual(certificate.get_serial_number(), 2 ** 64 + 1) certificate.set_serial_number(2 ** 128 + 1) self.assertEqual(certificate.get_serial_number(), 2 ** 128 + 1) def _setBoundTest(self, which): """ :py:obj:`X509Type.set_notBefore` takes a string in the format of an ASN1 GENERALIZEDTIME and sets the beginning of the certificate's validity period to it. """ certificate = X509() set = getattr(certificate, 'set_not' + which) get = getattr(certificate, 'get_not' + which) # Starts with no value. self.assertEqual(get(), None) # GMT (Or is it UTC?) -exarkun when = b("20040203040506Z") set(when) self.assertEqual(get(), when) # A plus two hours and thirty minutes offset when = b("20040203040506+0530") set(when) self.assertEqual(get(), when) # A minus one hour fifteen minutes offset when = b("20040203040506-0115") set(when) self.assertEqual(get(), when) # An invalid string results in a ValueError self.assertRaises(ValueError, set, b("foo bar")) # The wrong number of arguments results in a TypeError. self.assertRaises(TypeError, set) self.assertRaises(TypeError, set, b("20040203040506Z"), b("20040203040506Z")) self.assertRaises(TypeError, get, b("foo bar")) # XXX ASN1_TIME (not GENERALIZEDTIME) def test_set_notBefore(self): """ :py:obj:`X509Type.set_notBefore` takes a string in the format of an ASN1 GENERALIZEDTIME and sets the beginning of the certificate's validity period to it. """ self._setBoundTest("Before") def test_set_notAfter(self): """ :py:obj:`X509Type.set_notAfter` takes a string in the format of an ASN1 GENERALIZEDTIME and sets the end of the certificate's validity period to it. """ self._setBoundTest("After") def test_get_notBefore(self): """ :py:obj:`X509Type.get_notBefore` returns a string in the format of an ASN1 GENERALIZEDTIME even for certificates which store it as UTCTIME internally. """ cert = load_certificate(FILETYPE_PEM, self.pemData) self.assertEqual(cert.get_notBefore(), b("20090325123658Z")) def test_get_notAfter(self): """ :py:obj:`X509Type.get_notAfter` returns a string in the format of an ASN1 GENERALIZEDTIME even for certificates which store it as UTCTIME internally. """ cert = load_certificate(FILETYPE_PEM, self.pemData) self.assertEqual(cert.get_notAfter(), b("20170611123658Z")) def test_gmtime_adj_notBefore_wrong_args(self): """ :py:obj:`X509Type.gmtime_adj_notBefore` raises :py:obj:`TypeError` if called with the wrong number of arguments or a non-:py:obj:`int` argument. """ cert = X509() self.assertRaises(TypeError, cert.gmtime_adj_notBefore) self.assertRaises(TypeError, cert.gmtime_adj_notBefore, None) self.assertRaises(TypeError, cert.gmtime_adj_notBefore, 123, None) def test_gmtime_adj_notBefore(self): """ :py:obj:`X509Type.gmtime_adj_notBefore` changes the not-before timestamp to be the current time plus the number of seconds passed in. """ cert = load_certificate(FILETYPE_PEM, self.pemData) now = datetime.utcnow() + timedelta(seconds=100) cert.gmtime_adj_notBefore(100) self.assertEqual(cert.get_notBefore(), b(now.strftime("%Y%m%d%H%M%SZ"))) def test_gmtime_adj_notAfter_wrong_args(self): """ :py:obj:`X509Type.gmtime_adj_notAfter` raises :py:obj:`TypeError` if called with the wrong number of arguments or a non-:py:obj:`int` argument. """ cert = X509() self.assertRaises(TypeError, cert.gmtime_adj_notAfter) self.assertRaises(TypeError, cert.gmtime_adj_notAfter, None) self.assertRaises(TypeError, cert.gmtime_adj_notAfter, 123, None) def test_gmtime_adj_notAfter(self): """ :py:obj:`X509Type.gmtime_adj_notAfter` changes the not-after timestamp to be the current time plus the number of seconds passed in. """ cert = load_certificate(FILETYPE_PEM, self.pemData) now = datetime.utcnow() + timedelta(seconds=100) cert.gmtime_adj_notAfter(100) self.assertEqual(cert.get_notAfter(), b(now.strftime("%Y%m%d%H%M%SZ"))) def test_has_expired_wrong_args(self): """ :py:obj:`X509Type.has_expired` raises :py:obj:`TypeError` if called with any arguments. """ cert = X509() self.assertRaises(TypeError, cert.has_expired, None) def test_has_expired(self): """ :py:obj:`X509Type.has_expired` returns :py:obj:`True` if the certificate's not-after time is in the past. """ cert = X509() cert.gmtime_adj_notAfter(-1) self.assertTrue(cert.has_expired()) def test_has_not_expired(self): """ :py:obj:`X509Type.has_expired` returns :py:obj:`False` if the certificate's not-after time is in the future. """ cert = X509() cert.gmtime_adj_notAfter(2) self.assertFalse(cert.has_expired()) def test_digest(self): """ :py:obj:`X509.digest` returns a string giving ":"-separated hex-encoded words of the digest of the certificate. """ cert = X509() self.assertEqual( # This is MD5 instead of GOOD_DIGEST because the digest algorithm # actually matters to the assertion (ie, another arbitrary, good # digest will not product the same digest). cert.digest("MD5"), b("A8:EB:07:F8:53:25:0A:F2:56:05:C5:A5:C4:C4:C7:15")) def _extcert(self, pkey, extensions): cert = X509() cert.set_pubkey(pkey) cert.get_subject().commonName = "Unit Tests" cert.get_issuer().commonName = "Unit Tests" when = b(datetime.now().strftime("%Y%m%d%H%M%SZ")) cert.set_notBefore(when) cert.set_notAfter(when) cert.add_extensions(extensions) return load_certificate( FILETYPE_PEM, dump_certificate(FILETYPE_PEM, cert)) def test_extension_count(self): """ :py:obj:`X509.get_extension_count` returns the number of extensions that are present in the certificate. """ pkey = load_privatekey(FILETYPE_PEM, client_key_pem) ca = X509Extension(b('basicConstraints'), True, b('CA:FALSE')) key = X509Extension(b('keyUsage'), True, b('digitalSignature')) subjectAltName = X509Extension( b('subjectAltName'), True, b('DNS:example.com')) # Try a certificate with no extensions at all. c = self._extcert(pkey, []) self.assertEqual(c.get_extension_count(), 0) # And a certificate with one c = self._extcert(pkey, [ca]) self.assertEqual(c.get_extension_count(), 1) # And a certificate with several c = self._extcert(pkey, [ca, key, subjectAltName]) self.assertEqual(c.get_extension_count(), 3) def test_get_extension(self): """ :py:obj:`X509.get_extension` takes an integer and returns an :py:obj:`X509Extension` corresponding to the extension at that index. """ pkey = load_privatekey(FILETYPE_PEM, client_key_pem) ca = X509Extension(b('basicConstraints'), True, b('CA:FALSE')) key = X509Extension(b('keyUsage'), True, b('digitalSignature')) subjectAltName = X509Extension( b('subjectAltName'), False, b('DNS:example.com')) cert = self._extcert(pkey, [ca, key, subjectAltName]) ext = cert.get_extension(0) self.assertTrue(isinstance(ext, X509Extension)) self.assertTrue(ext.get_critical()) self.assertEqual(ext.get_short_name(), b('basicConstraints')) ext = cert.get_extension(1) self.assertTrue(isinstance(ext, X509Extension)) self.assertTrue(ext.get_critical()) self.assertEqual(ext.get_short_name(), b('keyUsage')) ext = cert.get_extension(2) self.assertTrue(isinstance(ext, X509Extension)) self.assertFalse(ext.get_critical()) self.assertEqual(ext.get_short_name(), b('subjectAltName')) self.assertRaises(IndexError, cert.get_extension, -1) self.assertRaises(IndexError, cert.get_extension, 4) self.assertRaises(TypeError, cert.get_extension, "hello") def test_nullbyte_subjectAltName(self): """ The fields of a `subjectAltName` extension on an X509 may contain NUL bytes and this value is reflected in the string representation of the extension object. """ cert = load_certificate(FILETYPE_PEM, nulbyteSubjectAltNamePEM) ext = cert.get_extension(3) self.assertEqual(ext.get_short_name(), b('subjectAltName')) self.assertEqual( b("DNS:altnull.python.org\x00example.com, " "email:null@python.org\x00user@example.org, " "URI:http://null.python.org\x00http://example.org, " "IP Address:192.0.2.1, IP Address:2001:DB8:0:0:0:0:0:1\n"), b(str(ext))) def test_invalid_digest_algorithm(self): """ :py:obj:`X509.digest` raises :py:obj:`ValueError` if called with an unrecognized hash algorithm. """ cert = X509() self.assertRaises(ValueError, cert.digest, BAD_DIGEST) def test_get_subject_wrong_args(self): """ :py:obj:`X509.get_subject` raises :py:obj:`TypeError` if called with any arguments. """ cert = X509() self.assertRaises(TypeError, cert.get_subject, None) def test_get_subject(self): """ :py:obj:`X509.get_subject` returns an :py:obj:`X509Name` instance. """ cert = load_certificate(FILETYPE_PEM, self.pemData) subj = cert.get_subject() self.assertTrue(isinstance(subj, X509Name)) self.assertEquals( subj.get_components(), [(b('C'), b('US')), (b('ST'), b('IL')), (b('L'), b('Chicago')), (b('O'), b('Testing')), (b('CN'), b('Testing Root CA'))]) def test_set_subject_wrong_args(self): """ :py:obj:`X509.set_subject` raises a :py:obj:`TypeError` if called with the wrong number of arguments or an argument not of type :py:obj:`X509Name`. """ cert = X509() self.assertRaises(TypeError, cert.set_subject) self.assertRaises(TypeError, cert.set_subject, None) self.assertRaises(TypeError, cert.set_subject, cert.get_subject(), None) def test_set_subject(self): """ :py:obj:`X509.set_subject` changes the subject of the certificate to the one passed in. """ cert = X509() name = cert.get_subject() name.C = 'AU' name.O = 'Unit Tests' cert.set_subject(name) self.assertEquals( cert.get_subject().get_components(), [(b('C'), b('AU')), (b('O'), b('Unit Tests'))]) def test_get_issuer_wrong_args(self): """ :py:obj:`X509.get_issuer` raises :py:obj:`TypeError` if called with any arguments. """ cert = X509() self.assertRaises(TypeError, cert.get_issuer, None) def test_get_issuer(self): """ :py:obj:`X509.get_issuer` returns an :py:obj:`X509Name` instance. """ cert = load_certificate(FILETYPE_PEM, self.pemData) subj = cert.get_issuer() self.assertTrue(isinstance(subj, X509Name)) comp = subj.get_components() self.assertEquals( comp, [(b('C'), b('US')), (b('ST'), b('IL')), (b('L'), b('Chicago')), (b('O'), b('Testing')), (b('CN'), b('Testing Root CA'))]) def test_set_issuer_wrong_args(self): """ :py:obj:`X509.set_issuer` raises a :py:obj:`TypeError` if called with the wrong number of arguments or an argument not of type :py:obj:`X509Name`. """ cert = X509() self.assertRaises(TypeError, cert.set_issuer) self.assertRaises(TypeError, cert.set_issuer, None) self.assertRaises(TypeError, cert.set_issuer, cert.get_issuer(), None) def test_set_issuer(self): """ :py:obj:`X509.set_issuer` changes the issuer of the certificate to the one passed in. """ cert = X509() name = cert.get_issuer() name.C = 'AU' name.O = 'Unit Tests' cert.set_issuer(name) self.assertEquals( cert.get_issuer().get_components(), [(b('C'), b('AU')), (b('O'), b('Unit Tests'))]) def test_get_pubkey_uninitialized(self): """ When called on a certificate with no public key, :py:obj:`X509.get_pubkey` raises :py:obj:`OpenSSL.crypto.Error`. """ cert = X509() self.assertRaises(Error, cert.get_pubkey) def test_subject_name_hash_wrong_args(self): """ :py:obj:`X509.subject_name_hash` raises :py:obj:`TypeError` if called with any arguments. """ cert = X509() self.assertRaises(TypeError, cert.subject_name_hash, None) def test_subject_name_hash(self): """ :py:obj:`X509.subject_name_hash` returns the hash of the certificate's subject name. """ cert = load_certificate(FILETYPE_PEM, self.pemData) self.assertIn( cert.subject_name_hash(), [3350047874, # OpenSSL 0.9.8, MD5 3278919224, # OpenSSL 1.0.0, SHA1 ]) def test_get_signature_algorithm(self): """ :py:obj:`X509Type.get_signature_algorithm` returns a string which means the algorithm used to sign the certificate. """ cert = load_certificate(FILETYPE_PEM, self.pemData) self.assertEqual( b("sha1WithRSAEncryption"), cert.get_signature_algorithm()) def test_get_undefined_signature_algorithm(self): """ :py:obj:`X509Type.get_signature_algorithm` raises :py:obj:`ValueError` if the signature algorithm is undefined or unknown. """ # This certificate has been modified to indicate a bogus OID in the # signature algorithm field so that OpenSSL does not recognize it. certPEM = b("""\ -----BEGIN CERTIFICATE----- MIIC/zCCAmigAwIBAgIBATAGBgJ8BQUAMHsxCzAJBgNVBAYTAlNHMREwDwYDVQQK EwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5 cHRvIENlcnRpZmljYXRlIE1hc3RlcjEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0 MS5jb20wHhcNMDAwOTEwMDk1MTMwWhcNMDIwOTEwMDk1MTMwWjBTMQswCQYDVQQG EwJTRzERMA8GA1UEChMITTJDcnlwdG8xEjAQBgNVBAMTCWxvY2FsaG9zdDEdMBsG CSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBI AkEArL57d26W9fNXvOhNlZzlPOACmvwOZ5AdNgLzJ1/MfsQQJ7hHVeHmTAjM664V +fXvwUGJLziCeBo1ysWLRnl8CQIDAQABo4IBBDCCAQAwCQYDVR0TBAIwADAsBglg hkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0O BBYEFM+EgpK+eyZiwFU1aOPSbczbPSpVMIGlBgNVHSMEgZ0wgZqAFPuHI2nrnDqT FeXFvylRT/7tKDgBoX+kfTB7MQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlw dG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQDExtNMkNyeXB0byBDZXJ0 aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tggEA MA0GCSqGSIb3DQEBBAUAA4GBADv8KpPo+gfJxN2ERK1Y1l17sz/ZhzoGgm5XCdbx jEY7xKfpQngV599k1xhl11IMqizDwu0855agrckg2MCTmOI9DZzDD77tAYb+Dk0O PEVk0Mk/V0aIsDE9bolfCi/i/QWZ3N8s5nTWMNyBBBmoSliWCm4jkkRZRD0ejgTN tgI5 -----END CERTIFICATE----- """) cert = load_certificate(FILETYPE_PEM, certPEM) self.assertRaises(ValueError, cert.get_signature_algorithm) class X509StoreTests(TestCase): """ Test for :py:obj:`OpenSSL.crypto.X509Store`. """ def test_type(self): """ :py:obj:`X509StoreType` is a type object. """ self.assertIdentical(X509Store, X509StoreType) self.assertConsistentType(X509Store, 'X509Store') def test_add_cert_wrong_args(self): store = X509Store() self.assertRaises(TypeError, store.add_cert) self.assertRaises(TypeError, store.add_cert, object()) self.assertRaises(TypeError, store.add_cert, X509(), object()) def test_add_cert(self): """ :py:obj:`X509Store.add_cert` adds a :py:obj:`X509` instance to the certificate store. """ cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) store = X509Store() store.add_cert(cert) def test_add_cert_rejects_duplicate(self): """ :py:obj:`X509Store.add_cert` raises :py:obj:`OpenSSL.crypto.Error` if an attempt is made to add the same certificate to the store more than once. """ cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) store = X509Store() store.add_cert(cert) self.assertRaises(Error, store.add_cert, cert) class PKCS12Tests(TestCase): """ Test for :py:obj:`OpenSSL.crypto.PKCS12` and :py:obj:`OpenSSL.crypto.load_pkcs12`. """ pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM def test_type(self): """ :py:obj:`PKCS12Type` is a type object. """ self.assertIdentical(PKCS12, PKCS12Type) self.assertConsistentType(PKCS12, 'PKCS12') def test_empty_construction(self): """ :py:obj:`PKCS12` returns a new instance of :py:obj:`PKCS12` with no certificate, private key, CA certificates, or friendly name. """ p12 = PKCS12() self.assertEqual(None, p12.get_certificate()) self.assertEqual(None, p12.get_privatekey()) self.assertEqual(None, p12.get_ca_certificates()) self.assertEqual(None, p12.get_friendlyname()) def test_type_errors(self): """ The :py:obj:`PKCS12` setter functions (:py:obj:`set_certificate`, :py:obj:`set_privatekey`, :py:obj:`set_ca_certificates`, and :py:obj:`set_friendlyname`) raise :py:obj:`TypeError` when passed objects of types other than those expected. """ p12 = PKCS12() self.assertRaises(TypeError, p12.set_certificate, 3) self.assertRaises(TypeError, p12.set_certificate, PKey()) self.assertRaises(TypeError, p12.set_certificate, X509) self.assertRaises(TypeError, p12.set_privatekey, 3) self.assertRaises(TypeError, p12.set_privatekey, 'legbone') self.assertRaises(TypeError, p12.set_privatekey, X509()) self.assertRaises(TypeError, p12.set_ca_certificates, 3) self.assertRaises(TypeError, p12.set_ca_certificates, X509()) self.assertRaises(TypeError, p12.set_ca_certificates, (3, 4)) self.assertRaises(TypeError, p12.set_ca_certificates, ( PKey(), )) self.assertRaises(TypeError, p12.set_friendlyname, 6) self.assertRaises(TypeError, p12.set_friendlyname, ('foo', 'bar')) def test_key_only(self): """ A :py:obj:`PKCS12` with only a private key can be exported using :py:obj:`PKCS12.export` and loaded again using :py:obj:`load_pkcs12`. """ passwd = b"blah" p12 = PKCS12() pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) p12.set_privatekey(pkey) self.assertEqual(None, p12.get_certificate()) self.assertEqual(pkey, p12.get_privatekey()) try: dumped_p12 = p12.export(passphrase=passwd, iter=2, maciter=3) except Error: # Some versions of OpenSSL will throw an exception # for this nearly useless PKCS12 we tried to generate: # [('PKCS12 routines', 'PKCS12_create', 'invalid null argument')] return p12 = load_pkcs12(dumped_p12, passwd) self.assertEqual(None, p12.get_ca_certificates()) self.assertEqual(None, p12.get_certificate()) # OpenSSL fails to bring the key back to us. So sad. Perhaps in the # future this will be improved. self.assertTrue(isinstance(p12.get_privatekey(), (PKey, type(None)))) def test_cert_only(self): """ A :py:obj:`PKCS12` with only a certificate can be exported using :py:obj:`PKCS12.export` and loaded again using :py:obj:`load_pkcs12`. """ passwd = b"blah" p12 = PKCS12() cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) p12.set_certificate(cert) self.assertEqual(cert, p12.get_certificate()) self.assertEqual(None, p12.get_privatekey()) try: dumped_p12 = p12.export(passphrase=passwd, iter=2, maciter=3) except Error: # Some versions of OpenSSL will throw an exception # for this nearly useless PKCS12 we tried to generate: # [('PKCS12 routines', 'PKCS12_create', 'invalid null argument')] return p12 = load_pkcs12(dumped_p12, passwd) self.assertEqual(None, p12.get_privatekey()) # OpenSSL fails to bring the cert back to us. Groany mcgroan. self.assertTrue(isinstance(p12.get_certificate(), (X509, type(None)))) # Oh ho. It puts the certificate into the ca certificates list, in # fact. Totally bogus, I would think. Nevertheless, let's exploit # that to check to see if it reconstructed the certificate we expected # it to. At some point, hopefully this will change so that # p12.get_certificate() is actually what returns the loaded # certificate. self.assertEqual( cleartextCertificatePEM, dump_certificate(FILETYPE_PEM, p12.get_ca_certificates()[0])) def gen_pkcs12(self, cert_pem=None, key_pem=None, ca_pem=None, friendly_name=None): """ Generate a PKCS12 object with components from PEM. Verify that the set functions return None. """ p12 = PKCS12() if cert_pem: ret = p12.set_certificate(load_certificate(FILETYPE_PEM, cert_pem)) self.assertEqual(ret, None) if key_pem: ret = p12.set_privatekey(load_privatekey(FILETYPE_PEM, key_pem)) self.assertEqual(ret, None) if ca_pem: ret = p12.set_ca_certificates((load_certificate(FILETYPE_PEM, ca_pem),)) self.assertEqual(ret, None) if friendly_name: ret = p12.set_friendlyname(friendly_name) self.assertEqual(ret, None) return p12 def check_recovery(self, p12_str, key=None, cert=None, ca=None, passwd=b"", extra=()): """ Use openssl program to confirm three components are recoverable from a PKCS12 string. """ if key: recovered_key = _runopenssl( p12_str, b"pkcs12", b"-nocerts", b"-nodes", b"-passin", b"pass:" + passwd, *extra) self.assertEqual(recovered_key[-len(key):], key) if cert: recovered_cert = _runopenssl( p12_str, b"pkcs12", b"-clcerts", b"-nodes", b"-passin", b"pass:" + passwd, b"-nokeys", *extra) self.assertEqual(recovered_cert[-len(cert):], cert) if ca: recovered_cert = _runopenssl( p12_str, b"pkcs12", b"-cacerts", b"-nodes", b"-passin", b"pass:" + passwd, b"-nokeys", *extra) self.assertEqual(recovered_cert[-len(ca):], ca) def verify_pkcs12_container(self, p12): """ Verify that the PKCS#12 container contains the correct client certificate and private key. :param p12: The PKCS12 instance to verify. :type p12: :py:class:`PKCS12` """ cert_pem = dump_certificate(FILETYPE_PEM, p12.get_certificate()) key_pem = dump_privatekey(FILETYPE_PEM, p12.get_privatekey()) self.assertEqual( (client_cert_pem, client_key_pem, None), (cert_pem, key_pem, p12.get_ca_certificates())) def test_load_pkcs12(self): """ A PKCS12 string generated using the openssl command line can be loaded with :py:obj:`load_pkcs12` and its components extracted and examined. """ passwd = b"whatever" pem = client_key_pem + client_cert_pem p12_str = _runopenssl( pem, b"pkcs12", b"-export", b"-clcerts", b"-passout", b"pass:" + passwd) p12 = load_pkcs12(p12_str, passphrase=passwd) self.verify_pkcs12_container(p12) def test_load_pkcs12_text_passphrase(self): """ A PKCS12 string generated using the openssl command line can be loaded with :py:obj:`load_pkcs12` and its components extracted and examined. Using text as passphrase instead of bytes. DeprecationWarning expected. """ pem = client_key_pem + client_cert_pem passwd = b"whatever" p12_str = _runopenssl(pem, b"pkcs12", b"-export", b"-clcerts", b"-passout", b"pass:" + passwd) with catch_warnings(record=True) as w: simplefilter("always") p12 = load_pkcs12(p12_str, passphrase=b"whatever".decode("ascii")) self.assertEqual( "{0} for passphrase is no longer accepted, use bytes".format( WARNING_TYPE_EXPECTED ), str(w[-1].message) ) self.assertIs(w[-1].category, DeprecationWarning) self.verify_pkcs12_container(p12) def test_load_pkcs12_no_passphrase(self): """ A PKCS12 string generated using openssl command line can be loaded with :py:obj:`load_pkcs12` without a passphrase and its components extracted and examined. """ pem = client_key_pem + client_cert_pem p12_str = _runopenssl( pem, b"pkcs12", b"-export", b"-clcerts", b"-passout", b"pass:") p12 = load_pkcs12(p12_str) self.verify_pkcs12_container(p12) def _dump_and_load(self, dump_passphrase, load_passphrase): """ A helper method to dump and load a PKCS12 object. """ p12 = self.gen_pkcs12(client_cert_pem, client_key_pem) dumped_p12 = p12.export(passphrase=dump_passphrase, iter=2, maciter=3) return load_pkcs12(dumped_p12, passphrase=load_passphrase) def test_load_pkcs12_null_passphrase_load_empty(self): """ A PKCS12 string can be dumped with a null passphrase, loaded with an empty passphrase with :py:obj:`load_pkcs12`, and its components extracted and examined. """ self.verify_pkcs12_container( self._dump_and_load(dump_passphrase=None, load_passphrase=b'')) def test_load_pkcs12_null_passphrase_load_null(self): """ A PKCS12 string can be dumped with a null passphrase, loaded with a null passphrase with :py:obj:`load_pkcs12`, and its components extracted and examined. """ self.verify_pkcs12_container( self._dump_and_load(dump_passphrase=None, load_passphrase=None)) def test_load_pkcs12_empty_passphrase_load_empty(self): """ A PKCS12 string can be dumped with an empty passphrase, loaded with an empty passphrase with :py:obj:`load_pkcs12`, and its components extracted and examined. """ self.verify_pkcs12_container( self._dump_and_load(dump_passphrase=b'', load_passphrase=b'')) def test_load_pkcs12_empty_passphrase_load_null(self): """ A PKCS12 string can be dumped with an empty passphrase, loaded with a null passphrase with :py:obj:`load_pkcs12`, and its components extracted and examined. """ self.verify_pkcs12_container( self._dump_and_load(dump_passphrase=b'', load_passphrase=None)) def test_load_pkcs12_garbage(self): """ :py:obj:`load_pkcs12` raises :py:obj:`OpenSSL.crypto.Error` when passed a string which is not a PKCS12 dump. """ passwd = 'whatever' e = self.assertRaises(Error, load_pkcs12, b'fruit loops', passwd) self.assertEqual( e.args[0][0][0], 'asn1 encoding routines') self.assertEqual( len(e.args[0][0]), 3) def test_replace(self): """ :py:obj:`PKCS12.set_certificate` replaces the certificate in a PKCS12 cluster. :py:obj:`PKCS12.set_privatekey` replaces the private key. :py:obj:`PKCS12.set_ca_certificates` replaces the CA certificates. """ p12 = self.gen_pkcs12(client_cert_pem, client_key_pem, root_cert_pem) p12.set_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) p12.set_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) root_cert = load_certificate(FILETYPE_PEM, root_cert_pem) client_cert = load_certificate(FILETYPE_PEM, client_cert_pem) p12.set_ca_certificates([root_cert]) # not a tuple self.assertEqual(1, len(p12.get_ca_certificates())) self.assertEqual(root_cert, p12.get_ca_certificates()[0]) p12.set_ca_certificates([client_cert, root_cert]) self.assertEqual(2, len(p12.get_ca_certificates())) self.assertEqual(client_cert, p12.get_ca_certificates()[0]) self.assertEqual(root_cert, p12.get_ca_certificates()[1]) def test_friendly_name(self): """ The *friendlyName* of a PKCS12 can be set and retrieved via :py:obj:`PKCS12.get_friendlyname` and :py:obj:`PKCS12_set_friendlyname`, and a :py:obj:`PKCS12` with a friendly name set can be dumped with :py:obj:`PKCS12.export`. """ passwd = b'Dogmeat[]{}!@#$%^&*()~`?/.,<>-_+=";:' p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) for friendly_name in [b('Serverlicious'), None, b('###')]: p12.set_friendlyname(friendly_name) self.assertEqual(p12.get_friendlyname(), friendly_name) dumped_p12 = p12.export(passphrase=passwd, iter=2, maciter=3) reloaded_p12 = load_pkcs12(dumped_p12, passwd) self.assertEqual( p12.get_friendlyname(), reloaded_p12.get_friendlyname()) # We would use the openssl program to confirm the friendly # name, but it is not possible. The pkcs12 command # does not store the friendly name in the cert's # alias, which we could then extract. self.check_recovery( dumped_p12, key=server_key_pem, cert=server_cert_pem, ca=root_cert_pem, passwd=passwd) def test_various_empty_passphrases(self): """ Test that missing, None, and '' passphrases are identical for PKCS12 export. """ p12 = self.gen_pkcs12(client_cert_pem, client_key_pem, root_cert_pem) passwd = b"" dumped_p12_empty = p12.export(iter=2, maciter=0, passphrase=passwd) dumped_p12_none = p12.export(iter=3, maciter=2, passphrase=None) dumped_p12_nopw = p12.export(iter=9, maciter=4) for dumped_p12 in [dumped_p12_empty, dumped_p12_none, dumped_p12_nopw]: self.check_recovery( dumped_p12, key=client_key_pem, cert=client_cert_pem, ca=root_cert_pem, passwd=passwd) def test_removing_ca_cert(self): """ Passing :py:obj:`None` to :py:obj:`PKCS12.set_ca_certificates` removes all CA certificates. """ p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) p12.set_ca_certificates(None) self.assertEqual(None, p12.get_ca_certificates()) def test_export_without_mac(self): """ Exporting a PKCS12 with a :py:obj:`maciter` of ``-1`` excludes the MAC entirely. """ passwd = b"Lake Michigan" p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) dumped_p12 = p12.export(maciter=-1, passphrase=passwd, iter=2) self.check_recovery( dumped_p12, key=server_key_pem, cert=server_cert_pem, passwd=passwd, extra=(b"-nomacver",)) def test_load_without_mac(self): """ Loading a PKCS12 without a MAC does something other than crash. """ passwd = b"Lake Michigan" p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) dumped_p12 = p12.export(maciter=-1, passphrase=passwd, iter=2) try: recovered_p12 = load_pkcs12(dumped_p12, passwd) # The person who generated this PCKS12 should be flogged, # or better yet we should have a means to determine # whether a PCKS12 had a MAC that was verified. # Anyway, libopenssl chooses to allow it, so the # pyopenssl binding does as well. self.assertTrue(isinstance(recovered_p12, PKCS12)) except Error: # Failing here with an exception is preferred as some openssl # versions do. pass def test_zero_len_list_for_ca(self): """ A PKCS12 with an empty CA certificates list can be exported. """ passwd = 'Hobie 18' p12 = self.gen_pkcs12(server_cert_pem, server_key_pem) # p12.set_ca_certificates([]) # self.assertEqual((), p12.get_ca_certificates()) # dumped_p12 = p12.export(passphrase=passwd, iter=3) # self.check_recovery( # dumped_p12, key=server_key_pem, cert=server_cert_pem, # passwd=passwd) def test_export_without_args(self): """ All the arguments to :py:obj:`PKCS12.export` are optional. """ p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) dumped_p12 = p12.export() # no args self.check_recovery( dumped_p12, key=server_key_pem, cert=server_cert_pem, passwd=b"") def test_export_without_bytes(self): """ Test :py:obj:`PKCS12.export` with text not bytes as passphrase """ p12 = self.gen_pkcs12(server_cert_pem, server_key_pem, root_cert_pem) with catch_warnings(record=True) as w: simplefilter("always") dumped_p12 = p12.export(passphrase=b"randomtext".decode("ascii")) self.assertEqual( "{0} for passphrase is no longer accepted, use bytes".format( WARNING_TYPE_EXPECTED ), str(w[-1].message) ) self.assertIs(w[-1].category, DeprecationWarning) self.check_recovery( dumped_p12, key=server_key_pem, cert=server_cert_pem, passwd=b"randomtext") def test_key_cert_mismatch(self): """ :py:obj:`PKCS12.export` raises an exception when a key and certificate mismatch. """ p12 = self.gen_pkcs12(server_cert_pem, client_key_pem, root_cert_pem) self.assertRaises(Error, p12.export) # These quoting functions taken directly from Twisted's twisted.python.win32. _cmdLineQuoteRe = re.compile(br'(\\*)"') _cmdLineQuoteRe2 = re.compile(br'(\\+)\Z') def cmdLineQuote(s): """ Internal method for quoting a single command-line argument. See http://www.perlmonks.org/?node_id=764004 :type: :py:obj:`str` :param s: A single unquoted string to quote for something that is expecting cmd.exe-style quoting :rtype: :py:obj:`str` :return: A cmd.exe-style quoted string """ s = _cmdLineQuoteRe2.sub(br"\1\1", _cmdLineQuoteRe.sub(br'\1\1\\"', s)) return b'"' + s + b'"' def quoteArguments(arguments): """ Quote an iterable of command-line arguments for passing to CreateProcess or a similar API. This allows the list passed to :py:obj:`reactor.spawnProcess` to match the child process's :py:obj:`sys.argv` properly. :type arguments: :py:obj:`iterable` of :py:obj:`str` :param arguments: An iterable of unquoted arguments to quote :rtype: :py:obj:`str` :return: A space-delimited string containing quoted versions of :py:obj:`arguments` """ return b' '.join(map(cmdLineQuote, arguments)) def _runopenssl(pem, *args): """ Run the command line openssl tool with the given arguments and write the given PEM to its stdin. Not safe for quotes. """ if os.name == 'posix': command = b"openssl " + b" ".join([ (b"'" + arg.replace(b"'", b"'\\''") + b"'") for arg in args]) else: command = b"openssl " + quoteArguments(args) proc = Popen(native(command), shell=True, stdin=PIPE, stdout=PIPE) proc.stdin.write(pem) proc.stdin.close() output = proc.stdout.read() proc.stdout.close() proc.wait() return output class FunctionTests(TestCase): """ Tests for free-functions in the :py:obj:`OpenSSL.crypto` module. """ def test_load_privatekey_invalid_format(self): """ :py:obj:`load_privatekey` raises :py:obj:`ValueError` if passed an unknown filetype. """ self.assertRaises(ValueError, load_privatekey, 100, root_key_pem) def test_load_privatekey_invalid_passphrase_type(self): """ :py:obj:`load_privatekey` raises :py:obj:`TypeError` if passed a passphrase that is neither a :py:obj:`str` nor a callable. """ self.assertRaises( TypeError, load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEMPassphrase, object()) def test_load_privatekey_wrong_args(self): """ :py:obj:`load_privatekey` raises :py:obj:`TypeError` if called with the wrong number of arguments. """ self.assertRaises(TypeError, load_privatekey) def test_load_privatekey_wrongPassphrase(self): """ :py:obj:`load_privatekey` raises :py:obj:`OpenSSL.crypto.Error` when it is passed an encrypted PEM and an incorrect passphrase. """ self.assertRaises( Error, load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, b("quack")) def test_load_privatekey_passphraseWrongType(self): """ :py:obj:`load_privatekey` raises :py:obj:`ValueError` when it is passed a passphrase with a private key encoded in a format, that doesn't support encryption. """ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) blob = dump_privatekey(FILETYPE_ASN1, key) self.assertRaises(ValueError, load_privatekey, FILETYPE_ASN1, blob, "secret") def test_load_privatekey_passphrase(self): """ :py:obj:`load_privatekey` can create a :py:obj:`PKey` object from an encrypted PEM string if given the passphrase. """ key = load_privatekey( FILETYPE_PEM, encryptedPrivateKeyPEM, encryptedPrivateKeyPEMPassphrase) self.assertTrue(isinstance(key, PKeyType)) def test_load_privatekey_passphrase_exception(self): """ If the passphrase callback raises an exception, that exception is raised by :py:obj:`load_privatekey`. """ def cb(ignored): raise ArithmeticError self.assertRaises(ArithmeticError, load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb) def test_load_privatekey_wrongPassphraseCallback(self): """ :py:obj:`load_privatekey` raises :py:obj:`OpenSSL.crypto.Error` when it is passed an encrypted PEM and a passphrase callback which returns an incorrect passphrase. """ called = [] def cb(*a): called.append(None) return b("quack") self.assertRaises( Error, load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb) self.assertTrue(called) def test_load_privatekey_passphraseCallback(self): """ :py:obj:`load_privatekey` can create a :py:obj:`PKey` object from an encrypted PEM string if given a passphrase callback which returns the correct password. """ called = [] def cb(writing): called.append(writing) return encryptedPrivateKeyPEMPassphrase key = load_privatekey(FILETYPE_PEM, encryptedPrivateKeyPEM, cb) self.assertTrue(isinstance(key, PKeyType)) self.assertEqual(called, [False]) def test_load_privatekey_passphrase_wrong_return_type(self): """ :py:obj:`load_privatekey` raises :py:obj:`ValueError` if the passphrase callback returns something other than a byte string. """ self.assertRaises( ValueError, load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, lambda *args: 3) def test_dump_privatekey_wrong_args(self): """ :py:obj:`dump_privatekey` raises :py:obj:`TypeError` if called with the wrong number of arguments. """ self.assertRaises(TypeError, dump_privatekey) # If cipher name is given, password is required. self.assertRaises( TypeError, dump_privatekey, FILETYPE_PEM, PKey(), GOOD_CIPHER) def test_dump_privatekey_unknown_cipher(self): """ :py:obj:`dump_privatekey` raises :py:obj:`ValueError` if called with an unrecognized cipher name. """ key = PKey() key.generate_key(TYPE_RSA, 512) self.assertRaises( ValueError, dump_privatekey, FILETYPE_PEM, key, BAD_CIPHER, "passphrase") def test_dump_privatekey_invalid_passphrase_type(self): """ :py:obj:`dump_privatekey` raises :py:obj:`TypeError` if called with a passphrase which is neither a :py:obj:`str` nor a callable. """ key = PKey() key.generate_key(TYPE_RSA, 512) self.assertRaises( TypeError, dump_privatekey, FILETYPE_PEM, key, GOOD_CIPHER, object()) def test_dump_privatekey_invalid_filetype(self): """ :py:obj:`dump_privatekey` raises :py:obj:`ValueError` if called with an unrecognized filetype. """ key = PKey() key.generate_key(TYPE_RSA, 512) self.assertRaises(ValueError, dump_privatekey, 100, key) def test_load_privatekey_passphraseCallbackLength(self): """ :py:obj:`crypto.load_privatekey` should raise an error when the passphrase provided by the callback is too long, not silently truncate it. """ def cb(ignored): return "a" * 1025 self.assertRaises(ValueError, load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb) def test_dump_privatekey_passphrase(self): """ :py:obj:`dump_privatekey` writes an encrypted PEM when given a passphrase. """ passphrase = b("foo") key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) pem = dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, passphrase) self.assertTrue(isinstance(pem, binary_type)) loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase) self.assertTrue(isinstance(loadedKey, PKeyType)) self.assertEqual(loadedKey.type(), key.type()) self.assertEqual(loadedKey.bits(), key.bits()) def test_dump_privatekey_passphraseWrongType(self): """ :py:obj:`dump_privatekey` raises :py:obj:`ValueError` when it is passed a passphrase with a private key encoded in a format, that doesn't support encryption. """ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) self.assertRaises(ValueError, dump_privatekey, FILETYPE_ASN1, key, GOOD_CIPHER, "secret") def test_dump_certificate(self): """ :py:obj:`dump_certificate` writes PEM, DER, and text. """ pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM cert = load_certificate(FILETYPE_PEM, pemData) dumped_pem = dump_certificate(FILETYPE_PEM, cert) self.assertEqual(dumped_pem, cleartextCertificatePEM) dumped_der = dump_certificate(FILETYPE_ASN1, cert) good_der = _runopenssl(dumped_pem, b"x509", b"-outform", b"DER") self.assertEqual(dumped_der, good_der) cert2 = load_certificate(FILETYPE_ASN1, dumped_der) dumped_pem2 = dump_certificate(FILETYPE_PEM, cert2) self.assertEqual(dumped_pem2, cleartextCertificatePEM) dumped_text = dump_certificate(FILETYPE_TEXT, cert) good_text = _runopenssl(dumped_pem, b"x509", b"-noout", b"-text") self.assertEqual(dumped_text, good_text) def test_dump_privatekey_pem(self): """ :py:obj:`dump_privatekey` writes a PEM """ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) self.assertTrue(key.check()) dumped_pem = dump_privatekey(FILETYPE_PEM, key) self.assertEqual(dumped_pem, cleartextPrivateKeyPEM) def test_dump_privatekey_asn1(self): """ :py:obj:`dump_privatekey` writes a DER """ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) dumped_pem = dump_privatekey(FILETYPE_PEM, key) dumped_der = dump_privatekey(FILETYPE_ASN1, key) # XXX This OpenSSL call writes "writing RSA key" to standard out. Sad. good_der = _runopenssl(dumped_pem, b"rsa", b"-outform", b"DER") self.assertEqual(dumped_der, good_der) key2 = load_privatekey(FILETYPE_ASN1, dumped_der) dumped_pem2 = dump_privatekey(FILETYPE_PEM, key2) self.assertEqual(dumped_pem2, cleartextPrivateKeyPEM) def test_dump_privatekey_text(self): """ :py:obj:`dump_privatekey` writes a text """ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) dumped_pem = dump_privatekey(FILETYPE_PEM, key) dumped_text = dump_privatekey(FILETYPE_TEXT, key) good_text = _runopenssl(dumped_pem, b"rsa", b"-noout", b"-text") self.assertEqual(dumped_text, good_text) def test_dump_certificate_request(self): """ :py:obj:`dump_certificate_request` writes a PEM, DER, and text. """ req = load_certificate_request(FILETYPE_PEM, cleartextCertificateRequestPEM) dumped_pem = dump_certificate_request(FILETYPE_PEM, req) self.assertEqual(dumped_pem, cleartextCertificateRequestPEM) dumped_der = dump_certificate_request(FILETYPE_ASN1, req) good_der = _runopenssl(dumped_pem, b"req", b"-outform", b"DER") self.assertEqual(dumped_der, good_der) req2 = load_certificate_request(FILETYPE_ASN1, dumped_der) dumped_pem2 = dump_certificate_request(FILETYPE_PEM, req2) self.assertEqual(dumped_pem2, cleartextCertificateRequestPEM) dumped_text = dump_certificate_request(FILETYPE_TEXT, req) good_text = _runopenssl(dumped_pem, b"req", b"-noout", b"-text") self.assertEqual(dumped_text, good_text) self.assertRaises(ValueError, dump_certificate_request, 100, req) def test_dump_privatekey_passphraseCallback(self): """ :py:obj:`dump_privatekey` writes an encrypted PEM when given a callback which returns the correct passphrase. """ passphrase = b("foo") called = [] def cb(writing): called.append(writing) return passphrase key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) pem = dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, cb) self.assertTrue(isinstance(pem, binary_type)) self.assertEqual(called, [True]) loadedKey = load_privatekey(FILETYPE_PEM, pem, passphrase) self.assertTrue(isinstance(loadedKey, PKeyType)) self.assertEqual(loadedKey.type(), key.type()) self.assertEqual(loadedKey.bits(), key.bits()) def test_dump_privatekey_passphrase_exception(self): """ :py:obj:`dump_privatekey` should not overwrite the exception raised by the passphrase callback. """ def cb(ignored): raise ArithmeticError key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) self.assertRaises(ArithmeticError, dump_privatekey, FILETYPE_PEM, key, GOOD_CIPHER, cb) def test_dump_privatekey_passphraseCallbackLength(self): """ :py:obj:`crypto.dump_privatekey` should raise an error when the passphrase provided by the callback is too long, not silently truncate it. """ def cb(ignored): return "a" * 1025 key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) self.assertRaises(ValueError, dump_privatekey, FILETYPE_PEM, key, GOOD_CIPHER, cb) def test_load_pkcs7_data_pem(self): """ :py:obj:`load_pkcs7_data` accepts a PKCS#7 string and returns an instance of :py:obj:`PKCS7Type`. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) self.assertTrue(isinstance(pkcs7, PKCS7Type)) def test_load_pkcs7_data_asn1(self): """ :py:obj:`load_pkcs7_data` accepts a bytes containing ASN1 data representing PKCS#7 and returns an instance of :py:obj`PKCS7Type`. """ pkcs7 = load_pkcs7_data(FILETYPE_ASN1, pkcs7DataASN1) self.assertTrue(isinstance(pkcs7, PKCS7Type)) def test_load_pkcs7_data_invalid(self): """ If the data passed to :py:obj:`load_pkcs7_data` is invalid, :py:obj:`Error` is raised. """ self.assertRaises(Error, load_pkcs7_data, FILETYPE_PEM, b"foo") class LoadCertificateTests(TestCase): """ Tests for :py:obj:`load_certificate_request`. """ def test_badFileType(self): """ If the file type passed to :py:obj:`load_certificate_request` is neither :py:obj:`FILETYPE_PEM` nor :py:obj:`FILETYPE_ASN1` then :py:class:`ValueError` is raised. """ self.assertRaises(ValueError, load_certificate_request, object(), b"") class PKCS7Tests(TestCase): """ Tests for :py:obj:`PKCS7Type`. """ def test_type(self): """ :py:obj:`PKCS7Type` is a type object. """ self.assertTrue(isinstance(PKCS7Type, type)) self.assertEqual(PKCS7Type.__name__, 'PKCS7') # XXX This doesn't currently work. # self.assertIdentical(PKCS7, PKCS7Type) # XXX Opposite results for all these following methods def test_type_is_signed_wrong_args(self): """ :py:obj:`PKCS7Type.type_is_signed` raises :py:obj:`TypeError` if called with any arguments. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) self.assertRaises(TypeError, pkcs7.type_is_signed, None) def test_type_is_signed(self): """ :py:obj:`PKCS7Type.type_is_signed` returns :py:obj:`True` if the PKCS7 object is of the type *signed*. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) self.assertTrue(pkcs7.type_is_signed()) def test_type_is_enveloped_wrong_args(self): """ :py:obj:`PKCS7Type.type_is_enveloped` raises :py:obj:`TypeError` if called with any arguments. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) self.assertRaises(TypeError, pkcs7.type_is_enveloped, None) def test_type_is_enveloped(self): """ :py:obj:`PKCS7Type.type_is_enveloped` returns :py:obj:`False` if the PKCS7 object is not of the type *enveloped*. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) self.assertFalse(pkcs7.type_is_enveloped()) def test_type_is_signedAndEnveloped_wrong_args(self): """ :py:obj:`PKCS7Type.type_is_signedAndEnveloped` raises :py:obj:`TypeError` if called with any arguments. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) self.assertRaises(TypeError, pkcs7.type_is_signedAndEnveloped, None) def test_type_is_signedAndEnveloped(self): """ :py:obj:`PKCS7Type.type_is_signedAndEnveloped` returns :py:obj:`False` if the PKCS7 object is not of the type *signed and enveloped*. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) self.assertFalse(pkcs7.type_is_signedAndEnveloped()) def test_type_is_data(self): """ :py:obj:`PKCS7Type.type_is_data` returns :py:obj:`False` if the PKCS7 object is not of the type data. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) self.assertFalse(pkcs7.type_is_data()) def test_type_is_data_wrong_args(self): """ :py:obj:`PKCS7Type.type_is_data` raises :py:obj:`TypeError` if called with any arguments. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) self.assertRaises(TypeError, pkcs7.type_is_data, None) def test_get_type_name_wrong_args(self): """ :py:obj:`PKCS7Type.get_type_name` raises :py:obj:`TypeError` if called with any arguments. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) self.assertRaises(TypeError, pkcs7.get_type_name, None) def test_get_type_name(self): """ :py:obj:`PKCS7Type.get_type_name` returns a :py:obj:`str` giving the type name. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) self.assertEquals(pkcs7.get_type_name(), b('pkcs7-signedData')) def test_attribute(self): """ If an attribute other than one of the methods tested here is accessed on an instance of :py:obj:`PKCS7Type`, :py:obj:`AttributeError` is raised. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) self.assertRaises(AttributeError, getattr, pkcs7, "foo") class NetscapeSPKITests(TestCase, _PKeyInteractionTestsMixin): """ Tests for :py:obj:`OpenSSL.crypto.NetscapeSPKI`. """ def signable(self): """ Return a new :py:obj:`NetscapeSPKI` for use with signing tests. """ return NetscapeSPKI() def test_type(self): """ :py:obj:`NetscapeSPKI` and :py:obj:`NetscapeSPKIType` refer to the same type object and can be used to create instances of that type. """ self.assertIdentical(NetscapeSPKI, NetscapeSPKIType) self.assertConsistentType(NetscapeSPKI, 'NetscapeSPKI') def test_construction(self): """ :py:obj:`NetscapeSPKI` returns an instance of :py:obj:`NetscapeSPKIType`. """ nspki = NetscapeSPKI() self.assertTrue(isinstance(nspki, NetscapeSPKIType)) def test_invalid_attribute(self): """ Accessing a non-existent attribute of a :py:obj:`NetscapeSPKI` instance causes an :py:obj:`AttributeError` to be raised. """ nspki = NetscapeSPKI() self.assertRaises(AttributeError, lambda: nspki.foo) def test_b64_encode(self): """ :py:obj:`NetscapeSPKI.b64_encode` encodes the certificate to a base64 blob. """ nspki = NetscapeSPKI() blob = nspki.b64_encode() self.assertTrue(isinstance(blob, binary_type)) class RevokedTests(TestCase): """ Tests for :py:obj:`OpenSSL.crypto.Revoked` """ def test_construction(self): """ Confirm we can create :py:obj:`OpenSSL.crypto.Revoked`. Check that it is empty. """ revoked = Revoked() self.assertTrue(isinstance(revoked, Revoked)) self.assertEquals(type(revoked), Revoked) self.assertEquals(revoked.get_serial(), b('00')) self.assertEquals(revoked.get_rev_date(), None) self.assertEquals(revoked.get_reason(), None) def test_construction_wrong_args(self): """ Calling :py:obj:`OpenSSL.crypto.Revoked` with any arguments results in a :py:obj:`TypeError` being raised. """ self.assertRaises(TypeError, Revoked, None) self.assertRaises(TypeError, Revoked, 1) self.assertRaises(TypeError, Revoked, "foo") def test_serial(self): """ Confirm we can set and get serial numbers from :py:obj:`OpenSSL.crypto.Revoked`. Confirm errors are handled with grace. """ revoked = Revoked() ret = revoked.set_serial(b('10b')) self.assertEquals(ret, None) ser = revoked.get_serial() self.assertEquals(ser, b('010B')) revoked.set_serial(b('31ppp')) # a type error would be nice ser = revoked.get_serial() self.assertEquals(ser, b('31')) self.assertRaises(ValueError, revoked.set_serial, b('pqrst')) self.assertRaises(TypeError, revoked.set_serial, 100) self.assertRaises(TypeError, revoked.get_serial, 1) self.assertRaises(TypeError, revoked.get_serial, None) self.assertRaises(TypeError, revoked.get_serial, "") def test_date(self): """ Confirm we can set and get revocation dates from :py:obj:`OpenSSL.crypto.Revoked`. Confirm errors are handled with grace. """ revoked = Revoked() date = revoked.get_rev_date() self.assertEquals(date, None) now = b(datetime.now().strftime("%Y%m%d%H%M%SZ")) ret = revoked.set_rev_date(now) self.assertEqual(ret, None) date = revoked.get_rev_date() self.assertEqual(date, now) def test_reason(self): """ Confirm we can set and get revocation reasons from :py:obj:`OpenSSL.crypto.Revoked`. The "get" need to work as "set". Likewise, each reason of all_reasons() must work. """ revoked = Revoked() for r in revoked.all_reasons(): for x in range(2): ret = revoked.set_reason(r) self.assertEquals(ret, None) reason = revoked.get_reason() self.assertEquals( reason.lower().replace(b(' '), b('')), r.lower().replace(b(' '), b(''))) r = reason # again with the resp of get revoked.set_reason(None) self.assertEqual(revoked.get_reason(), None) def test_set_reason_wrong_arguments(self): """ Calling :py:obj:`OpenSSL.crypto.Revoked.set_reason` with other than one argument, or an argument which isn't a valid reason, results in :py:obj:`TypeError` or :py:obj:`ValueError` being raised. """ revoked = Revoked() self.assertRaises(TypeError, revoked.set_reason, 100) self.assertRaises(ValueError, revoked.set_reason, b('blue')) def test_get_reason_wrong_arguments(self): """ Calling :py:obj:`OpenSSL.crypto.Revoked.get_reason` with any arguments results in :py:obj:`TypeError` being raised. """ revoked = Revoked() self.assertRaises(TypeError, revoked.get_reason, None) self.assertRaises(TypeError, revoked.get_reason, 1) self.assertRaises(TypeError, revoked.get_reason, "foo") class CRLTests(TestCase): """ Tests for :py:obj:`OpenSSL.crypto.CRL` """ cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) def test_construction(self): """ Confirm we can create :py:obj:`OpenSSL.crypto.CRL`. Check that it is empty """ crl = CRL() self.assertTrue( isinstance(crl, CRL) ) self.assertEqual(crl.get_revoked(), None) def test_construction_wrong_args(self): """ Calling :py:obj:`OpenSSL.crypto.CRL` with any number of arguments results in a :py:obj:`TypeError` being raised. """ self.assertRaises(TypeError, CRL, 1) self.assertRaises(TypeError, CRL, "") self.assertRaises(TypeError, CRL, None) def _get_crl(self): """ Get a new ``CRL`` with a revocation. """ crl = CRL() revoked = Revoked() now = b(datetime.now().strftime("%Y%m%d%H%M%SZ")) revoked.set_rev_date(now) revoked.set_serial(b('3ab')) revoked.set_reason(b('sUpErSeDEd')) crl.add_revoked(revoked) return crl def test_export_pem(self): """ If not passed a format, ``CRL.export`` returns a "PEM" format string representing a serial number, a revoked reason, and certificate issuer information. """ crl = self._get_crl() # PEM format dumped_crl = crl.export(self.cert, self.pkey, days=20) text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") # These magic values are based on the way the CRL above was constructed # and with what certificate it was exported. text.index(b('Serial Number: 03AB')) text.index(b('Superseded')) text.index( b('Issuer: /C=US/ST=IL/L=Chicago/O=Testing/CN=Testing Root CA') ) def test_export_der(self): """ If passed ``FILETYPE_ASN1`` for the format, ``CRL.export`` returns a "DER" format string representing a serial number, a revoked reason, and certificate issuer information. """ crl = self._get_crl() # DER format dumped_crl = crl.export(self.cert, self.pkey, FILETYPE_ASN1) text = _runopenssl( dumped_crl, b"crl", b"-noout", b"-text", b"-inform", b"DER" ) text.index(b('Serial Number: 03AB')) text.index(b('Superseded')) text.index( b('Issuer: /C=US/ST=IL/L=Chicago/O=Testing/CN=Testing Root CA') ) def test_export_text(self): """ If passed ``FILETYPE_TEXT`` for the format, ``CRL.export`` returns a text format string like the one produced by the openssl command line tool. """ crl = self._get_crl() dumped_crl = crl.export(self.cert, self.pkey, FILETYPE_ASN1) text = _runopenssl( dumped_crl, b"crl", b"-noout", b"-text", b"-inform", b"DER" ) # text format dumped_text = crl.export(self.cert, self.pkey, type=FILETYPE_TEXT) self.assertEqual(text, dumped_text) def test_export_custom_digest(self): """ If passed the name of a digest function, ``CRL.export`` uses a signature algorithm based on that digest function. """ crl = self._get_crl() dumped_crl = crl.export(self.cert, self.pkey, digest=b"sha1") text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") text.index(b('Signature Algorithm: sha1')) def test_export_md5_digest(self): """ If passed md5 as the digest function, ``CRL.export`` uses md5 and does not emit a deprecation warning. """ crl = self._get_crl() with catch_warnings(record=True) as catcher: simplefilter("always") self.assertEqual(0, len(catcher)) dumped_crl = crl.export(self.cert, self.pkey, digest=b"md5") text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") text.index(b('Signature Algorithm: md5')) def test_export_default_digest(self): """ If not passed the name of a digest function, ``CRL.export`` uses a signature algorithm based on MD5 and emits a deprecation warning. """ crl = self._get_crl() with catch_warnings(record=True) as catcher: simplefilter("always") dumped_crl = crl.export(self.cert, self.pkey) self.assertEqual( "The default message digest (md5) is deprecated. " "Pass the name of a message digest explicitly.", str(catcher[0].message), ) text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") text.index(b('Signature Algorithm: md5')) def test_export_invalid(self): """ If :py:obj:`CRL.export` is used with an uninitialized :py:obj:`X509` instance, :py:obj:`OpenSSL.crypto.Error` is raised. """ crl = CRL() self.assertRaises(Error, crl.export, X509(), PKey()) def test_add_revoked_keyword(self): """ :py:obj:`OpenSSL.CRL.add_revoked` accepts its single argument as the ``revoked`` keyword argument. """ crl = CRL() revoked = Revoked() crl.add_revoked(revoked=revoked) self.assertTrue(isinstance(crl.get_revoked()[0], Revoked)) def test_export_wrong_args(self): """ Calling :py:obj:`OpenSSL.CRL.export` with fewer than two or more than four arguments, or with arguments other than the certificate, private key, integer file type, and integer number of days it expects, results in a :py:obj:`TypeError` being raised. """ crl = CRL() self.assertRaises(TypeError, crl.export) self.assertRaises(TypeError, crl.export, self.cert) self.assertRaises(TypeError, crl.export, self.cert, self.pkey, FILETYPE_PEM, 10, "md5", "foo") self.assertRaises(TypeError, crl.export, None, self.pkey, FILETYPE_PEM, 10) self.assertRaises(TypeError, crl.export, self.cert, None, FILETYPE_PEM, 10) self.assertRaises(TypeError, crl.export, self.cert, self.pkey, None, 10) self.assertRaises(TypeError, crl.export, self.cert, FILETYPE_PEM, None) def test_export_unknown_filetype(self): """ Calling :py:obj:`OpenSSL.CRL.export` with a file type other than :py:obj:`FILETYPE_PEM`, :py:obj:`FILETYPE_ASN1`, or :py:obj:`FILETYPE_TEXT` results in a :py:obj:`ValueError` being raised. """ crl = CRL() self.assertRaises(ValueError, crl.export, self.cert, self.pkey, 100, 10) def test_export_unknown_digest(self): """ Calling :py:obj:`OpenSSL.CRL.export` with a unsupported digest results in a :py:obj:`ValueError` being raised. """ crl = CRL() self.assertRaises( ValueError, crl.export, self.cert, self.pkey, FILETYPE_PEM, 10, b"strange-digest" ) def test_get_revoked(self): """ Use python to create a simple CRL with two revocations. Get back the :py:obj:`Revoked` using :py:obj:`OpenSSL.CRL.get_revoked` and verify them. """ crl = CRL() revoked = Revoked() now = b(datetime.now().strftime("%Y%m%d%H%M%SZ")) revoked.set_rev_date(now) revoked.set_serial(b('3ab')) crl.add_revoked(revoked) revoked.set_serial(b('100')) revoked.set_reason(b('sUpErSeDEd')) crl.add_revoked(revoked) revs = crl.get_revoked() self.assertEqual(len(revs), 2) self.assertEqual(type(revs[0]), Revoked) self.assertEqual(type(revs[1]), Revoked) self.assertEqual(revs[0].get_serial(), b('03AB')) self.assertEqual(revs[1].get_serial(), b('0100')) self.assertEqual(revs[0].get_rev_date(), now) self.assertEqual(revs[1].get_rev_date(), now) def test_get_revoked_wrong_args(self): """ Calling :py:obj:`OpenSSL.CRL.get_revoked` with any arguments results in a :py:obj:`TypeError` being raised. """ crl = CRL() self.assertRaises(TypeError, crl.get_revoked, None) self.assertRaises(TypeError, crl.get_revoked, 1) self.assertRaises(TypeError, crl.get_revoked, "") self.assertRaises(TypeError, crl.get_revoked, "", 1, None) def test_add_revoked_wrong_args(self): """ Calling :py:obj:`OpenSSL.CRL.add_revoked` with other than one argument results in a :py:obj:`TypeError` being raised. """ crl = CRL() self.assertRaises(TypeError, crl.add_revoked) self.assertRaises(TypeError, crl.add_revoked, 1, 2) self.assertRaises(TypeError, crl.add_revoked, "foo", "bar") def test_load_crl(self): """ Load a known CRL and inspect its revocations. Both PEM and DER formats are loaded. """ crl = load_crl(FILETYPE_PEM, crlData) revs = crl.get_revoked() self.assertEqual(len(revs), 2) self.assertEqual(revs[0].get_serial(), b('03AB')) self.assertEqual(revs[0].get_reason(), None) self.assertEqual(revs[1].get_serial(), b('0100')) self.assertEqual(revs[1].get_reason(), b('Superseded')) der = _runopenssl(crlData, b"crl", b"-outform", b"DER") crl = load_crl(FILETYPE_ASN1, der) revs = crl.get_revoked() self.assertEqual(len(revs), 2) self.assertEqual(revs[0].get_serial(), b('03AB')) self.assertEqual(revs[0].get_reason(), None) self.assertEqual(revs[1].get_serial(), b('0100')) self.assertEqual(revs[1].get_reason(), b('Superseded')) def test_load_crl_wrong_args(self): """ Calling :py:obj:`OpenSSL.crypto.load_crl` with other than two arguments results in a :py:obj:`TypeError` being raised. """ self.assertRaises(TypeError, load_crl) self.assertRaises(TypeError, load_crl, FILETYPE_PEM) self.assertRaises(TypeError, load_crl, FILETYPE_PEM, crlData, None) def test_load_crl_bad_filetype(self): """ Calling :py:obj:`OpenSSL.crypto.load_crl` with an unknown file type raises a :py:obj:`ValueError`. """ self.assertRaises(ValueError, load_crl, 100, crlData) def test_load_crl_bad_data(self): """ Calling :py:obj:`OpenSSL.crypto.load_crl` with file data which can't be loaded raises a :py:obj:`OpenSSL.crypto.Error`. """ self.assertRaises(Error, load_crl, FILETYPE_PEM, b"hello, world") class X509StoreContextTests(TestCase): """ Tests for :py:obj:`OpenSSL.crypto.X509StoreContext`. """ root_cert = load_certificate(FILETYPE_PEM, root_cert_pem) intermediate_cert = load_certificate(FILETYPE_PEM, intermediate_cert_pem) intermediate_server_cert = load_certificate(FILETYPE_PEM, intermediate_server_cert_pem) def test_valid(self): """ :py:obj:`verify_certificate` returns ``None`` when called with a certificate and valid chain. """ store = X509Store() store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) store_ctx = X509StoreContext(store, self.intermediate_server_cert) self.assertEqual(store_ctx.verify_certificate(), None) def test_reuse(self): """ :py:obj:`verify_certificate` can be called multiple times with the same ``X509StoreContext`` instance to produce the same result. """ store = X509Store() store.add_cert(self.root_cert) store.add_cert(self.intermediate_cert) store_ctx = X509StoreContext(store, self.intermediate_server_cert) self.assertEqual(store_ctx.verify_certificate(), None) self.assertEqual(store_ctx.verify_certificate(), None) def test_trusted_self_signed(self): """ :py:obj:`verify_certificate` returns ``None`` when called with a self-signed certificate and itself in the chain. """ store = X509Store() store.add_cert(self.root_cert) store_ctx = X509StoreContext(store, self.root_cert) self.assertEqual(store_ctx.verify_certificate(), None) def test_untrusted_self_signed(self): """ :py:obj:`verify_certificate` raises error when a self-signed certificate is verified without itself in the chain. """ store = X509Store() store_ctx = X509StoreContext(store, self.root_cert) e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate) self.assertEqual(e.args[0][2], 'self signed certificate') self.assertEqual(e.certificate.get_subject().CN, 'Testing Root CA') def test_invalid_chain_no_root(self): """ :py:obj:`verify_certificate` raises error when a root certificate is missing from the chain. """ store = X509Store() store.add_cert(self.intermediate_cert) store_ctx = X509StoreContext(store, self.intermediate_server_cert) e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate) self.assertEqual(e.args[0][2], 'unable to get issuer certificate') self.assertEqual(e.certificate.get_subject().CN, 'intermediate') def test_invalid_chain_no_intermediate(self): """ :py:obj:`verify_certificate` raises error when an intermediate certificate is missing from the chain. """ store = X509Store() store.add_cert(self.root_cert) store_ctx = X509StoreContext(store, self.intermediate_server_cert) e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate) self.assertEqual(e.args[0][2], 'unable to get local issuer certificate') self.assertEqual(e.certificate.get_subject().CN, 'intermediate-service') def test_modification_pre_verify(self): """ :py:obj:`verify_certificate` can use a store context modified after instantiation. """ store_bad = X509Store() store_bad.add_cert(self.intermediate_cert) store_good = X509Store() store_good.add_cert(self.root_cert) store_good.add_cert(self.intermediate_cert) store_ctx = X509StoreContext(store_bad, self.intermediate_server_cert) e = self.assertRaises(X509StoreContextError, store_ctx.verify_certificate) self.assertEqual(e.args[0][2], 'unable to get issuer certificate') self.assertEqual(e.certificate.get_subject().CN, 'intermediate') store_ctx.set_store(store_good) self.assertEqual(store_ctx.verify_certificate(), None) class SignVerifyTests(TestCase): """ Tests for :py:obj:`OpenSSL.crypto.sign` and :py:obj:`OpenSSL.crypto.verify`. """ def test_sign_verify(self): """ :py:obj:`sign` generates a cryptographic signature which :py:obj:`verify` can check. """ content = b( "It was a bright cold day in April, and the clocks were striking " "thirteen. Winston Smith, his chin nuzzled into his breast in an " "effort to escape the vile wind, slipped quickly through the " "glass doors of Victory Mansions, though not quickly enough to " "prevent a swirl of gritty dust from entering along with him.") # sign the content with this private key priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) # verify the content with this cert good_cert = load_certificate(FILETYPE_PEM, root_cert_pem) # certificate unrelated to priv_key, used to trigger an error bad_cert = load_certificate(FILETYPE_PEM, server_cert_pem) for digest in ['md5', 'sha1']: sig = sign(priv_key, content, digest) # Verify the signature of content, will throw an exception if error. verify(good_cert, sig, content, digest) # This should fail because the certificate doesn't match the # private key that was used to sign the content. self.assertRaises(Error, verify, bad_cert, sig, content, digest) # This should fail because we've "tainted" the content after # signing it. self.assertRaises( Error, verify, good_cert, sig, content + b("tainted"), digest) # test that unknown digest types fail self.assertRaises( ValueError, sign, priv_key, content, "strange-digest") self.assertRaises( ValueError, verify, good_cert, sig, content, "strange-digest") def test_sign_verify_with_text(self): """ :py:obj:`sign` generates a cryptographic signature which :py:obj:`verify` can check. Deprecation warnings raised because using text instead of bytes as content """ content = ( b"It was a bright cold day in April, and the clocks were striking " b"thirteen. Winston Smith, his chin nuzzled into his breast in an " b"effort to escape the vile wind, slipped quickly through the " b"glass doors of Victory Mansions, though not quickly enough to " b"prevent a swirl of gritty dust from entering along with him." ).decode("ascii") priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) cert = load_certificate(FILETYPE_PEM, root_cert_pem) for digest in ['md5', 'sha1']: with catch_warnings(record=True) as w: simplefilter("always") sig = sign(priv_key, content, digest) self.assertEqual( "{0} for data is no longer accepted, use bytes".format( WARNING_TYPE_EXPECTED ), str(w[-1].message) ) self.assertIs(w[-1].category, DeprecationWarning) with catch_warnings(record=True) as w: simplefilter("always") verify(cert, sig, content, digest) self.assertEqual( "{0} for data is no longer accepted, use bytes".format( WARNING_TYPE_EXPECTED ), str(w[-1].message) ) self.assertIs(w[-1].category, DeprecationWarning) def test_sign_nulls(self): """ :py:obj:`sign` produces a signature for a string with embedded nulls. """ content = b("Watch out! \0 Did you see it?") priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) good_cert = load_certificate(FILETYPE_PEM, root_cert_pem) sig = sign(priv_key, content, "sha1") verify(good_cert, sig, content, "sha1") class EllipticCurveTests(TestCase): """ Tests for :py:class:`_EllipticCurve`, :py:obj:`get_elliptic_curve`, and :py:obj:`get_elliptic_curves`. """ def test_set(self): """ :py:obj:`get_elliptic_curves` returns a :py:obj:`set`. """ self.assertIsInstance(get_elliptic_curves(), set) def test_some_curves(self): """ If :py:mod:`cryptography` has elliptic curve support then the set returned by :py:obj:`get_elliptic_curves` has some elliptic curves in it. There could be an OpenSSL that violates this assumption. If so, this test will fail and we'll find out. """ curves = get_elliptic_curves() if lib.Cryptography_HAS_EC: self.assertTrue(curves) else: self.assertFalse(curves) def test_a_curve(self): """ :py:obj:`get_elliptic_curve` can be used to retrieve a particular supported curve. """ curves = get_elliptic_curves() if curves: curve = next(iter(curves)) self.assertEqual(curve.name, get_elliptic_curve(curve.name).name) else: self.assertRaises(ValueError, get_elliptic_curve, u("prime256v1")) def test_not_a_curve(self): """ :py:obj:`get_elliptic_curve` raises :py:class:`ValueError` if called with a name which does not identify a supported curve. """ self.assertRaises( ValueError, get_elliptic_curve, u("this curve was just invented")) def test_repr(self): """ The string representation of a curve object includes simply states the object is a curve and what its name is. """ curves = get_elliptic_curves() if curves: curve = next(iter(curves)) self.assertEqual("" % (curve.name,), repr(curve)) def test_to_EC_KEY(self): """ The curve object can export a version of itself as an EC_KEY* via the private :py:meth:`_EllipticCurve._to_EC_KEY`. """ curves = get_elliptic_curves() if curves: curve = next(iter(curves)) # It's not easy to assert anything about this object. However, see # leakcheck/crypto.py for a test that demonstrates it at least does # not leak memory. curve._to_EC_KEY() class EllipticCurveFactory(object): """ A helper to get the names of two curves. """ def __init__(self): curves = iter(get_elliptic_curves()) try: self.curve_name = next(curves).name self.another_curve_name = next(curves).name except StopIteration: self.curve_name = self.another_curve_name = None class EllipticCurveEqualityTests(TestCase, EqualityTestsMixin): """ Tests :py:type:`_EllipticCurve`\ 's implementation of ``==`` and ``!=``. """ curve_factory = EllipticCurveFactory() if curve_factory.curve_name is None: skip = "There are no curves available there can be no curve objects." def anInstance(self): """ Get the curve object for an arbitrary curve supported by the system. """ return get_elliptic_curve(self.curve_factory.curve_name) def anotherInstance(self): """ Get the curve object for an arbitrary curve supported by the system - but not the one returned by C{anInstance}. """ return get_elliptic_curve(self.curve_factory.another_curve_name) class EllipticCurveHashTests(TestCase): """ Tests for :py:type:`_EllipticCurve`\ 's implementation of hashing (thus use as an item in a :py:type:`dict` or :py:type:`set`). """ curve_factory = EllipticCurveFactory() if curve_factory.curve_name is None: skip = "There are no curves available there can be no curve objects." def test_contains(self): """ The ``in`` operator reports that a :py:type:`set` containing a curve does contain that curve. """ curve = get_elliptic_curve(self.curve_factory.curve_name) curves = set([curve]) self.assertIn(curve, curves) def test_does_not_contain(self): """ The ``in`` operator reports that a :py:type:`set` not containing a curve does not contain that curve. """ curve = get_elliptic_curve(self.curve_factory.curve_name) curves = set([get_elliptic_curve(self.curve_factory.another_curve_name)]) self.assertNotIn(curve, curves) if __name__ == '__main__': main() pyOpenSSL-0.15.1/OpenSSL/test/test_rand.py0000644000076500000240000001604312513035756020505 0ustar hynekstaff00000000000000# Copyright (c) Frederick Dean # See LICENSE for details. """ Unit tests for :py:obj:`OpenSSL.rand`. """ from unittest import main import os import stat import sys from OpenSSL.test.util import NON_ASCII, TestCase, b from OpenSSL import rand class RandTests(TestCase): def test_bytes_wrong_args(self): """ :py:obj:`OpenSSL.rand.bytes` raises :py:obj:`TypeError` if called with the wrong number of arguments or with a non-:py:obj:`int` argument. """ self.assertRaises(TypeError, rand.bytes) self.assertRaises(TypeError, rand.bytes, None) self.assertRaises(TypeError, rand.bytes, 3, None) def test_insufficientMemory(self): """ :py:obj:`OpenSSL.rand.bytes` raises :py:obj:`MemoryError` if more bytes are requested than will fit in memory. """ self.assertRaises(MemoryError, rand.bytes, sys.maxsize) def test_bytes(self): """ Verify that we can obtain bytes from rand_bytes() and that they are different each time. Test the parameter of rand_bytes() for bad values. """ b1 = rand.bytes(50) self.assertEqual(len(b1), 50) b2 = rand.bytes(num_bytes=50) # parameter by name self.assertNotEqual(b1, b2) # Hip, Hip, Horay! FIPS complaince b3 = rand.bytes(num_bytes=0) self.assertEqual(len(b3), 0) exc = self.assertRaises(ValueError, rand.bytes, -1) self.assertEqual(str(exc), "num_bytes must not be negative") def test_add_wrong_args(self): """ When called with the wrong number of arguments, or with arguments not of type :py:obj:`str` and :py:obj:`int`, :py:obj:`OpenSSL.rand.add` raises :py:obj:`TypeError`. """ self.assertRaises(TypeError, rand.add) self.assertRaises(TypeError, rand.add, b("foo"), None) self.assertRaises(TypeError, rand.add, None, 3) self.assertRaises(TypeError, rand.add, b("foo"), 3, None) def test_add(self): """ :py:obj:`OpenSSL.rand.add` adds entropy to the PRNG. """ rand.add(b('hamburger'), 3) def test_seed_wrong_args(self): """ When called with the wrong number of arguments, or with a non-:py:obj:`str` argument, :py:obj:`OpenSSL.rand.seed` raises :py:obj:`TypeError`. """ self.assertRaises(TypeError, rand.seed) self.assertRaises(TypeError, rand.seed, None) self.assertRaises(TypeError, rand.seed, b("foo"), None) def test_seed(self): """ :py:obj:`OpenSSL.rand.seed` adds entropy to the PRNG. """ rand.seed(b('milk shake')) def test_status_wrong_args(self): """ :py:obj:`OpenSSL.rand.status` raises :py:obj:`TypeError` when called with any arguments. """ self.assertRaises(TypeError, rand.status, None) def test_status(self): """ :py:obj:`OpenSSL.rand.status` returns :py:obj:`True` if the PRNG has sufficient entropy, :py:obj:`False` otherwise. """ # It's hard to know what it is actually going to return. Different # OpenSSL random engines decide differently whether they have enough # entropy or not. self.assertTrue(rand.status() in (1, 2)) def test_egd_wrong_args(self): """ :py:obj:`OpenSSL.rand.egd` raises :py:obj:`TypeError` when called with the wrong number of arguments or with arguments not of type :py:obj:`str` and :py:obj:`int`. """ self.assertRaises(TypeError, rand.egd) self.assertRaises(TypeError, rand.egd, None) self.assertRaises(TypeError, rand.egd, "foo", None) self.assertRaises(TypeError, rand.egd, None, 3) self.assertRaises(TypeError, rand.egd, "foo", 3, None) def test_egd_missing(self): """ :py:obj:`OpenSSL.rand.egd` returns :py:obj:`0` or :py:obj:`-1` if the EGD socket passed to it does not exist. """ result = rand.egd(self.mktemp()) expected = (-1, 0) self.assertTrue( result in expected, "%r not in %r" % (result, expected)) def test_egd_missing_and_bytes(self): """ :py:obj:`OpenSSL.rand.egd` returns :py:obj:`0` or :py:obj:`-1` if the EGD socket passed to it does not exist even if a size argument is explicitly passed. """ result = rand.egd(self.mktemp(), 1024) expected = (-1, 0) self.assertTrue( result in expected, "%r not in %r" % (result, expected)) def test_cleanup_wrong_args(self): """ :py:obj:`OpenSSL.rand.cleanup` raises :py:obj:`TypeError` when called with any arguments. """ self.assertRaises(TypeError, rand.cleanup, None) def test_cleanup(self): """ :py:obj:`OpenSSL.rand.cleanup` releases the memory used by the PRNG and returns :py:obj:`None`. """ self.assertIdentical(rand.cleanup(), None) def test_load_file_wrong_args(self): """ :py:obj:`OpenSSL.rand.load_file` raises :py:obj:`TypeError` when called the wrong number of arguments or arguments not of type :py:obj:`str` and :py:obj:`int`. """ self.assertRaises(TypeError, rand.load_file) self.assertRaises(TypeError, rand.load_file, "foo", None) self.assertRaises(TypeError, rand.load_file, None, 1) self.assertRaises(TypeError, rand.load_file, "foo", 1, None) def test_write_file_wrong_args(self): """ :py:obj:`OpenSSL.rand.write_file` raises :py:obj:`TypeError` when called with the wrong number of arguments or a non-:py:obj:`str` argument. """ self.assertRaises(TypeError, rand.write_file) self.assertRaises(TypeError, rand.write_file, None) self.assertRaises(TypeError, rand.write_file, "foo", None) def _read_write_test(self, path): """ Verify that ``rand.write_file`` and ``rand.load_file`` can be used. """ # Create the file so cleanup is more straightforward with open(path, "w"): pass try: # Write random bytes to a file rand.write_file(path) # Verify length of written file size = os.stat(path)[stat.ST_SIZE] self.assertEqual(1024, size) # Read random bytes from file rand.load_file(path) rand.load_file(path, 4) # specify a length finally: # Cleanup os.unlink(path) def test_bytes_paths(self): """ Random data can be saved and loaded to files with paths specified as bytes. """ path = self.mktemp() path += NON_ASCII.encode(sys.getfilesystemencoding()) self._read_write_test(path) def test_unicode_paths(self): """ Random data can be saved and loaded to files with paths specified as unicode. """ path = self.mktemp().decode('utf-8') + NON_ASCII self._read_write_test(path) if __name__ == '__main__': main() pyOpenSSL-0.15.1/OpenSSL/test/test_ssl.py0000644000076500000240000041622312513314057020360 0ustar hynekstaff00000000000000# Copyright (C) Jean-Paul Calderone # See LICENSE for details. """ Unit tests for :py:obj:`OpenSSL.SSL`. """ from gc import collect, get_referrers from errno import ECONNREFUSED, EINPROGRESS, EWOULDBLOCK, EPIPE, ESHUTDOWN from sys import platform, getfilesystemencoding from socket import SHUT_RDWR, error, socket from os import makedirs from os.path import join from unittest import main from weakref import ref from warnings import catch_warnings, simplefilter from six import PY3, text_type, u from OpenSSL.crypto import TYPE_RSA, FILETYPE_PEM from OpenSSL.crypto import PKey, X509, X509Extension, X509Store from OpenSSL.crypto import dump_privatekey, load_privatekey from OpenSSL.crypto import dump_certificate, load_certificate from OpenSSL.crypto import get_elliptic_curves from OpenSSL.SSL import OPENSSL_VERSION_NUMBER, SSLEAY_VERSION, SSLEAY_CFLAGS from OpenSSL.SSL import SSLEAY_PLATFORM, SSLEAY_DIR, SSLEAY_BUILT_ON from OpenSSL.SSL import SENT_SHUTDOWN, RECEIVED_SHUTDOWN from OpenSSL.SSL import ( SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD, TLSv1_1_METHOD, TLSv1_2_METHOD) from OpenSSL.SSL import OP_SINGLE_DH_USE, OP_NO_SSLv2, OP_NO_SSLv3 from OpenSSL.SSL import ( VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_CLIENT_ONCE, VERIFY_NONE) from OpenSSL.SSL import ( SESS_CACHE_OFF, SESS_CACHE_CLIENT, SESS_CACHE_SERVER, SESS_CACHE_BOTH, SESS_CACHE_NO_AUTO_CLEAR, SESS_CACHE_NO_INTERNAL_LOOKUP, SESS_CACHE_NO_INTERNAL_STORE, SESS_CACHE_NO_INTERNAL) from OpenSSL.SSL import ( Error, SysCallError, WantReadError, WantWriteError, ZeroReturnError) from OpenSSL.SSL import ( Context, ContextType, Session, Connection, ConnectionType, SSLeay_version) from OpenSSL._util import lib as _lib from OpenSSL.test.util import WARNING_TYPE_EXPECTED, NON_ASCII, TestCase, b from OpenSSL.test.test_crypto import ( cleartextCertificatePEM, cleartextPrivateKeyPEM, client_cert_pem, client_key_pem, server_cert_pem, server_key_pem, root_cert_pem) try: from OpenSSL.SSL import OP_NO_QUERY_MTU except ImportError: OP_NO_QUERY_MTU = None try: from OpenSSL.SSL import OP_COOKIE_EXCHANGE except ImportError: OP_COOKIE_EXCHANGE = None try: from OpenSSL.SSL import OP_NO_TICKET except ImportError: OP_NO_TICKET = None try: from OpenSSL.SSL import OP_NO_COMPRESSION except ImportError: OP_NO_COMPRESSION = None try: from OpenSSL.SSL import MODE_RELEASE_BUFFERS except ImportError: MODE_RELEASE_BUFFERS = None try: from OpenSSL.SSL import OP_NO_TLSv1, OP_NO_TLSv1_1, OP_NO_TLSv1_2 except ImportError: OP_NO_TLSv1 = OP_NO_TLSv1_1 = OP_NO_TLSv1_2 = None from OpenSSL.SSL import ( SSL_ST_CONNECT, SSL_ST_ACCEPT, SSL_ST_MASK, SSL_ST_INIT, SSL_ST_BEFORE, SSL_ST_OK, SSL_ST_RENEGOTIATE, SSL_CB_LOOP, SSL_CB_EXIT, SSL_CB_READ, SSL_CB_WRITE, SSL_CB_ALERT, SSL_CB_READ_ALERT, SSL_CB_WRITE_ALERT, SSL_CB_ACCEPT_LOOP, SSL_CB_ACCEPT_EXIT, SSL_CB_CONNECT_LOOP, SSL_CB_CONNECT_EXIT, SSL_CB_HANDSHAKE_START, SSL_CB_HANDSHAKE_DONE) # openssl dhparam 128 -out dh-128.pem (note that 128 is a small number of bits # to use) dhparam = """\ -----BEGIN DH PARAMETERS----- MBYCEQCobsg29c9WZP/54oAPcwiDAgEC -----END DH PARAMETERS----- """ def join_bytes_or_unicode(prefix, suffix): """ Join two path components of either ``bytes`` or ``unicode``. The return type is the same as the type of ``prefix``. """ # If the types are the same, nothing special is necessary. if type(prefix) == type(suffix): return join(prefix, suffix) # Otherwise, coerce suffix to the type of prefix. if isinstance(prefix, text_type): return join(prefix, suffix.decode(getfilesystemencoding())) else: return join(prefix, suffix.encode(getfilesystemencoding())) def verify_cb(conn, cert, errnum, depth, ok): return ok def socket_pair(): """ Establish and return a pair of network sockets connected to each other. """ # Connect a pair of sockets port = socket() port.bind(('', 0)) port.listen(1) client = socket() client.setblocking(False) client.connect_ex(("127.0.0.1", port.getsockname()[1])) client.setblocking(True) server = port.accept()[0] # Let's pass some unencrypted data to make sure our socket connection is # fine. Just one byte, so we don't have to worry about buffers getting # filled up or fragmentation. server.send(b("x")) assert client.recv(1024) == b("x") client.send(b("y")) assert server.recv(1024) == b("y") # Most of our callers want non-blocking sockets, make it easy for them. server.setblocking(False) client.setblocking(False) return (server, client) def handshake(client, server): conns = [client, server] while conns: for conn in conns: try: conn.do_handshake() except WantReadError: pass else: conns.remove(conn) def _create_certificate_chain(): """ Construct and return a chain of certificates. 1. A new self-signed certificate authority certificate (cacert) 2. A new intermediate certificate signed by cacert (icert) 3. A new server certificate signed by icert (scert) """ caext = X509Extension(b('basicConstraints'), False, b('CA:true')) # Step 1 cakey = PKey() cakey.generate_key(TYPE_RSA, 512) cacert = X509() cacert.get_subject().commonName = "Authority Certificate" cacert.set_issuer(cacert.get_subject()) cacert.set_pubkey(cakey) cacert.set_notBefore(b("20000101000000Z")) cacert.set_notAfter(b("20200101000000Z")) cacert.add_extensions([caext]) cacert.set_serial_number(0) cacert.sign(cakey, "sha1") # Step 2 ikey = PKey() ikey.generate_key(TYPE_RSA, 512) icert = X509() icert.get_subject().commonName = "Intermediate Certificate" icert.set_issuer(cacert.get_subject()) icert.set_pubkey(ikey) icert.set_notBefore(b("20000101000000Z")) icert.set_notAfter(b("20200101000000Z")) icert.add_extensions([caext]) icert.set_serial_number(0) icert.sign(cakey, "sha1") # Step 3 skey = PKey() skey.generate_key(TYPE_RSA, 512) scert = X509() scert.get_subject().commonName = "Server Certificate" scert.set_issuer(icert.get_subject()) scert.set_pubkey(skey) scert.set_notBefore(b("20000101000000Z")) scert.set_notAfter(b("20200101000000Z")) scert.add_extensions([ X509Extension(b('basicConstraints'), True, b('CA:false'))]) scert.set_serial_number(0) scert.sign(ikey, "sha1") return [(cakey, cacert), (ikey, icert), (skey, scert)] class _LoopbackMixin: """ Helper mixin which defines methods for creating a connected socket pair and for forcing two connected SSL sockets to talk to each other via memory BIOs. """ def _loopbackClientFactory(self, socket): client = Connection(Context(TLSv1_METHOD), socket) client.set_connect_state() return client def _loopbackServerFactory(self, socket): ctx = Context(TLSv1_METHOD) ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) server = Connection(ctx, socket) server.set_accept_state() return server def _loopback(self, serverFactory=None, clientFactory=None): if serverFactory is None: serverFactory = self._loopbackServerFactory if clientFactory is None: clientFactory = self._loopbackClientFactory (server, client) = socket_pair() server = serverFactory(server) client = clientFactory(client) handshake(client, server) server.setblocking(True) client.setblocking(True) return server, client def _interactInMemory(self, client_conn, server_conn): """ Try to read application bytes from each of the two :py:obj:`Connection` objects. Copy bytes back and forth between their send/receive buffers for as long as there is anything to copy. When there is nothing more to copy, return :py:obj:`None`. If one of them actually manages to deliver some application bytes, return a two-tuple of the connection from which the bytes were read and the bytes themselves. """ wrote = True while wrote: # Loop until neither side has anything to say wrote = False # Copy stuff from each side's send buffer to the other side's # receive buffer. for (read, write) in [(client_conn, server_conn), (server_conn, client_conn)]: # Give the side a chance to generate some more bytes, or # succeed. try: data = read.recv(2 ** 16) except WantReadError: # It didn't succeed, so we'll hope it generated some # output. pass else: # It did succeed, so we'll stop now and let the caller deal # with it. return (read, data) while True: # Keep copying as long as there's more stuff there. try: dirty = read.bio_read(4096) except WantReadError: # Okay, nothing more waiting to be sent. Stop # processing this send buffer. break else: # Keep track of the fact that someone generated some # output. wrote = True write.bio_write(dirty) def _handshakeInMemory(self, client_conn, server_conn): """ Perform the TLS handshake between two :py:class:`Connection` instances connected to each other via memory BIOs. """ client_conn.set_connect_state() server_conn.set_accept_state() for conn in [client_conn, server_conn]: try: conn.do_handshake() except WantReadError: pass self._interactInMemory(client_conn, server_conn) class VersionTests(TestCase): """ Tests for version information exposed by :py:obj:`OpenSSL.SSL.SSLeay_version` and :py:obj:`OpenSSL.SSL.OPENSSL_VERSION_NUMBER`. """ def test_OPENSSL_VERSION_NUMBER(self): """ :py:obj:`OPENSSL_VERSION_NUMBER` is an integer with status in the low byte and the patch, fix, minor, and major versions in the nibbles above that. """ self.assertTrue(isinstance(OPENSSL_VERSION_NUMBER, int)) def test_SSLeay_version(self): """ :py:obj:`SSLeay_version` takes a version type indicator and returns one of a number of version strings based on that indicator. """ versions = {} for t in [SSLEAY_VERSION, SSLEAY_CFLAGS, SSLEAY_BUILT_ON, SSLEAY_PLATFORM, SSLEAY_DIR]: version = SSLeay_version(t) versions[version] = t self.assertTrue(isinstance(version, bytes)) self.assertEqual(len(versions), 5) class ContextTests(TestCase, _LoopbackMixin): """ Unit tests for :py:obj:`OpenSSL.SSL.Context`. """ def test_method(self): """ :py:obj:`Context` can be instantiated with one of :py:obj:`SSLv2_METHOD`, :py:obj:`SSLv3_METHOD`, :py:obj:`SSLv23_METHOD`, :py:obj:`TLSv1_METHOD`, :py:obj:`TLSv1_1_METHOD`, or :py:obj:`TLSv1_2_METHOD`. """ methods = [ SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD] for meth in methods: Context(meth) maybe = [SSLv2_METHOD, TLSv1_1_METHOD, TLSv1_2_METHOD] for meth in maybe: try: Context(meth) except (Error, ValueError): # Some versions of OpenSSL have SSLv2 / TLSv1.1 / TLSv1.2, some # don't. Difficult to say in advance. pass self.assertRaises(TypeError, Context, "") self.assertRaises(ValueError, Context, 10) if not PY3: def test_method_long(self): """ On Python 2 :py:class:`Context` accepts values of type :py:obj:`long` as well as :py:obj:`int`. """ Context(long(TLSv1_METHOD)) def test_type(self): """ :py:obj:`Context` and :py:obj:`ContextType` refer to the same type object and can be used to create instances of that type. """ self.assertIdentical(Context, ContextType) self.assertConsistentType(Context, 'Context', TLSv1_METHOD) def test_use_privatekey(self): """ :py:obj:`Context.use_privatekey` takes an :py:obj:`OpenSSL.crypto.PKey` instance. """ key = PKey() key.generate_key(TYPE_RSA, 128) ctx = Context(TLSv1_METHOD) ctx.use_privatekey(key) self.assertRaises(TypeError, ctx.use_privatekey, "") def test_use_privatekey_file_missing(self): """ :py:obj:`Context.use_privatekey_file` raises :py:obj:`OpenSSL.SSL.Error` when passed the name of a file which does not exist. """ ctx = Context(TLSv1_METHOD) self.assertRaises(Error, ctx.use_privatekey_file, self.mktemp()) def _use_privatekey_file_test(self, pemfile, filetype): """ Verify that calling ``Context.use_privatekey_file`` with the given arguments does not raise an exception. """ key = PKey() key.generate_key(TYPE_RSA, 128) with open(pemfile, "wt") as pem: pem.write( dump_privatekey(FILETYPE_PEM, key).decode("ascii") ) ctx = Context(TLSv1_METHOD) ctx.use_privatekey_file(pemfile, filetype) def test_use_privatekey_file_bytes(self): """ A private key can be specified from a file by passing a ``bytes`` instance giving the file name to ``Context.use_privatekey_file``. """ self._use_privatekey_file_test( self.mktemp() + NON_ASCII.encode(getfilesystemencoding()), FILETYPE_PEM, ) def test_use_privatekey_file_unicode(self): """ A private key can be specified from a file by passing a ``unicode`` instance giving the file name to ``Context.use_privatekey_file``. """ self._use_privatekey_file_test( self.mktemp().decode(getfilesystemencoding()) + NON_ASCII, FILETYPE_PEM, ) if not PY3: def test_use_privatekey_file_long(self): """ On Python 2 :py:obj:`Context.use_privatekey_file` accepts a filetype of type :py:obj:`long` as well as :py:obj:`int`. """ self._use_privatekey_file_test(self.mktemp(), long(FILETYPE_PEM)) def test_use_certificate_wrong_args(self): """ :py:obj:`Context.use_certificate_wrong_args` raises :py:obj:`TypeError` when not passed exactly one :py:obj:`OpenSSL.crypto.X509` instance as an argument. """ ctx = Context(TLSv1_METHOD) self.assertRaises(TypeError, ctx.use_certificate) self.assertRaises(TypeError, ctx.use_certificate, "hello, world") self.assertRaises(TypeError, ctx.use_certificate, X509(), "hello, world") def test_use_certificate_uninitialized(self): """ :py:obj:`Context.use_certificate` raises :py:obj:`OpenSSL.SSL.Error` when passed a :py:obj:`OpenSSL.crypto.X509` instance which has not been initialized (ie, which does not actually have any certificate data). """ ctx = Context(TLSv1_METHOD) self.assertRaises(Error, ctx.use_certificate, X509()) def test_use_certificate(self): """ :py:obj:`Context.use_certificate` sets the certificate which will be used to identify connections created using the context. """ # TODO # Hard to assert anything. But we could set a privatekey then ask # OpenSSL if the cert and key agree using check_privatekey. Then as # long as check_privatekey works right we're good... ctx = Context(TLSv1_METHOD) ctx.use_certificate(load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) def test_use_certificate_file_wrong_args(self): """ :py:obj:`Context.use_certificate_file` raises :py:obj:`TypeError` if called with zero arguments or more than two arguments, or if the first argument is not a byte string or the second argumnent is not an integer. """ ctx = Context(TLSv1_METHOD) self.assertRaises(TypeError, ctx.use_certificate_file) self.assertRaises(TypeError, ctx.use_certificate_file, b"somefile", object()) self.assertRaises( TypeError, ctx.use_certificate_file, b"somefile", FILETYPE_PEM, object()) self.assertRaises( TypeError, ctx.use_certificate_file, object(), FILETYPE_PEM) self.assertRaises( TypeError, ctx.use_certificate_file, b"somefile", object()) def test_use_certificate_file_missing(self): """ :py:obj:`Context.use_certificate_file` raises `:py:obj:`OpenSSL.SSL.Error` if passed the name of a file which does not exist. """ ctx = Context(TLSv1_METHOD) self.assertRaises(Error, ctx.use_certificate_file, self.mktemp()) def _use_certificate_file_test(self, certificate_file): """ Verify that calling ``Context.use_certificate_file`` with the given filename doesn't raise an exception. """ # TODO # Hard to assert anything. But we could set a privatekey then ask # OpenSSL if the cert and key agree using check_privatekey. Then as # long as check_privatekey works right we're good... with open(certificate_file, "wb") as pem_file: pem_file.write(cleartextCertificatePEM) ctx = Context(TLSv1_METHOD) ctx.use_certificate_file(certificate_file) def test_use_certificate_file_bytes(self): """ :py:obj:`Context.use_certificate_file` sets the certificate (given as a ``bytes`` filename) which will be used to identify connections created using the context. """ filename = self.mktemp() + NON_ASCII.encode(getfilesystemencoding()) self._use_certificate_file_test(filename) def test_use_certificate_file_unicode(self): """ :py:obj:`Context.use_certificate_file` sets the certificate (given as a ``bytes`` filename) which will be used to identify connections created using the context. """ filename = self.mktemp().decode(getfilesystemencoding()) + NON_ASCII self._use_certificate_file_test(filename) if not PY3: def test_use_certificate_file_long(self): """ On Python 2 :py:obj:`Context.use_certificate_file` accepts a filetype of type :py:obj:`long` as well as :py:obj:`int`. """ pem_filename = self.mktemp() with open(pem_filename, "wb") as pem_file: pem_file.write(cleartextCertificatePEM) ctx = Context(TLSv1_METHOD) ctx.use_certificate_file(pem_filename, long(FILETYPE_PEM)) def test_check_privatekey_valid(self): """ :py:obj:`Context.check_privatekey` returns :py:obj:`None` if the :py:obj:`Context` instance has been configured to use a matched key and certificate pair. """ key = load_privatekey(FILETYPE_PEM, client_key_pem) cert = load_certificate(FILETYPE_PEM, client_cert_pem) context = Context(TLSv1_METHOD) context.use_privatekey(key) context.use_certificate(cert) self.assertIs(None, context.check_privatekey()) def test_check_privatekey_invalid(self): """ :py:obj:`Context.check_privatekey` raises :py:obj:`Error` if the :py:obj:`Context` instance has been configured to use a key and certificate pair which don't relate to each other. """ key = load_privatekey(FILETYPE_PEM, client_key_pem) cert = load_certificate(FILETYPE_PEM, server_cert_pem) context = Context(TLSv1_METHOD) context.use_privatekey(key) context.use_certificate(cert) self.assertRaises(Error, context.check_privatekey) def test_check_privatekey_wrong_args(self): """ :py:obj:`Context.check_privatekey` raises :py:obj:`TypeError` if called with other than no arguments. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.check_privatekey, object()) def test_set_app_data_wrong_args(self): """ :py:obj:`Context.set_app_data` raises :py:obj:`TypeError` if called with other than one argument. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.set_app_data) self.assertRaises(TypeError, context.set_app_data, None, None) def test_get_app_data_wrong_args(self): """ :py:obj:`Context.get_app_data` raises :py:obj:`TypeError` if called with any arguments. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.get_app_data, None) def test_app_data(self): """ :py:obj:`Context.set_app_data` stores an object for later retrieval using :py:obj:`Context.get_app_data`. """ app_data = object() context = Context(TLSv1_METHOD) context.set_app_data(app_data) self.assertIdentical(context.get_app_data(), app_data) def test_set_options_wrong_args(self): """ :py:obj:`Context.set_options` raises :py:obj:`TypeError` if called with the wrong number of arguments or a non-:py:obj:`int` argument. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.set_options) self.assertRaises(TypeError, context.set_options, None) self.assertRaises(TypeError, context.set_options, 1, None) def test_set_options(self): """ :py:obj:`Context.set_options` returns the new options value. """ context = Context(TLSv1_METHOD) options = context.set_options(OP_NO_SSLv2) self.assertTrue(OP_NO_SSLv2 & options) if not PY3: def test_set_options_long(self): """ On Python 2 :py:obj:`Context.set_options` accepts values of type :py:obj:`long` as well as :py:obj:`int`. """ context = Context(TLSv1_METHOD) options = context.set_options(long(OP_NO_SSLv2)) self.assertTrue(OP_NO_SSLv2 & options) def test_set_mode_wrong_args(self): """ :py:obj:`Context.set`mode} raises :py:obj:`TypeError` if called with the wrong number of arguments or a non-:py:obj:`int` argument. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.set_mode) self.assertRaises(TypeError, context.set_mode, None) self.assertRaises(TypeError, context.set_mode, 1, None) if MODE_RELEASE_BUFFERS is not None: def test_set_mode(self): """ :py:obj:`Context.set_mode` accepts a mode bitvector and returns the newly set mode. """ context = Context(TLSv1_METHOD) self.assertTrue( MODE_RELEASE_BUFFERS & context.set_mode(MODE_RELEASE_BUFFERS)) if not PY3: def test_set_mode_long(self): """ On Python 2 :py:obj:`Context.set_mode` accepts values of type :py:obj:`long` as well as :py:obj:`int`. """ context = Context(TLSv1_METHOD) mode = context.set_mode(long(MODE_RELEASE_BUFFERS)) self.assertTrue(MODE_RELEASE_BUFFERS & mode) else: "MODE_RELEASE_BUFFERS unavailable - OpenSSL version may be too old" def test_set_timeout_wrong_args(self): """ :py:obj:`Context.set_timeout` raises :py:obj:`TypeError` if called with the wrong number of arguments or a non-:py:obj:`int` argument. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.set_timeout) self.assertRaises(TypeError, context.set_timeout, None) self.assertRaises(TypeError, context.set_timeout, 1, None) def test_get_timeout_wrong_args(self): """ :py:obj:`Context.get_timeout` raises :py:obj:`TypeError` if called with any arguments. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.get_timeout, None) def test_timeout(self): """ :py:obj:`Context.set_timeout` sets the session timeout for all connections created using the context object. :py:obj:`Context.get_timeout` retrieves this value. """ context = Context(TLSv1_METHOD) context.set_timeout(1234) self.assertEquals(context.get_timeout(), 1234) if not PY3: def test_timeout_long(self): """ On Python 2 :py:obj:`Context.set_timeout` accepts values of type `long` as well as int. """ context = Context(TLSv1_METHOD) context.set_timeout(long(1234)) self.assertEquals(context.get_timeout(), 1234) def test_set_verify_depth_wrong_args(self): """ :py:obj:`Context.set_verify_depth` raises :py:obj:`TypeError` if called with the wrong number of arguments or a non-:py:obj:`int` argument. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.set_verify_depth) self.assertRaises(TypeError, context.set_verify_depth, None) self.assertRaises(TypeError, context.set_verify_depth, 1, None) def test_get_verify_depth_wrong_args(self): """ :py:obj:`Context.get_verify_depth` raises :py:obj:`TypeError` if called with any arguments. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.get_verify_depth, None) def test_verify_depth(self): """ :py:obj:`Context.set_verify_depth` sets the number of certificates in a chain to follow before giving up. The value can be retrieved with :py:obj:`Context.get_verify_depth`. """ context = Context(TLSv1_METHOD) context.set_verify_depth(11) self.assertEquals(context.get_verify_depth(), 11) if not PY3: def test_verify_depth_long(self): """ On Python 2 :py:obj:`Context.set_verify_depth` accepts values of type `long` as well as int. """ context = Context(TLSv1_METHOD) context.set_verify_depth(long(11)) self.assertEquals(context.get_verify_depth(), 11) def _write_encrypted_pem(self, passphrase): """ Write a new private key out to a new file, encrypted using the given passphrase. Return the path to the new file. """ key = PKey() key.generate_key(TYPE_RSA, 128) pemFile = self.mktemp() fObj = open(pemFile, 'w') pem = dump_privatekey(FILETYPE_PEM, key, "blowfish", passphrase) fObj.write(pem.decode('ascii')) fObj.close() return pemFile def test_set_passwd_cb_wrong_args(self): """ :py:obj:`Context.set_passwd_cb` raises :py:obj:`TypeError` if called with the wrong arguments or with a non-callable first argument. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.set_passwd_cb) self.assertRaises(TypeError, context.set_passwd_cb, None) self.assertRaises(TypeError, context.set_passwd_cb, lambda: None, None, None) def test_set_passwd_cb(self): """ :py:obj:`Context.set_passwd_cb` accepts a callable which will be invoked when a private key is loaded from an encrypted PEM. """ passphrase = b("foobar") pemFile = self._write_encrypted_pem(passphrase) calledWith = [] def passphraseCallback(maxlen, verify, extra): calledWith.append((maxlen, verify, extra)) return passphrase context = Context(TLSv1_METHOD) context.set_passwd_cb(passphraseCallback) context.use_privatekey_file(pemFile) self.assertTrue(len(calledWith), 1) self.assertTrue(isinstance(calledWith[0][0], int)) self.assertTrue(isinstance(calledWith[0][1], int)) self.assertEqual(calledWith[0][2], None) def test_passwd_callback_exception(self): """ :py:obj:`Context.use_privatekey_file` propagates any exception raised by the passphrase callback. """ pemFile = self._write_encrypted_pem(b("monkeys are nice")) def passphraseCallback(maxlen, verify, extra): raise RuntimeError("Sorry, I am a fail.") context = Context(TLSv1_METHOD) context.set_passwd_cb(passphraseCallback) self.assertRaises(RuntimeError, context.use_privatekey_file, pemFile) def test_passwd_callback_false(self): """ :py:obj:`Context.use_privatekey_file` raises :py:obj:`OpenSSL.SSL.Error` if the passphrase callback returns a false value. """ pemFile = self._write_encrypted_pem(b("monkeys are nice")) def passphraseCallback(maxlen, verify, extra): return b"" context = Context(TLSv1_METHOD) context.set_passwd_cb(passphraseCallback) self.assertRaises(Error, context.use_privatekey_file, pemFile) def test_passwd_callback_non_string(self): """ :py:obj:`Context.use_privatekey_file` raises :py:obj:`OpenSSL.SSL.Error` if the passphrase callback returns a true non-string value. """ pemFile = self._write_encrypted_pem(b("monkeys are nice")) def passphraseCallback(maxlen, verify, extra): return 10 context = Context(TLSv1_METHOD) context.set_passwd_cb(passphraseCallback) self.assertRaises(ValueError, context.use_privatekey_file, pemFile) def test_passwd_callback_too_long(self): """ If the passphrase returned by the passphrase callback returns a string longer than the indicated maximum length, it is truncated. """ # A priori knowledge! passphrase = b("x") * 1024 pemFile = self._write_encrypted_pem(passphrase) def passphraseCallback(maxlen, verify, extra): assert maxlen == 1024 return passphrase + b("y") context = Context(TLSv1_METHOD) context.set_passwd_cb(passphraseCallback) # This shall succeed because the truncated result is the correct # passphrase. context.use_privatekey_file(pemFile) def test_set_info_callback(self): """ :py:obj:`Context.set_info_callback` accepts a callable which will be invoked when certain information about an SSL connection is available. """ (server, client) = socket_pair() clientSSL = Connection(Context(TLSv1_METHOD), client) clientSSL.set_connect_state() called = [] def info(conn, where, ret): called.append((conn, where, ret)) context = Context(TLSv1_METHOD) context.set_info_callback(info) context.use_certificate( load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) context.use_privatekey( load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) serverSSL = Connection(context, server) serverSSL.set_accept_state() handshake(clientSSL, serverSSL) # The callback must always be called with a Connection instance as the # first argument. It would probably be better to split this into # separate tests for client and server side info callbacks so we could # assert it is called with the right Connection instance. It would # also be good to assert *something* about `where` and `ret`. notConnections = [ conn for (conn, where, ret) in called if not isinstance(conn, Connection)] self.assertEqual( [], notConnections, "Some info callback arguments were not Connection instaces.") def _load_verify_locations_test(self, *args): """ Create a client context which will verify the peer certificate and call its :py:obj:`load_verify_locations` method with the given arguments. Then connect it to a server and ensure that the handshake succeeds. """ (server, client) = socket_pair() clientContext = Context(TLSv1_METHOD) clientContext.load_verify_locations(*args) # Require that the server certificate verify properly or the # connection will fail. clientContext.set_verify( VERIFY_PEER, lambda conn, cert, errno, depth, preverify_ok: preverify_ok) clientSSL = Connection(clientContext, client) clientSSL.set_connect_state() serverContext = Context(TLSv1_METHOD) serverContext.use_certificate( load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) serverContext.use_privatekey( load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) serverSSL = Connection(serverContext, server) serverSSL.set_accept_state() # Without load_verify_locations above, the handshake # will fail: # Error: [('SSL routines', 'SSL3_GET_SERVER_CERTIFICATE', # 'certificate verify failed')] handshake(clientSSL, serverSSL) cert = clientSSL.get_peer_certificate() self.assertEqual(cert.get_subject().CN, 'Testing Root CA') def _load_verify_cafile(self, cafile): """ Verify that if path to a file containing a certificate is passed to ``Context.load_verify_locations`` for the ``cafile`` parameter, that certificate is used as a trust root for the purposes of verifying connections created using that ``Context``. """ fObj = open(cafile, 'w') fObj.write(cleartextCertificatePEM.decode('ascii')) fObj.close() self._load_verify_locations_test(cafile) def test_load_verify_bytes_cafile(self): """ :py:obj:`Context.load_verify_locations` accepts a file name as a ``bytes`` instance and uses the certificates within for verification purposes. """ cafile = self.mktemp() + NON_ASCII.encode(getfilesystemencoding()) self._load_verify_cafile(cafile) def test_load_verify_unicode_cafile(self): """ :py:obj:`Context.load_verify_locations` accepts a file name as a ``unicode`` instance and uses the certificates within for verification purposes. """ self._load_verify_cafile( self.mktemp().decode(getfilesystemencoding()) + NON_ASCII ) def test_load_verify_invalid_file(self): """ :py:obj:`Context.load_verify_locations` raises :py:obj:`Error` when passed a non-existent cafile. """ clientContext = Context(TLSv1_METHOD) self.assertRaises( Error, clientContext.load_verify_locations, self.mktemp()) def _load_verify_directory_locations_capath(self, capath): """ Verify that if path to a directory containing certificate files is passed to ``Context.load_verify_locations`` for the ``capath`` parameter, those certificates are used as trust roots for the purposes of verifying connections created using that ``Context``. """ makedirs(capath) # Hash values computed manually with c_rehash to avoid depending on # c_rehash in the test suite. One is from OpenSSL 0.9.8, the other # from OpenSSL 1.0.0. for name in [b'c7adac82.0', b'c3705638.0']: cafile = join_bytes_or_unicode(capath, name) with open(cafile, 'w') as fObj: fObj.write(cleartextCertificatePEM.decode('ascii')) self._load_verify_locations_test(None, capath) def test_load_verify_directory_bytes_capath(self): """ :py:obj:`Context.load_verify_locations` accepts a directory name as a ``bytes`` instance and uses the certificates within for verification purposes. """ self._load_verify_directory_locations_capath( self.mktemp() + NON_ASCII.encode(getfilesystemencoding()) ) def test_load_verify_directory_unicode_capath(self): """ :py:obj:`Context.load_verify_locations` accepts a directory name as a ``unicode`` instance and uses the certificates within for verification purposes. """ self._load_verify_directory_locations_capath( self.mktemp().decode(getfilesystemencoding()) + NON_ASCII ) def test_load_verify_locations_wrong_args(self): """ :py:obj:`Context.load_verify_locations` raises :py:obj:`TypeError` if called with the wrong number of arguments or with non-:py:obj:`str` arguments. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.load_verify_locations) self.assertRaises(TypeError, context.load_verify_locations, object()) self.assertRaises(TypeError, context.load_verify_locations, object(), object()) self.assertRaises(TypeError, context.load_verify_locations, None, None, None) if platform == "win32": "set_default_verify_paths appears not to work on Windows. " "See LP#404343 and LP#404344." else: def test_set_default_verify_paths(self): """ :py:obj:`Context.set_default_verify_paths` causes the platform-specific CA certificate locations to be used for verification purposes. """ # Testing this requires a server with a certificate signed by one of # the CAs in the platform CA location. Getting one of those costs # money. Fortunately (or unfortunately, depending on your # perspective), it's easy to think of a public server on the # internet which has such a certificate. Connecting to the network # in a unit test is bad, but it's the only way I can think of to # really test this. -exarkun # Arg, verisign.com doesn't speak anything newer than TLS 1.0 context = Context(TLSv1_METHOD) context.set_default_verify_paths() context.set_verify( VERIFY_PEER, lambda conn, cert, errno, depth, preverify_ok: preverify_ok) client = socket() client.connect(('verisign.com', 443)) clientSSL = Connection(context, client) clientSSL.set_connect_state() clientSSL.do_handshake() clientSSL.send(b"GET / HTTP/1.0\r\n\r\n") self.assertTrue(clientSSL.recv(1024)) def test_set_default_verify_paths_signature(self): """ :py:obj:`Context.set_default_verify_paths` takes no arguments and raises :py:obj:`TypeError` if given any. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.set_default_verify_paths, None) self.assertRaises(TypeError, context.set_default_verify_paths, 1) self.assertRaises(TypeError, context.set_default_verify_paths, "") def test_add_extra_chain_cert_invalid_cert(self): """ :py:obj:`Context.add_extra_chain_cert` raises :py:obj:`TypeError` if called with other than one argument or if called with an object which is not an instance of :py:obj:`X509`. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.add_extra_chain_cert) self.assertRaises(TypeError, context.add_extra_chain_cert, object()) self.assertRaises(TypeError, context.add_extra_chain_cert, object(), object()) def _handshake_test(self, serverContext, clientContext): """ Verify that a client and server created with the given contexts can successfully handshake and communicate. """ serverSocket, clientSocket = socket_pair() server = Connection(serverContext, serverSocket) server.set_accept_state() client = Connection(clientContext, clientSocket) client.set_connect_state() # Make them talk to each other. # self._interactInMemory(client, server) for i in range(3): for s in [client, server]: try: s.do_handshake() except WantReadError: pass def test_set_verify_callback_connection_argument(self): """ The first argument passed to the verify callback is the :py:class:`Connection` instance for which verification is taking place. """ serverContext = Context(TLSv1_METHOD) serverContext.use_privatekey( load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) serverContext.use_certificate( load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) serverConnection = Connection(serverContext, None) class VerifyCallback(object): def callback(self, connection, *args): self.connection = connection return 1 verify = VerifyCallback() clientContext = Context(TLSv1_METHOD) clientContext.set_verify(VERIFY_PEER, verify.callback) clientConnection = Connection(clientContext, None) clientConnection.set_connect_state() self._handshakeInMemory(clientConnection, serverConnection) self.assertIdentical(verify.connection, clientConnection) def test_set_verify_callback_exception(self): """ If the verify callback passed to :py:obj:`Context.set_verify` raises an exception, verification fails and the exception is propagated to the caller of :py:obj:`Connection.do_handshake`. """ serverContext = Context(TLSv1_METHOD) serverContext.use_privatekey( load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) serverContext.use_certificate( load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) clientContext = Context(TLSv1_METHOD) def verify_callback(*args): raise Exception("silly verify failure") clientContext.set_verify(VERIFY_PEER, verify_callback) exc = self.assertRaises( Exception, self._handshake_test, serverContext, clientContext) self.assertEqual("silly verify failure", str(exc)) def test_add_extra_chain_cert(self): """ :py:obj:`Context.add_extra_chain_cert` accepts an :py:obj:`X509` instance to add to the certificate chain. See :py:obj:`_create_certificate_chain` for the details of the certificate chain tested. The chain is tested by starting a server with scert and connecting to it with a client which trusts cacert and requires verification to succeed. """ chain = _create_certificate_chain() [(cakey, cacert), (ikey, icert), (skey, scert)] = chain # Dump the CA certificate to a file because that's the only way to load # it as a trusted CA in the client context. for cert, name in [(cacert, 'ca.pem'), (icert, 'i.pem'), (scert, 's.pem')]: fObj = open(name, 'w') fObj.write(dump_certificate(FILETYPE_PEM, cert).decode('ascii')) fObj.close() for key, name in [(cakey, 'ca.key'), (ikey, 'i.key'), (skey, 's.key')]: fObj = open(name, 'w') fObj.write(dump_privatekey(FILETYPE_PEM, key).decode('ascii')) fObj.close() # Create the server context serverContext = Context(TLSv1_METHOD) serverContext.use_privatekey(skey) serverContext.use_certificate(scert) # The client already has cacert, we only need to give them icert. serverContext.add_extra_chain_cert(icert) # Create the client clientContext = Context(TLSv1_METHOD) clientContext.set_verify( VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) clientContext.load_verify_locations(b"ca.pem") # Try it out. self._handshake_test(serverContext, clientContext) def _use_certificate_chain_file_test(self, certdir): """ Verify that :py:obj:`Context.use_certificate_chain_file` reads a certificate chain from a specified file. The chain is tested by starting a server with scert and connecting to it with a client which trusts cacert and requires verification to succeed. """ chain = _create_certificate_chain() [(cakey, cacert), (ikey, icert), (skey, scert)] = chain makedirs(certdir) chainFile = join_bytes_or_unicode(certdir, "chain.pem") caFile = join_bytes_or_unicode(certdir, "ca.pem") # Write out the chain file. with open(chainFile, 'wb') as fObj: # Most specific to least general. fObj.write(dump_certificate(FILETYPE_PEM, scert)) fObj.write(dump_certificate(FILETYPE_PEM, icert)) fObj.write(dump_certificate(FILETYPE_PEM, cacert)) with open(caFile, 'w') as fObj: fObj.write(dump_certificate(FILETYPE_PEM, cacert).decode('ascii')) serverContext = Context(TLSv1_METHOD) serverContext.use_certificate_chain_file(chainFile) serverContext.use_privatekey(skey) clientContext = Context(TLSv1_METHOD) clientContext.set_verify( VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) clientContext.load_verify_locations(caFile) self._handshake_test(serverContext, clientContext) def test_use_certificate_chain_file_bytes(self): """ ``Context.use_certificate_chain_file`` accepts the name of a file (as an instance of ``bytes``) to specify additional certificates to use to construct and verify a trust chain. """ self._use_certificate_chain_file_test( self.mktemp() + NON_ASCII.encode(getfilesystemencoding()) ) def test_use_certificate_chain_file_unicode(self): """ ``Context.use_certificate_chain_file`` accepts the name of a file (as an instance of ``unicode``) to specify additional certificates to use to construct and verify a trust chain. """ self._use_certificate_chain_file_test( self.mktemp().decode(getfilesystemencoding()) + NON_ASCII ) def test_use_certificate_chain_file_wrong_args(self): """ :py:obj:`Context.use_certificate_chain_file` raises :py:obj:`TypeError` if passed zero or more than one argument or when passed a non-byte string single argument. It also raises :py:obj:`OpenSSL.SSL.Error` when passed a bad chain file name (for example, the name of a file which does not exist). """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.use_certificate_chain_file) self.assertRaises(TypeError, context.use_certificate_chain_file, object()) self.assertRaises(TypeError, context.use_certificate_chain_file, b"foo", object()) self.assertRaises(Error, context.use_certificate_chain_file, self.mktemp()) # XXX load_client_ca # XXX set_session_id def test_get_verify_mode_wrong_args(self): """ :py:obj:`Context.get_verify_mode` raises :py:obj:`TypeError` if called with any arguments. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.get_verify_mode, None) def test_set_verify_mode(self): """ :py:obj:`Context.get_verify_mode` returns the verify mode flags previously passed to :py:obj:`Context.set_verify`. """ context = Context(TLSv1_METHOD) self.assertEquals(context.get_verify_mode(), 0) context.set_verify( VERIFY_PEER | VERIFY_CLIENT_ONCE, lambda *args: None) self.assertEquals( context.get_verify_mode(), VERIFY_PEER | VERIFY_CLIENT_ONCE) if not PY3: def test_set_verify_mode_long(self): """ On Python 2 :py:obj:`Context.set_verify_mode` accepts values of type :py:obj:`long` as well as :py:obj:`int`. """ context = Context(TLSv1_METHOD) self.assertEquals(context.get_verify_mode(), 0) context.set_verify( long(VERIFY_PEER | VERIFY_CLIENT_ONCE), lambda *args: None) self.assertEquals( context.get_verify_mode(), VERIFY_PEER | VERIFY_CLIENT_ONCE) def test_load_tmp_dh_wrong_args(self): """ :py:obj:`Context.load_tmp_dh` raises :py:obj:`TypeError` if called with the wrong number of arguments or with a non-:py:obj:`str` argument. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.load_tmp_dh) self.assertRaises(TypeError, context.load_tmp_dh, "foo", None) self.assertRaises(TypeError, context.load_tmp_dh, object()) def test_load_tmp_dh_missing_file(self): """ :py:obj:`Context.load_tmp_dh` raises :py:obj:`OpenSSL.SSL.Error` if the specified file does not exist. """ context = Context(TLSv1_METHOD) self.assertRaises(Error, context.load_tmp_dh, b"hello") def _load_tmp_dh_test(self, dhfilename): """ Verify that calling ``Context.load_tmp_dh`` with the given filename does not raise an exception. """ context = Context(TLSv1_METHOD) with open(dhfilename, "w") as dhfile: dhfile.write(dhparam) context.load_tmp_dh(dhfilename) # XXX What should I assert here? -exarkun def test_load_tmp_dh_bytes(self): """ :py:obj:`Context.load_tmp_dh` loads Diffie-Hellman parameters from the specified file (given as ``bytes``). """ self._load_tmp_dh_test( self.mktemp() + NON_ASCII.encode(getfilesystemencoding()), ) def test_load_tmp_dh_unicode(self): """ :py:obj:`Context.load_tmp_dh` loads Diffie-Hellman parameters from the specified file (given as ``unicode``). """ self._load_tmp_dh_test( self.mktemp().decode(getfilesystemencoding()) + NON_ASCII, ) def test_set_tmp_ecdh(self): """ :py:obj:`Context.set_tmp_ecdh` sets the elliptic curve for Diffie-Hellman to the specified curve. """ context = Context(TLSv1_METHOD) for curve in get_elliptic_curves(): # The only easily "assertable" thing is that it does not raise an # exception. context.set_tmp_ecdh(curve) def test_set_cipher_list_bytes(self): """ :py:obj:`Context.set_cipher_list` accepts a :py:obj:`bytes` naming the ciphers which connections created with the context object will be able to choose from. """ context = Context(TLSv1_METHOD) context.set_cipher_list(b"hello world:EXP-RC4-MD5") conn = Connection(context, None) self.assertEquals(conn.get_cipher_list(), ["EXP-RC4-MD5"]) def test_set_cipher_list_text(self): """ :py:obj:`Context.set_cipher_list` accepts a :py:obj:`unicode` naming the ciphers which connections created with the context object will be able to choose from. """ context = Context(TLSv1_METHOD) context.set_cipher_list(u("hello world:EXP-RC4-MD5")) conn = Connection(context, None) self.assertEquals(conn.get_cipher_list(), ["EXP-RC4-MD5"]) def test_set_cipher_list_wrong_args(self): """ :py:obj:`Context.set_cipher_list` raises :py:obj:`TypeError` when passed zero arguments or more than one argument or when passed a non-string single argument and raises :py:obj:`OpenSSL.SSL.Error` when passed an incorrect cipher list string. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.set_cipher_list) self.assertRaises(TypeError, context.set_cipher_list, object()) self.assertRaises(TypeError, context.set_cipher_list, b"EXP-RC4-MD5", object()) self.assertRaises(Error, context.set_cipher_list, "imaginary-cipher") def test_set_session_cache_mode_wrong_args(self): """ :py:obj:`Context.set_session_cache_mode` raises :py:obj:`TypeError` if called with other than one integer argument. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.set_session_cache_mode) self.assertRaises(TypeError, context.set_session_cache_mode, object()) def test_get_session_cache_mode_wrong_args(self): """ :py:obj:`Context.get_session_cache_mode` raises :py:obj:`TypeError` if called with any arguments. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.get_session_cache_mode, 1) def test_session_cache_mode(self): """ :py:obj:`Context.set_session_cache_mode` specifies how sessions are cached. The setting can be retrieved via :py:obj:`Context.get_session_cache_mode`. """ context = Context(TLSv1_METHOD) context.set_session_cache_mode(SESS_CACHE_OFF) off = context.set_session_cache_mode(SESS_CACHE_BOTH) self.assertEqual(SESS_CACHE_OFF, off) self.assertEqual(SESS_CACHE_BOTH, context.get_session_cache_mode()) if not PY3: def test_session_cache_mode_long(self): """ On Python 2 :py:obj:`Context.set_session_cache_mode` accepts values of type :py:obj:`long` as well as :py:obj:`int`. """ context = Context(TLSv1_METHOD) context.set_session_cache_mode(long(SESS_CACHE_BOTH)) self.assertEqual( SESS_CACHE_BOTH, context.get_session_cache_mode()) def test_get_cert_store(self): """ :py:obj:`Context.get_cert_store` returns a :py:obj:`X509Store` instance. """ context = Context(TLSv1_METHOD) store = context.get_cert_store() self.assertIsInstance(store, X509Store) class ServerNameCallbackTests(TestCase, _LoopbackMixin): """ Tests for :py:obj:`Context.set_tlsext_servername_callback` and its interaction with :py:obj:`Connection`. """ def test_wrong_args(self): """ :py:obj:`Context.set_tlsext_servername_callback` raises :py:obj:`TypeError` if called with other than one argument. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.set_tlsext_servername_callback) self.assertRaises( TypeError, context.set_tlsext_servername_callback, 1, 2) def test_old_callback_forgotten(self): """ If :py:obj:`Context.set_tlsext_servername_callback` is used to specify a new callback, the one it replaces is dereferenced. """ def callback(connection): pass def replacement(connection): pass context = Context(TLSv1_METHOD) context.set_tlsext_servername_callback(callback) tracker = ref(callback) del callback context.set_tlsext_servername_callback(replacement) # One run of the garbage collector happens to work on CPython. PyPy # doesn't collect the underlying object until a second run for whatever # reason. That's fine, it still demonstrates our code has properly # dropped the reference. collect() collect() callback = tracker() if callback is not None: referrers = get_referrers(callback) if len(referrers) > 1: self.fail("Some references remain: %r" % (referrers,)) def test_no_servername(self): """ When a client specifies no server name, the callback passed to :py:obj:`Context.set_tlsext_servername_callback` is invoked and the result of :py:obj:`Connection.get_servername` is :py:obj:`None`. """ args = [] def servername(conn): args.append((conn, conn.get_servername())) context = Context(TLSv1_METHOD) context.set_tlsext_servername_callback(servername) # Lose our reference to it. The Context is responsible for keeping it # alive now. del servername collect() # Necessary to actually accept the connection context.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) context.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(context, None) server.set_accept_state() client = Connection(Context(TLSv1_METHOD), None) client.set_connect_state() self._interactInMemory(server, client) self.assertEqual([(server, None)], args) def test_servername(self): """ When a client specifies a server name in its hello message, the callback passed to :py:obj:`Contexts.set_tlsext_servername_callback` is invoked and the result of :py:obj:`Connection.get_servername` is that server name. """ args = [] def servername(conn): args.append((conn, conn.get_servername())) context = Context(TLSv1_METHOD) context.set_tlsext_servername_callback(servername) # Necessary to actually accept the connection context.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) context.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(context, None) server.set_accept_state() client = Connection(Context(TLSv1_METHOD), None) client.set_connect_state() client.set_tlsext_host_name(b("foo1.example.com")) self._interactInMemory(server, client) self.assertEqual([(server, b("foo1.example.com"))], args) class NextProtoNegotiationTests(TestCase, _LoopbackMixin): """ Test for Next Protocol Negotiation in PyOpenSSL. """ if _lib.Cryptography_HAS_NEXTPROTONEG: def test_npn_success(self): """ Tests that clients and servers that agree on the negotiated next protocol can correct establish a connection, and that the agreed protocol is reported by the connections. """ advertise_args = [] select_args = [] def advertise(conn): advertise_args.append((conn,)) return [b'http/1.1', b'spdy/2'] def select(conn, options): select_args.append((conn, options)) return b'spdy/2' server_context = Context(TLSv1_METHOD) server_context.set_npn_advertise_callback(advertise) client_context = Context(TLSv1_METHOD) client_context.set_npn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem)) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() self._interactInMemory(server, client) self.assertEqual([(server,)], advertise_args) self.assertEqual([(client, [b'http/1.1', b'spdy/2'])], select_args) self.assertEqual(server.get_next_proto_negotiated(), b'spdy/2') self.assertEqual(client.get_next_proto_negotiated(), b'spdy/2') def test_npn_client_fail(self): """ Tests that when clients and servers cannot agree on what protocol to use next that the TLS connection does not get established. """ advertise_args = [] select_args = [] def advertise(conn): advertise_args.append((conn,)) return [b'http/1.1', b'spdy/2'] def select(conn, options): select_args.append((conn, options)) return b'' server_context = Context(TLSv1_METHOD) server_context.set_npn_advertise_callback(advertise) client_context = Context(TLSv1_METHOD) client_context.set_npn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem)) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() # If the client doesn't return anything, the connection will fail. self.assertRaises(Error, self._interactInMemory, server, client) self.assertEqual([(server,)], advertise_args) self.assertEqual([(client, [b'http/1.1', b'spdy/2'])], select_args) def test_npn_select_error(self): """ Test that we can handle exceptions in the select callback. If select fails it should be fatal to the connection. """ advertise_args = [] def advertise(conn): advertise_args.append((conn,)) return [b'http/1.1', b'spdy/2'] def select(conn, options): raise TypeError server_context = Context(TLSv1_METHOD) server_context.set_npn_advertise_callback(advertise) client_context = Context(TLSv1_METHOD) client_context.set_npn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem)) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() # If the callback throws an exception it should be raised here. self.assertRaises( TypeError, self._interactInMemory, server, client ) self.assertEqual([(server,)], advertise_args) def test_npn_advertise_error(self): """ Test that we can handle exceptions in the advertise callback. If advertise fails no NPN is advertised to the client. """ select_args = [] def advertise(conn): raise TypeError def select(conn, options): select_args.append((conn, options)) return b'' server_context = Context(TLSv1_METHOD) server_context.set_npn_advertise_callback(advertise) client_context = Context(TLSv1_METHOD) client_context.set_npn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem)) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() # If the client doesn't return anything, the connection will fail. self.assertRaises( TypeError, self._interactInMemory, server, client ) self.assertEqual([], select_args) else: # No NPN. def test_npn_not_implemented(self): # Test the context methods first. context = Context(TLSv1_METHOD) fail_methods = [ context.set_npn_advertise_callback, context.set_npn_select_callback, ] for method in fail_methods: self.assertRaises( NotImplementedError, method, None ) # Now test a connection. conn = Connection(context) fail_methods = [ conn.get_next_proto_negotiated, ] for method in fail_methods: self.assertRaises(NotImplementedError, method) class ApplicationLayerProtoNegotiationTests(TestCase, _LoopbackMixin): """ Tests for ALPN in PyOpenSSL. """ # Skip tests on versions that don't support ALPN. if _lib.Cryptography_HAS_ALPN: def test_alpn_success(self): """ Clients and servers that agree on the negotiated ALPN protocol can correct establish a connection, and the agreed protocol is reported by the connections. """ select_args = [] def select(conn, options): select_args.append((conn, options)) return b'spdy/2' client_context = Context(TLSv1_METHOD) client_context.set_alpn_protos([b'http/1.1', b'spdy/2']) server_context = Context(TLSv1_METHOD) server_context.set_alpn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem)) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() self._interactInMemory(server, client) self.assertEqual([(server, [b'http/1.1', b'spdy/2'])], select_args) self.assertEqual(server.get_alpn_proto_negotiated(), b'spdy/2') self.assertEqual(client.get_alpn_proto_negotiated(), b'spdy/2') def test_alpn_set_on_connection(self): """ The same as test_alpn_success, but setting the ALPN protocols on the connection rather than the context. """ select_args = [] def select(conn, options): select_args.append((conn, options)) return b'spdy/2' # Setup the client context but don't set any ALPN protocols. client_context = Context(TLSv1_METHOD) server_context = Context(TLSv1_METHOD) server_context.set_alpn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem)) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() # Set the ALPN protocols on the client connection. client = Connection(client_context, None) client.set_alpn_protos([b'http/1.1', b'spdy/2']) client.set_connect_state() self._interactInMemory(server, client) self.assertEqual([(server, [b'http/1.1', b'spdy/2'])], select_args) self.assertEqual(server.get_alpn_proto_negotiated(), b'spdy/2') self.assertEqual(client.get_alpn_proto_negotiated(), b'spdy/2') def test_alpn_server_fail(self): """ When clients and servers cannot agree on what protocol to use next the TLS connection does not get established. """ select_args = [] def select(conn, options): select_args.append((conn, options)) return b'' client_context = Context(TLSv1_METHOD) client_context.set_alpn_protos([b'http/1.1', b'spdy/2']) server_context = Context(TLSv1_METHOD) server_context.set_alpn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem)) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() # If the client doesn't return anything, the connection will fail. self.assertRaises(Error, self._interactInMemory, server, client) self.assertEqual([(server, [b'http/1.1', b'spdy/2'])], select_args) def test_alpn_no_server(self): """ When clients and servers cannot agree on what protocol to use next because the server doesn't offer ALPN, no protocol is negotiated. """ client_context = Context(TLSv1_METHOD) client_context.set_alpn_protos([b'http/1.1', b'spdy/2']) server_context = Context(TLSv1_METHOD) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem)) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() # Do the dance. self._interactInMemory(server, client) self.assertEqual(client.get_alpn_proto_negotiated(), b'') def test_alpn_callback_exception(self): """ We can handle exceptions in the ALPN select callback. """ select_args = [] def select(conn, options): select_args.append((conn, options)) raise TypeError() client_context = Context(TLSv1_METHOD) client_context.set_alpn_protos([b'http/1.1', b'spdy/2']) server_context = Context(TLSv1_METHOD) server_context.set_alpn_select_callback(select) # Necessary to actually accept the connection server_context.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem)) server_context.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem)) # Do a little connection to trigger the logic server = Connection(server_context, None) server.set_accept_state() client = Connection(client_context, None) client.set_connect_state() self.assertRaises( TypeError, self._interactInMemory, server, client ) self.assertEqual([(server, [b'http/1.1', b'spdy/2'])], select_args) else: # No ALPN. def test_alpn_not_implemented(self): """ If ALPN is not in OpenSSL, we should raise NotImplementedError. """ # Test the context methods first. context = Context(TLSv1_METHOD) self.assertRaises( NotImplementedError, context.set_alpn_protos, None ) self.assertRaises( NotImplementedError, context.set_alpn_select_callback, None ) # Now test a connection. conn = Connection(context) self.assertRaises( NotImplementedError, context.set_alpn_protos, None ) class SessionTests(TestCase): """ Unit tests for :py:obj:`OpenSSL.SSL.Session`. """ def test_construction(self): """ :py:class:`Session` can be constructed with no arguments, creating a new instance of that type. """ new_session = Session() self.assertTrue(isinstance(new_session, Session)) def test_construction_wrong_args(self): """ If any arguments are passed to :py:class:`Session`, :py:obj:`TypeError` is raised. """ self.assertRaises(TypeError, Session, 123) self.assertRaises(TypeError, Session, "hello") self.assertRaises(TypeError, Session, object()) class ConnectionTests(TestCase, _LoopbackMixin): """ Unit tests for :py:obj:`OpenSSL.SSL.Connection`. """ # XXX get_peer_certificate -> None # XXX sock_shutdown # XXX master_key -> TypeError # XXX server_random -> TypeError # XXX state_string # XXX connect -> TypeError # XXX connect_ex -> TypeError # XXX set_connect_state -> TypeError # XXX set_accept_state -> TypeError # XXX renegotiate_pending # XXX do_handshake -> TypeError # XXX bio_read -> TypeError # XXX recv -> TypeError # XXX send -> TypeError # XXX bio_write -> TypeError def test_type(self): """ :py:obj:`Connection` and :py:obj:`ConnectionType` refer to the same type object and can be used to create instances of that type. """ self.assertIdentical(Connection, ConnectionType) ctx = Context(TLSv1_METHOD) self.assertConsistentType(Connection, 'Connection', ctx, None) def test_get_context(self): """ :py:obj:`Connection.get_context` returns the :py:obj:`Context` instance used to construct the :py:obj:`Connection` instance. """ context = Context(TLSv1_METHOD) connection = Connection(context, None) self.assertIdentical(connection.get_context(), context) def test_get_context_wrong_args(self): """ :py:obj:`Connection.get_context` raises :py:obj:`TypeError` if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, connection.get_context, None) def test_set_context_wrong_args(self): """ :py:obj:`Connection.set_context` raises :py:obj:`TypeError` if called with a non-:py:obj:`Context` instance argument or with any number of arguments other than 1. """ ctx = Context(TLSv1_METHOD) connection = Connection(ctx, None) self.assertRaises(TypeError, connection.set_context) self.assertRaises(TypeError, connection.set_context, object()) self.assertRaises(TypeError, connection.set_context, "hello") self.assertRaises(TypeError, connection.set_context, 1) self.assertRaises(TypeError, connection.set_context, 1, 2) self.assertRaises( TypeError, connection.set_context, Context(TLSv1_METHOD), 2) self.assertIdentical(ctx, connection.get_context()) def test_set_context(self): """ :py:obj:`Connection.set_context` specifies a new :py:obj:`Context` instance to be used for the connection. """ original = Context(SSLv23_METHOD) replacement = Context(TLSv1_METHOD) connection = Connection(original, None) connection.set_context(replacement) self.assertIdentical(replacement, connection.get_context()) # Lose our references to the contexts, just in case the Connection isn't # properly managing its own contributions to their reference counts. del original, replacement collect() def test_set_tlsext_host_name_wrong_args(self): """ If :py:obj:`Connection.set_tlsext_host_name` is called with a non-byte string argument or a byte string with an embedded NUL or other than one argument, :py:obj:`TypeError` is raised. """ conn = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, conn.set_tlsext_host_name) self.assertRaises(TypeError, conn.set_tlsext_host_name, object()) self.assertRaises(TypeError, conn.set_tlsext_host_name, 123, 456) self.assertRaises( TypeError, conn.set_tlsext_host_name, b("with\0null")) if PY3: # On Python 3.x, don't accidentally implicitly convert from text. self.assertRaises( TypeError, conn.set_tlsext_host_name, b("example.com").decode("ascii")) def test_get_servername_wrong_args(self): """ :py:obj:`Connection.get_servername` raises :py:obj:`TypeError` if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, connection.get_servername, object()) self.assertRaises(TypeError, connection.get_servername, 1) self.assertRaises(TypeError, connection.get_servername, "hello") def test_pending(self): """ :py:obj:`Connection.pending` returns the number of bytes available for immediate read. """ connection = Connection(Context(TLSv1_METHOD), None) self.assertEquals(connection.pending(), 0) def test_pending_wrong_args(self): """ :py:obj:`Connection.pending` raises :py:obj:`TypeError` if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, connection.pending, None) def test_connect_wrong_args(self): """ :py:obj:`Connection.connect` raises :py:obj:`TypeError` if called with a non-address argument or with the wrong number of arguments. """ connection = Connection(Context(TLSv1_METHOD), socket()) self.assertRaises(TypeError, connection.connect, None) self.assertRaises(TypeError, connection.connect) self.assertRaises(TypeError, connection.connect, ("127.0.0.1", 1), None) def test_connect_refused(self): """ :py:obj:`Connection.connect` raises :py:obj:`socket.error` if the underlying socket connect method raises it. """ client = socket() context = Context(TLSv1_METHOD) clientSSL = Connection(context, client) exc = self.assertRaises(error, clientSSL.connect, ("127.0.0.1", 1)) self.assertEquals(exc.args[0], ECONNREFUSED) def test_connect(self): """ :py:obj:`Connection.connect` establishes a connection to the specified address. """ port = socket() port.bind(('', 0)) port.listen(3) clientSSL = Connection(Context(TLSv1_METHOD), socket()) clientSSL.connect(('127.0.0.1', port.getsockname()[1])) # XXX An assertion? Or something? if platform == "darwin": "connect_ex sometimes causes a kernel panic on OS X 10.6.4" else: def test_connect_ex(self): """ If there is a connection error, :py:obj:`Connection.connect_ex` returns the errno instead of raising an exception. """ port = socket() port.bind(('', 0)) port.listen(3) clientSSL = Connection(Context(TLSv1_METHOD), socket()) clientSSL.setblocking(False) result = clientSSL.connect_ex(port.getsockname()) expected = (EINPROGRESS, EWOULDBLOCK) self.assertTrue( result in expected, "%r not in %r" % (result, expected)) def test_accept_wrong_args(self): """ :py:obj:`Connection.accept` raises :py:obj:`TypeError` if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), socket()) self.assertRaises(TypeError, connection.accept, None) def test_accept(self): """ :py:obj:`Connection.accept` accepts a pending connection attempt and returns a tuple of a new :py:obj:`Connection` (the accepted client) and the address the connection originated from. """ ctx = Context(TLSv1_METHOD) ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) port = socket() portSSL = Connection(ctx, port) portSSL.bind(('', 0)) portSSL.listen(3) clientSSL = Connection(Context(TLSv1_METHOD), socket()) # Calling portSSL.getsockname() here to get the server IP address sounds # great, but frequently fails on Windows. clientSSL.connect(('127.0.0.1', portSSL.getsockname()[1])) serverSSL, address = portSSL.accept() self.assertTrue(isinstance(serverSSL, Connection)) self.assertIdentical(serverSSL.get_context(), ctx) self.assertEquals(address, clientSSL.getsockname()) def test_shutdown_wrong_args(self): """ :py:obj:`Connection.shutdown` raises :py:obj:`TypeError` if called with the wrong number of arguments or with arguments other than integers. """ connection = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, connection.shutdown, None) self.assertRaises(TypeError, connection.get_shutdown, None) self.assertRaises(TypeError, connection.set_shutdown) self.assertRaises(TypeError, connection.set_shutdown, None) self.assertRaises(TypeError, connection.set_shutdown, 0, 1) def test_shutdown(self): """ :py:obj:`Connection.shutdown` performs an SSL-level connection shutdown. """ server, client = self._loopback() self.assertFalse(server.shutdown()) self.assertEquals(server.get_shutdown(), SENT_SHUTDOWN) self.assertRaises(ZeroReturnError, client.recv, 1024) self.assertEquals(client.get_shutdown(), RECEIVED_SHUTDOWN) client.shutdown() self.assertEquals(client.get_shutdown(), SENT_SHUTDOWN|RECEIVED_SHUTDOWN) self.assertRaises(ZeroReturnError, server.recv, 1024) self.assertEquals(server.get_shutdown(), SENT_SHUTDOWN|RECEIVED_SHUTDOWN) def test_shutdown_closed(self): """ If the underlying socket is closed, :py:obj:`Connection.shutdown` propagates the write error from the low level write call. """ server, client = self._loopback() server.sock_shutdown(2) exc = self.assertRaises(SysCallError, server.shutdown) if platform == "win32": self.assertEqual(exc.args[0], ESHUTDOWN) else: self.assertEqual(exc.args[0], EPIPE) def test_shutdown_truncated(self): """ If the underlying connection is truncated, :obj:`Connection.shutdown` raises an :obj:`Error`. """ server_ctx = Context(TLSv1_METHOD) client_ctx = Context(TLSv1_METHOD) server_ctx.use_privatekey( load_privatekey(FILETYPE_PEM, server_key_pem)) server_ctx.use_certificate( load_certificate(FILETYPE_PEM, server_cert_pem)) server = Connection(server_ctx, None) client = Connection(client_ctx, None) self._handshakeInMemory(client, server) self.assertEqual(server.shutdown(), False) self.assertRaises(WantReadError, server.shutdown) server.bio_shutdown() self.assertRaises(Error, server.shutdown) def test_set_shutdown(self): """ :py:obj:`Connection.set_shutdown` sets the state of the SSL connection shutdown process. """ connection = Connection(Context(TLSv1_METHOD), socket()) connection.set_shutdown(RECEIVED_SHUTDOWN) self.assertEquals(connection.get_shutdown(), RECEIVED_SHUTDOWN) if not PY3: def test_set_shutdown_long(self): """ On Python 2 :py:obj:`Connection.set_shutdown` accepts an argument of type :py:obj:`long` as well as :py:obj:`int`. """ connection = Connection(Context(TLSv1_METHOD), socket()) connection.set_shutdown(long(RECEIVED_SHUTDOWN)) self.assertEquals(connection.get_shutdown(), RECEIVED_SHUTDOWN) def test_app_data_wrong_args(self): """ :py:obj:`Connection.set_app_data` raises :py:obj:`TypeError` if called with other than one argument. :py:obj:`Connection.get_app_data` raises :py:obj:`TypeError` if called with any arguments. """ conn = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, conn.get_app_data, None) self.assertRaises(TypeError, conn.set_app_data) self.assertRaises(TypeError, conn.set_app_data, None, None) def test_app_data(self): """ Any object can be set as app data by passing it to :py:obj:`Connection.set_app_data` and later retrieved with :py:obj:`Connection.get_app_data`. """ conn = Connection(Context(TLSv1_METHOD), None) app_data = object() conn.set_app_data(app_data) self.assertIdentical(conn.get_app_data(), app_data) def test_makefile(self): """ :py:obj:`Connection.makefile` is not implemented and calling that method raises :py:obj:`NotImplementedError`. """ conn = Connection(Context(TLSv1_METHOD), None) self.assertRaises(NotImplementedError, conn.makefile) def test_get_peer_cert_chain_wrong_args(self): """ :py:obj:`Connection.get_peer_cert_chain` raises :py:obj:`TypeError` if called with any arguments. """ conn = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, conn.get_peer_cert_chain, 1) self.assertRaises(TypeError, conn.get_peer_cert_chain, "foo") self.assertRaises(TypeError, conn.get_peer_cert_chain, object()) self.assertRaises(TypeError, conn.get_peer_cert_chain, []) def test_get_peer_cert_chain(self): """ :py:obj:`Connection.get_peer_cert_chain` returns a list of certificates which the connected server returned for the certification verification. """ chain = _create_certificate_chain() [(cakey, cacert), (ikey, icert), (skey, scert)] = chain serverContext = Context(TLSv1_METHOD) serverContext.use_privatekey(skey) serverContext.use_certificate(scert) serverContext.add_extra_chain_cert(icert) serverContext.add_extra_chain_cert(cacert) server = Connection(serverContext, None) server.set_accept_state() # Create the client clientContext = Context(TLSv1_METHOD) clientContext.set_verify(VERIFY_NONE, verify_cb) client = Connection(clientContext, None) client.set_connect_state() self._interactInMemory(client, server) chain = client.get_peer_cert_chain() self.assertEqual(len(chain), 3) self.assertEqual( "Server Certificate", chain[0].get_subject().CN) self.assertEqual( "Intermediate Certificate", chain[1].get_subject().CN) self.assertEqual( "Authority Certificate", chain[2].get_subject().CN) def test_get_peer_cert_chain_none(self): """ :py:obj:`Connection.get_peer_cert_chain` returns :py:obj:`None` if the peer sends no certificate chain. """ ctx = Context(TLSv1_METHOD) ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) server = Connection(ctx, None) server.set_accept_state() client = Connection(Context(TLSv1_METHOD), None) client.set_connect_state() self._interactInMemory(client, server) self.assertIdentical(None, server.get_peer_cert_chain()) def test_get_session_wrong_args(self): """ :py:obj:`Connection.get_session` raises :py:obj:`TypeError` if called with any arguments. """ ctx = Context(TLSv1_METHOD) server = Connection(ctx, None) self.assertRaises(TypeError, server.get_session, 123) self.assertRaises(TypeError, server.get_session, "hello") self.assertRaises(TypeError, server.get_session, object()) def test_get_session_unconnected(self): """ :py:obj:`Connection.get_session` returns :py:obj:`None` when used with an object which has not been connected. """ ctx = Context(TLSv1_METHOD) server = Connection(ctx, None) session = server.get_session() self.assertIdentical(None, session) def test_server_get_session(self): """ On the server side of a connection, :py:obj:`Connection.get_session` returns a :py:class:`Session` instance representing the SSL session for that connection. """ server, client = self._loopback() session = server.get_session() self.assertIsInstance(session, Session) def test_client_get_session(self): """ On the client side of a connection, :py:obj:`Connection.get_session` returns a :py:class:`Session` instance representing the SSL session for that connection. """ server, client = self._loopback() session = client.get_session() self.assertIsInstance(session, Session) def test_set_session_wrong_args(self): """ If called with an object that is not an instance of :py:class:`Session`, or with other than one argument, :py:obj:`Connection.set_session` raises :py:obj:`TypeError`. """ ctx = Context(TLSv1_METHOD) connection = Connection(ctx, None) self.assertRaises(TypeError, connection.set_session) self.assertRaises(TypeError, connection.set_session, 123) self.assertRaises(TypeError, connection.set_session, "hello") self.assertRaises(TypeError, connection.set_session, object()) self.assertRaises( TypeError, connection.set_session, Session(), Session()) def test_client_set_session(self): """ :py:obj:`Connection.set_session`, when used prior to a connection being established, accepts a :py:class:`Session` instance and causes an attempt to re-use the session it represents when the SSL handshake is performed. """ key = load_privatekey(FILETYPE_PEM, server_key_pem) cert = load_certificate(FILETYPE_PEM, server_cert_pem) ctx = Context(TLSv1_METHOD) ctx.use_privatekey(key) ctx.use_certificate(cert) ctx.set_session_id("unity-test") def makeServer(socket): server = Connection(ctx, socket) server.set_accept_state() return server originalServer, originalClient = self._loopback( serverFactory=makeServer) originalSession = originalClient.get_session() def makeClient(socket): client = self._loopbackClientFactory(socket) client.set_session(originalSession) return client resumedServer, resumedClient = self._loopback( serverFactory=makeServer, clientFactory=makeClient) # This is a proxy: in general, we have no access to any unique # identifier for the session (new enough versions of OpenSSL expose a # hash which could be usable, but "new enough" is very, very new). # Instead, exploit the fact that the master key is re-used if the # session is re-used. As long as the master key for the two connections # is the same, the session was re-used! self.assertEqual( originalServer.master_key(), resumedServer.master_key()) def test_set_session_wrong_method(self): """ If :py:obj:`Connection.set_session` is passed a :py:class:`Session` instance associated with a context using a different SSL method than the :py:obj:`Connection` is using, a :py:class:`OpenSSL.SSL.Error` is raised. """ key = load_privatekey(FILETYPE_PEM, server_key_pem) cert = load_certificate(FILETYPE_PEM, server_cert_pem) ctx = Context(TLSv1_METHOD) ctx.use_privatekey(key) ctx.use_certificate(cert) ctx.set_session_id("unity-test") def makeServer(socket): server = Connection(ctx, socket) server.set_accept_state() return server originalServer, originalClient = self._loopback( serverFactory=makeServer) originalSession = originalClient.get_session() def makeClient(socket): # Intentionally use a different, incompatible method here. client = Connection(Context(SSLv3_METHOD), socket) client.set_connect_state() client.set_session(originalSession) return client self.assertRaises( Error, self._loopback, clientFactory=makeClient, serverFactory=makeServer) def test_wantWriteError(self): """ :py:obj:`Connection` methods which generate output raise :py:obj:`OpenSSL.SSL.WantWriteError` if writing to the connection's BIO fail indicating a should-write state. """ client_socket, server_socket = socket_pair() # Fill up the client's send buffer so Connection won't be able to write # anything. Only write a single byte at a time so we can be sure we # completely fill the buffer. Even though the socket API is allowed to # signal a short write via its return value it seems this doesn't # always happen on all platforms (FreeBSD and OS X particular) for the # very last bit of available buffer space. msg = b"x" for i in range(1024 * 1024 * 4): try: client_socket.send(msg) except error as e: if e.errno == EWOULDBLOCK: break raise else: self.fail( "Failed to fill socket buffer, cannot test BIO want write") ctx = Context(TLSv1_METHOD) conn = Connection(ctx, client_socket) # Client's speak first, so make it an SSL client conn.set_connect_state() self.assertRaises(WantWriteError, conn.do_handshake) # XXX want_read def test_get_finished_before_connect(self): """ :py:obj:`Connection.get_finished` returns :py:obj:`None` before TLS handshake is completed. """ ctx = Context(TLSv1_METHOD) connection = Connection(ctx, None) self.assertEqual(connection.get_finished(), None) def test_get_peer_finished_before_connect(self): """ :py:obj:`Connection.get_peer_finished` returns :py:obj:`None` before TLS handshake is completed. """ ctx = Context(TLSv1_METHOD) connection = Connection(ctx, None) self.assertEqual(connection.get_peer_finished(), None) def test_get_finished(self): """ :py:obj:`Connection.get_finished` method returns the TLS Finished message send from client, or server. Finished messages are send during TLS handshake. """ server, client = self._loopback() self.assertNotEqual(server.get_finished(), None) self.assertTrue(len(server.get_finished()) > 0) def test_get_peer_finished(self): """ :py:obj:`Connection.get_peer_finished` method returns the TLS Finished message received from client, or server. Finished messages are send during TLS handshake. """ server, client = self._loopback() self.assertNotEqual(server.get_peer_finished(), None) self.assertTrue(len(server.get_peer_finished()) > 0) def test_tls_finished_message_symmetry(self): """ The TLS Finished message send by server must be the TLS Finished message received by client. The TLS Finished message send by client must be the TLS Finished message received by server. """ server, client = self._loopback() self.assertEqual(server.get_finished(), client.get_peer_finished()) self.assertEqual(client.get_finished(), server.get_peer_finished()) def test_get_cipher_name_before_connect(self): """ :py:obj:`Connection.get_cipher_name` returns :py:obj:`None` if no connection has been established. """ ctx = Context(TLSv1_METHOD) conn = Connection(ctx, None) self.assertIdentical(conn.get_cipher_name(), None) def test_get_cipher_name(self): """ :py:obj:`Connection.get_cipher_name` returns a :py:class:`unicode` string giving the name of the currently used cipher. """ server, client = self._loopback() server_cipher_name, client_cipher_name = \ server.get_cipher_name(), client.get_cipher_name() self.assertIsInstance(server_cipher_name, text_type) self.assertIsInstance(client_cipher_name, text_type) self.assertEqual(server_cipher_name, client_cipher_name) def test_get_cipher_version_before_connect(self): """ :py:obj:`Connection.get_cipher_version` returns :py:obj:`None` if no connection has been established. """ ctx = Context(TLSv1_METHOD) conn = Connection(ctx, None) self.assertIdentical(conn.get_cipher_version(), None) def test_get_cipher_version(self): """ :py:obj:`Connection.get_cipher_version` returns a :py:class:`unicode` string giving the protocol name of the currently used cipher. """ server, client = self._loopback() server_cipher_version, client_cipher_version = \ server.get_cipher_version(), client.get_cipher_version() self.assertIsInstance(server_cipher_version, text_type) self.assertIsInstance(client_cipher_version, text_type) self.assertEqual(server_cipher_version, client_cipher_version) def test_get_cipher_bits_before_connect(self): """ :py:obj:`Connection.get_cipher_bits` returns :py:obj:`None` if no connection has been established. """ ctx = Context(TLSv1_METHOD) conn = Connection(ctx, None) self.assertIdentical(conn.get_cipher_bits(), None) def test_get_cipher_bits(self): """ :py:obj:`Connection.get_cipher_bits` returns the number of secret bits of the currently used cipher. """ server, client = self._loopback() server_cipher_bits, client_cipher_bits = \ server.get_cipher_bits(), client.get_cipher_bits() self.assertIsInstance(server_cipher_bits, int) self.assertIsInstance(client_cipher_bits, int) self.assertEqual(server_cipher_bits, client_cipher_bits) class ConnectionGetCipherListTests(TestCase): """ Tests for :py:obj:`Connection.get_cipher_list`. """ def test_wrong_args(self): """ :py:obj:`Connection.get_cipher_list` raises :py:obj:`TypeError` if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, connection.get_cipher_list, None) def test_result(self): """ :py:obj:`Connection.get_cipher_list` returns a :py:obj:`list` of :py:obj:`bytes` giving the names of the ciphers which might be used. """ connection = Connection(Context(TLSv1_METHOD), None) ciphers = connection.get_cipher_list() self.assertTrue(isinstance(ciphers, list)) for cipher in ciphers: self.assertTrue(isinstance(cipher, str)) class ConnectionSendTests(TestCase, _LoopbackMixin): """ Tests for :py:obj:`Connection.send` """ def test_wrong_args(self): """ When called with arguments other than string argument for its first parameter or more than two arguments, :py:obj:`Connection.send` raises :py:obj:`TypeError`. """ connection = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, connection.send) self.assertRaises(TypeError, connection.send, object()) self.assertRaises(TypeError, connection.send, "foo", object(), "bar") def test_short_bytes(self): """ When passed a short byte string, :py:obj:`Connection.send` transmits all of it and returns the number of bytes sent. """ server, client = self._loopback() count = server.send(b('xy')) self.assertEquals(count, 2) self.assertEquals(client.recv(2), b('xy')) def test_text(self): """ When passed a text, :py:obj:`Connection.send` transmits all of it and returns the number of bytes sent. It also raises a DeprecationWarning. """ server, client = self._loopback() with catch_warnings(record=True) as w: simplefilter("always") count = server.send(b"xy".decode("ascii")) self.assertEqual( "{0} for buf is no longer accepted, use bytes".format( WARNING_TYPE_EXPECTED ), str(w[-1].message) ) self.assertIs(w[-1].category, DeprecationWarning) self.assertEquals(count, 2) self.assertEquals(client.recv(2), b"xy") try: memoryview except NameError: "cannot test sending memoryview without memoryview" else: def test_short_memoryview(self): """ When passed a memoryview onto a small number of bytes, :py:obj:`Connection.send` transmits all of them and returns the number of bytes sent. """ server, client = self._loopback() count = server.send(memoryview(b('xy'))) self.assertEquals(count, 2) self.assertEquals(client.recv(2), b('xy')) try: buffer except NameError: "cannot test sending buffer without buffer" else: def test_short_buffer(self): """ When passed a buffer containing a small number of bytes, :py:obj:`Connection.send` transmits all of them and returns the number of bytes sent. """ server, client = self._loopback() count = server.send(buffer(b('xy'))) self.assertEquals(count, 2) self.assertEquals(client.recv(2), b('xy')) def _make_memoryview(size): """ Create a new ``memoryview`` wrapped around a ``bytearray`` of the given size. """ return memoryview(bytearray(size)) class ConnectionRecvIntoTests(TestCase, _LoopbackMixin): """ Tests for :py:obj:`Connection.recv_into` """ def _no_length_test(self, factory): """ Assert that when the given buffer is passed to ``Connection.recv_into``, whatever bytes are available to be received that fit into that buffer are written into that buffer. """ output_buffer = factory(5) server, client = self._loopback() server.send(b('xy')) self.assertEqual(client.recv_into(output_buffer), 2) self.assertEqual(output_buffer, bytearray(b('xy\x00\x00\x00'))) def test_bytearray_no_length(self): """ :py:obj:`Connection.recv_into` can be passed a ``bytearray`` instance and data in the receive buffer is written to it. """ self._no_length_test(bytearray) def _respects_length_test(self, factory): """ Assert that when the given buffer is passed to ``Connection.recv_into`` along with a value for ``nbytes`` that is less than the size of that buffer, only ``nbytes`` bytes are written into the buffer. """ output_buffer = factory(10) server, client = self._loopback() server.send(b('abcdefghij')) self.assertEqual(client.recv_into(output_buffer, 5), 5) self.assertEqual( output_buffer, bytearray(b('abcde\x00\x00\x00\x00\x00')) ) def test_bytearray_respects_length(self): """ When called with a ``bytearray`` instance, :py:obj:`Connection.recv_into` respects the ``nbytes`` parameter and doesn't copy in more than that number of bytes. """ self._respects_length_test(bytearray) def _doesnt_overfill_test(self, factory): """ Assert that if there are more bytes available to be read from the receive buffer than would fit into the buffer passed to :py:obj:`Connection.recv_into`, only as many as fit are written into it. """ output_buffer = factory(5) server, client = self._loopback() server.send(b('abcdefghij')) self.assertEqual(client.recv_into(output_buffer), 5) self.assertEqual(output_buffer, bytearray(b('abcde'))) rest = client.recv(5) self.assertEqual(b('fghij'), rest) def test_bytearray_doesnt_overfill(self): """ When called with a ``bytearray`` instance, :py:obj:`Connection.recv_into` respects the size of the array and doesn't write more bytes into it than will fit. """ self._doesnt_overfill_test(bytearray) def _really_doesnt_overfill_test(self, factory): """ Assert that if the value given by ``nbytes`` is greater than the actual size of the output buffer passed to :py:obj:`Connection.recv_into`, the behavior is as if no value was given for ``nbytes`` at all. """ output_buffer = factory(5) server, client = self._loopback() server.send(b('abcdefghij')) self.assertEqual(client.recv_into(output_buffer, 50), 5) self.assertEqual(output_buffer, bytearray(b('abcde'))) rest = client.recv(5) self.assertEqual(b('fghij'), rest) def test_bytearray_really_doesnt_overfill(self): """ When called with a ``bytearray`` instance and an ``nbytes`` value that is too large, :py:obj:`Connection.recv_into` respects the size of the array and not the ``nbytes`` value and doesn't write more bytes into the buffer than will fit. """ self._doesnt_overfill_test(bytearray) try: memoryview except NameError: "cannot test recv_into memoryview without memoryview" else: def test_memoryview_no_length(self): """ :py:obj:`Connection.recv_into` can be passed a ``memoryview`` instance and data in the receive buffer is written to it. """ self._no_length_test(_make_memoryview) def test_memoryview_respects_length(self): """ When called with a ``memoryview`` instance, :py:obj:`Connection.recv_into` respects the ``nbytes`` parameter and doesn't copy more than that number of bytes in. """ self._respects_length_test(_make_memoryview) def test_memoryview_doesnt_overfill(self): """ When called with a ``memoryview`` instance, :py:obj:`Connection.recv_into` respects the size of the array and doesn't write more bytes into it than will fit. """ self._doesnt_overfill_test(_make_memoryview) def test_memoryview_really_doesnt_overfill(self): """ When called with a ``memoryview`` instance and an ``nbytes`` value that is too large, :py:obj:`Connection.recv_into` respects the size of the array and not the ``nbytes`` value and doesn't write more bytes into the buffer than will fit. """ self._doesnt_overfill_test(_make_memoryview) class ConnectionSendallTests(TestCase, _LoopbackMixin): """ Tests for :py:obj:`Connection.sendall`. """ def test_wrong_args(self): """ When called with arguments other than a string argument for its first parameter or with more than two arguments, :py:obj:`Connection.sendall` raises :py:obj:`TypeError`. """ connection = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, connection.sendall) self.assertRaises(TypeError, connection.sendall, object()) self.assertRaises( TypeError, connection.sendall, "foo", object(), "bar") def test_short(self): """ :py:obj:`Connection.sendall` transmits all of the bytes in the string passed to it. """ server, client = self._loopback() server.sendall(b('x')) self.assertEquals(client.recv(1), b('x')) def test_text(self): """ :py:obj:`Connection.sendall` transmits all the content in the string passed to it raising a DeprecationWarning in case of this being a text. """ server, client = self._loopback() with catch_warnings(record=True) as w: simplefilter("always") server.sendall(b"x".decode("ascii")) self.assertEqual( "{0} for buf is no longer accepted, use bytes".format( WARNING_TYPE_EXPECTED ), str(w[-1].message) ) self.assertIs(w[-1].category, DeprecationWarning) self.assertEquals(client.recv(1), b"x") try: memoryview except NameError: "cannot test sending memoryview without memoryview" else: def test_short_memoryview(self): """ When passed a memoryview onto a small number of bytes, :py:obj:`Connection.sendall` transmits all of them. """ server, client = self._loopback() server.sendall(memoryview(b('x'))) self.assertEquals(client.recv(1), b('x')) try: buffer except NameError: "cannot test sending buffers without buffers" else: def test_short_buffers(self): """ When passed a buffer containing a small number of bytes, :py:obj:`Connection.sendall` transmits all of them. """ server, client = self._loopback() server.sendall(buffer(b('x'))) self.assertEquals(client.recv(1), b('x')) def test_long(self): """ :py:obj:`Connection.sendall` transmits all of the bytes in the string passed to it even if this requires multiple calls of an underlying write function. """ server, client = self._loopback() # Should be enough, underlying SSL_write should only do 16k at a time. # On Windows, after 32k of bytes the write will block (forever - because # no one is yet reading). message = b('x') * (1024 * 32 - 1) + b('y') server.sendall(message) accum = [] received = 0 while received < len(message): data = client.recv(1024) accum.append(data) received += len(data) self.assertEquals(message, b('').join(accum)) def test_closed(self): """ If the underlying socket is closed, :py:obj:`Connection.sendall` propagates the write error from the low level write call. """ server, client = self._loopback() server.sock_shutdown(2) exc = self.assertRaises(SysCallError, server.sendall, b"hello, world") if platform == "win32": self.assertEqual(exc.args[0], ESHUTDOWN) else: self.assertEqual(exc.args[0], EPIPE) class ConnectionRenegotiateTests(TestCase, _LoopbackMixin): """ Tests for SSL renegotiation APIs. """ def test_renegotiate_wrong_args(self): """ :py:obj:`Connection.renegotiate` raises :py:obj:`TypeError` if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, connection.renegotiate, None) def test_total_renegotiations_wrong_args(self): """ :py:obj:`Connection.total_renegotiations` raises :py:obj:`TypeError` if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, connection.total_renegotiations, None) def test_total_renegotiations(self): """ :py:obj:`Connection.total_renegotiations` returns :py:obj:`0` before any renegotiations have happened. """ connection = Connection(Context(TLSv1_METHOD), None) self.assertEquals(connection.total_renegotiations(), 0) # def test_renegotiate(self): # """ # """ # server, client = self._loopback() # server.send("hello world") # self.assertEquals(client.recv(len("hello world")), "hello world") # self.assertEquals(server.total_renegotiations(), 0) # self.assertTrue(server.renegotiate()) # server.setblocking(False) # client.setblocking(False) # while server.renegotiate_pending(): # client.do_handshake() # server.do_handshake() # self.assertEquals(server.total_renegotiations(), 1) class ErrorTests(TestCase): """ Unit tests for :py:obj:`OpenSSL.SSL.Error`. """ def test_type(self): """ :py:obj:`Error` is an exception type. """ self.assertTrue(issubclass(Error, Exception)) self.assertEqual(Error.__name__, 'Error') class ConstantsTests(TestCase): """ Tests for the values of constants exposed in :py:obj:`OpenSSL.SSL`. These are values defined by OpenSSL intended only to be used as flags to OpenSSL APIs. The only assertions it seems can be made about them is their values. """ # unittest.TestCase has no skip mechanism if OP_NO_QUERY_MTU is not None: def test_op_no_query_mtu(self): """ The value of :py:obj:`OpenSSL.SSL.OP_NO_QUERY_MTU` is 0x1000, the value of :py:const:`SSL_OP_NO_QUERY_MTU` defined by :file:`openssl/ssl.h`. """ self.assertEqual(OP_NO_QUERY_MTU, 0x1000) else: "OP_NO_QUERY_MTU unavailable - OpenSSL version may be too old" if OP_COOKIE_EXCHANGE is not None: def test_op_cookie_exchange(self): """ The value of :py:obj:`OpenSSL.SSL.OP_COOKIE_EXCHANGE` is 0x2000, the value of :py:const:`SSL_OP_COOKIE_EXCHANGE` defined by :file:`openssl/ssl.h`. """ self.assertEqual(OP_COOKIE_EXCHANGE, 0x2000) else: "OP_COOKIE_EXCHANGE unavailable - OpenSSL version may be too old" if OP_NO_TICKET is not None: def test_op_no_ticket(self): """ The value of :py:obj:`OpenSSL.SSL.OP_NO_TICKET` is 0x4000, the value of :py:const:`SSL_OP_NO_TICKET` defined by :file:`openssl/ssl.h`. """ self.assertEqual(OP_NO_TICKET, 0x4000) else: "OP_NO_TICKET unavailable - OpenSSL version may be too old" if OP_NO_COMPRESSION is not None: def test_op_no_compression(self): """ The value of :py:obj:`OpenSSL.SSL.OP_NO_COMPRESSION` is 0x20000, the value of :py:const:`SSL_OP_NO_COMPRESSION` defined by :file:`openssl/ssl.h`. """ self.assertEqual(OP_NO_COMPRESSION, 0x20000) else: "OP_NO_COMPRESSION unavailable - OpenSSL version may be too old" def test_sess_cache_off(self): """ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_OFF` 0x0, the value of :py:obj:`SSL_SESS_CACHE_OFF` defined by ``openssl/ssl.h``. """ self.assertEqual(0x0, SESS_CACHE_OFF) def test_sess_cache_client(self): """ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_CLIENT` 0x1, the value of :py:obj:`SSL_SESS_CACHE_CLIENT` defined by ``openssl/ssl.h``. """ self.assertEqual(0x1, SESS_CACHE_CLIENT) def test_sess_cache_server(self): """ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_SERVER` 0x2, the value of :py:obj:`SSL_SESS_CACHE_SERVER` defined by ``openssl/ssl.h``. """ self.assertEqual(0x2, SESS_CACHE_SERVER) def test_sess_cache_both(self): """ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_BOTH` 0x3, the value of :py:obj:`SSL_SESS_CACHE_BOTH` defined by ``openssl/ssl.h``. """ self.assertEqual(0x3, SESS_CACHE_BOTH) def test_sess_cache_no_auto_clear(self): """ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_AUTO_CLEAR` 0x80, the value of :py:obj:`SSL_SESS_CACHE_NO_AUTO_CLEAR` defined by ``openssl/ssl.h``. """ self.assertEqual(0x80, SESS_CACHE_NO_AUTO_CLEAR) def test_sess_cache_no_internal_lookup(self): """ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_INTERNAL_LOOKUP` 0x100, the value of :py:obj:`SSL_SESS_CACHE_NO_INTERNAL_LOOKUP` defined by ``openssl/ssl.h``. """ self.assertEqual(0x100, SESS_CACHE_NO_INTERNAL_LOOKUP) def test_sess_cache_no_internal_store(self): """ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_INTERNAL_STORE` 0x200, the value of :py:obj:`SSL_SESS_CACHE_NO_INTERNAL_STORE` defined by ``openssl/ssl.h``. """ self.assertEqual(0x200, SESS_CACHE_NO_INTERNAL_STORE) def test_sess_cache_no_internal(self): """ The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_INTERNAL` 0x300, the value of :py:obj:`SSL_SESS_CACHE_NO_INTERNAL` defined by ``openssl/ssl.h``. """ self.assertEqual(0x300, SESS_CACHE_NO_INTERNAL) class MemoryBIOTests(TestCase, _LoopbackMixin): """ Tests for :py:obj:`OpenSSL.SSL.Connection` using a memory BIO. """ def _server(self, sock): """ Create a new server-side SSL :py:obj:`Connection` object wrapped around :py:obj:`sock`. """ # Create the server side Connection. This is mostly setup boilerplate # - use TLSv1, use a particular certificate, etc. server_ctx = Context(TLSv1_METHOD) server_ctx.set_options(OP_NO_SSLv2 | OP_NO_SSLv3 | OP_SINGLE_DH_USE ) server_ctx.set_verify(VERIFY_PEER|VERIFY_FAIL_IF_NO_PEER_CERT|VERIFY_CLIENT_ONCE, verify_cb) server_store = server_ctx.get_cert_store() server_ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) server_ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) server_ctx.check_privatekey() server_store.add_cert(load_certificate(FILETYPE_PEM, root_cert_pem)) # Here the Connection is actually created. If None is passed as the 2nd # parameter, it indicates a memory BIO should be created. server_conn = Connection(server_ctx, sock) server_conn.set_accept_state() return server_conn def _client(self, sock): """ Create a new client-side SSL :py:obj:`Connection` object wrapped around :py:obj:`sock`. """ # Now create the client side Connection. Similar boilerplate to the # above. client_ctx = Context(TLSv1_METHOD) client_ctx.set_options(OP_NO_SSLv2 | OP_NO_SSLv3 | OP_SINGLE_DH_USE ) client_ctx.set_verify(VERIFY_PEER|VERIFY_FAIL_IF_NO_PEER_CERT|VERIFY_CLIENT_ONCE, verify_cb) client_store = client_ctx.get_cert_store() client_ctx.use_privatekey(load_privatekey(FILETYPE_PEM, client_key_pem)) client_ctx.use_certificate(load_certificate(FILETYPE_PEM, client_cert_pem)) client_ctx.check_privatekey() client_store.add_cert(load_certificate(FILETYPE_PEM, root_cert_pem)) client_conn = Connection(client_ctx, sock) client_conn.set_connect_state() return client_conn def test_memoryConnect(self): """ Two :py:obj:`Connection`s which use memory BIOs can be manually connected by reading from the output of each and writing those bytes to the input of the other and in this way establish a connection and exchange application-level bytes with each other. """ server_conn = self._server(None) client_conn = self._client(None) # There should be no key or nonces yet. self.assertIdentical(server_conn.master_key(), None) self.assertIdentical(server_conn.client_random(), None) self.assertIdentical(server_conn.server_random(), None) # First, the handshake needs to happen. We'll deliver bytes back and # forth between the client and server until neither of them feels like # speaking any more. self.assertIdentical( self._interactInMemory(client_conn, server_conn), None) # Now that the handshake is done, there should be a key and nonces. self.assertNotIdentical(server_conn.master_key(), None) self.assertNotIdentical(server_conn.client_random(), None) self.assertNotIdentical(server_conn.server_random(), None) self.assertEquals(server_conn.client_random(), client_conn.client_random()) self.assertEquals(server_conn.server_random(), client_conn.server_random()) self.assertNotEquals(server_conn.client_random(), server_conn.server_random()) self.assertNotEquals(client_conn.client_random(), client_conn.server_random()) # Here are the bytes we'll try to send. important_message = b('One if by land, two if by sea.') server_conn.write(important_message) self.assertEquals( self._interactInMemory(client_conn, server_conn), (client_conn, important_message)) client_conn.write(important_message[::-1]) self.assertEquals( self._interactInMemory(client_conn, server_conn), (server_conn, important_message[::-1])) def test_socketConnect(self): """ Just like :py:obj:`test_memoryConnect` but with an actual socket. This is primarily to rule out the memory BIO code as the source of any problems encountered while passing data over a :py:obj:`Connection` (if this test fails, there must be a problem outside the memory BIO code, as no memory BIO is involved here). Even though this isn't a memory BIO test, it's convenient to have it here. """ server_conn, client_conn = self._loopback() important_message = b("Help me Obi Wan Kenobi, you're my only hope.") client_conn.send(important_message) msg = server_conn.recv(1024) self.assertEqual(msg, important_message) # Again in the other direction, just for fun. important_message = important_message[::-1] server_conn.send(important_message) msg = client_conn.recv(1024) self.assertEqual(msg, important_message) def test_socketOverridesMemory(self): """ Test that :py:obj:`OpenSSL.SSL.bio_read` and :py:obj:`OpenSSL.SSL.bio_write` don't work on :py:obj:`OpenSSL.SSL.Connection`() that use sockets. """ context = Context(SSLv3_METHOD) client = socket() clientSSL = Connection(context, client) self.assertRaises( TypeError, clientSSL.bio_read, 100) self.assertRaises( TypeError, clientSSL.bio_write, "foo") self.assertRaises( TypeError, clientSSL.bio_shutdown ) def test_outgoingOverflow(self): """ If more bytes than can be written to the memory BIO are passed to :py:obj:`Connection.send` at once, the number of bytes which were written is returned and that many bytes from the beginning of the input can be read from the other end of the connection. """ server = self._server(None) client = self._client(None) self._interactInMemory(client, server) size = 2 ** 15 sent = client.send(b"x" * size) # Sanity check. We're trying to test what happens when the entire # input can't be sent. If the entire input was sent, this test is # meaningless. self.assertTrue(sent < size) receiver, received = self._interactInMemory(client, server) self.assertIdentical(receiver, server) # We can rely on all of these bytes being received at once because # _loopback passes 2 ** 16 to recv - more than 2 ** 15. self.assertEquals(len(received), sent) def test_shutdown(self): """ :py:obj:`Connection.bio_shutdown` signals the end of the data stream from which the :py:obj:`Connection` reads. """ server = self._server(None) server.bio_shutdown() e = self.assertRaises(Error, server.recv, 1024) # We don't want WantReadError or ZeroReturnError or anything - it's a # handshake failure. self.assertEquals(e.__class__, Error) def test_unexpectedEndOfFile(self): """ If the connection is lost before an orderly SSL shutdown occurs, :py:obj:`OpenSSL.SSL.SysCallError` is raised with a message of "Unexpected EOF". """ server_conn, client_conn = self._loopback() client_conn.sock_shutdown(SHUT_RDWR) exc = self.assertRaises(SysCallError, server_conn.recv, 1024) self.assertEqual(exc.args, (-1, "Unexpected EOF")) def _check_client_ca_list(self, func): """ Verify the return value of the :py:obj:`get_client_ca_list` method for server and client connections. :param func: A function which will be called with the server context before the client and server are connected to each other. This function should specify a list of CAs for the server to send to the client and return that same list. The list will be used to verify that :py:obj:`get_client_ca_list` returns the proper value at various times. """ server = self._server(None) client = self._client(None) self.assertEqual(client.get_client_ca_list(), []) self.assertEqual(server.get_client_ca_list(), []) ctx = server.get_context() expected = func(ctx) self.assertEqual(client.get_client_ca_list(), []) self.assertEqual(server.get_client_ca_list(), expected) self._interactInMemory(client, server) self.assertEqual(client.get_client_ca_list(), expected) self.assertEqual(server.get_client_ca_list(), expected) def test_set_client_ca_list_errors(self): """ :py:obj:`Context.set_client_ca_list` raises a :py:obj:`TypeError` if called with a non-list or a list that contains objects other than X509Names. """ ctx = Context(TLSv1_METHOD) self.assertRaises(TypeError, ctx.set_client_ca_list, "spam") self.assertRaises(TypeError, ctx.set_client_ca_list, ["spam"]) self.assertIdentical(ctx.set_client_ca_list([]), None) def test_set_empty_ca_list(self): """ If passed an empty list, :py:obj:`Context.set_client_ca_list` configures the context to send no CA names to the client and, on both the server and client sides, :py:obj:`Connection.get_client_ca_list` returns an empty list after the connection is set up. """ def no_ca(ctx): ctx.set_client_ca_list([]) return [] self._check_client_ca_list(no_ca) def test_set_one_ca_list(self): """ If passed a list containing a single X509Name, :py:obj:`Context.set_client_ca_list` configures the context to send that CA name to the client and, on both the server and client sides, :py:obj:`Connection.get_client_ca_list` returns a list containing that X509Name after the connection is set up. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) cadesc = cacert.get_subject() def single_ca(ctx): ctx.set_client_ca_list([cadesc]) return [cadesc] self._check_client_ca_list(single_ca) def test_set_multiple_ca_list(self): """ If passed a list containing multiple X509Name objects, :py:obj:`Context.set_client_ca_list` configures the context to send those CA names to the client and, on both the server and client sides, :py:obj:`Connection.get_client_ca_list` returns a list containing those X509Names after the connection is set up. """ secert = load_certificate(FILETYPE_PEM, server_cert_pem) clcert = load_certificate(FILETYPE_PEM, server_cert_pem) sedesc = secert.get_subject() cldesc = clcert.get_subject() def multiple_ca(ctx): L = [sedesc, cldesc] ctx.set_client_ca_list(L) return L self._check_client_ca_list(multiple_ca) def test_reset_ca_list(self): """ If called multiple times, only the X509Names passed to the final call of :py:obj:`Context.set_client_ca_list` are used to configure the CA names sent to the client. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) secert = load_certificate(FILETYPE_PEM, server_cert_pem) clcert = load_certificate(FILETYPE_PEM, server_cert_pem) cadesc = cacert.get_subject() sedesc = secert.get_subject() cldesc = clcert.get_subject() def changed_ca(ctx): ctx.set_client_ca_list([sedesc, cldesc]) ctx.set_client_ca_list([cadesc]) return [cadesc] self._check_client_ca_list(changed_ca) def test_mutated_ca_list(self): """ If the list passed to :py:obj:`Context.set_client_ca_list` is mutated afterwards, this does not affect the list of CA names sent to the client. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) secert = load_certificate(FILETYPE_PEM, server_cert_pem) cadesc = cacert.get_subject() sedesc = secert.get_subject() def mutated_ca(ctx): L = [cadesc] ctx.set_client_ca_list([cadesc]) L.append(sedesc) return [cadesc] self._check_client_ca_list(mutated_ca) def test_add_client_ca_errors(self): """ :py:obj:`Context.add_client_ca` raises :py:obj:`TypeError` if called with a non-X509 object or with a number of arguments other than one. """ ctx = Context(TLSv1_METHOD) cacert = load_certificate(FILETYPE_PEM, root_cert_pem) self.assertRaises(TypeError, ctx.add_client_ca) self.assertRaises(TypeError, ctx.add_client_ca, "spam") self.assertRaises(TypeError, ctx.add_client_ca, cacert, cacert) def test_one_add_client_ca(self): """ A certificate's subject can be added as a CA to be sent to the client with :py:obj:`Context.add_client_ca`. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) cadesc = cacert.get_subject() def single_ca(ctx): ctx.add_client_ca(cacert) return [cadesc] self._check_client_ca_list(single_ca) def test_multiple_add_client_ca(self): """ Multiple CA names can be sent to the client by calling :py:obj:`Context.add_client_ca` with multiple X509 objects. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) secert = load_certificate(FILETYPE_PEM, server_cert_pem) cadesc = cacert.get_subject() sedesc = secert.get_subject() def multiple_ca(ctx): ctx.add_client_ca(cacert) ctx.add_client_ca(secert) return [cadesc, sedesc] self._check_client_ca_list(multiple_ca) def test_set_and_add_client_ca(self): """ A call to :py:obj:`Context.set_client_ca_list` followed by a call to :py:obj:`Context.add_client_ca` results in using the CA names from the first call and the CA name from the second call. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) secert = load_certificate(FILETYPE_PEM, server_cert_pem) clcert = load_certificate(FILETYPE_PEM, server_cert_pem) cadesc = cacert.get_subject() sedesc = secert.get_subject() cldesc = clcert.get_subject() def mixed_set_add_ca(ctx): ctx.set_client_ca_list([cadesc, sedesc]) ctx.add_client_ca(clcert) return [cadesc, sedesc, cldesc] self._check_client_ca_list(mixed_set_add_ca) def test_set_after_add_client_ca(self): """ A call to :py:obj:`Context.set_client_ca_list` after a call to :py:obj:`Context.add_client_ca` replaces the CA name specified by the former call with the names specified by the latter cal. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) secert = load_certificate(FILETYPE_PEM, server_cert_pem) clcert = load_certificate(FILETYPE_PEM, server_cert_pem) cadesc = cacert.get_subject() sedesc = secert.get_subject() def set_replaces_add_ca(ctx): ctx.add_client_ca(clcert) ctx.set_client_ca_list([cadesc]) ctx.add_client_ca(secert) return [cadesc, sedesc] self._check_client_ca_list(set_replaces_add_ca) class ConnectionBIOTests(TestCase): """ Tests for :py:obj:`Connection.bio_read` and :py:obj:`Connection.bio_write`. """ def test_wantReadError(self): """ :py:obj:`Connection.bio_read` raises :py:obj:`OpenSSL.SSL.WantReadError` if there are no bytes available to be read from the BIO. """ ctx = Context(TLSv1_METHOD) conn = Connection(ctx, None) self.assertRaises(WantReadError, conn.bio_read, 1024) def test_buffer_size(self): """ :py:obj:`Connection.bio_read` accepts an integer giving the maximum number of bytes to read and return. """ ctx = Context(TLSv1_METHOD) conn = Connection(ctx, None) conn.set_connect_state() try: conn.do_handshake() except WantReadError: pass data = conn.bio_read(2) self.assertEqual(2, len(data)) if not PY3: def test_buffer_size_long(self): """ On Python 2 :py:obj:`Connection.bio_read` accepts values of type :py:obj:`long` as well as :py:obj:`int`. """ ctx = Context(TLSv1_METHOD) conn = Connection(ctx, None) conn.set_connect_state() try: conn.do_handshake() except WantReadError: pass data = conn.bio_read(long(2)) self.assertEqual(2, len(data)) class InfoConstantTests(TestCase): """ Tests for assorted constants exposed for use in info callbacks. """ def test_integers(self): """ All of the info constants are integers. This is a very weak test. It would be nice to have one that actually verifies that as certain info events happen, the value passed to the info callback matches up with the constant exposed by OpenSSL.SSL. """ for const in [ SSL_ST_CONNECT, SSL_ST_ACCEPT, SSL_ST_MASK, SSL_ST_INIT, SSL_ST_BEFORE, SSL_ST_OK, SSL_ST_RENEGOTIATE, SSL_CB_LOOP, SSL_CB_EXIT, SSL_CB_READ, SSL_CB_WRITE, SSL_CB_ALERT, SSL_CB_READ_ALERT, SSL_CB_WRITE_ALERT, SSL_CB_ACCEPT_LOOP, SSL_CB_ACCEPT_EXIT, SSL_CB_CONNECT_LOOP, SSL_CB_CONNECT_EXIT, SSL_CB_HANDSHAKE_START, SSL_CB_HANDSHAKE_DONE]: self.assertTrue(isinstance(const, int)) if __name__ == '__main__': main() pyOpenSSL-0.15.1/OpenSSL/test/test_tsafe.py0000644000076500000240000000132412373217023020650 0ustar hynekstaff00000000000000# Copyright (C) Jean-Paul Calderone # See LICENSE for details. """ Unit tests for :py:obj:`OpenSSL.tsafe`. """ from OpenSSL.SSL import TLSv1_METHOD, Context from OpenSSL.tsafe import Connection from OpenSSL.test.util import TestCase class ConnectionTest(TestCase): """ Tests for :py:obj:`OpenSSL.tsafe.Connection`. """ def test_instantiation(self): """ :py:obj:`OpenSSL.tsafe.Connection` can be instantiated. """ # The following line should not throw an error. This isn't an ideal # test. It would be great to refactor the other Connection tests so # they could automatically be applied to this class too. Connection(Context(TLSv1_METHOD), None) pyOpenSSL-0.15.1/OpenSSL/test/test_util.py0000644000076500000240000000117412513035756020535 0ustar hynekstaff00000000000000from OpenSSL._util import exception_from_error_queue, lib from OpenSSL.test.util import TestCase class ErrorTests(TestCase): """ Tests for handling of certain OpenSSL error cases. """ def test_exception_from_error_queue_nonexistent_reason(self): """ :py:func:`exception_from_error_queue` raises ``ValueError`` when it encounters an OpenSSL error code which does not have a reason string. """ lib.ERR_put_error(lib.ERR_LIB_EVP, 0, 1112, b"", 10) exc = self.assertRaises(ValueError, exception_from_error_queue, ValueError) self.assertEqual(exc.args[0][0][2], "") pyOpenSSL-0.15.1/OpenSSL/test/util.py0000644000076500000240000004135212513077752017502 0ustar hynekstaff00000000000000# Copyright (C) Jean-Paul Calderone # Copyright (C) Twisted Matrix Laboratories. # See LICENSE for details. """ Helpers for the OpenSSL test suite, largely copied from U{Twisted}. """ import shutil import traceback import os, os.path from tempfile import mktemp from unittest import TestCase import sys from six import PY3 from OpenSSL._util import exception_from_error_queue from OpenSSL.crypto import Error try: import memdbg except Exception: class _memdbg(object): heap = None memdbg = _memdbg() from OpenSSL._util import ffi, lib, byte_string as b # This is the UTF-8 encoding of the SNOWMAN unicode code point. NON_ASCII = b("\xe2\x98\x83").decode("utf-8") class TestCase(TestCase): """ :py:class:`TestCase` adds useful testing functionality beyond what is available from the standard library :py:class:`unittest.TestCase`. """ def run(self, result): run = super(TestCase, self).run if memdbg.heap is None: return run(result) # Run the test as usual before = set(memdbg.heap) run(result) # Clean up some long-lived allocations so they won't be reported as # memory leaks. lib.CRYPTO_cleanup_all_ex_data() lib.ERR_remove_thread_state(ffi.NULL) after = set(memdbg.heap) if not after - before: # No leaks, fast succeed return if result.wasSuccessful(): # If it passed, run it again with memory debugging before = set(memdbg.heap) run(result) # Clean up some long-lived allocations so they won't be reported as # memory leaks. lib.CRYPTO_cleanup_all_ex_data() lib.ERR_remove_thread_state(ffi.NULL) after = set(memdbg.heap) self._reportLeaks(after - before, result) def _reportLeaks(self, leaks, result): def format_leak(p): stacks = memdbg.heap[p] # Eventually look at multiple stacks for the realloc() case. For # now just look at the original allocation location. (size, python_stack, c_stack) = stacks[0] stack = traceback.format_list(python_stack)[:-1] # c_stack looks something like this (interesting parts indicated # with inserted arrows not part of the data): # # /home/exarkun/Projects/pyOpenSSL/branches/use-opentls/__pycache__/_cffi__x89095113xb9185b9b.so(+0x12cf) [0x7fe2e20582cf] # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a] # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52] # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e] # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419] # /home/exarkun/Projects/cpython/2.7/python() [0x4d6129] # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e] # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalCodeEx+0x1043) [0x4d3726] # /home/exarkun/Projects/cpython/2.7/python() [0x55fd51] # /home/exarkun/Projects/cpython/2.7/python(PyObject_Call+0x7e) [0x420ee6] # /home/exarkun/Projects/cpython/2.7/python(PyEval_CallObjectWithKeywords+0x158) [0x4d56ec] # /home/exarkun/.local/lib/python2.7/site-packages/cffi-0.5-py2.7-linux-x86_64.egg/_cffi_backend.so(+0xe96e) [0x7fe2e38be96e] # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64_inner+0x1b9) [0x7fe2e36ad819] # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64+0x46) [0x7fe2e36adb7c] # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(CRYPTO_malloc+0x64) [0x7fe2e1cef784] <------ end interesting # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(lh_insert+0x16b) [0x7fe2e1d6a24b] . # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x61c18) [0x7fe2e1cf0c18] . # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x625ec) [0x7fe2e1cf15ec] . # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_new_method+0xe6) [0x7fe2e1d524d6] . # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_generate_parameters+0x3a) [0x7fe2e1d5364a] <------ begin interesting # /home/exarkun/Projects/opentls/trunk/tls/c/__pycache__/_cffi__x305d4698xb539baaa.so(+0x1f397) [0x7fe2df84d397] # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a] # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52] # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e] # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419] # ... # # Notice the stack is upside down compared to a Python traceback. # Identify the start and end of interesting bits and stuff it into the stack we report. saved = list(c_stack) # Figure the first interesting frame will be after a the cffi-compiled module while c_stack and '/__pycache__/_cffi__' not in c_stack[-1]: c_stack.pop() # Figure the last interesting frame will always be CRYPTO_malloc, # since that's where we hooked in to things. while c_stack and 'CRYPTO_malloc' not in c_stack[0] and 'CRYPTO_realloc' not in c_stack[0]: c_stack.pop(0) if c_stack: c_stack.reverse() else: c_stack = saved[::-1] stack.extend([frame + "\n" for frame in c_stack]) stack.insert(0, "Leaked (%s) at:\n") return "".join(stack) if leaks: unique_leaks = {} for p in leaks: size = memdbg.heap[p][-1][0] new_leak = format_leak(p) if new_leak not in unique_leaks: unique_leaks[new_leak] = [(size, p)] else: unique_leaks[new_leak].append((size, p)) memdbg.free(p) for (stack, allocs) in unique_leaks.iteritems(): allocs_accum = [] for (size, pointer) in allocs: addr = int(ffi.cast('uintptr_t', pointer)) allocs_accum.append("%d@0x%x" % (size, addr)) allocs_report = ", ".join(sorted(allocs_accum)) result.addError( self, (None, Exception(stack % (allocs_report,)), None)) def tearDown(self): """ Clean up any files or directories created using :py:meth:`TestCase.mktemp`. Subclasses must invoke this method if they override it or the cleanup will not occur. """ if False and self._temporaryFiles is not None: for temp in self._temporaryFiles: if os.path.isdir(temp): shutil.rmtree(temp) elif os.path.exists(temp): os.unlink(temp) try: exception_from_error_queue(Error) except Error: e = sys.exc_info()[1] if e.args != ([],): self.fail("Left over errors in OpenSSL error queue: " + repr(e)) def assertIsInstance(self, instance, classOrTuple, message=None): """ Fail if C{instance} is not an instance of the given class or of one of the given classes. @param instance: the object to test the type (first argument of the C{isinstance} call). @type instance: any. @param classOrTuple: the class or classes to test against (second argument of the C{isinstance} call). @type classOrTuple: class, type, or tuple. @param message: Custom text to include in the exception text if the assertion fails. """ if not isinstance(instance, classOrTuple): if message is None: suffix = "" else: suffix = ": " + message self.fail("%r is not an instance of %s%s" % ( instance, classOrTuple, suffix)) def failUnlessIn(self, containee, container, msg=None): """ Fail the test if :py:data:`containee` is not found in :py:data:`container`. :param containee: the value that should be in :py:class:`container` :param container: a sequence type, or in the case of a mapping type, will follow semantics of 'if key in dict.keys()' :param msg: if msg is None, then the failure message will be '%r not in %r' % (first, second) """ if containee not in container: raise self.failureException(msg or "%r not in %r" % (containee, container)) return containee assertIn = failUnlessIn def assertNotIn(self, containee, container, msg=None): """ Fail the test if C{containee} is found in C{container}. @param containee: the value that should not be in C{container} @param container: a sequence type, or in the case of a mapping type, will follow semantics of 'if key in dict.keys()' @param msg: if msg is None, then the failure message will be '%r in %r' % (first, second) """ if containee in container: raise self.failureException(msg or "%r in %r" % (containee, container)) return containee failIfIn = assertNotIn def assertIs(self, first, second, msg=None): """ Fail the test if :py:data:`first` is not :py:data:`second`. This is an obect-identity-equality test, not an object equality (i.e. :py:func:`__eq__`) test. :param msg: if msg is None, then the failure message will be '%r is not %r' % (first, second) """ if first is not second: raise self.failureException(msg or '%r is not %r' % (first, second)) return first assertIdentical = failUnlessIdentical = assertIs def assertIsNot(self, first, second, msg=None): """ Fail the test if :py:data:`first` is :py:data:`second`. This is an obect-identity-equality test, not an object equality (i.e. :py:func:`__eq__`) test. :param msg: if msg is None, then the failure message will be '%r is %r' % (first, second) """ if first is second: raise self.failureException(msg or '%r is %r' % (first, second)) return first assertNotIdentical = failIfIdentical = assertIsNot def failUnlessRaises(self, exception, f, *args, **kwargs): """ Fail the test unless calling the function :py:data:`f` with the given :py:data:`args` and :py:data:`kwargs` raises :py:data:`exception`. The failure will report the traceback and call stack of the unexpected exception. :param exception: exception type that is to be expected :param f: the function to call :return: The raised exception instance, if it is of the given type. :raise self.failureException: Raised if the function call does not raise an exception or if it raises an exception of a different type. """ try: result = f(*args, **kwargs) except exception: inst = sys.exc_info()[1] return inst except: raise self.failureException('%s raised instead of %s' % (sys.exc_info()[0], exception.__name__, )) else: raise self.failureException('%s not raised (%r returned)' % (exception.__name__, result)) assertRaises = failUnlessRaises _temporaryFiles = None def mktemp(self): """ Pathetic substitute for twisted.trial.unittest.TestCase.mktemp. """ if self._temporaryFiles is None: self._temporaryFiles = [] temp = b(mktemp(dir=".")) self._temporaryFiles.append(temp) return temp # Other stuff def assertConsistentType(self, theType, name, *constructionArgs): """ Perform various assertions about :py:data:`theType` to ensure that it is a well-defined type. This is useful for extension types, where it's pretty easy to do something wacky. If something about the type is unusual, an exception will be raised. :param theType: The type object about which to make assertions. :param name: A string giving the name of the type. :param constructionArgs: Positional arguments to use with :py:data:`theType` to create an instance of it. """ self.assertEqual(theType.__name__, name) self.assertTrue(isinstance(theType, type)) instance = theType(*constructionArgs) self.assertIdentical(type(instance), theType) class EqualityTestsMixin(object): """ A mixin defining tests for the standard implementation of C{==} and C{!=}. """ def anInstance(self): """ Return an instance of the class under test. Each call to this method must return a different object. All objects returned must be equal to each other. """ raise NotImplementedError() def anotherInstance(self): """ Return an instance of the class under test. Each call to this method must return a different object. The objects must not be equal to the objects returned by C{anInstance}. They may or may not be equal to each other (they will not be compared against each other). """ raise NotImplementedError() def test_identicalEq(self): """ An object compares equal to itself using the C{==} operator. """ o = self.anInstance() self.assertTrue(o == o) def test_identicalNe(self): """ An object doesn't compare not equal to itself using the C{!=} operator. """ o = self.anInstance() self.assertFalse(o != o) def test_sameEq(self): """ Two objects that are equal to each other compare equal to each other using the C{==} operator. """ a = self.anInstance() b = self.anInstance() self.assertTrue(a == b) def test_sameNe(self): """ Two objects that are equal to each other do not compare not equal to each other using the C{!=} operator. """ a = self.anInstance() b = self.anInstance() self.assertFalse(a != b) def test_differentEq(self): """ Two objects that are not equal to each other do not compare equal to each other using the C{==} operator. """ a = self.anInstance() b = self.anotherInstance() self.assertFalse(a == b) def test_differentNe(self): """ Two objects that are not equal to each other compare not equal to each other using the C{!=} operator. """ a = self.anInstance() b = self.anotherInstance() self.assertTrue(a != b) def test_anotherTypeEq(self): """ The object does not compare equal to an object of an unrelated type (which does not implement the comparison) using the C{==} operator. """ a = self.anInstance() b = object() self.assertFalse(a == b) def test_anotherTypeNe(self): """ The object compares not equal to an object of an unrelated type (which does not implement the comparison) using the C{!=} operator. """ a = self.anInstance() b = object() self.assertTrue(a != b) def test_delegatedEq(self): """ The result of comparison using C{==} is delegated to the right-hand operand if it is of an unrelated type. """ class Delegate(object): def __eq__(self, other): # Do something crazy and obvious. return [self] a = self.anInstance() b = Delegate() self.assertEqual(a == b, [b]) def test_delegateNe(self): """ The result of comparison using C{!=} is delegated to the right-hand operand if it is of an unrelated type. """ class Delegate(object): def __ne__(self, other): # Do something crazy and obvious. return [self] a = self.anInstance() b = Delegate() self.assertEqual(a != b, [b]) # The type name expected in warnings about using the wrong string type. if PY3: WARNING_TYPE_EXPECTED = "str" else: WARNING_TYPE_EXPECTED = "unicode" pyOpenSSL-0.15.1/OpenSSL/tsafe.py0000644000076500000240000000175412373217023016641 0ustar hynekstaff00000000000000from OpenSSL import SSL _ssl = SSL del SSL import threading _RLock = threading.RLock del threading class Connection: def __init__(self, *args): self._ssl_conn = _ssl.Connection(*args) self._lock = _RLock() for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read', 'renegotiate', 'bind', 'listen', 'connect', 'accept', 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list', 'getpeername', 'getsockname', 'getsockopt', 'setsockopt', 'makefile', 'get_app_data', 'set_app_data', 'state_string', 'sock_shutdown', 'get_peer_certificate', 'get_peer_cert_chain', 'want_read', 'want_write', 'set_connect_state', 'set_accept_state', 'connect_ex', 'sendall'): exec("""def %s(self, *args): self._lock.acquire() try: return self._ssl_conn.%s(*args) finally: self._lock.release()\n""" % (f, f)) pyOpenSSL-0.15.1/OpenSSL/version.py0000644000076500000240000000026212513316203017211 0ustar hynekstaff00000000000000# Copyright (C) AB Strakt # Copyright (C) Jean-Paul Calderone # See LICENSE for details. """ pyOpenSSL - A simple wrapper around the OpenSSL library """ __version__ = '0.15.1' pyOpenSSL-0.15.1/PKG-INFO0000644000076500000240000000270012513316322014765 0ustar hynekstaff00000000000000Metadata-Version: 1.1 Name: pyOpenSSL Version: 0.15.1 Summary: Python wrapper module around the OpenSSL library Home-page: https://github.com/pyca/pyopenssl Author: Jean-Paul Calderone Author-email: exarkun@twistedmatrix.com License: APL2 Description: High-level wrapper around a subset of the OpenSSL library, includes * SSL.Connection objects, wrapping the methods of Python's portable sockets * Callbacks written in Python * Extensive error-handling mechanism, mirroring OpenSSL's error codes ... and much more ;) Platform: UNKNOWN Classifier: Development Status :: 6 - Mature Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Networking pyOpenSSL-0.15.1/pyOpenSSL.egg-info/0000755000076500000240000000000012513316322017217 5ustar hynekstaff00000000000000pyOpenSSL-0.15.1/pyOpenSSL.egg-info/dependency_links.txt0000644000076500000240000000000112513316322023265 0ustar hynekstaff00000000000000 pyOpenSSL-0.15.1/pyOpenSSL.egg-info/PKG-INFO0000644000076500000240000000270012513316322020313 0ustar hynekstaff00000000000000Metadata-Version: 1.1 Name: pyOpenSSL Version: 0.15.1 Summary: Python wrapper module around the OpenSSL library Home-page: https://github.com/pyca/pyopenssl Author: Jean-Paul Calderone Author-email: exarkun@twistedmatrix.com License: APL2 Description: High-level wrapper around a subset of the OpenSSL library, includes * SSL.Connection objects, wrapping the methods of Python's portable sockets * Callbacks written in Python * Extensive error-handling mechanism, mirroring OpenSSL's error codes ... and much more ;) Platform: UNKNOWN Classifier: Development Status :: 6 - Mature Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: MacOS :: MacOS X Classifier: Operating System :: Microsoft :: Windows Classifier: Operating System :: POSIX Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 2.6 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.2 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Networking pyOpenSSL-0.15.1/pyOpenSSL.egg-info/requires.txt0000644000076500000240000000003512513316322021615 0ustar hynekstaff00000000000000cryptography>=0.7 six>=1.5.2 pyOpenSSL-0.15.1/pyOpenSSL.egg-info/SOURCES.txt0000644000076500000240000000242712513316322021110 0ustar hynekstaff00000000000000CONTRIBUTING.rst ChangeLog INSTALL.rst LICENSE MANIFEST.in README.rst TODO memdbg.py runtests.py setup.cfg setup.py tox.ini OpenSSL/SSL.py OpenSSL/__init__.py OpenSSL/_util.py OpenSSL/crypto.py OpenSSL/rand.py OpenSSL/tsafe.py OpenSSL/version.py OpenSSL/test/README OpenSSL/test/__init__.py OpenSSL/test/test_crypto.py OpenSSL/test/test_rand.py OpenSSL/test/test_ssl.py OpenSSL/test/test_tsafe.py OpenSSL/test/test_util.py OpenSSL/test/util.py doc/Makefile doc/Quotes doc/README doc/api.rst doc/conf.py doc/index.rst doc/internals.rst doc/introduction.rst doc/make.bat doc/api/crypto.rst doc/api/rand.rst doc/api/ssl.rst doc/images/pyopenssl-brand.png doc/images/pyopenssl-icon.png doc/images/pyopenssl-logo.png doc/images/pyopenssl.svg examples/README examples/SecureXMLRPCServer.py examples/certgen.py examples/mk_simple_certs.py examples/proxy.py examples/simple/README examples/simple/client.py examples/simple/server.py examples/sni/README examples/sni/another.invalid.crt examples/sni/another.invalid.key examples/sni/client.py examples/sni/example.invalid.crt examples/sni/example.invalid.key examples/sni/server.py pyOpenSSL.egg-info/PKG-INFO pyOpenSSL.egg-info/SOURCES.txt pyOpenSSL.egg-info/dependency_links.txt pyOpenSSL.egg-info/requires.txt pyOpenSSL.egg-info/top_level.txt rpm/build_scriptpyOpenSSL-0.15.1/pyOpenSSL.egg-info/top_level.txt0000644000076500000240000000001012513316322021740 0ustar hynekstaff00000000000000OpenSSL pyOpenSSL-0.15.1/README.rst0000644000076500000240000000100312513100143015342 0ustar hynekstaff00000000000000 pyOpenSSL - A Python wrapper around the OpenSSL library ------------------------------------------------------------------------------ See the file INSTALL.rst for installation instructions. See https://github.com/pyca/pyopenssl for development. See https://pyopenssl.readthedocs.org for documentation. See https://mail.python.org/mailman/listinfo/pyopenssl-users for the discussion mailing list. .. image:: https://coveralls.io/repos/pyca/pyopenssl/badge.png :target: https://coveralls.io/r/pyca/pyopenssl pyOpenSSL-0.15.1/rpm/0000755000076500000240000000000012513316322014467 5ustar hynekstaff00000000000000pyOpenSSL-0.15.1/rpm/build_script0000644000076500000240000000003112263743062017076 0ustar hynekstaff00000000000000make -C doc text ps html pyOpenSSL-0.15.1/runtests.py0000644000076500000240000000024712273461431016142 0ustar hynekstaff00000000000000import sys sys.modules['ssl'] = None sys.modules['_hashlib'] = None try: import memdbg except Exception as e: pass from twisted.scripts.trial import run run() pyOpenSSL-0.15.1/setup.cfg0000644000076500000240000000044012513316322015510 0ustar hynekstaff00000000000000[sdist] force_manifest = 1 [wheel] universal = 1 [bdist_rpm] release = 1 build-requires = lynx openssl-devel python-devel python-sphinx group = Development/Libraries build_script = rpm/build_script doc-files = doc/_build/html [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 pyOpenSSL-0.15.1/setup.py0000755000076500000240000000575412513316203015417 0ustar hynekstaff00000000000000#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) Jean-Paul Calderone 2008-2015, All rights reserved # """ Installation script for the OpenSSL module """ from setuptools import setup # XXX Deduplicate this __version__ = '0.15.1' setup(name='pyOpenSSL', version=__version__, packages = ['OpenSSL'], package_dir = {'OpenSSL': 'OpenSSL'}, py_modules = ['OpenSSL.__init__', 'OpenSSL.tsafe', 'OpenSSL.rand', 'OpenSSL.crypto', 'OpenSSL.SSL', 'OpenSSL.version', 'OpenSSL.test.__init__', 'OpenSSL.test.util', 'OpenSSL.test.test_crypto', 'OpenSSL.test.test_rand', 'OpenSSL.test.test_ssl', 'OpenSSL.test.test_tsafe', 'OpenSSL.test.test_util',], description = 'Python wrapper module around the OpenSSL library', author = 'Jean-Paul Calderone', author_email = 'exarkun@twistedmatrix.com', maintainer = 'Jean-Paul Calderone', maintainer_email = 'exarkun@twistedmatrix.com', url = 'https://github.com/pyca/pyopenssl', license = 'APL2', install_requires=["cryptography>=0.7", "six>=1.5.2"], long_description = """\ High-level wrapper around a subset of the OpenSSL library, includes * SSL.Connection objects, wrapping the methods of Python's portable sockets * Callbacks written in Python * Extensive error-handling mechanism, mirroring OpenSSL's error codes ... and much more ;)""", classifiers = [ 'Development Status :: 6 - Mature', 'Intended Audience :: Developers', 'License :: OSI Approved :: Apache Software License', 'Operating System :: MacOS :: MacOS X', 'Operating System :: Microsoft :: Windows', 'Operating System :: POSIX', # General classifiers to indicate "this project supports Python 2" and # "this project supports Python 3". 'Programming Language :: Python :: 2', # In particular, this makes pyOpenSSL show up on # https://pypi.python.org/pypi?:action=browse&c=533&show=all and is in # accordance with # http://docs.python.org/2/howto/pyporting.html#universal-bits-of-advice 'Programming Language :: Python :: 3', # More specific classifiers to indicate more precisely which versions # of those languages the project supports. 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.2', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', 'Topic :: Security :: Cryptography', 'Topic :: Software Development :: Libraries :: Python Modules', 'Topic :: System :: Networking', ], test_suite="OpenSSL") pyOpenSSL-0.15.1/TODO0000644000076500000240000000037412263743062014374 0ustar hynekstaff00000000000000TODO list * Think more carefully about the relation between X509 and X509_NAME _set_{subject,issuer} dup the new name and free the old one. * Consider Pyrex * Updated docs! (rpm, ...) * _Somehow_ get makefile to work! * httpslib, imapslib, ftpslib? pyOpenSSL-0.15.1/tox.ini0000644000076500000240000000044412264311746015216 0ustar hynekstaff00000000000000[tox] envlist = pypy,py26,py27,py32,py33 [testenv] setenv = # Do not allowed the executing environment to pollute the test environment # with extra packages. PYTHONPATH= # The standard library unittest module can run tests on Python 2.7 and newer commands = python setup.py test