From 9cb2b01b3a7aa067caef283472ff17f717e9505a Mon Sep 17 00:00:00 2001 From: Jimmy Huang Date: Fri, 31 Aug 2012 15:10:46 -0700 Subject: [PATCH] Initial import to Tizen Signed-off-by: Jimmy Huang --- ChangeLog | 589 ++++++++ INSTALL | 146 ++ MANIFEST.in | 7 + OpenSSL/RATIONALE | 61 + OpenSSL/__init__.py | 45 + OpenSSL/crypto/crl.c | 287 ++++ OpenSSL/crypto/crl.h | 19 + OpenSSL/crypto/crypto.c | 888 ++++++++++++ OpenSSL/crypto/crypto.h | 129 ++ OpenSSL/crypto/netscape_spki.c | 310 ++++ OpenSSL/crypto/netscape_spki.h | 30 + OpenSSL/crypto/pkcs12.c | 564 +++++++ OpenSSL/crypto/pkcs12.h | 39 + OpenSSL/crypto/pkcs7.c | 213 +++ OpenSSL/crypto/pkcs7.h | 30 + OpenSSL/crypto/pkey.c | 265 ++++ OpenSSL/crypto/pkey.h | 52 + OpenSSL/crypto/revoked.c | 441 ++++++ OpenSSL/crypto/revoked.h | 18 + OpenSSL/crypto/x509.c | 896 ++++++++++++ OpenSSL/crypto/x509.h | 34 + OpenSSL/crypto/x509ext.c | 330 +++++ OpenSSL/crypto/x509ext.h | 33 + OpenSSL/crypto/x509name.c | 533 +++++++ OpenSSL/crypto/x509name.h | 33 + OpenSSL/crypto/x509req.c | 425 ++++++ OpenSSL/crypto/x509req.h | 30 + OpenSSL/crypto/x509store.c | 146 ++ OpenSSL/crypto/x509store.h | 30 + OpenSSL/py3k.h | 55 + OpenSSL/pymemcompat.h | 86 ++ OpenSSL/rand/rand.c | 303 ++++ OpenSSL/ssl/connection.c | 1445 ++++++++++++++++++ OpenSSL/ssl/connection.h | 53 + OpenSSL/ssl/context.c | 1323 +++++++++++++++++ OpenSSL/ssl/context.h | 42 + OpenSSL/ssl/ssl.c | 252 ++++ OpenSSL/ssl/ssl.h | 76 + OpenSSL/test/__init__.py | 6 + OpenSSL/test/test_crypto.py | 2580 +++++++++++++++++++++++++++++++++ OpenSSL/test/test_rand.py | 182 +++ OpenSSL/test/test_ssl.py | 1710 ++++++++++++++++++++++ OpenSSL/test/util.py | 150 ++ OpenSSL/tsafe.py | 28 + OpenSSL/util.c | 96 ++ OpenSSL/util.h | 140 ++ OpenSSL/version.py | 9 + PKG-INFO | 15 + README | 8 + TODO | 8 + doc/Makefile | 20 + doc/pyOpenSSL.tex | 1402 ++++++++++++++++++ doc/tools/anno-api.py | 71 + doc/tools/buildindex.py | 353 +++++ doc/tools/checkargs.pm | 112 ++ doc/tools/cklatex | 26 + doc/tools/custlib.py | 73 + doc/tools/cvsinfo.py | 81 ++ doc/tools/findacks | 161 ++ doc/tools/findmodrefs | 63 + doc/tools/fix_hack | 2 + doc/tools/fix_libaux.sed | 3 + doc/tools/fixinfo.el | 15 + doc/tools/getpagecounts | 88 ++ doc/tools/html/about.dat | 24 + doc/tools/html/about.html | 74 + doc/tools/html/icons/blank.gif | Bin 0 -> 1958 bytes doc/tools/html/icons/blank.png | Bin 0 -> 1031 bytes doc/tools/html/icons/contents.gif | Bin 0 -> 438 bytes doc/tools/html/icons/contents.png | Bin 0 -> 649 bytes doc/tools/html/icons/index.gif | Bin 0 -> 289 bytes doc/tools/html/icons/index.png | Bin 0 -> 529 bytes doc/tools/html/icons/modules.gif | Bin 0 -> 385 bytes doc/tools/html/icons/modules.png | Bin 0 -> 598 bytes doc/tools/html/icons/next.gif | Bin 0 -> 253 bytes doc/tools/html/icons/next.png | Bin 0 -> 511 bytes doc/tools/html/icons/previous.gif | Bin 0 -> 252 bytes doc/tools/html/icons/previous.png | Bin 0 -> 511 bytes doc/tools/html/icons/up.gif | Bin 0 -> 316 bytes doc/tools/html/icons/up.png | Bin 0 -> 577 bytes doc/tools/html/index.html.in | 117 ++ doc/tools/html/stdabout.dat | 48 + doc/tools/html/style.css | 88 ++ doc/tools/html2texi.pl | 1750 ++++++++++++++++++++++ doc/tools/indfix.py | 101 ++ doc/tools/info/Makefile | 73 + doc/tools/info/README | 21 + doc/tools/info/python.dir | 9 + doc/tools/keywords.py | 20 + doc/tools/listmodules | 183 +++ doc/tools/mkackshtml | 65 + doc/tools/mkhowto | 597 ++++++++ doc/tools/mkinfo | 48 + doc/tools/mkmodindex | 136 ++ doc/tools/mksourcepkg | 163 +++ doc/tools/node2label.pl | 55 + doc/tools/paper-a4/pypaper.sty | 5 + doc/tools/perl/SynopsisTable.pm | 89 ++ doc/tools/perl/distutils.perl | 21 + doc/tools/perl/howto.perl | 12 + doc/tools/perl/l2hinit.perl | 594 ++++++++ doc/tools/perl/ltxmarkup.perl | 67 + doc/tools/perl/manual.perl | 15 + doc/tools/perl/python.perl | 1651 +++++++++++++++++++++ doc/tools/push-docs.sh | 42 + doc/tools/refcounts.py | 97 ++ doc/tools/sgmlconv/Makefile | 67 + doc/tools/sgmlconv/README | 58 + doc/tools/sgmlconv/conversion.xml | 757 ++++++++++ doc/tools/sgmlconv/docfixer.py | 1033 +++++++++++++ doc/tools/sgmlconv/esis2sgml.py | 263 ++++ doc/tools/sgmlconv/esistools.py | 309 ++++ doc/tools/sgmlconv/latex2esis.py | 555 +++++++ doc/tools/sgmlconv/make.rules | 48 + doc/tools/support.py | 149 ++ doc/tools/templates/howto.tex | 105 ++ doc/tools/templates/manual.tex | 82 ++ doc/tools/templates/module.tex | 163 +++ doc/tools/texinputs/boilerplate.tex | 10 + doc/tools/texinputs/copyright.tex | 108 ++ doc/tools/texinputs/distutils.sty | 33 + doc/tools/texinputs/fncychap.sty | 433 ++++++ doc/tools/texinputs/howto.cls | 106 ++ doc/tools/texinputs/ltxmarkup.sty | 40 + doc/tools/texinputs/manual.cls | 152 ++ doc/tools/texinputs/pypaper.sty | 18 + doc/tools/texinputs/python.ist | 11 + doc/tools/texinputs/python.sty | 1082 ++++++++++++++ doc/tools/texinputs/reportingbugs.tex | 65 + doc/tools/toc2bkm.py | 143 ++ doc/tools/update-docs.sh | 21 + doc/tools/whichlibs | 2 + examples/README | 48 + examples/SecureXMLRPCServer.py | 102 ++ examples/certgen.py | 79 + examples/mk_simple_certs.py | 17 + examples/proxy.py | 70 + examples/simple/README | 3 + examples/simple/client.py | 52 + examples/simple/server.py | 100 ++ packaging/python-pyOpenSSL.changes | 2 + packaging/python-pyOpenSSL.spec | 27 + rpm/build_script | 1 + setup.cfg | 10 + setup.py | 222 +++ 145 files changed, 30371 insertions(+) create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 MANIFEST.in create mode 100644 OpenSSL/RATIONALE create mode 100644 OpenSSL/__init__.py create mode 100644 OpenSSL/crypto/crl.c create mode 100644 OpenSSL/crypto/crl.h create mode 100644 OpenSSL/crypto/crypto.c create mode 100644 OpenSSL/crypto/crypto.h create mode 100644 OpenSSL/crypto/netscape_spki.c create mode 100644 OpenSSL/crypto/netscape_spki.h create mode 100644 OpenSSL/crypto/pkcs12.c create mode 100644 OpenSSL/crypto/pkcs12.h create mode 100644 OpenSSL/crypto/pkcs7.c create mode 100644 OpenSSL/crypto/pkcs7.h create mode 100644 OpenSSL/crypto/pkey.c create mode 100644 OpenSSL/crypto/pkey.h create mode 100644 OpenSSL/crypto/revoked.c create mode 100644 OpenSSL/crypto/revoked.h create mode 100644 OpenSSL/crypto/x509.c create mode 100644 OpenSSL/crypto/x509.h create mode 100644 OpenSSL/crypto/x509ext.c create mode 100644 OpenSSL/crypto/x509ext.h create mode 100644 OpenSSL/crypto/x509name.c create mode 100644 OpenSSL/crypto/x509name.h create mode 100644 OpenSSL/crypto/x509req.c create mode 100644 OpenSSL/crypto/x509req.h create mode 100644 OpenSSL/crypto/x509store.c create mode 100644 OpenSSL/crypto/x509store.h create mode 100644 OpenSSL/py3k.h create mode 100644 OpenSSL/pymemcompat.h create mode 100644 OpenSSL/rand/rand.c create mode 100755 OpenSSL/ssl/connection.c create mode 100644 OpenSSL/ssl/connection.h create mode 100644 OpenSSL/ssl/context.c create mode 100644 OpenSSL/ssl/context.h create mode 100644 OpenSSL/ssl/ssl.c create mode 100644 OpenSSL/ssl/ssl.h create mode 100644 OpenSSL/test/__init__.py create mode 100644 OpenSSL/test/test_crypto.py create mode 100644 OpenSSL/test/test_rand.py create mode 100644 OpenSSL/test/test_ssl.py create mode 100644 OpenSSL/test/util.py create mode 100644 OpenSSL/tsafe.py create mode 100644 OpenSSL/util.c create mode 100644 OpenSSL/util.h create mode 100644 OpenSSL/version.py create mode 100644 PKG-INFO create mode 100644 README create mode 100644 TODO create mode 100644 doc/Makefile create mode 100644 doc/pyOpenSSL.tex create mode 100755 doc/tools/anno-api.py create mode 100755 doc/tools/buildindex.py create mode 100644 doc/tools/checkargs.pm create mode 100755 doc/tools/cklatex create mode 100644 doc/tools/custlib.py create mode 100644 doc/tools/cvsinfo.py create mode 100755 doc/tools/findacks create mode 100755 doc/tools/findmodrefs create mode 100755 doc/tools/fix_hack create mode 100755 doc/tools/fix_libaux.sed create mode 100644 doc/tools/fixinfo.el create mode 100755 doc/tools/getpagecounts create mode 100644 doc/tools/html/about.dat create mode 100644 doc/tools/html/about.html create mode 100644 doc/tools/html/icons/blank.gif create mode 100644 doc/tools/html/icons/blank.png create mode 100644 doc/tools/html/icons/contents.gif create mode 100644 doc/tools/html/icons/contents.png create mode 100644 doc/tools/html/icons/index.gif create mode 100644 doc/tools/html/icons/index.png create mode 100644 doc/tools/html/icons/modules.gif create mode 100644 doc/tools/html/icons/modules.png create mode 100644 doc/tools/html/icons/next.gif create mode 100644 doc/tools/html/icons/next.png create mode 100644 doc/tools/html/icons/previous.gif create mode 100644 doc/tools/html/icons/previous.png create mode 100644 doc/tools/html/icons/up.gif create mode 100644 doc/tools/html/icons/up.png create mode 100644 doc/tools/html/index.html.in create mode 100644 doc/tools/html/stdabout.dat create mode 100644 doc/tools/html/style.css create mode 100755 doc/tools/html2texi.pl create mode 100755 doc/tools/indfix.py create mode 100644 doc/tools/info/Makefile create mode 100644 doc/tools/info/README create mode 100644 doc/tools/info/python.dir create mode 100644 doc/tools/keywords.py create mode 100755 doc/tools/listmodules create mode 100755 doc/tools/mkackshtml create mode 100755 doc/tools/mkhowto create mode 100755 doc/tools/mkinfo create mode 100755 doc/tools/mkmodindex create mode 100755 doc/tools/mksourcepkg create mode 100755 doc/tools/node2label.pl create mode 100644 doc/tools/paper-a4/pypaper.sty create mode 100644 doc/tools/perl/SynopsisTable.pm create mode 100644 doc/tools/perl/distutils.perl create mode 100644 doc/tools/perl/howto.perl create mode 100644 doc/tools/perl/l2hinit.perl create mode 100644 doc/tools/perl/ltxmarkup.perl create mode 100644 doc/tools/perl/manual.perl create mode 100644 doc/tools/perl/python.perl create mode 100755 doc/tools/push-docs.sh create mode 100644 doc/tools/refcounts.py create mode 100644 doc/tools/sgmlconv/Makefile create mode 100644 doc/tools/sgmlconv/README create mode 100644 doc/tools/sgmlconv/conversion.xml create mode 100755 doc/tools/sgmlconv/docfixer.py create mode 100755 doc/tools/sgmlconv/esis2sgml.py create mode 100644 doc/tools/sgmlconv/esistools.py create mode 100755 doc/tools/sgmlconv/latex2esis.py create mode 100644 doc/tools/sgmlconv/make.rules create mode 100644 doc/tools/support.py create mode 100644 doc/tools/templates/howto.tex create mode 100644 doc/tools/templates/manual.tex create mode 100644 doc/tools/templates/module.tex create mode 100644 doc/tools/texinputs/boilerplate.tex create mode 100644 doc/tools/texinputs/copyright.tex create mode 100644 doc/tools/texinputs/distutils.sty create mode 100644 doc/tools/texinputs/fncychap.sty create mode 100644 doc/tools/texinputs/howto.cls create mode 100644 doc/tools/texinputs/ltxmarkup.sty create mode 100644 doc/tools/texinputs/manual.cls create mode 100644 doc/tools/texinputs/pypaper.sty create mode 100644 doc/tools/texinputs/python.ist create mode 100644 doc/tools/texinputs/python.sty create mode 100644 doc/tools/texinputs/reportingbugs.tex create mode 100755 doc/tools/toc2bkm.py create mode 100755 doc/tools/update-docs.sh create mode 100755 doc/tools/whichlibs create mode 100644 examples/README create mode 100644 examples/SecureXMLRPCServer.py create mode 100644 examples/certgen.py create mode 100644 examples/mk_simple_certs.py create mode 100644 examples/proxy.py create mode 100644 examples/simple/README create mode 100644 examples/simple/client.py create mode 100644 examples/simple/server.py create mode 100644 packaging/python-pyOpenSSL.changes create mode 100644 packaging/python-pyOpenSSL.spec create mode 100644 rpm/build_script create mode 100644 setup.cfg create mode 100755 setup.py diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..680a6d0 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,589 @@ +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 :-) + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..f4635c5 --- /dev/null +++ b/INSTALL @@ -0,0 +1,146 @@ + +INSTALLATION INSTRUCTIONS FOR pyOpenSSL +------------------------------------------------------------------------------ + +I have tested this on Debian Linux systems (woody and sid), Solaris 2.6 and +2.7. Others have successfully compiled it on Windows and NT. + + +-- Building the Module on a Unix System -- + +pyOpenSSL uses distutils, so there really shouldn't be any problems. To build +the library: + + $ python setup.py build + +If your OpenSSL header files aren't in /usr/include, you may need to supply +the -I flag to let the setup script know where to look. The same goes for the +libraries of course, use the -L flag. Note that build won't accept these +flags, so you have to run first build_ext and then build! Example: + + $ python setup.py build_ext -I/usr/local/ssl/include -L/usr/local/ssl/lib + $ python setup.py build + +Now you should have a directory called OpenSSL that contains e.g. SSL.so and +__init__.py somewhere in the build dicrectory, so just: + + $ python setup.py install + +If you, for some arcane reason, don't want the module to appear in the +site-packages directory, use the --prefix option. + +You can, of course, do + + $ python setup.py --help + +to find out more about how to use the script. + + +-- Building the Module on a Windows System -- + +First you should get OpenSSL linked with the same runtime library that Python +uses. If you are using Python 2.6 you can use the installer at: + + http://www.slproweb.com/products/Win32OpenSSL.html + +The binaries in the installer are built with Visual Studio 2008 at the +time of this writing, which is the same compiler used for building the +official Python 2.6 installers. + +If you want to build pyOpenSSL for an older Python version, it is preferred +to build OpenSSL yourself, either with the Visual Studio 2003 compiler or +with the MinGW compiler. This way you avoid all potential incompatibilities +between different versions of runtime library (msvcrt.dll). To build +OpenSSL follow the instructions in its source distribution and make sure +that you build a shared library, not a static one. pyOpenSSL fails some of +its tests when linked with the static OpenSSL libraries. Use the same +compiler for OpenSSL that you will use for pyOpenSSL later. Make sure that +OpenSSL is properly installed before continuing. To install OpenSSL when +building with MinGW, use the folowing script: + +set OPENSSL_INSTALL_DIR=%1 +mkdir %OPENSSL_INSTALL_DIR% +mkdir %OPENSSL_INSTALL_DIR%\bin +mkdir %OPENSSL_INSTALL_DIR%\include +mkdir %OPENSSL_INSTALL_DIR%\include\openssl +mkdir %OPENSSL_INSTALL_DIR%\lib +copy /b .\*.dll %OPENSSL_INSTALL_DIR%\bin +copy /b .\out\openssl.exe %OPENSSL_INSTALL_DIR%\bin +copy /b .\outinc\openssl\* %OPENSSL_INSTALL_DIR%\include\openssl +copy /b .\out\*.a %OPENSSL_INSTALL_DIR%\lib + +Ensure that OpenSSL's openssl.exe executable can be found on PATH before +running pyOpenSSL's setup script. The setup script finds OpenSSL's include +dir and lib dir based on the location of openssl.exe, and the test suite +requires openssl.exe for output comparison. Alternatively, you can specify +the --with-openssl option to setup.py's build_ext command with the path to +the OpenSSL installation dir: + + > python setup.py build_ext --with-openssl=C:\path\to\openssl build + +pyOpenSSL is known to build with mingw32 for Python 2.3 through Python 2.5. +Before using the mingw32 compiler for Python 2.3, you will have to create +a Python library that MinGW understands. Find and download the pexports +program, put it and MinGW's bin directory on path, then run from Python's +install dir: + +> pexports python23.dll > libs\python23.def +> dlltool --dllname python23.dll --def libs\python23.def \ + --output-lib libs\libpython23.a + +For Python 2.4 and 2.5, no special preparation is needed, just make sure that +MinGW's gcc is on PATH. You can specify that mingw32 be used by passing +the --compiler argument to build_ext: + + C:\pyOpenSSL-X.Y> setup.py build_ext -c mingw32 bdist_msi + +The bdist_msi command will build an MSI installer. It can be substituted +with another bdist command if another kind of installer is desired or with +the install command if you want to install directly. + +For Python 2.4 and 2.5 you can use Visual Studio 2003 in addition to MinGW. +For Python 2.6, the official Windows installer of which is built with +Microsoft Visual Studio 2008 (version 9.0), Microsoft Visual Studio 2008 +(version 9.0) is required. + +To build with MSVC, just omit the compiler specific option: + + C:\pyOpenSSL-X.Y> setup.py bdist_msi + +The resulting binary distribution will be placed in the dist directory. To +install it, depending on what kind of distribution you create, run it, +unzip it, or copy it to Python installation's site-packages. + +And similarily, you can do + + setup.py --help + +to get more information. + +Big thanks to Itamar Shtull-Trauring, Oleg Orlov, Zooko O'Whielacronx, Chris +Galvan, Žiga Seilnacht, and #python and #distutils on FreeNode for their +help with Windows build instructions and to Michael Schneider for providing +Windows build hosts. + +-- Documentation -- + +The documentation is written in LaTeX, using the standard Python templates, +and tools to compile it into a number of forms are included. You need to +supply things like dvips, latex2html yourself of course! + +To build the text, html, postscript or dvi forms of the documentation, this is +what you do: + + cd doc + # To make the text-only documentation: + make text + # To make the dvi form: + make dvi + +It's as simple as that. Note that since Python's mkhowto script is used, if +you do first ``make dvi'' and then ``make ps'', the dvi file will disappear. +I included a special build target ``make all'' that will build all the +documentation in an order that won't let anything disappear. + + +@(#) $Id: INSTALL,v 1.7 2002/06/14 12:14:19 martin Exp $ diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..a26f83f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,7 @@ +recursive-include OpenSSL *.h +include COPYING ChangeLog INSTALL README TODO MANIFEST.in OpenSSL/RATIONALE +include doc/pyOpenSSL.tex doc/Makefile +recursive-include doc/tools * +recursive-include examples * +recursive-include rpm * +global-exclude *.pyc diff --git a/OpenSSL/RATIONALE b/OpenSSL/RATIONALE new file mode 100644 index 0000000..a0e389c --- /dev/null +++ b/OpenSSL/RATIONALE @@ -0,0 +1,61 @@ + RATIONALE + +The reason this module exists at all is that the SSL support in the socket +module in the Python 2.1 distribution (which is what we used, of course I +cannot speak for later versions) is severely limited. + + Update this list whenever needed! The communications module isn't +written yet, so we don't know exactly how this'll work! +This is a list of things we need from an OpenSSL module: + + Context objects (in OpenSSL called SSL_CTX) that can be manipulated from + Python modules. They must support a number of operations: + - Loading certificates from file and memory, both the client + certificate and the certificates used for the verification chain. + - Loading private keys from file and memory. + - Setting the verification mode (basically VERIFY_NONE and + VERIFY_PEER). + - Callbacks mechanism for prompting for pass phrases and verifying + certificates. The callbacks have to work under a multi-threaded + environment (see the comment in ssl/context.c). Of course the + callbacks will have to be written in Python! + + The Connection objects (in OpenSSL called SSL) have to support a few + things: + - Renegotiation, this is really important, especially for connections + that are up and running for a long time, since renegotiation + generates new encryption keys. + - Server-side SSL must work! As far as I know this doesn't work in + the SSL support of the socket module as of Python 2.1. + - Wrapping the methods of the underlying transport object is nice, so + you don't have to keep track of more than one object per connection. + This could of course be done a lot better than the way it works now, + so more transport layers than sockets are possible! + + A well-organized error system that mimics OpenSSL's error system is + desireable. Specifically there has to be a way to find out wether the + operation was successful, or if it failed, why it failed, so some sort + of interface to OpenSSL's error queue mechanism is needed. + + Certificate objects (X509) and certificate name objects (X509_NAME) are + needed, especially for verification purposes. Certificates will + probably also be generated by the server which is another reason for + them to exist. The same thing goes for key objects (EVP_PKEY) + + Since this is an OpenSSL module, there has to be an interface to the + OpenSSL PRNG, so it can be seeded in a good way. + +When asking about SSL on the comp.lang.python newsgroup (or on +python-list@python.org) people usually pointed you to the M2Crypto package. +The M2Crypto.SSL module does implement a lot of OpenSSL's functionality but +unfortunately its error handling system does not seem to be finished, +especially for non-blocking I/O. I think that much of the reason for this +is that M2Crypto is developed using SWIG. This makes it awkward to create +functions that e.g. can return both an integer and NULL since (as far as I +know) you basically write C functions and SWIG makes wrapper functions that +parses the Python argument list and calls your C function, and finally +transforms your return value to a Python object. + +Finally, a good book on the topic of SSL (that I read and learned a lot +from) is "SSL and TLS - Designing and Building Secure Systems" (ISBN +0201615983) by Eric Rescorla. A good mailinglist to subscribe to is the +openssl-users@openssl.org list. + +This comment was written July 2001, discussing Python 2.1. Feel free to +modify it as the SSL support in the socket module changes. + diff --git a/OpenSSL/__init__.py b/OpenSSL/__init__.py new file mode 100644 index 0000000..c9ea33b --- /dev/null +++ b/OpenSSL/__init__.py @@ -0,0 +1,45 @@ +# Copyright (C) AB Strakt +# See LICENSE for details. + +""" +pyOpenSSL - A simple wrapper around the OpenSSL library +""" + +import sys + +try: + orig = sys.getdlopenflags() +except AttributeError: + from OpenSSL import crypto +else: + try: + import DLFCN + except ImportError: + try: + import dl + except ImportError: + try: + import ctypes + except ImportError: + flags = 2 | 256 + else: + flags = 2 | ctypes.RTLD_GLOBAL + del ctypes + else: + flags = dl.RTLD_NOW | dl.RTLD_GLOBAL + del dl + else: + flags = DLFCN.RTLD_NOW | DLFCN.RTLD_GLOBAL + del DLFCN + + sys.setdlopenflags(flags) + from OpenSSL import crypto + sys.setdlopenflags(orig) + del orig, flags +del sys + +from OpenSSL import rand, SSL +from OpenSSL.version import __version__ + +__all__ = [ + 'rand', 'crypto', 'SSL', 'tsafe', '__version__'] diff --git a/OpenSSL/crypto/crl.c b/OpenSSL/crypto/crl.c new file mode 100644 index 0000000..bc76f22 --- /dev/null +++ b/OpenSSL/crypto/crl.c @@ -0,0 +1,287 @@ +#include +#define crypto_MODULE +#include "crypto.h" + + +static X509_REVOKED * X509_REVOKED_dup(X509_REVOKED *orig) { + X509_REVOKED *dupe = NULL; + + dupe = X509_REVOKED_new(); + if (dupe == NULL) { + return NULL; + } + if (orig->serialNumber) { + dupe->serialNumber = M_ASN1_INTEGER_dup(orig->serialNumber); + } + if (orig->revocationDate) { + dupe->revocationDate = M_ASN1_INTEGER_dup(orig->revocationDate); + } + if (orig->extensions) { + STACK_OF(X509_EXTENSION) *sk = NULL; + X509_EXTENSION * ext; + int j; + + sk = sk_X509_EXTENSION_new_null(); + for (j = 0; j < sk_X509_EXTENSION_num(orig->extensions); j++) { + ext = sk_X509_EXTENSION_value(orig->extensions, j); + ext = X509_EXTENSION_dup(ext); + sk_X509_EXTENSION_push(sk, ext); + } + dupe->extensions = sk; + } + dupe->sequence = orig->sequence; + return dupe; +} + +static char crypto_CRL_get_revoked_doc[] = "\n\ +Return revoked portion of the CRL structure (by value\n\ +not reference).\n\ +\n\ +@return: A tuple of Revoked objects.\n\ +"; +static PyObject * +crypto_CRL_get_revoked(crypto_CRLObj *self, PyObject *args) { + int j, num_rev; + X509_REVOKED *r = NULL; + PyObject *obj = NULL, *rev_obj; + + if (!PyArg_ParseTuple(args, ":get_revoked")) { + return NULL; + } + + num_rev = sk_X509_REVOKED_num(self->crl->crl->revoked); + if (num_rev < 0) { + Py_INCREF(Py_None); + return Py_None; + } + if ((obj = PyTuple_New(num_rev)) == NULL) { + return NULL; + } + + for (j = 0; j < num_rev; j++) { + r = sk_X509_REVOKED_value(self->crl->crl->revoked, j); + r = X509_REVOKED_dup(r); + if (r == NULL ) { + goto error; + } + rev_obj = (PyObject *) crypto_Revoked_New(r); + if (rev_obj == NULL) { + goto error; + } + r = NULL; /* it's now owned by rev_obj */ + PyTuple_SET_ITEM(obj, j, rev_obj); + } + return obj; + + error: + if (r) { + X509_REVOKED_free(r); + } + Py_XDECREF(obj); + return NULL; +} + +static char crypto_CRL_add_revoked_doc[] = "\n\ +Add a revoked (by value not reference) to the CRL structure\n\ +\n\ +@param cert: The new revoked.\n\ +@type cert: L{X509}\n\ +@return: None\n\ +"; +static PyObject * +crypto_CRL_add_revoked(crypto_CRLObj *self, PyObject *args, PyObject *keywds) { + crypto_RevokedObj * rev_obj = NULL; + static char *kwlist[] = {"revoked", NULL}; + X509_REVOKED * dup; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!:add_revoked", + kwlist, &crypto_Revoked_Type, &rev_obj)) { + return NULL; + } + + dup = X509_REVOKED_dup( rev_obj->revoked ); + if (dup == NULL) { + return NULL; + } + X509_CRL_add0_revoked(self->crl, dup); + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_CRL_export_doc[] = "\n\ +export(cert, key[, type[, days]]) -> export a CRL as a string\n\ +\n\ +@param cert: Used to sign CRL.\n\ +@type cert: L{X509}\n\ +@param key: Used to sign CRL.\n\ +@type key: L{PKey}\n\ +@param type: The export format, either L{FILETYPE_PEM}, L{FILETYPE_ASN1}, or L{FILETYPE_TEXT}.\n\ +@param days: The number of days until the next update of this CRL.\n\ +@type days: L{int}\n\ +@return: L{str}\n\ +"; +static PyObject * +crypto_CRL_export(crypto_CRLObj *self, PyObject *args, PyObject *keywds) { + int ret, buf_len, type = X509_FILETYPE_PEM, days = 100; + char *temp; + BIO *bio; + PyObject *buffer; + crypto_PKeyObj *key; + ASN1_TIME *tmptm; + crypto_X509Obj *x509; + static char *kwlist[] = {"cert", "key", "type", "days", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O!O!|ii:dump_crl", kwlist, + &crypto_X509_Type, &x509, + &crypto_PKey_Type, &key, &type, &days)) { + return NULL; + } + + bio = BIO_new(BIO_s_mem()); + tmptm = ASN1_TIME_new(); + if (!tmptm) { + return 0; + } + X509_gmtime_adj(tmptm,0); + X509_CRL_set_lastUpdate(self->crl, tmptm); + X509_gmtime_adj(tmptm,days*24*60*60); + X509_CRL_set_nextUpdate(self->crl, tmptm); + ASN1_TIME_free(tmptm); + X509_CRL_set_issuer_name(self->crl, X509_get_subject_name(x509->x509)); + X509_CRL_sign(self->crl, key->pkey, EVP_md5()); + switch (type) { + case X509_FILETYPE_PEM: + ret = PEM_write_bio_X509_CRL(bio, self->crl); + break; + + case X509_FILETYPE_ASN1: + ret = (int) i2d_X509_CRL_bio(bio, self->crl); + break; + + case X509_FILETYPE_TEXT: + ret = X509_CRL_print(bio, self->crl); + break; + + default: + PyErr_SetString( + PyExc_ValueError, + "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT"); + return NULL; + } + if (!ret) { + exception_from_error_queue(crypto_Error); + BIO_free(bio); + return NULL; + } + buf_len = BIO_get_mem_data(bio, &temp); + buffer = PyBytes_FromStringAndSize(temp, buf_len); + BIO_free(bio); + return buffer; +} + +crypto_CRLObj * +crypto_CRL_New(X509_CRL *crl) { + crypto_CRLObj *self; + + self = PyObject_New(crypto_CRLObj, &crypto_CRL_Type); + if (self == NULL) { + return NULL; + } + self->crl = crl; + return self; +} + +/* + * ADD_METHOD(name) expands to a correct PyMethodDef declaration + * { 'name', (PyCFunction)crypto_CRL_name, METH_VARARGS, crypto_CRL_name_doc } + * for convenience + */ +#define ADD_METHOD(name) \ + { #name, (PyCFunction)crypto_CRL_##name, METH_VARARGS, crypto_CRL_##name##_doc } +#define ADD_KW_METHOD(name) \ + { #name, (PyCFunction)crypto_CRL_##name, METH_VARARGS | METH_KEYWORDS, crypto_CRL_##name##_doc } +static PyMethodDef crypto_CRL_methods[] = { + ADD_KW_METHOD(add_revoked), + ADD_METHOD(get_revoked), + ADD_KW_METHOD(export), + { NULL, NULL } +}; +#undef ADD_METHOD + + +static void +crypto_CRL_dealloc(crypto_CRLObj *self) { + X509_CRL_free(self->crl); + self->crl = NULL; + + PyObject_Del(self); +} + +static char crypto_CRL_doc[] = "\n\ +CRL() -> CRL instance\n\ +\n\ +Create a new empty CRL object.\n\ +\n\ +@returns: The CRL object\n\ +"; + +static PyObject* crypto_CRL_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) { + if (!PyArg_ParseTuple(args, ":CRL")) { + return NULL; + } + + return (PyObject *)crypto_CRL_New(X509_CRL_new()); +} + +PyTypeObject crypto_CRL_Type = { + PyOpenSSL_HEAD_INIT(&PyType_Type, 0) + "CRL", + sizeof(crypto_CRLObj), + 0, + (destructor)crypto_CRL_dealloc, + NULL, /* print */ + NULL, /* getattr */ + NULL, /* setattr */ + NULL, /* compare */ + NULL, /* repr */ + NULL, /* as_number */ + NULL, /* as_sequence */ + NULL, /* as_mapping */ + NULL, /* hash */ + NULL, /* call */ + NULL, /* str */ + NULL, /* getattro */ + NULL, /* setattro */ + NULL, /* as_buffer */ + Py_TPFLAGS_DEFAULT, + crypto_CRL_doc, /* doc */ + NULL, /* traverse */ + NULL, /* clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + crypto_CRL_methods, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + crypto_CRL_new, /* tp_new */ +}; + +int init_crypto_crl(PyObject *module) { + if (PyType_Ready(&crypto_CRL_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "CRL", (PyObject *)&crypto_CRL_Type) != 0) { + return 0; + } + return 1; +} diff --git a/OpenSSL/crypto/crl.h b/OpenSSL/crypto/crl.h new file mode 100644 index 0000000..87f5048 --- /dev/null +++ b/OpenSSL/crypto/crl.h @@ -0,0 +1,19 @@ +#ifndef PyOpenSSL_crypto_CRL_H_ +#define PyOpenSSL_crypto_CRL_H_ + +#include + +extern int init_crypto_crl (PyObject *); + +extern PyTypeObject crypto_CRL_Type; + +#define crypto_CRL_Check(v) ((v)->ob_type == &crypto_CRL_Type) + +typedef struct { + PyObject_HEAD + X509_CRL *crl; +} crypto_CRLObj; + +crypto_CRLObj * crypto_CRL_New(X509_CRL *crl); + +#endif diff --git a/OpenSSL/crypto/crypto.c b/OpenSSL/crypto/crypto.c new file mode 100644 index 0000000..1e2abc2 --- /dev/null +++ b/OpenSSL/crypto/crypto.c @@ -0,0 +1,888 @@ +/* + * crypto.c + * + * Copyright (C) AB Strakt + * Copyright (C) Keyphrene + * Copyright (C) Jean-Paul Calderone + * See LICENSE for details. + * + * Main file of crypto sub module. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + */ +#include +#define crypto_MODULE +#include "crypto.h" +#include "pkcs12.h" + +static char crypto_doc[] = "\n\ +Main file of crypto sub module.\n\ +See the file RATIONALE for a short explanation of why this module was written.\n\ +"; + +void **ssl_API; + +PyObject *crypto_Error; + +int crypto_byte_converter(PyObject *input, void* output) { + char **message = output; + if (input == Py_None) { + *message = NULL; + } else if (PyBytes_CheckExact(input)) { + *message = PyBytes_AsString(input); + } else { + return 0; + } + return 1; +} + +static int +global_passphrase_callback(char *buf, int len, int rwflag, void *cb_arg) +{ + PyObject *func, *argv, *ret; + int nchars; + + func = (PyObject *)cb_arg; + argv = Py_BuildValue("(i)", rwflag); + ret = PyEval_CallObject(func, argv); + Py_DECREF(argv); + if (ret == NULL) + return 0; + if (!PyBytes_Check(ret)) + { + PyErr_SetString(PyExc_ValueError, "String expected"); + return 0; + } + nchars = PyBytes_Size(ret); + if (nchars > len) + nchars = len; + strncpy(buf, PyBytes_AsString(ret), nchars); + return nchars; +} + +static char crypto_load_privatekey_doc[] = "\n\ +Load a private key from a buffer\n\ +\n\ +@param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)\n\ +@param buffer: The buffer the key is stored in\n\ +@param passphrase: (optional) if encrypted PEM format, this can be\n\ + either the passphrase to use, or a callback for\n\ + providing the passphrase.\n\ +\n\ +@return: The PKey object\n\ +"; + +static PyObject * +crypto_load_privatekey(PyObject *spam, PyObject *args) +{ + crypto_PKeyObj *crypto_PKey_New(EVP_PKEY *, int); + int type, len; + char *buffer; + PyObject *pw = NULL; + pem_password_cb *cb = NULL; + void *cb_arg = NULL; + BIO *bio; + EVP_PKEY *pkey; + + if (!PyArg_ParseTuple(args, "is#|O:load_privatekey", &type, &buffer, &len, &pw)) + return NULL; + + if (pw != NULL) + { + if (PyBytes_Check(pw)) + { + cb = NULL; + cb_arg = PyBytes_AsString(pw); + } + else if (PyCallable_Check(pw)) + { + cb = global_passphrase_callback; + cb_arg = pw; + } + else + { + PyErr_SetString(PyExc_TypeError, "Last argument must be string or callable"); + return NULL; + } + } + + bio = BIO_new_mem_buf(buffer, len); + switch (type) + { + case X509_FILETYPE_PEM: + pkey = PEM_read_bio_PrivateKey(bio, NULL, cb, cb_arg); + break; + + case X509_FILETYPE_ASN1: + pkey = d2i_PrivateKey_bio(bio, NULL); + break; + + default: + PyErr_SetString(PyExc_ValueError, "type argument must be FILETYPE_PEM or FILETYPE_ASN1"); + BIO_free(bio); + return NULL; + } + BIO_free(bio); + + if (pkey == NULL) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + return (PyObject *)crypto_PKey_New(pkey, 1); +} + +static char crypto_dump_privatekey_doc[] = "\n\ +Dump a private key to a buffer\n\ +\n\ +@param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)\n\ +@param pkey: The PKey to dump\n\ +@param cipher: (optional) if encrypted PEM format, the cipher to\n\ + use\n\ +@param passphrase - (optional) if encrypted PEM format, this can be either\n\ + the passphrase to use, or a callback for providing the\n\ + passphrase.\n\ +@return: The buffer with the dumped key in\n\ +@rtype: C{str}\n\ +"; + +static PyObject * +crypto_dump_privatekey(PyObject *spam, PyObject *args) +{ + int type, ret, buf_len; + char *temp; + PyObject *buffer; + char *cipher_name = NULL; + const EVP_CIPHER *cipher = NULL; + PyObject *pw = NULL; + pem_password_cb *cb = NULL; + void *cb_arg = NULL; + BIO *bio; + RSA *rsa; + crypto_PKeyObj *pkey; + + if (!PyArg_ParseTuple(args, "iO!|sO:dump_privatekey", &type, + &crypto_PKey_Type, &pkey, &cipher_name, &pw)) + return NULL; + + if (cipher_name != NULL && pw == NULL) + { + PyErr_SetString(PyExc_ValueError, "Illegal number of arguments"); + return NULL; + } + if (cipher_name != NULL) + { + cipher = EVP_get_cipherbyname(cipher_name); + if (cipher == NULL) + { + PyErr_SetString(PyExc_ValueError, "Invalid cipher name"); + return NULL; + } + if (PyBytes_Check(pw)) + { + cb = NULL; + cb_arg = PyBytes_AsString(pw); + } + else if (PyCallable_Check(pw)) + { + cb = global_passphrase_callback; + cb_arg = pw; + } + else + { + PyErr_SetString(PyExc_TypeError, "Last argument must be string or callable"); + return NULL; + } + } + + bio = BIO_new(BIO_s_mem()); + switch (type) + { + case X509_FILETYPE_PEM: + ret = PEM_write_bio_PrivateKey(bio, pkey->pkey, cipher, NULL, 0, cb, cb_arg); + if (PyErr_Occurred()) + { + BIO_free(bio); + return NULL; + } + break; + + case X509_FILETYPE_ASN1: + ret = i2d_PrivateKey_bio(bio, pkey->pkey); + break; + + case X509_FILETYPE_TEXT: + rsa = EVP_PKEY_get1_RSA(pkey->pkey); + ret = RSA_print(bio, rsa, 0); + RSA_free(rsa); + break; + + default: + PyErr_SetString(PyExc_ValueError, "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT"); + BIO_free(bio); + return NULL; + } + + if (ret == 0) + { + BIO_free(bio); + exception_from_error_queue(crypto_Error); + return NULL; + } + + buf_len = BIO_get_mem_data(bio, &temp); + buffer = PyBytes_FromStringAndSize(temp, buf_len); + BIO_free(bio); + + return buffer; +} + +static char crypto_load_certificate_doc[] = "\n\ +Load a certificate from a buffer\n\ +\n\ +@param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)\n\ + buffer - The buffer the certificate is stored in\n\ +@return: The X509 object\n\ +"; + +static PyObject * +crypto_load_certificate(PyObject *spam, PyObject *args) +{ + crypto_X509Obj *crypto_X509_New(X509 *, int); + int type, len; + char *buffer; + BIO *bio; + X509 *cert; + + if (!PyArg_ParseTuple(args, "is#:load_certificate", &type, &buffer, &len)) + return NULL; + + bio = BIO_new_mem_buf(buffer, len); + switch (type) + { + case X509_FILETYPE_PEM: + cert = PEM_read_bio_X509(bio, NULL, NULL, NULL); + break; + + case X509_FILETYPE_ASN1: + cert = d2i_X509_bio(bio, NULL); + break; + + default: + PyErr_SetString(PyExc_ValueError, "type argument must be FILETYPE_PEM or FILETYPE_ASN1"); + BIO_free(bio); + return NULL; + } + BIO_free(bio); + + if (cert == NULL) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + return (PyObject *)crypto_X509_New(cert, 1); +} + +static char crypto_dump_certificate_doc[] = "\n\ +Dump a certificate to a buffer\n\ +\n\ +@param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)\n\ +@param cert: The certificate to dump\n\ +@return: The buffer with the dumped certificate in\n\ +"; + +static PyObject * +crypto_dump_certificate(PyObject *spam, PyObject *args) +{ + int type, ret, buf_len; + char *temp; + PyObject *buffer; + BIO *bio; + crypto_X509Obj *cert; + + if (!PyArg_ParseTuple(args, "iO!:dump_certificate", &type, + &crypto_X509_Type, &cert)) + return NULL; + + bio = BIO_new(BIO_s_mem()); + switch (type) + { + case X509_FILETYPE_PEM: + ret = PEM_write_bio_X509(bio, cert->x509); + break; + + case X509_FILETYPE_ASN1: + ret = i2d_X509_bio(bio, cert->x509); + break; + + case X509_FILETYPE_TEXT: + ret = X509_print_ex(bio, cert->x509, 0, 0); + break; + + default: + PyErr_SetString(PyExc_ValueError, "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT"); + BIO_free(bio); + return NULL; + } + + if (ret == 0) + { + BIO_free(bio); + exception_from_error_queue(crypto_Error); + return NULL; + } + + buf_len = BIO_get_mem_data(bio, &temp); + buffer = PyBytes_FromStringAndSize(temp, buf_len); + BIO_free(bio); + + return buffer; +} + +static char crypto_load_certificate_request_doc[] = "\n\ +Load a certificate request from a buffer\n\ +\n\ +@param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)\n\ + buffer - The buffer the certificate request is stored in\n\ +@return: The X509Req object\n\ +"; + +static PyObject * +crypto_load_certificate_request(PyObject *spam, PyObject *args) +{ + crypto_X509ReqObj *crypto_X509Req_New(X509_REQ *, int); + int type, len; + char *buffer; + BIO *bio; + X509_REQ *req; + + if (!PyArg_ParseTuple(args, "is#:load_certificate_request", &type, &buffer, &len)) + return NULL; + + bio = BIO_new_mem_buf(buffer, len); + switch (type) + { + case X509_FILETYPE_PEM: + req = PEM_read_bio_X509_REQ(bio, NULL, NULL, NULL); + break; + + case X509_FILETYPE_ASN1: + req = d2i_X509_REQ_bio(bio, NULL); + break; + + default: + PyErr_SetString(PyExc_ValueError, "type argument must be FILETYPE_PEM or FILETYPE_ASN1"); + BIO_free(bio); + return NULL; + } + BIO_free(bio); + + if (req == NULL) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + return (PyObject *)crypto_X509Req_New(req, 1); +} + +static char crypto_dump_certificate_request_doc[] = "\n\ +Dump a certificate request to a buffer\n\ +\n\ +@param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)\n\ + req - The certificate request to dump\n\ +@return: The buffer with the dumped certificate request in\n\ +"; + +static PyObject * +crypto_dump_certificate_request(PyObject *spam, PyObject *args) +{ + int type, ret, buf_len; + char *temp; + PyObject *buffer; + BIO *bio; + crypto_X509ReqObj *req; + + if (!PyArg_ParseTuple(args, "iO!:dump_certificate_request", &type, + &crypto_X509Req_Type, &req)) + return NULL; + + bio = BIO_new(BIO_s_mem()); + switch (type) + { + case X509_FILETYPE_PEM: + ret = PEM_write_bio_X509_REQ(bio, req->x509_req); + break; + + case X509_FILETYPE_ASN1: + ret = i2d_X509_REQ_bio(bio, req->x509_req); + break; + + case X509_FILETYPE_TEXT: + ret = X509_REQ_print_ex(bio, req->x509_req, 0, 0); + break; + + default: + PyErr_SetString(PyExc_ValueError, "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT"); + BIO_free(bio); + return NULL; + } + + if (ret == 0) + { + BIO_free(bio); + exception_from_error_queue(crypto_Error); + return NULL; + } + + buf_len = BIO_get_mem_data(bio, &temp); + buffer = PyBytes_FromStringAndSize(temp, buf_len); + BIO_free(bio); + + return buffer; +} + +static char crypto_load_crl_doc[] = "\n\ +Load a certificate revocation list from a buffer\n\ +\n\ +@param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1)\n\ +@param buffer: The buffer the CRL is stored in\n\ +\n\ +@return: The PKey object\n\ +"; + +static PyObject * +crypto_load_crl(PyObject *spam, PyObject *args) { + int type, len; + char *buffer; + BIO *bio; + X509_CRL *crl; + + if (!PyArg_ParseTuple(args, "is#:load_crl", &type, &buffer, &len)) { + return NULL; + } + + bio = BIO_new_mem_buf(buffer, len); + switch (type) { + case X509_FILETYPE_PEM: + crl = PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL); + break; + + case X509_FILETYPE_ASN1: + crl = d2i_X509_CRL_bio(bio, NULL); + break; + + default: + PyErr_SetString(PyExc_ValueError, "type argument must be FILETYPE_PEM or FILETYPE_ASN1"); + BIO_free(bio); + return NULL; + } + BIO_free(bio); + + if (crl == NULL) { + exception_from_error_queue(crypto_Error); + return NULL; + } + + return (PyObject *)crypto_CRL_New(crl); +} + +static char crypto_load_pkcs7_data_doc[] = "\n\ +Load pkcs7 data from a buffer\n\ +\n\ +@param type: The file type (one of FILETYPE_PEM or FILETYPE_ASN1)\n\ + buffer - The buffer with the pkcs7 data.\n\ +@return: The PKCS7 object\n\ +"; + +static PyObject * +crypto_load_pkcs7_data(PyObject *spam, PyObject *args) +{ + int type, len; + char *buffer; + BIO *bio; + PKCS7 *pkcs7 = NULL; + + if (!PyArg_ParseTuple(args, "is#:load_pkcs7_data", &type, &buffer, &len)) + return NULL; + + /* + * Try to read the pkcs7 data from the bio + */ + bio = BIO_new_mem_buf(buffer, len); + switch (type) + { + case X509_FILETYPE_PEM: + pkcs7 = PEM_read_bio_PKCS7(bio, NULL, NULL, NULL); + break; + + case X509_FILETYPE_ASN1: + pkcs7 = d2i_PKCS7_bio(bio, NULL); + break; + + default: + PyErr_SetString(PyExc_ValueError, + "type argument must be FILETYPE_PEM or FILETYPE_ASN1"); + return NULL; + } + BIO_free(bio); + + /* + * Check if we got a PKCS7 structure + */ + if (pkcs7 == NULL) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + return (PyObject *)crypto_PKCS7_New(pkcs7, 1); +} + +static char crypto_load_pkcs12_doc[] = "\n\ +Load a PKCS12 object from a buffer\n\ +\n\ +@param buffer: The buffer the certificate is stored in\n\ + passphrase (Optional) - The password to decrypt the PKCS12 lump\n\ +@returns: The PKCS12 object\n\ +"; + +static PyObject * +crypto_load_pkcs12(PyObject *spam, PyObject *args) +{ + int len; + char *buffer, *passphrase = NULL; + BIO *bio; + PKCS12 *p12; + + if (!PyArg_ParseTuple(args, "s#|s:load_pkcs12", &buffer, &len, &passphrase)) + return NULL; + + bio = BIO_new_mem_buf(buffer, len); + if ((p12 = d2i_PKCS12_bio(bio, NULL)) == NULL) + { + BIO_free(bio); + exception_from_error_queue(crypto_Error); + return NULL; + } + BIO_free(bio); + + return (PyObject *)crypto_PKCS12_New(p12, passphrase); +} + + +static char crypto_X509_verify_cert_error_string_doc[] = "\n\ +Get X509 verify certificate error string.\n\ +\n\ +@param errnum: The error number.\n\ +@return: Error string as a Python string\n\ +"; + +static PyObject * +crypto_X509_verify_cert_error_string(PyObject *spam, PyObject *args) +{ + int errnum; + const char *str; + + if (!PyArg_ParseTuple(args, "i", &errnum)) + return NULL; + + str = X509_verify_cert_error_string(errnum); + return PyText_FromString(str); +} + +static char crypto_exception_from_error_queue_doc[] = "\n\ +Raise an exception from the current OpenSSL error queue.\n\ +"; + +static PyObject * +crypto_exception_from_error_queue(PyObject *spam, PyObject *eggs) { + exception_from_error_queue(crypto_Error); + return NULL; +} + +static char crypto_sign_doc[] = "\n\ +Sign data with a digest\n\ +\n\ +@param pkey: Pkey to sign with\n\ +@param data: data to be signed\n\ +@param digest: message digest to use\n\ +@return: signature\n\ +"; + +static PyObject * +crypto_sign(PyObject *spam, PyObject *args) { + PyObject *buffer; + crypto_PKeyObj *pkey; + char *data = NULL; + char *digest_name; + int err; + unsigned int sig_len; + const EVP_MD *digest; + EVP_MD_CTX md_ctx; + unsigned char sig_buf[512]; + + if (!PyArg_ParseTuple( + args, "O!" BYTESTRING_FMT "s:sign", &crypto_PKey_Type, + &pkey, &data, &digest_name)) { + return NULL; + } + + if ((digest = EVP_get_digestbyname(digest_name)) == NULL) { + PyErr_SetString(PyExc_ValueError, "No such digest method"); + return NULL; + } + + EVP_SignInit(&md_ctx, digest); + EVP_SignUpdate(&md_ctx, data, strlen(data)); + sig_len = sizeof(sig_buf); + err = EVP_SignFinal(&md_ctx, sig_buf, &sig_len, pkey->pkey); + + if (err != 1) { + exception_from_error_queue(crypto_Error); + return NULL; + } + + buffer = PyBytes_FromStringAndSize((char*)sig_buf, sig_len); + return buffer; +} + +static char crypto_verify_doc[] = "\n\ +Verify a signature\n\ +\n\ +@param cert: signing certificate (X509 object)\n\ +@param signature: signature returned by sign function\n\ +@param data: data to be verified\n\ +@param digest: message digest to use\n\ +@return: None if the signature is correct, raise exception otherwise\n\ +"; + +static PyObject * +crypto_verify(PyObject *spam, PyObject *args) { + crypto_X509Obj *cert; + unsigned char *signature; + int sig_len; + char *data, *digest_name; + int err; + const EVP_MD *digest; + EVP_MD_CTX md_ctx; + EVP_PKEY *pkey; + +#ifdef PY3 + if (!PyArg_ParseTuple(args, "O!" BYTESTRING_FMT "#" BYTESTRING_FMT "s:verify", &crypto_X509_Type, &cert, &signature, &sig_len, &data, &digest_name)) { +#else + if (!PyArg_ParseTuple(args, "O!t#ss:verify", &crypto_X509_Type, &cert, &signature, &sig_len, &data, &digest_name)) { +#endif + return NULL; + } + + if ((digest = EVP_get_digestbyname(digest_name)) == NULL){ + PyErr_SetString(PyExc_ValueError, "No such digest method"); + return NULL; + } + + pkey = X509_get_pubkey(cert->x509); + if (pkey == NULL) { + PyErr_SetString(PyExc_ValueError, "No public key"); + return NULL; + } + + EVP_VerifyInit(&md_ctx, digest); + EVP_VerifyUpdate(&md_ctx, data, strlen((char*)data)); + err = EVP_VerifyFinal(&md_ctx, signature, sig_len, pkey); + EVP_PKEY_free(pkey); + + if (err != 1) { + exception_from_error_queue(crypto_Error); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +/* Methods in the OpenSSL.crypto module (i.e. none) */ +static PyMethodDef crypto_methods[] = { + /* Module functions */ + { "load_privatekey", (PyCFunction)crypto_load_privatekey, METH_VARARGS, crypto_load_privatekey_doc }, + { "dump_privatekey", (PyCFunction)crypto_dump_privatekey, METH_VARARGS, crypto_dump_privatekey_doc }, + { "load_certificate", (PyCFunction)crypto_load_certificate, METH_VARARGS, crypto_load_certificate_doc }, + { "dump_certificate", (PyCFunction)crypto_dump_certificate, METH_VARARGS, crypto_dump_certificate_doc }, + { "load_certificate_request", (PyCFunction)crypto_load_certificate_request, METH_VARARGS, crypto_load_certificate_request_doc }, + { "dump_certificate_request", (PyCFunction)crypto_dump_certificate_request, METH_VARARGS, crypto_dump_certificate_request_doc }, + { "load_crl", (PyCFunction)crypto_load_crl, METH_VARARGS, crypto_load_crl_doc }, + { "load_pkcs7_data", (PyCFunction)crypto_load_pkcs7_data, METH_VARARGS, crypto_load_pkcs7_data_doc }, + { "load_pkcs12", (PyCFunction)crypto_load_pkcs12, METH_VARARGS, crypto_load_pkcs12_doc }, + { "sign", (PyCFunction)crypto_sign, METH_VARARGS, crypto_sign_doc }, + { "verify", (PyCFunction)crypto_verify, METH_VARARGS, crypto_verify_doc }, + { "X509_verify_cert_error_string", (PyCFunction)crypto_X509_verify_cert_error_string, METH_VARARGS, crypto_X509_verify_cert_error_string_doc }, + { "_exception_from_error_queue", (PyCFunction)crypto_exception_from_error_queue, METH_NOARGS, crypto_exception_from_error_queue_doc }, + { NULL, NULL } +}; + + +#ifdef WITH_THREAD + +#include + +/** + * This array will store all of the mutexes available to OpenSSL. + */ +static PyThread_type_lock *mutex_buf = NULL; + + +/** + * Callback function supplied to OpenSSL to acquire or release a lock. + * + */ +static void locking_function(int mode, int n, const char * file, int line) { + if (mode & CRYPTO_LOCK) { + PyThread_acquire_lock(mutex_buf[n], WAIT_LOCK); + } else { + PyThread_release_lock(mutex_buf[n]); + } +} + + +/** + * Initialize OpenSSL for use from multiple threads. + * + * Returns: 0 if initialization fails, 1 otherwise. + */ +static int init_openssl_threads(void) { + int i; + + mutex_buf = (PyThread_type_lock *)malloc( + CRYPTO_num_locks() * sizeof(PyThread_type_lock)); + if (!mutex_buf) { + return 0; + } + for (i = 0; i < CRYPTO_num_locks(); ++i) { + mutex_buf[i] = PyThread_allocate_lock(); + } + CRYPTO_set_id_callback((unsigned long (*)(void))PyThread_get_thread_ident); + CRYPTO_set_locking_callback(locking_function); + return 1; +} + +/* /\** */ +/* * Clean up after OpenSSL thread initialization. */ +/* *\/ */ +/* static int deinit_openssl_threads() { */ +/* int i; */ + +/* if (!mutex_buf) { */ +/* return 0; */ +/* } */ +/* CRYPTO_set_id_callback(NULL); */ +/* CRYPTO_set_locking_callback(NULL); */ +/* for (i = 0; i < CRYPTO_num_locks(); i++) { */ +/* PyThread_free_lock(mutex_buf[i]); */ +/* } */ +/* free(mutex_buf); */ +/* mutex_buf = NULL; */ +/* return 1; */ +/* } */ + +#endif + +#ifdef PY3 +static struct PyModuleDef cryptomodule = { + PyModuleDef_HEAD_INIT, + "crypto", + crypto_doc, + -1, + crypto_methods +}; +#endif + +/* + * Initialize crypto sub module + * + * Arguments: None + * Returns: None + */ +PyOpenSSL_MODINIT(crypto) { +#ifndef PY3 + static void *crypto_API[crypto_API_pointers]; + PyObject *c_api_object; +#endif + PyObject *module; + + ERR_load_crypto_strings(); + OpenSSL_add_all_algorithms(); + +#ifdef PY3 + module = PyModule_Create(&cryptomodule); +#else + module = Py_InitModule3("crypto", crypto_methods, crypto_doc); +#endif + + if (module == NULL) { + PyOpenSSL_MODRETURN(NULL); + } + +#ifndef PY3 + /* Initialize the C API pointer array */ + crypto_API[crypto_X509_New_NUM] = (void *)crypto_X509_New; + crypto_API[crypto_X509Name_New_NUM] = (void *)crypto_X509Name_New; + crypto_API[crypto_X509Req_New_NUM] = (void *)crypto_X509Req_New; + crypto_API[crypto_X509Store_New_NUM] = (void *)crypto_X509Store_New; + crypto_API[crypto_PKey_New_NUM] = (void *)crypto_PKey_New; + crypto_API[crypto_X509Extension_New_NUM] = (void *)crypto_X509Extension_New; + crypto_API[crypto_PKCS7_New_NUM] = (void *)crypto_PKCS7_New; + crypto_API[crypto_NetscapeSPKI_New_NUM] = (void *)crypto_NetscapeSPKI_New; + c_api_object = PyCObject_FromVoidPtr((void *)crypto_API, NULL); + if (c_api_object != NULL) + PyModule_AddObject(module, "_C_API", c_api_object); +#endif + + crypto_Error = PyErr_NewException("OpenSSL.crypto.Error", NULL, NULL); + if (crypto_Error == NULL) + goto error; + if (PyModule_AddObject(module, "Error", crypto_Error) != 0) + goto error; + + PyModule_AddIntConstant(module, "FILETYPE_PEM", X509_FILETYPE_PEM); + PyModule_AddIntConstant(module, "FILETYPE_ASN1", X509_FILETYPE_ASN1); + PyModule_AddIntConstant(module, "FILETYPE_TEXT", X509_FILETYPE_TEXT); + + PyModule_AddIntConstant(module, "TYPE_RSA", crypto_TYPE_RSA); + PyModule_AddIntConstant(module, "TYPE_DSA", crypto_TYPE_DSA); + +#ifdef WITH_THREAD + if (!init_openssl_threads()) + goto error; +#endif + if (!init_crypto_x509(module)) + goto error; + if (!init_crypto_x509name(module)) + goto error; + if (!init_crypto_x509store(module)) + goto error; + if (!init_crypto_x509req(module)) + goto error; + if (!init_crypto_pkey(module)) + goto error; + if (!init_crypto_x509extension(module)) + goto error; + if (!init_crypto_pkcs7(module)) + goto error; + if (!init_crypto_pkcs12(module)) + goto error; + if (!init_crypto_netscape_spki(module)) + goto error; + if (!init_crypto_crl(module)) + goto error; + if (!init_crypto_revoked(module)) + goto error; + + PyOpenSSL_MODRETURN(module); + +error: + PyOpenSSL_MODRETURN(NULL); + ; +} diff --git a/OpenSSL/crypto/crypto.h b/OpenSSL/crypto/crypto.h new file mode 100644 index 0000000..8c803a8 --- /dev/null +++ b/OpenSSL/crypto/crypto.h @@ -0,0 +1,129 @@ +/* + * crypto.h + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * Exports from crypto.c. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + * + */ +#ifndef PyOpenSSL_CRYPTO_H_ +#define PyOpenSSL_CRYPTO_H_ + +#include +#include "x509.h" +#include "x509name.h" +#include "netscape_spki.h" +#include "x509store.h" +#include "x509req.h" +#include "pkey.h" +#include "x509ext.h" +#include "pkcs7.h" +#include "pkcs12.h" +#include "crl.h" +#include "revoked.h" +#include "../util.h" + +extern PyObject *crypto_Error; + +#define crypto_X509_New_NUM 0 +#define crypto_X509_New_RETURN crypto_X509Obj * +#define crypto_X509_New_PROTO (X509 *, int) + +#define crypto_X509Req_New_NUM 1 +#define crypto_X509Req_New_RETURN crypto_X509ReqObj * +#define crypto_X509Req_New_PROTO (X509_REQ *, int) + +#define crypto_X509Store_New_NUM 2 +#define crypto_X509Store_New_RETURN crypto_X509StoreObj * +#define crypto_X509Store_New_PROTO (X509_STORE *, int) + +#define crypto_PKey_New_NUM 3 +#define crypto_PKey_New_RETURN crypto_PKeyObj * +#define crypto_PKey_New_PROTO (EVP_PKEY *, int) + +#define crypto_X509Name_New_NUM 4 +#define crypto_X509Name_New_RETURN crypto_X509NameObj * +#define crypto_X509Name_New_PROTO (X509_NAME *, int) + +#define crypto_X509Extension_New_NUM 5 +#define crypto_X509Extension_New_RETURN crypto_X509ExtensionObj * +#define crypto_X509Extension_New_PROTO (char *, int, char *, crypto_X509Obj *, crypto_X509Obj *) + +#define crypto_PKCS7_New_NUM 6 +#define crypto_PKCS7_New_RETURN crypto_PKCS7Obj * +#define crypto_PKCS7_New_PROTO (PKCS7 *, int) + +#define crypto_NetscapeSPKI_New_NUM 7 +#define crypto_NetscapeSPKI_New_RETURN crypto_NetscapeSPKIObj * +#define crypto_NetscapeSPKI_New_PROTO (NETSCAPE_SPKI *, int) + +#define crypto_API_pointers 8 + +#if defined(PY3) || defined(crypto_MODULE) + +#ifdef _WIN32 +#define EXPORT __declspec(dllexport) +#else +#define EXPORT +#endif + +extern EXPORT crypto_X509_New_RETURN crypto_X509_New crypto_X509_New_PROTO; +extern EXPORT crypto_X509Name_New_RETURN crypto_X509Name_New crypto_X509Name_New_PROTO; +extern crypto_X509Req_New_RETURN crypto_X509Req_New crypto_X509Req_New_PROTO; +extern EXPORT crypto_X509Store_New_RETURN crypto_X509Store_New crypto_X509Store_New_PROTO; +extern crypto_PKey_New_RETURN crypto_PKey_New crypto_PKey_New_PROTO; +extern crypto_X509Extension_New_RETURN crypto_X509Extension_New crypto_X509Extension_New_PROTO; +extern crypto_PKCS7_New_RETURN crypto_PKCS7_New crypto_PKCS7_New_PROTO; +extern crypto_NetscapeSPKI_New_RETURN crypto_NetscapeSPKI_New crypto_NetscapeSPKI_New_PROTO; + +int crypto_byte_converter(PyObject *input, void *output); + +#else /* crypto_MODULE */ + +extern void **crypto_API; + +#define crypto_X509_New \ + (*(crypto_X509_New_RETURN (*)crypto_X509_New_PROTO) crypto_API[crypto_X509_New_NUM]) +#define crypto_X509Name_New \ + (*(crypto_X509Name_New_RETURN (*)crypto_X509Name_New_PROTO) crypto_API[crypto_X509Name_New_NUM]) +#define crypto_X509Req_New \ + (*(crypto_X509Req_New_RETURN (*)crypto_X509Req_New_PROTO) crypto_API[crypto_X509Req_New_NUM]) +#define crypto_X509Store_New \ + (*(crypto_X509Store_New_RETURN (*)crypto_X509Store_New_PROTO) crypto_API[crypto_X509Store_New_NUM]) +#define crypto_PKey_New \ + (*(crypto_PKey_New_RETURN (*)crypto_PKey_New_PROTO) crypto_API[crypto_PKey_New_NUM]) +#define crypto_X509Extension_New\ + (*(crypto_X509Extension_New_RETURN (*)crypto_X509Extension_New_PROTO) crypto_API[crypto_X509Extension_New_NUM]) +#define crypto_PKCS7_New \ + (*(crypto_PKCS7_New_RETURN (*)crypto_PKCS7_New_PROTO) crypto_API[crypto_PKCS7_New_NUM]) +#define crypto_NetscapeSPKI_New \ + (*(crypto_NetscapeSPKI_New_RETURN (*)crypto_NetscapeSPKI_New_PROTO) crypto_API[crypto_NetscapeSPKI_New_NUM]) + +#define import_crypto() \ +{ \ + PyObject *crypto_module = PyImport_ImportModule("OpenSSL.crypto"); \ + if (crypto_module != NULL) { \ + PyObject *crypto_dict, *crypto_api_object; \ + crypto_dict = PyModule_GetDict(crypto_module); \ + crypto_api_object = PyDict_GetItemString(crypto_dict, "_C_API"); \ + if (PyCObject_Check(crypto_api_object)) { \ + crypto_API = (void **)PyCObject_AsVoidPtr(crypto_api_object); \ + } \ + } \ +} + +#endif /* crypto_MODULE */ + +/* Define a new type for emitting text. Hopefully these don't collide with + * future official OpenSSL constants, but the switch statement of + * dump_certificate() will alert us if it matters. + */ +#ifndef X509_FILETYPE_TEXT +#define X509_FILETYPE_TEXT (58) +#endif + +#endif /* PyOpenSSL_CRYPTO_H_ */ diff --git a/OpenSSL/crypto/netscape_spki.c b/OpenSSL/crypto/netscape_spki.c new file mode 100644 index 0000000..1792178 --- /dev/null +++ b/OpenSSL/crypto/netscape_spki.c @@ -0,0 +1,310 @@ +/* + * netscape_spki.c + * + * Copyright (C) Tollef Fog Heen + * See LICENSE for details. + * + * Netscape SPKI handling, thin wrapper + */ +#include +#define crypto_MODULE +#include "crypto.h" + +/* + * Constructor for Nestcape_SPKI, never called by Python code directly + * + * Arguments: name - A "real" NetscapeSPKI object + * dealloc - Boolean value to specify whether the destructor should + * free the "real" NetscapeSPKI object + * Returns: The newly created NetscapeSPKI object + */ +crypto_NetscapeSPKIObj * +crypto_NetscapeSPKI_New(NETSCAPE_SPKI *name, int dealloc) +{ + crypto_NetscapeSPKIObj *self; + + self = PyObject_New(crypto_NetscapeSPKIObj, &crypto_NetscapeSPKI_Type); + + if (self == NULL) + return NULL; + + self->netscape_spki = name; + self->dealloc = dealloc; + + return self; +} + + +static char crypto_NetscapeSPKI_doc[] = "\n\ +NetscapeSPKI([enc]) -> NetscapeSPKI instance\n\ +\n\ +@param enc: Base64 encoded NetscapeSPKI object.\n\ +@type enc: C{str}\n\ +@return: The NetscapeSPKI object\n\ +"; + +static PyObject * +crypto_NetscapeSPKI_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) { + char *enc = NULL; + int enc_len = -1; + NETSCAPE_SPKI *spki; + + if (!PyArg_ParseTuple(args, "|s#:NetscapeSPKI", &enc, &enc_len)) + return NULL; + + if (enc_len >= 0) + spki = NETSCAPE_SPKI_b64_decode(enc, enc_len); + else + spki = NETSCAPE_SPKI_new(); + if (spki == NULL) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + return (PyObject *)crypto_NetscapeSPKI_New(spki, 1); +} + + +/* + * Deallocate the memory used by the NetscapeSPKI object + * + * Arguments: self - The NetscapeSPKI object + * Returns: None + */ +static void +crypto_NetscapeSPKI_dealloc(crypto_NetscapeSPKIObj *self) +{ + /* Sometimes we don't have to dealloc this */ + if (self->dealloc) + NETSCAPE_SPKI_free(self->netscape_spki); + + PyObject_Del(self); +} + +static char crypto_NetscapeSPKI_sign_doc[] = "\n\ +Sign the certificate request using the supplied key and digest\n\ +\n\ +@param pkey: The key to sign with\n\ +@param digest: The message digest to use\n\ +@return: None\n\ +"; + +static PyObject * +crypto_NetscapeSPKI_sign(crypto_NetscapeSPKIObj *self, PyObject *args) +{ + crypto_PKeyObj *pkey; + char *digest_name; + const EVP_MD *digest; + + if (!PyArg_ParseTuple(args, "O!s:sign", &crypto_PKey_Type, &pkey, + &digest_name)) + return NULL; + + if (pkey->only_public) { + PyErr_SetString(PyExc_ValueError, "Key has only public part"); + return NULL; + } + + if (!pkey->initialized) { + PyErr_SetString(PyExc_ValueError, "Key is uninitialized"); + return NULL; + } + + if ((digest = EVP_get_digestbyname(digest_name)) == NULL) + { + PyErr_SetString(PyExc_ValueError, "No such digest method"); + return NULL; + } + + if (!NETSCAPE_SPKI_sign(self->netscape_spki, pkey->pkey, digest)) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_NetscapeSPKI_verify_doc[] = "\n\ +Verifies a certificate request using the supplied public key\n\ +\n\ +@param key: a public key\n\ +@return: True if the signature is correct.\n\ +@raise OpenSSL.crypto.Error: If the signature is invalid or there is a\n\ + problem verifying the signature.\n\ +"; + +PyObject * +crypto_NetscapeSPKI_verify(crypto_NetscapeSPKIObj *self, PyObject *args) +{ + crypto_PKeyObj *pkey; + int answer; + + if (!PyArg_ParseTuple(args, "O!:verify", &crypto_PKey_Type, &pkey)) { + return NULL; + } + + if ((answer = NETSCAPE_SPKI_verify(self->netscape_spki, pkey->pkey)) <= 0) { + exception_from_error_queue(crypto_Error); + return NULL; + } + + return PyLong_FromLong((long)answer); +} + +static char crypto_NetscapeSPKI_b64_encode_doc[] = "\n\ +Generate a base64 encoded string from an SPKI\n\ +\n\ +@return: The base64 encoded string\n\ +"; + +PyObject * +crypto_NetscapeSPKI_b64_encode(crypto_NetscapeSPKIObj *self, PyObject *args) +{ + char *str; + + if (!PyArg_ParseTuple(args, ":b64_encode")) + return NULL; + + str = NETSCAPE_SPKI_b64_encode(self->netscape_spki); + return PyBytes_FromString(str); +} + + +static char crypto_NetscapeSPKI_get_pubkey_doc[] = "\n\ +Get the public key of the certificate\n\ +\n\ +@return: The public key\n\ +"; + +static PyObject * +crypto_NetscapeSPKI_get_pubkey(crypto_NetscapeSPKIObj *self, PyObject *args) +{ + crypto_PKeyObj *crypto_PKey_New(EVP_PKEY *, int); + EVP_PKEY *pkey; + crypto_PKeyObj *py_pkey; + + if (!PyArg_ParseTuple(args, ":get_pubkey")) + return NULL; + + if ((pkey = NETSCAPE_SPKI_get_pubkey(self->netscape_spki)) == NULL) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + py_pkey = crypto_PKey_New(pkey, 1); + if (py_pkey != NULL) { + py_pkey->only_public = 1; + } + return (PyObject *)py_pkey; +} + +static char crypto_NetscapeSPKI_set_pubkey_doc[] = "\n\ +Set the public key of the certificate\n\ +\n\ +@param pkey: The public key\n\ +@return: None\n\ +"; + +static PyObject * +crypto_NetscapeSPKI_set_pubkey(crypto_NetscapeSPKIObj *self, PyObject *args) +{ + crypto_PKeyObj *pkey; + + if (!PyArg_ParseTuple(args, "O!:set_pubkey", &crypto_PKey_Type, &pkey)) + return NULL; + + if (!NETSCAPE_SPKI_set_pubkey(self->netscape_spki, pkey->pkey)) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +/* + * ADD_METHOD(name) expands to a correct PyMethodDef declaration + * { 'name', (PyCFunction)crypto_NetscapeSPKI_name, METH_VARARGS } + * for convenience + */ +#define ADD_METHOD(name) \ + { #name, (PyCFunction)crypto_NetscapeSPKI_##name, METH_VARARGS, crypto_NetscapeSPKI_##name##_doc } +static PyMethodDef crypto_NetscapeSPKI_methods[] = +{ + ADD_METHOD(get_pubkey), + ADD_METHOD(set_pubkey), + ADD_METHOD(b64_encode), + ADD_METHOD(sign), + ADD_METHOD(verify), + { NULL, NULL } +}; +#undef ADD_METHOD + +PyTypeObject crypto_NetscapeSPKI_Type = { + PyOpenSSL_HEAD_INIT(&PyType_Type, 0) + "NetscapeSPKI", + sizeof(crypto_NetscapeSPKIObj), + 0, + (destructor)crypto_NetscapeSPKI_dealloc, + NULL, /* print */ + NULL, /* getattr */ + NULL, /* setattr */ + NULL, /* compare */ + NULL, /* repr */ + NULL, /* as_number */ + NULL, /* as_sequence */ + NULL, /* as_mapping */ + NULL, /* hash */ + NULL, /* call */ + NULL, /* str */ + NULL, /* getattro */ + NULL, /* setattro */ + NULL, /* as_buffer */ + Py_TPFLAGS_DEFAULT, + crypto_NetscapeSPKI_doc, /* doc */ + NULL, /* traverse */ + NULL, /* clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + crypto_NetscapeSPKI_methods, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + crypto_NetscapeSPKI_new, /* tp_new */ +}; + + +/* + * Initialize the X509Name part of the crypto module + * + * Arguments: module - The crypto module + * Returns: None + */ +int +init_crypto_netscape_spki(PyObject *module) { + if (PyType_Ready(&crypto_NetscapeSPKI_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "NetscapeSPKI", (PyObject *)&crypto_NetscapeSPKI_Type) != 0) { + return 0; + } + + if (PyModule_AddObject(module, "NetscapeSPKIType", (PyObject *)&crypto_NetscapeSPKI_Type) != 0) { + return 0; + } + + return 1; +} diff --git a/OpenSSL/crypto/netscape_spki.h b/OpenSSL/crypto/netscape_spki.h new file mode 100644 index 0000000..2f07307 --- /dev/null +++ b/OpenSSL/crypto/netscape_spki.h @@ -0,0 +1,30 @@ +/* + * netscape_spki.h + * + * Copyright (C) Tollef Fog Heen + * See LICENSE for details. + * + * Handle Netscape SPKI (challenge response) certificate requests. + * + * + */ +#ifndef PyOpenSSL_crypto_Netscape_SPKI_H_ +#define PyOpenSSL_crypto_Netscape_SPKI_H_ + +#include +#include + +extern int init_crypto_netscape_spki (PyObject *); + +extern PyTypeObject crypto_NetscapeSPKI_Type; + +#define crypto_NetscapeSPKI_Check(v) ((v)->ob_type == &crypto_NetscapeSPKI_Type) + +typedef struct { + PyObject_HEAD + NETSCAPE_SPKI *netscape_spki; + int dealloc; +} crypto_NetscapeSPKIObj; + + +#endif diff --git a/OpenSSL/crypto/pkcs12.c b/OpenSSL/crypto/pkcs12.c new file mode 100644 index 0000000..6f795c5 --- /dev/null +++ b/OpenSSL/crypto/pkcs12.c @@ -0,0 +1,564 @@ +/* + * pkcs12.c + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * Certificate transport (PKCS12) handling code, + * mostly thin wrappers around OpenSSL. + * See the file RATIONALE for a short explanation of why + * this module was written. + * + * Reviewed 2001-07-23 + */ +#include +#define crypto_MODULE +#include "crypto.h" + +/* + * PKCS12 is a standard exchange format for digital certificates. + * See e.g. the OpenSSL homepage http://www.openssl.org/ for more information + */ + +static void crypto_PKCS12_dealloc(crypto_PKCS12Obj *self); +static int crypto_PKCS12_clear(crypto_PKCS12Obj *self); + +static char crypto_PKCS12_get_certificate_doc[] = "\n\ +Return certificate portion of the PKCS12 structure\n\ +\n\ +@return: X509 object containing the certificate\n\ +"; +static PyObject * +crypto_PKCS12_get_certificate(crypto_PKCS12Obj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":get_certificate")) + return NULL; + + Py_INCREF(self->cert); + return self->cert; +} + +static char crypto_PKCS12_set_certificate_doc[] = "\n\ +Replace the certificate portion of the PKCS12 structure\n\ +\n\ +@param cert: The new certificate.\n\ +@type cert: L{X509} or L{NoneType}\n\ +@return: None\n\ +"; +static PyObject * +crypto_PKCS12_set_certificate(crypto_PKCS12Obj *self, PyObject *args, PyObject *keywds) { + PyObject *cert = NULL; + static char *kwlist[] = {"cert", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O:set_certificate", + kwlist, &cert)) + return NULL; + + if (cert != Py_None && ! crypto_X509_Check(cert)) { + PyErr_SetString(PyExc_TypeError, "cert must be type X509 or None"); + return NULL; + } + + Py_INCREF(cert); /* Make consistent before calling Py_DECREF() */ + Py_DECREF(self->cert); + self->cert = cert; + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_PKCS12_get_privatekey_doc[] = "\n\ +Return private key portion of the PKCS12 structure\n\ +\n\ +@returns: PKey object containing the private key\n\ +"; +static crypto_PKeyObj * +crypto_PKCS12_get_privatekey(crypto_PKCS12Obj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":get_privatekey")) + return NULL; + + Py_INCREF(self->key); + return (crypto_PKeyObj *) self->key; +} + +static char crypto_PKCS12_set_privatekey_doc[] = "\n\ +Replace or set the certificate portion of the PKCS12 structure\n\ +\n\ +@param pkey: The new private key.\n\ +@type pkey: L{PKey}\n\ +@return: None\n\ +"; +static PyObject * +crypto_PKCS12_set_privatekey(crypto_PKCS12Obj *self, PyObject *args, PyObject *keywds) { + PyObject *pkey = NULL; + static char *kwlist[] = {"pkey", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O:set_privatekey", + kwlist, &pkey)) + return NULL; + + if (pkey != Py_None && ! crypto_PKey_Check(pkey)) { + PyErr_SetString(PyExc_TypeError, "pkey must be type X509 or None"); + return NULL; + } + + Py_INCREF(pkey); /* Make consistent before calling Py_DECREF() */ + Py_DECREF(self->key); + self->key = pkey; + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_PKCS12_get_ca_certificates_doc[] = "\n\ +Return CA certificates within of the PKCS12 object\n\ +\n\ +@return: A newly created tuple containing the CA certificates in the chain,\n\ + if any are present, or None if no CA certificates are present.\n\ +"; +static PyObject * +crypto_PKCS12_get_ca_certificates(crypto_PKCS12Obj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":get_ca_certificates")) + return NULL; + + Py_INCREF(self->cacerts); + return self->cacerts; +} + +static char crypto_PKCS12_set_ca_certificates_doc[] = "\n\ +Replace or set the CA certificates withing the PKCS12 object.\n\ +\n\ +@param cacerts: The new CA certificates.\n\ +@type cacerts: Iterable of L{X509} or L{NoneType}\n\ +@return: None\n\ +"; +static PyObject * +crypto_PKCS12_set_ca_certificates(crypto_PKCS12Obj *self, PyObject *args, PyObject *keywds) +{ + PyObject *obj; + PyObject *cacerts; + static char *kwlist[] = {"cacerts", NULL}; + int i, len; /* Py_ssize_t for Python 2.5+ */ + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O:set_ca_certificates", + kwlist, &cacerts)) + return NULL; + if (cacerts == Py_None) { + Py_INCREF(cacerts); + } else { + /* It's iterable */ + cacerts = PySequence_Tuple(cacerts); + if (cacerts == NULL) { + return NULL; + } + len = PyTuple_Size(cacerts); + + /* Check is's a simple list filled only with X509 objects. */ + for (i = 0; i < len; i++) { + obj = PyTuple_GetItem(cacerts, i); + if (!crypto_X509_Check(obj)) { + Py_DECREF(cacerts); + PyErr_SetString(PyExc_TypeError, "iterable must only contain X509Type"); + return NULL; + } + } + } + + Py_DECREF(self->cacerts); + self->cacerts = cacerts; + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_PKCS12_get_friendlyname_doc[] = "\n\ +Return friendly name portion of the PKCS12 structure\n\ +\n\ +@returns: String containing the friendlyname\n\ +"; +static PyObject * +crypto_PKCS12_get_friendlyname(crypto_PKCS12Obj *self, PyObject *args) { + if (!PyArg_ParseTuple(args, ":get_friendlyname")) + return NULL; + + Py_INCREF(self->friendlyname); + return (PyObject *) self->friendlyname; +} + +static char crypto_PKCS12_set_friendlyname_doc[] = "\n\ +Replace or set the certificate portion of the PKCS12 structure\n\ +\n\ +@param name: The new friendly name.\n\ +@type name: L{str}\n\ +@return: None\n\ +"; +static PyObject * +crypto_PKCS12_set_friendlyname(crypto_PKCS12Obj *self, PyObject *args, PyObject *keywds) { + PyObject *name = NULL; + static char *kwlist[] = {"name", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "O:set_friendlyname", + kwlist, &name)) + return NULL; + + if (name != Py_None && ! PyBytes_CheckExact(name)) { + PyErr_SetString(PyExc_TypeError, "name must be a byte string or None"); + return NULL; + } + + Py_INCREF(name); /* Make consistent before calling Py_DECREF() */ + Py_DECREF(self->friendlyname); + self->friendlyname = name; + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_PKCS12_export_doc[] = "\n\ +export([passphrase=None][, friendly_name=None][, iter=2048][, maciter=1]\n\ +Dump a PKCS12 object as a string. See also \"man PKCS12_create\".\n\ +\n\ +@param passphrase: used to encrypt the PKCS12\n\ +@type passphrase: L{str}\n\ +@param iter: How many times to repeat the encryption\n\ +@type iter: L{int}\n\ +@param maciter: How many times to repeat the MAC\n\ +@type maciter: L{int}\n\ +@return: The string containing the PKCS12\n\ +"; +static PyObject * +crypto_PKCS12_export(crypto_PKCS12Obj *self, PyObject *args, PyObject *keywds) { + int i; /* Py_ssize_t for Python 2.5+ */ + PyObject *obj; + int buf_len; + PyObject *buffer; + char *temp, *passphrase = NULL, *friendly_name = NULL; + BIO *bio; + PKCS12 *p12; + EVP_PKEY *pkey = NULL; + STACK_OF(X509) *cacerts = NULL; + X509 *x509 = NULL; + int iter = 0; /* defaults to PKCS12_DEFAULT_ITER */ + int maciter = 0; + static char *kwlist[] = {"passphrase", "iter", "maciter", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|zii:export", + kwlist, &passphrase, &iter, &maciter)) + return NULL; + + if (self->key != Py_None) { + pkey = ((crypto_PKeyObj*) self->key)->pkey; + } + if (self->cert != Py_None) { + x509 = ((crypto_X509Obj*) self->cert)->x509; + } + if (self->cacerts != Py_None) { + cacerts = sk_X509_new_null(); + for (i = 0; i < PyTuple_Size(self->cacerts); i++) { /* For each CA cert */ + obj = PySequence_GetItem(self->cacerts, i); + /* assert(PyObject_IsInstance(obj, (PyObject *) &crypto_X509_Type )); */ + sk_X509_push(cacerts, (( crypto_X509Obj* ) obj)->x509); + Py_DECREF(obj); + } + } + if (self->friendlyname != Py_None) { + friendly_name = PyBytes_AsString(self->friendlyname); + } + + p12 = PKCS12_create(passphrase, friendly_name, pkey, x509, cacerts, + NID_pbe_WithSHA1And3_Key_TripleDES_CBC, + NID_pbe_WithSHA1And3_Key_TripleDES_CBC, + iter, maciter, 0); + sk_X509_free(cacerts); /* NULL safe. Free just the container. */ + if (p12 == NULL) { + exception_from_error_queue(crypto_Error); + return NULL; + } + bio = BIO_new(BIO_s_mem()); + i2d_PKCS12_bio(bio, p12); + buf_len = BIO_get_mem_data(bio, &temp); + buffer = PyBytes_FromStringAndSize(temp, buf_len); + BIO_free(bio); + return buffer; +} + +/* + * ADD_METHOD(name) expands to a correct PyMethodDef declaration + * { 'name', (PyCFunction)crypto_PKCS12_name, METH_VARARGS, crypto_PKCS12_name_doc } + * for convenience + */ +#define ADD_METHOD(name) \ + { #name, (PyCFunction)crypto_PKCS12_##name, METH_VARARGS, crypto_PKCS12_##name##_doc } +#define ADD_KW_METHOD(name) \ + { #name, (PyCFunction)crypto_PKCS12_##name, METH_VARARGS | METH_KEYWORDS, crypto_PKCS12_##name##_doc } +static PyMethodDef crypto_PKCS12_methods[] = +{ + ADD_METHOD(get_certificate), + ADD_KW_METHOD(set_certificate), + ADD_METHOD(get_privatekey), + ADD_KW_METHOD(set_privatekey), + ADD_METHOD(get_ca_certificates), + ADD_KW_METHOD(set_ca_certificates), + ADD_METHOD(get_friendlyname), + ADD_KW_METHOD(set_friendlyname), + ADD_KW_METHOD(export), + { NULL, NULL } +}; +#undef ADD_METHOD + +/* + * Constructor for PKCS12 objects, never called by Python code directly. + * The strategy for this object is to create all the Python objects + * corresponding to the cert/key/CA certs right away + * + * Arguments: p12 - A "real" PKCS12 object or NULL + * passphrase - Passphrase to use when decrypting the PKCS12 object + * Returns: The newly created PKCS12 object + */ +crypto_PKCS12Obj * +crypto_PKCS12_New(PKCS12 *p12, char *passphrase) { + crypto_PKCS12Obj *self = NULL; + PyObject *cacertobj = NULL; + + unsigned char *alias_str; + int alias_len; + + X509 *cert = NULL; + EVP_PKEY *pkey = NULL; + STACK_OF(X509) *cacerts = NULL; + + int i, cacert_count = 0; + + /* allocate space for the CA cert stack */ + if((cacerts = sk_X509_new_null()) == NULL) { + goto error; /* out of memory? */ + } + + /* parse the PKCS12 lump */ + if (p12 && !PKCS12_parse(p12, passphrase, &pkey, &cert, &cacerts)) { + /* + * If PKCS12_parse fails, and it allocated cacerts, it seems to free + * cacerts, but not re-NULL the pointer. Zounds! Make sure it is + * re-set to NULL here, else we'll have a double-free below. + */ + cacerts = NULL; + exception_from_error_queue(crypto_Error); + goto error; + } + + if (!(self = PyObject_GC_New(crypto_PKCS12Obj, &crypto_PKCS12_Type))) { + goto error; + } + + /* client certificate and friendlyName */ + if (cert == NULL) { + Py_INCREF(Py_None); + self->cert = Py_None; + Py_INCREF(Py_None); + self->friendlyname = Py_None; + } else { + if ((self->cert = (PyObject *)crypto_X509_New(cert, 1)) == NULL) { + goto error; + } + + /* Now we need to extract the friendlyName of the PKCS12 + * that was stored by PKCS_parse() in the alias of the + * certificate. */ + alias_str = X509_alias_get0(cert, &alias_len); + if (alias_str) { + self->friendlyname = Py_BuildValue(BYTESTRING_FMT "#", alias_str, alias_len); + if (!self->friendlyname) { + /* + * XXX Untested + */ + goto error; + } + /* success */ + } else { + Py_INCREF(Py_None); + self->friendlyname = Py_None; + } + } + + /* private key */ + if (pkey == NULL) { + Py_INCREF(Py_None); + self->key = Py_None; + } else { + if ((self->key = (PyObject *)crypto_PKey_New(pkey, 1)) == NULL) + goto error; + } + + /* CA certs */ + cacert_count = sk_X509_num(cacerts); + if (cacert_count <= 0) { + Py_INCREF(Py_None); + self->cacerts = Py_None; + } else { + if ((self->cacerts = PyTuple_New(cacert_count)) == NULL) { + goto error; + } + + for (i = 0; i < cacert_count; i++) { + cert = sk_X509_value(cacerts, i); + if ((cacertobj = (PyObject *)crypto_X509_New(cert, 1)) == NULL) { + goto error; + } + PyTuple_SET_ITEM(self->cacerts, i, cacertobj); + } + } + + sk_X509_free(cacerts); /* Don't free the certs, just the container. */ + PyObject_GC_Track(self); + + return self; + +error: + sk_X509_free(cacerts); /* NULL safe. Free just the container. */ + if (self) { + crypto_PKCS12_clear(self); + PyObject_GC_Del(self); + } + return NULL; +} + +static char crypto_PKCS12_doc[] = "\n\ +PKCS12() -> PKCS12 instance\n\ +\n\ +Create a new empty PKCS12 object.\n\ +\n\ +@returns: The PKCS12 object\n\ +"; +static PyObject * +crypto_PKCS12_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) { + if (!PyArg_ParseTuple(args, ":PKCS12")) { + return NULL; + } + + return (PyObject *)crypto_PKCS12_New(NULL, NULL); +} + +/* + * Call the visitproc on all contained objects. + * + * Arguments: self - The PKCS12 object + * visit - Function to call + * arg - Extra argument to visit + * Returns: 0 if all goes well, otherwise the return code from the first + * call that gave non-zero result. + */ +static int +crypto_PKCS12_traverse(crypto_PKCS12Obj *self, visitproc visit, void *arg) +{ + int ret = 0; + + if (ret == 0 && self->cert != NULL) + ret = visit(self->cert, arg); + if (ret == 0 && self->key != NULL) + ret = visit(self->key, arg); + if (ret == 0 && self->cacerts != NULL) + ret = visit(self->cacerts, arg); + if (ret == 0 && self->friendlyname != NULL) + ret = visit(self->friendlyname, arg); + return ret; +} + +/* + * Decref all contained objects and zero the pointers. + * + * Arguments: self - The PKCS12 object + * Returns: Always 0. + */ +static int +crypto_PKCS12_clear(crypto_PKCS12Obj *self) +{ + Py_XDECREF(self->cert); + self->cert = NULL; + Py_XDECREF(self->key); + self->key = NULL; + Py_XDECREF(self->cacerts); + self->cacerts = NULL; + Py_XDECREF(self->friendlyname); + self->friendlyname = NULL; + return 0; +} + +/* + * Deallocate the memory used by the PKCS12 object + * + * Arguments: self - The PKCS12 object + * Returns: None + */ +static void +crypto_PKCS12_dealloc(crypto_PKCS12Obj *self) +{ + PyObject_GC_UnTrack(self); + crypto_PKCS12_clear(self); + PyObject_GC_Del(self); +} + +PyTypeObject crypto_PKCS12_Type = { + PyOpenSSL_HEAD_INIT(&PyType_Type, 0) + "PKCS12", + sizeof(crypto_PKCS12Obj), + 0, + (destructor)crypto_PKCS12_dealloc, + NULL, /* print */ + NULL, /* getattr */ + NULL, /* setattr */ + NULL, /* compare */ + NULL, /* repr */ + NULL, /* as_number */ + NULL, /* as_sequence */ + NULL, /* as_mapping */ + NULL, /* hash */ + NULL, /* call */ + NULL, /* str */ + NULL, /* getattro */ + NULL, /* setattro */ + NULL, /* as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + crypto_PKCS12_doc, + (traverseproc)crypto_PKCS12_traverse, + (inquiry)crypto_PKCS12_clear, + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + crypto_PKCS12_methods, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + crypto_PKCS12_new, /* tp_new */ +}; + +/* + * Initialize the PKCS12 part of the crypto sub module + * + * Arguments: module - The crypto module + * Returns: None + */ +int +init_crypto_pkcs12(PyObject *module) { + if (PyType_Ready(&crypto_PKCS12_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "PKCS12", (PyObject *)&crypto_PKCS12_Type) != 0) { + return 0; + } + + if (PyModule_AddObject(module, "PKCS12Type", (PyObject *)&crypto_PKCS12_Type) != 0) { + return 0; + } + + return 1; +} diff --git a/OpenSSL/crypto/pkcs12.h b/OpenSSL/crypto/pkcs12.h new file mode 100644 index 0000000..f0de1a8 --- /dev/null +++ b/OpenSSL/crypto/pkcs12.h @@ -0,0 +1,39 @@ +/* + * pkcs12.h + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * Export PKCS12 functions and data structure. + * + */ +#ifndef PyOpenSSL_crypto_PKCS12_H_ +#define PyOpenSSL_crypto_PKCS12_H_ + +#include +#include +#include + +extern int init_crypto_pkcs12 (PyObject *); + +extern PyTypeObject crypto_PKCS12_Type; + +#define crypto_PKCS12_Check(v) ((v)->ob_type == &crypto_PKCS12_Type) + +typedef struct { + PyObject_HEAD + /* + * These either refer to a PyObject* of the appropriate type, or Py_None if + * they don't have a value. They aren't set to NULL except during + * finalization. + */ + PyObject *cert; + PyObject *key; + PyObject *cacerts; + PyObject *friendlyname; +} crypto_PKCS12Obj; + +crypto_PKCS12Obj * +crypto_PKCS12_New(PKCS12 *p12, char *passphrase); + +#endif diff --git a/OpenSSL/crypto/pkcs7.c b/OpenSSL/crypto/pkcs7.c new file mode 100644 index 0000000..a074800 --- /dev/null +++ b/OpenSSL/crypto/pkcs7.c @@ -0,0 +1,213 @@ +/* + * pkcs7.c + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * PKCS7 handling code, mostly thin wrappers around OpenSSL. + * See the file RATIONALE for a short explanation of why this module was written. + * + */ +#include +#define crypto_MODULE +#include "crypto.h" + +static char crypto_PKCS7_type_is_signed_doc[] = "\n\ +Check if this NID_pkcs7_signed object\n\ +\n\ +@return: True if the PKCS7 is of type signed\n\ +"; + +static PyObject * +crypto_PKCS7_type_is_signed(crypto_PKCS7Obj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":type_is_signed")) + return NULL; + + if (PKCS7_type_is_signed(self->pkcs7)) + return PyLong_FromLong(1L); + else + return PyLong_FromLong(0L); +} + +static char crypto_PKCS7_type_is_enveloped_doc[] = "\n\ +Check if this NID_pkcs7_enveloped object\n\ +\n\ +@returns: True if the PKCS7 is of type enveloped\n\ +"; + +static PyObject * +crypto_PKCS7_type_is_enveloped(crypto_PKCS7Obj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":type_is_enveloped")) + return NULL; + + if (PKCS7_type_is_enveloped(self->pkcs7)) + return PyLong_FromLong(1L); + else + return PyLong_FromLong(0L); +} + +static char crypto_PKCS7_type_is_signedAndEnveloped_doc[] = "\n\ +Check if this NID_pkcs7_signedAndEnveloped object\n\ +\n\ +@returns: True if the PKCS7 is of type signedAndEnveloped\n\ +"; + +static PyObject * +crypto_PKCS7_type_is_signedAndEnveloped(crypto_PKCS7Obj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":type_is_signedAndEnveloped")) + return NULL; + + if (PKCS7_type_is_signedAndEnveloped(self->pkcs7)) + return PyLong_FromLong(1L); + else + return PyLong_FromLong(0L); +} + +static char crypto_PKCS7_type_is_data_doc[] = "\n\ +Check if this NID_pkcs7_data object\n\ +\n\ +@return: True if the PKCS7 is of type data\n\ +"; + +static PyObject * +crypto_PKCS7_type_is_data(crypto_PKCS7Obj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":type_is_data")) + return NULL; + + if (PKCS7_type_is_data(self->pkcs7)) + return PyLong_FromLong(1L); + else + return PyLong_FromLong(0L); +} + +static char crypto_PKCS7_get_type_name_doc[] = "\n\ +Returns the type name of the PKCS7 structure\n\ +\n\ +@return: A string with the typename\n\ +"; + +static PyObject * +crypto_PKCS7_get_type_name(crypto_PKCS7Obj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":get_type_name")) + return NULL; + + /* + * return a string with the typename + */ + return PyBytes_FromString(OBJ_nid2sn(OBJ_obj2nid(self->pkcs7->type))); +} + +/* + * ADD_METHOD(name) expands to a correct PyMethodDef declaration + * { 'name', (PyCFunction)crypto_PKCS7_name, METH_VARARGS } + * for convenience + */ +#define ADD_METHOD(name) \ + { #name, (PyCFunction)crypto_PKCS7_##name, METH_VARARGS, crypto_PKCS7_##name##_doc } +static PyMethodDef crypto_PKCS7_methods[] = +{ + ADD_METHOD(type_is_signed), + ADD_METHOD(type_is_enveloped), + ADD_METHOD(type_is_signedAndEnveloped), + ADD_METHOD(type_is_data), + ADD_METHOD(get_type_name), + { NULL, NULL } +}; +#undef ADD_METHOD + + +/* + * Constructor for PKCS7 objects, never called by Python code directly + * + * Arguments: pkcs7 - A "real" pkcs7 certificate object + * dealloc - Boolean value to specify whether the destructor should + * free the "real" pkcs7 object + * Returns: The newly created pkcs7 object + */ +crypto_PKCS7Obj * +crypto_PKCS7_New(PKCS7 *pkcs7, int dealloc) +{ + crypto_PKCS7Obj *self; + + self = PyObject_New(crypto_PKCS7Obj, &crypto_PKCS7_Type); + + if (self == NULL) + return NULL; + + self->pkcs7 = pkcs7; + self->dealloc = dealloc; + + return self; +} + +/* + * Deallocate the memory used by the PKCS7 object + * + * Arguments: self - The PKCS7 object + * Returns: None + */ +static void +crypto_PKCS7_dealloc(crypto_PKCS7Obj *self) +{ + /* Sometimes we don't have to dealloc the "real" PKCS7 pointer ourselves */ + if (self->dealloc) + PKCS7_free(self->pkcs7); + + PyObject_Del(self); +} + +PyTypeObject crypto_PKCS7_Type = { + PyOpenSSL_HEAD_INIT(&PyType_Type, 0) + "PKCS7", + sizeof(crypto_PKCS7Obj), + 0, + (destructor)crypto_PKCS7_dealloc, + NULL, /* print */ + NULL, /* getattr */ + NULL, /* setattr */ + NULL, /* compare */ + NULL, /* repr */ + NULL, /* as_number */ + NULL, /* as_sequence */ + NULL, /* as_mapping */ + NULL, /* hash */ + NULL, /* call */ + NULL, /* str */ + NULL, /* getattro */ + NULL, /* setattro */ + NULL, /* as_buffer */ + Py_TPFLAGS_DEFAULT, + NULL, /* doc */ + NULL, /* traverse */ + NULL, /* clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + crypto_PKCS7_methods, /* tp_methods */ +}; + +/* + * Initialize the PKCS7 part of the crypto sub module + * + * Arguments: module - The crypto module + * Returns: None + */ +int +init_crypto_pkcs7(PyObject *module) { + if (PyType_Ready(&crypto_PKCS7_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "PKCS7Type", (PyObject *)&crypto_PKCS7_Type) != 0) { + return 0; + } + + return 1; +} + diff --git a/OpenSSL/crypto/pkcs7.h b/OpenSSL/crypto/pkcs7.h new file mode 100644 index 0000000..d8453b2 --- /dev/null +++ b/OpenSSL/crypto/pkcs7.h @@ -0,0 +1,30 @@ +/* + * pkcs7.h + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * Export pkcs7 functions and data structure. + * See the file RATIONALE for a short explanation of why this module was written. + * + */ +#ifndef PyOpenSSL_crypto_PKCS7_H_ +#define PyOpenSSL_crypto_PKCS7_H_ + +#include +#include + +extern int init_crypto_pkcs7 (PyObject *); + +extern PyTypeObject crypto_PKCS7_Type; + +#define crypto_PKCS7_Check(v) ((v)->ob_type == &crypto_PKCS7_Type) + +typedef struct { + PyObject_HEAD + PKCS7 *pkcs7; + int dealloc; +} crypto_PKCS7Obj; + + +#endif diff --git a/OpenSSL/crypto/pkey.c b/OpenSSL/crypto/pkey.c new file mode 100644 index 0000000..938a222 --- /dev/null +++ b/OpenSSL/crypto/pkey.c @@ -0,0 +1,265 @@ +/* + * pkey.c + * + * Copyright (C) AB Strakt + * Copyright (C) Jean-Paul Calderone + * See LICENSE for details. + * + * Public/rivate key handling code, mostly thin wrappers around OpenSSL. + * See the file RATIONALE for a short explanation of why this module was written. + * + */ +#include +#define crypto_MODULE +#include "crypto.h" + +/* + * This is done every time something fails, so turning it into a macro is + * really nice. + * + * Arguments: None + * Returns: Doesn't return + */ +#define FAIL() \ +do { \ + exception_from_error_queue(crypto_Error); \ + return NULL; \ +} while (0) + + +static char crypto_PKey_generate_key_doc[] = "\n\ +Generate a key of a given type, with a given number of a bits\n\ +\n\ +@param type: The key type (TYPE_RSA or TYPE_DSA)\n\ +@param bits: The number of bits\n\ +@return: None\n\ +"; + +static PyObject * +crypto_PKey_generate_key(crypto_PKeyObj *self, PyObject *args) +{ + int type, bits; + RSA *rsa; + DSA *dsa; + + if (!PyArg_ParseTuple(args, "ii:generate_key", &type, &bits)) + return NULL; + + switch (type) + { + case crypto_TYPE_RSA: + if (bits <= 0) { + PyErr_SetString(PyExc_ValueError, "Invalid number of bits"); + return NULL; + } + if ((rsa = RSA_generate_key(bits, 0x10001, NULL, NULL)) == NULL) + FAIL(); + if (!EVP_PKEY_assign_RSA(self->pkey, rsa)) + FAIL(); + break; + + case crypto_TYPE_DSA: + if ((dsa = DSA_generate_parameters(bits, NULL, 0, NULL, NULL, NULL, NULL)) == NULL) + FAIL(); + if (!DSA_generate_key(dsa)) + FAIL(); + if (!EVP_PKEY_assign_DSA(self->pkey, dsa)) + FAIL(); + break; + + default: + PyErr_SetString(crypto_Error, "No such key type"); + return NULL; + + } + self->initialized = 1; + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_PKey_bits_doc[] = "\n\ +Returns the number of bits of the key\n\ +\n\ +@return: The number of bits of the key.\n\ +"; + +static PyObject * +crypto_PKey_bits(crypto_PKeyObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":bits")) + return NULL; + + return PyLong_FromLong(EVP_PKEY_bits(self->pkey)); +} + +static char crypto_PKey_type_doc[] = "\n\ +Returns the type of the key\n\ +\n\ +@return: The type of the key.\n\ +"; + +static PyObject * +crypto_PKey_type(crypto_PKeyObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":type")) + return NULL; + + return PyLong_FromLong(self->pkey->type); +} + + +/* + * ADD_METHOD(name) expands to a correct PyMethodDef declaration + * { 'name', (PyCFunction)crypto_PKey_name, METH_VARARGS } + * for convenience + */ +#define ADD_METHOD(name) \ + { #name, (PyCFunction)crypto_PKey_##name, METH_VARARGS, crypto_PKey_##name##_doc } +static PyMethodDef crypto_PKey_methods[] = +{ + ADD_METHOD(generate_key), + ADD_METHOD(bits), + ADD_METHOD(type), + { NULL, NULL } +}; +#undef ADD_METHOD + + +/* + * Constructor for PKey objects, never called by Python code directly + * + * Arguments: pkey - A "real" EVP_PKEY object + * dealloc - Boolean value to specify whether the destructor should + * free the "real" EVP_PKEY object + * Returns: The newly created PKey object + */ +crypto_PKeyObj * +crypto_PKey_New(EVP_PKEY *pkey, int dealloc) +{ + crypto_PKeyObj *self; + + self = PyObject_New(crypto_PKeyObj, &crypto_PKey_Type); + + if (self == NULL) + return NULL; + + self->pkey = pkey; + self->dealloc = dealloc; + self->only_public = 0; + + /* + * Heuristic. Most call-sites pass an initialized EVP_PKEY. Not + * necessarily the case that they will, though. That's part of why this is + * a hack. -exarkun + */ + self->initialized = 1; + + return self; +} + +static char crypto_PKey_doc[] = "\n\ +PKey() -> PKey instance\n\ +\n\ +Create a new PKey object.\n\ +\n\ +@return: The PKey object\n\ +"; +static PyObject* +crypto_PKey_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) { + crypto_PKeyObj *self; + + if (!PyArg_ParseTuple(args, ":PKey")) { + return NULL; + } + + self = crypto_PKey_New(EVP_PKEY_new(), 1); + if (self) { + self->initialized = 0; + } + + return (PyObject *)self; +} + + +/* + * Deallocate the memory used by the PKey object + * + * Arguments: self - The PKey object + * Returns: None + */ +static void +crypto_PKey_dealloc(crypto_PKeyObj *self) +{ + /* Sometimes we don't have to dealloc the "real" EVP_PKEY pointer ourselves */ + if (self->dealloc) + EVP_PKEY_free(self->pkey); + + PyObject_Del(self); +} + +PyTypeObject crypto_PKey_Type = { + PyOpenSSL_HEAD_INIT(&PyType_Type, 0) + "OpenSSL.crypto.PKey", + sizeof(crypto_PKeyObj), + 0, + (destructor)crypto_PKey_dealloc, + NULL, /* print */ + NULL, /* getattr */ + NULL, /* setattr */ + NULL, /* compare */ + NULL, /* repr */ + NULL, /* as_number */ + NULL, /* as_sequence */ + NULL, /* as_mapping */ + NULL, /* hash */ + NULL, /* call */ + NULL, /* str */ + NULL, /* getattro */ + NULL, /* setattro */ + NULL, /* as_buffer */ + Py_TPFLAGS_DEFAULT, + crypto_PKey_doc, /* doc */ + NULL, /* traverse */ + NULL, /* clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + crypto_PKey_methods, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + crypto_PKey_new, /* tp_new */ +}; + + +/* + * Initialize the PKey part of the crypto sub module + * + * Arguments: module - The crypto module + * Returns: None + */ +int +init_crypto_pkey(PyObject *module) +{ + if (PyType_Ready(&crypto_PKey_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "PKey", (PyObject *)&crypto_PKey_Type) != 0) { + return 0; + } + + if (PyModule_AddObject(module, "PKeyType", (PyObject *)&crypto_PKey_Type) != 0) { + return 0; + } + + return 1; +} + diff --git a/OpenSSL/crypto/pkey.h b/OpenSSL/crypto/pkey.h new file mode 100644 index 0000000..dc5e52e --- /dev/null +++ b/OpenSSL/crypto/pkey.h @@ -0,0 +1,52 @@ +/* + * pkey.h + * + * Copyright (C) AB Strakt + * Copyright (C) Jean-Paul Calderone + * See LICENSE for details. + * + * Export pkey functions and data structure. + * See the file RATIONALE for a short explanation of why this module was written. + * + */ +#ifndef PyOpenSSL_crypto_PKEY_H_ +#define PyOpenSSL_crypto_PKEY_H_ + +extern int init_crypto_pkey (PyObject *); + +extern PyTypeObject crypto_PKey_Type; + +#define crypto_PKey_Check(v) ((v)->ob_type == &crypto_PKey_Type) + +typedef struct { + PyObject_HEAD + + /* + * A pointer to the underlying OpenSSL structure. + */ + EVP_PKEY *pkey; + + /* + * A flag indicating the underlying pkey object has no private parts (so it + * can't sign, for example). This is a bit of a temporary hack. + * Public-only should be represented as a different type. -exarkun + */ + int only_public; + + /* + * A flag indicating whether the underlying pkey object has no meaningful + * data in it whatsoever. This is a temporary hack. It should be + * impossible to create PKeys in an unusable state. -exarkun + */ + int initialized; + + /* + * A flag indicating whether pkey will be freed when this object is freed. + */ + int dealloc; +} crypto_PKeyObj; + +#define crypto_TYPE_RSA EVP_PKEY_RSA +#define crypto_TYPE_DSA EVP_PKEY_DSA + +#endif diff --git a/OpenSSL/crypto/revoked.c b/OpenSSL/crypto/revoked.c new file mode 100644 index 0000000..e9b1297 --- /dev/null +++ b/OpenSSL/crypto/revoked.c @@ -0,0 +1,441 @@ +#include +#define crypto_MODULE +#include "crypto.h" + +#ifdef _WIN32 +#define strcasecmp(string1, string2) _stricmp(string1, string2) +#endif + +/* 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. */ +static const char *crl_reasons[] = { + "unspecified", + "keyCompromise", + "CACompromise", + "affiliationChanged", + "superseded", + "cessationOfOperation", + "certificateHold", + NULL, + "removeFromCRL", +}; + +#define NUM_REASONS (sizeof(crl_reasons) / sizeof(char *)) + +static char crypto_Revoked_all_reasons_doc[] = "\n\ +Return a list of all the supported reason strings.\n\ +\n\ +@return: A list of reason strings.\n\ +"; +static PyObject * +crypto_Revoked_all_reasons(crypto_RevokedObj *self, PyObject *args) { + PyObject *list, *str; + int j; + + list = PyList_New(0); + for (j = 0; j < NUM_REASONS; j++) { + if(crl_reasons[j]) { + str = PyBytes_FromString(crl_reasons[j]); + PyList_Append(list, str); + Py_DECREF(str); + } + } + return list; +} + +static PyObject * +X509_EXTENSION_value_to_PyString(X509_EXTENSION *ex) { + BIO *bio = NULL; + PyObject *str = NULL; + int str_len; + char *tmp_str; + + /* Create a openssl BIO buffer */ + bio = BIO_new(BIO_s_mem()); + if (bio == NULL) { + goto err; + } + + /* These are not the droids you are looking for. */ + if (!X509V3_EXT_print(bio, ex, 0, 0)) { + if (M_ASN1_OCTET_STRING_print(bio, ex->value) == 0) { + goto err; + } + } + + /* Convert to a Python string. */ + str_len = BIO_get_mem_data(bio, &tmp_str); + str = PyBytes_FromStringAndSize(tmp_str, str_len); + + /* Cleanup */ + BIO_free(bio); + return str; + + err: + if (bio) { + BIO_free(bio); + } + if (str) { + Py_DECREF(str); + } + return NULL; +} + +static void +delete_reason(STACK_OF(X509_EXTENSION) *sk) { + X509_EXTENSION * ext; + int j; + + for (j = 0; j < sk_X509_EXTENSION_num(sk); j++) { + ext = sk_X509_EXTENSION_value(sk, j); + if (OBJ_obj2nid(ext->object) == NID_crl_reason) { + X509_EXTENSION_free(ext); + (void) sk_X509_EXTENSION_delete(sk, j); + break; + } + } +} + +static int +reason_str_to_code(const char * reason_str) { + int reason_code = -1, j; + char *spaceless_reason, * sp; + + /* Remove spaces so that the responses of + * get_reason() work in set_reason() */ + if ((spaceless_reason = strdup(reason_str)) == NULL) { + return -1; + } + + while ((sp = strchr(spaceless_reason, ' '))) { + memmove(sp, sp+1, strlen(sp)); + } + + for (j = 0; j < NUM_REASONS; j++) { + if(crl_reasons[j] && !strcasecmp(spaceless_reason, crl_reasons[j])) { + reason_code = j; + break; + } + } + free(spaceless_reason); + return reason_code; +} + + +static char crypto_Revoked_set_reason_doc[] = "\n\ +Set the reason of a Revoked object.\n\ +\n\ +@param reason: The reason string.\n\ +@type reason: L{str}\n\ +@return: None\n\ +"; +static PyObject * +crypto_Revoked_set_reason(crypto_RevokedObj *self, PyObject *args, PyObject *keywds) { + static char *kwlist[] = {"reason", NULL}; + const char *reason_str = NULL; + int reason_code; + ASN1_ENUMERATED *rtmp = NULL; + + if (!PyArg_ParseTupleAndKeywords( + args, keywds, "O&:set_reason", kwlist, + crypto_byte_converter, &reason_str)) { + return NULL; + } + + if(reason_str == NULL) { + delete_reason(self->revoked->extensions); + goto done; + } + + reason_code = reason_str_to_code(reason_str); + if (reason_code == -1) { + PyErr_SetString(PyExc_ValueError, "bad reason string"); + return NULL; + } + + rtmp = ASN1_ENUMERATED_new(); + if (!rtmp || !ASN1_ENUMERATED_set(rtmp, reason_code)) { + goto err; + } + delete_reason(self->revoked->extensions); + if (!X509_REVOKED_add1_ext_i2d(self->revoked, NID_crl_reason, rtmp, 0, 0)) { + goto err; + } + + done: + Py_INCREF(Py_None); + return Py_None; + + err: + exception_from_error_queue(crypto_Error); + return NULL; +} + + +static char crypto_Revoked_get_reason_doc[] = "\n\ +Return the reason of a Revoked object.\n\ +\n\ +@return: The reason as a string\n\ +"; +static PyObject * +crypto_Revoked_get_reason(crypto_RevokedObj *self, PyObject *args) { + X509_EXTENSION * ext; + int j; + STACK_OF(X509_EXTENSION) *sk = NULL; + + if (!PyArg_ParseTuple(args, ":get_reason")) { + return NULL; + } + + sk = self->revoked->extensions; + for (j = 0; j < sk_X509_EXTENSION_num(sk); j++) { + ext = sk_X509_EXTENSION_value(sk, j); + if (OBJ_obj2nid(ext->object) == NID_crl_reason) { + return X509_EXTENSION_value_to_PyString(ext); + } + } + + Py_INCREF(Py_None); + return Py_None; +} + + +static char crypto_Revoked_get_rev_date_doc[] = "\n\ +Retrieve the revocation date\n\ +\n\ +@return: A string giving the timestamp, in the format:\n\ +\n\ + YYYYMMDDhhmmssZ\n\ + YYYYMMDDhhmmss+hhmm\n\ + YYYYMMDDhhmmss-hhmm\n\ +"; + +static PyObject* +crypto_Revoked_get_rev_date(crypto_RevokedObj *self, PyObject *args) { + /* returns a borrowed reference. */ + return _get_asn1_time( + ":get_rev_date", self->revoked->revocationDate, args); +} + +static char crypto_Revoked_set_rev_date_doc[] = "\n\ +Set the revocation timestamp\n\ +\n\ +@param when: A string giving the timestamp, in the format:\n\ +\n\ + YYYYMMDDhhmmssZ\n\ + YYYYMMDDhhmmss+hhmm\n\ + YYYYMMDDhhmmss-hhmm\n\ +\n\ +@return: None\n\ +"; + +static PyObject* +crypto_Revoked_set_rev_date(crypto_RevokedObj *self, PyObject *args) { + return _set_asn1_time( + BYTESTRING_FMT ":set_rev_date", self->revoked->revocationDate, args); +} + +/* The integer is converted to an upper-case hex string + * without a '0x' prefix. */ +static PyObject * +ASN1_INTEGER_to_PyString(ASN1_INTEGER *asn1_int) { + BIO *bio = NULL; + PyObject *str = NULL; + int str_len; + char *tmp_str; + + /* Create a openssl BIO buffer */ + bio = BIO_new(BIO_s_mem()); + if (bio == NULL) { + goto err; + } + + /* Write the integer to the BIO as a hex string. */ + if (i2a_ASN1_INTEGER(bio, asn1_int) < 0) { + goto err; + } + + /* Convert to a Python string. */ + str_len = BIO_get_mem_data(bio, &tmp_str); + str = PyBytes_FromStringAndSize(tmp_str, str_len); + + /* Cleanup */ + BIO_free(bio); + return str; + + err: + if (bio) { + BIO_free(bio); + } + if (str) { + Py_DECREF(str); + } + return NULL; +} + + +static char crypto_Revoked_get_serial_doc[] = "\n\ +Return the serial number of a Revoked structure\n\ +\n\ +@return: The serial number as a string\n\ +"; +static PyObject * +crypto_Revoked_get_serial(crypto_RevokedObj *self, PyObject *args) { + if (!PyArg_ParseTuple(args, ":get_serial")) { + return NULL; + } + + if (self->revoked->serialNumber == NULL) { + /* never happens */ + Py_INCREF(Py_None); + return Py_None; + } else { + return ASN1_INTEGER_to_PyString(self->revoked->serialNumber); + } +} + +static char crypto_Revoked_set_serial_doc[] = "\n\ +Set the serial number of a revoked Revoked structure\n\ +\n\ +@param hex_str: The new serial number.\n\ +@type hex_str: L{str}\n\ +@return: None\n\ +"; +static PyObject * +crypto_Revoked_set_serial(crypto_RevokedObj *self, PyObject *args, PyObject *keywds) { + static char *kwlist[] = {"hex_str", NULL}; + const char *hex_str = NULL; + BIGNUM *serial = NULL; + ASN1_INTEGER *tmpser = NULL; + + if (!PyArg_ParseTupleAndKeywords(args, keywds, BYTESTRING_FMT ":set_serial", + kwlist, &hex_str)) { + return NULL; + } + + if (!BN_hex2bn(&serial, hex_str) ) { + PyErr_SetString(PyExc_ValueError, "bad hex string"); + return NULL; + } + + tmpser = BN_to_ASN1_INTEGER(serial, NULL); + BN_free(serial); + serial = NULL; + X509_REVOKED_set_serialNumber(self->revoked, tmpser); + ASN1_INTEGER_free(tmpser); + + Py_INCREF(Py_None); + return Py_None; +} + + +crypto_RevokedObj * +crypto_Revoked_New(X509_REVOKED *revoked) { + crypto_RevokedObj *self; + + self = PyObject_New(crypto_RevokedObj, &crypto_Revoked_Type); + if (self == NULL) { + return NULL; + } + self->revoked = revoked; + return self; +} + +/* + * ADD_METHOD(name) expands to a correct PyMethodDef declaration + * { 'name', (PyCFunction)crypto_Revoked_name, METH_VARARGS, crypto_Revoked_name_doc } + * for convenience + */ +#define ADD_METHOD(name) \ + { #name, (PyCFunction)crypto_Revoked_##name, METH_VARARGS, crypto_Revoked_##name##_doc } +#define ADD_KW_METHOD(name) \ + { #name, (PyCFunction)crypto_Revoked_##name, METH_VARARGS | METH_KEYWORDS, crypto_Revoked_##name##_doc } +static PyMethodDef crypto_Revoked_methods[] = { + ADD_METHOD(all_reasons), + ADD_METHOD(get_reason), + ADD_KW_METHOD(set_reason), + ADD_METHOD(get_rev_date), + ADD_METHOD(set_rev_date), + ADD_METHOD(get_serial), + ADD_KW_METHOD(set_serial), + { NULL, NULL } +}; +#undef ADD_METHOD + + +static void +crypto_Revoked_dealloc(crypto_RevokedObj *self) { + X509_REVOKED_free(self->revoked); + self->revoked = NULL; + + PyObject_Del(self); +} + +static char crypto_Revoked_doc[] = "\n\ +Revoked() -> Revoked instance\n\ +\n\ +Create a new empty Revoked object.\n\ +\n\ +@returns: The Revoked object\n\ +"; + +static PyObject* crypto_Revoked_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) { + if (!PyArg_ParseTuple(args, ":Revoked")) { + return NULL; + } + + return (PyObject *)crypto_Revoked_New(X509_REVOKED_new()); +} + +PyTypeObject crypto_Revoked_Type = { + PyOpenSSL_HEAD_INIT(&PyType_Type, 0) + "Revoked", + sizeof(crypto_RevokedObj), + 0, + (destructor)crypto_Revoked_dealloc, + NULL, /* print */ + NULL, /* getattr */ + NULL, /* setattr */ + NULL, /* compare */ + NULL, /* repr */ + NULL, /* as_number */ + NULL, /* as_sequence */ + NULL, /* as_mapping */ + NULL, /* hash */ + NULL, /* call */ + NULL, /* str */ + NULL, /* getattro */ + NULL, /* setattro */ + NULL, /* as_buffer */ + Py_TPFLAGS_DEFAULT, + crypto_Revoked_doc, /* doc */ + NULL, /* traverse */ + NULL, /* clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + crypto_Revoked_methods, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + crypto_Revoked_new, /* tp_new */ +}; + +int init_crypto_revoked(PyObject *module) { + if(PyType_Ready(&crypto_Revoked_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "Revoked", (PyObject *)&crypto_Revoked_Type) != 0) { + return 0; + } + return 1; +} diff --git a/OpenSSL/crypto/revoked.h b/OpenSSL/crypto/revoked.h new file mode 100644 index 0000000..fb85ac6 --- /dev/null +++ b/OpenSSL/crypto/revoked.h @@ -0,0 +1,18 @@ +#ifndef PyOpenSSL_crypto_REVOKED_H_ +#define PyOpenSSL_crypto_REVOKED_H_ + +#include + +extern PyTypeObject crypto_Revoked_Type; + +#define crypto_Revoked_Check(v) ((v)->ob_type == &crypto_Revoked_Type) + +typedef struct { + PyObject_HEAD + X509_REVOKED *revoked; +} crypto_RevokedObj; + +extern int init_crypto_revoked (PyObject *); +extern crypto_RevokedObj * crypto_Revoked_New(X509_REVOKED *revoked); + +#endif diff --git a/OpenSSL/crypto/x509.c b/OpenSSL/crypto/x509.c new file mode 100644 index 0000000..5d03d2e --- /dev/null +++ b/OpenSSL/crypto/x509.c @@ -0,0 +1,896 @@ +/* + * x509.c + * + * Copyright (C) AB Strakt + * Copyright (C) Jean-Paul Calderone + * See LICENSE for details. + * + * Certificate (X.509) handling code, mostly thin wrappers around OpenSSL. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + */ +#include +#define crypto_MODULE +#include "crypto.h" +#include "x509ext.h" + +/* + * X.509 is a standard for digital certificates. See e.g. the OpenSSL homepage + * http://www.openssl.org/ for more information + */ + +static char crypto_X509_get_version_doc[] = "\n\ +Return version number of the certificate\n\ +\n\ +@return: Version number as a Python integer\n\ +"; + +static PyObject * +crypto_X509_get_version(crypto_X509Obj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":get_version")) + return NULL; + + return PyLong_FromLong((long)X509_get_version(self->x509)); +} + +static char crypto_X509_set_version_doc[] = "\n\ +Set version number of the certificate\n\ +\n\ +@param version: The version number\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509_set_version(crypto_X509Obj *self, PyObject *args) +{ + int version; + + if (!PyArg_ParseTuple(args, "i:set_version", &version)) + return NULL; + + X509_set_version(self->x509, version); + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_X509_get_serial_number_doc[] = "\n\ +Return serial number of the certificate\n\ +\n\ +@return: Serial number as a Python integer\n\ +"; + +static PyObject * +crypto_X509_get_serial_number(crypto_X509Obj *self, PyObject *args) +{ + ASN1_INTEGER *asn1_i; + BIGNUM *bignum; + char *hex; + PyObject *res; + + if (!PyArg_ParseTuple(args, ":get_serial_number")) + return NULL; + + asn1_i = X509_get_serialNumber(self->x509); + bignum = ASN1_INTEGER_to_BN(asn1_i, NULL); + hex = BN_bn2hex(bignum); + res = PyLong_FromString(hex, NULL, 16); + BN_free(bignum); + free(hex); + return res; +} + +static char crypto_X509_set_serial_number_doc[] = "\n\ +Set serial number of the certificate\n\ +\n\ +@param serial: The serial number\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509_set_serial_number(crypto_X509Obj *self, PyObject *args) +{ + long small_serial; + PyObject *serial = NULL; + PyObject *hex = NULL; + ASN1_INTEGER *asn1_i = NULL; + BIGNUM *bignum = NULL; + char *hexstr; + + if (!PyArg_ParseTuple(args, "O:set_serial_number", &serial)) { + return NULL; + } + + if (!PyOpenSSL_Integer_Check(serial)) { + PyErr_SetString( + PyExc_TypeError, "serial number must be integer"); + goto err; + } + + if ((hex = PyOpenSSL_LongToHex(serial)) == NULL) { + goto err; + } + +#ifdef PY3 + { + PyObject *hexbytes = PyUnicode_AsASCIIString(hex); + Py_DECREF(hex); + hex = hexbytes; + } +#endif + + /** + * 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 + */ + hexstr = PyBytes_AsString(hex); + if (hexstr[1] == 'x') { + /* +2 to skip the "0x" */ + hexstr += 2; + } + small_serial = BN_hex2bn(&bignum, hexstr); + + Py_DECREF(hex); + hex = NULL; + + if (bignum == NULL) { + if (ASN1_INTEGER_set(X509_get_serialNumber(self->x509), small_serial)) { + exception_from_error_queue(crypto_Error); + goto err; + } + } else { + asn1_i = BN_to_ASN1_INTEGER(bignum, NULL); + BN_free(bignum); + bignum = NULL; + if (asn1_i == NULL) { + exception_from_error_queue(crypto_Error); + goto err; + } + if (!X509_set_serialNumber(self->x509, asn1_i)) { + exception_from_error_queue(crypto_Error); + goto err; + } + ASN1_INTEGER_free(asn1_i); + asn1_i = NULL; + } + + Py_INCREF(Py_None); + return Py_None; + + err: + if (hex) { + Py_DECREF(hex); + } + if (bignum) { + BN_free(bignum); + } + if (asn1_i) { + ASN1_INTEGER_free(asn1_i); + } + return NULL; +} + +static char crypto_X509_get_issuer_doc[] = "\n\ +Create an X509Name object for the issuer of the certificate\n\ +\n\ +@return: An X509Name object\n\ +"; + +static PyObject * +crypto_X509_get_issuer(crypto_X509Obj *self, PyObject *args) +{ + crypto_X509NameObj *pyname; + X509_NAME *name; + + if (!PyArg_ParseTuple(args, ":get_issuer")) + return NULL; + + name = X509_get_issuer_name(self->x509); + pyname = crypto_X509Name_New(name, 0); + if (pyname != NULL) + { + pyname->parent_cert = (PyObject *)self; + Py_INCREF(self); + } + return (PyObject *)pyname; +} + +static char crypto_X509_set_issuer_doc[] = "\n\ +Set the issuer of the certificate\n\ +\n\ +@param issuer: The issuer name\n\ +@type issuer: L{X509Name}\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509_set_issuer(crypto_X509Obj *self, PyObject *args) +{ + crypto_X509NameObj *issuer; + + if (!PyArg_ParseTuple(args, "O!:set_issuer", &crypto_X509Name_Type, + &issuer)) + return NULL; + + if (!X509_set_issuer_name(self->x509, issuer->x509_name)) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_X509_get_subject_doc[] = "\n\ +Create an X509Name object for the subject of the certificate\n\ +\n\ +@return: An X509Name object\n\ +"; + +static PyObject * +crypto_X509_get_subject(crypto_X509Obj *self, PyObject *args) +{ + crypto_X509NameObj *pyname; + X509_NAME *name; + + if (!PyArg_ParseTuple(args, ":get_subject")) + return NULL; + + name = X509_get_subject_name(self->x509); + pyname = crypto_X509Name_New(name, 0); + if (pyname != NULL) + { + pyname->parent_cert = (PyObject *)self; + Py_INCREF(self); + } + return (PyObject *)pyname; +} + +static char crypto_X509_set_subject_doc[] = "\n\ +Set the subject of the certificate\n\ +\n\ +@param subject: The subject name\n\ +@type subject: L{X509Name}\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509_set_subject(crypto_X509Obj *self, PyObject *args) +{ + crypto_X509NameObj *subject; + + if (!PyArg_ParseTuple(args, "O!:set_subject", &crypto_X509Name_Type, + &subject)) + return NULL; + + if (!X509_set_subject_name(self->x509, subject->x509_name)) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_X509_get_pubkey_doc[] = "\n\ +Get the public key of the certificate\n\ +\n\ +@return: The public key\n\ +"; + +static PyObject * +crypto_X509_get_pubkey(crypto_X509Obj *self, PyObject *args) +{ + crypto_PKeyObj *crypto_PKey_New(EVP_PKEY *, int); + EVP_PKEY *pkey; + crypto_PKeyObj *py_pkey; + + if (!PyArg_ParseTuple(args, ":get_pubkey")) + return NULL; + + if ((pkey = X509_get_pubkey(self->x509)) == NULL) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + py_pkey = crypto_PKey_New(pkey, 1); + if (py_pkey != NULL) { + py_pkey->only_public = 1; + } + return (PyObject *)py_pkey; +} + +static char crypto_X509_set_pubkey_doc[] = "\n\ +Set the public key of the certificate\n\ +\n\ +@param pkey: The public key\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509_set_pubkey(crypto_X509Obj *self, PyObject *args) +{ + crypto_PKeyObj *pkey; + + if (!PyArg_ParseTuple(args, "O!:set_pubkey", &crypto_PKey_Type, &pkey)) + return NULL; + + if (!X509_set_pubkey(self->x509, pkey->pkey)) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +PyObject* +_set_asn1_time(char *format, ASN1_TIME* timestamp, PyObject *args) +{ + char *when; + + if (!PyArg_ParseTuple(args, format, &when)) + return NULL; + + if (ASN1_GENERALIZEDTIME_set_string(timestamp, when) == 0) { + ASN1_GENERALIZEDTIME dummy; + dummy.type = V_ASN1_GENERALIZEDTIME; + dummy.length = strlen(when); + dummy.data = (unsigned char *)when; + if (!ASN1_GENERALIZEDTIME_check(&dummy)) { + PyErr_SetString(PyExc_ValueError, "Invalid string"); + } else { + PyErr_SetString(PyExc_RuntimeError, "Unknown ASN1_GENERALIZEDTIME_set_string failure"); + } + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_X509_set_notBefore_doc[] = "\n\ +Set the time stamp for when the certificate starts being valid\n\ +\n\ +@param when: A string giving the timestamp, in the format:\n\ +\n\ + YYYYMMDDhhmmssZ\n\ + YYYYMMDDhhmmss+hhmm\n\ + YYYYMMDDhhmmss-hhmm\n\ +\n\ +@return: None\n\ +"; + +static PyObject* +crypto_X509_set_notBefore(crypto_X509Obj *self, PyObject *args) +{ + return _set_asn1_time( + BYTESTRING_FMT ":set_notBefore", + X509_get_notBefore(self->x509), args); +} + +static char crypto_X509_set_notAfter_doc[] = "\n\ +Set the time stamp for when the certificate stops being valid\n\ +\n\ +@param when: A string giving the timestamp, in the format:\n\ +\n\ + YYYYMMDDhhmmssZ\n\ + YYYYMMDDhhmmss+hhmm\n\ + YYYYMMDDhhmmss-hhmm\n\ +\n\ +@return: None\n\ +"; + +static PyObject* +crypto_X509_set_notAfter(crypto_X509Obj *self, PyObject *args) +{ + return _set_asn1_time( + BYTESTRING_FMT ":set_notAfter", + X509_get_notAfter(self->x509), args); +} + +PyObject* +_get_asn1_time(char *format, ASN1_TIME* timestamp, PyObject *args) +{ + ASN1_GENERALIZEDTIME *gt_timestamp = NULL; + PyObject *py_timestamp = NULL; + + if (!PyArg_ParseTuple(args, format)) { + return NULL; + } + + /* + * http://www.columbia.edu/~ariel/ssleay/asn1-time.html + */ + /* + * There must be a way to do this without touching timestamp->data + * directly. -exarkun + */ + if (timestamp->length == 0) { + Py_INCREF(Py_None); + return Py_None; + } else if (timestamp->type == V_ASN1_GENERALIZEDTIME) { + return PyBytes_FromString((char *)timestamp->data); + } else { + ASN1_TIME_to_generalizedtime(timestamp, >_timestamp); + if (gt_timestamp == NULL) { + exception_from_error_queue(crypto_Error); + return NULL; + } else { + py_timestamp = PyBytes_FromString((char *)gt_timestamp->data); + ASN1_GENERALIZEDTIME_free(gt_timestamp); + return py_timestamp; + } + } +} + +static char crypto_X509_get_notBefore_doc[] = "\n\ +Retrieve the time stamp for when the certificate starts being valid\n\ +\n\ +@return: A string giving the timestamp, in the format:\n\ +\n\ + YYYYMMDDhhmmssZ\n\ + YYYYMMDDhhmmss+hhmm\n\ + YYYYMMDDhhmmss-hhmm\n\ + or None if there is no value set.\n\ +"; + +static PyObject* +crypto_X509_get_notBefore(crypto_X509Obj *self, PyObject *args) +{ + /* + * X509_get_notBefore returns a borrowed reference. + */ + return _get_asn1_time( + ":get_notBefore", X509_get_notBefore(self->x509), args); +} + + +static char crypto_X509_get_notAfter_doc[] = "\n\ +Retrieve the time stamp for when the certificate stops being valid\n\ +\n\ +@return: A string giving the timestamp, in the format:\n\ +\n\ + YYYYMMDDhhmmssZ\n\ + YYYYMMDDhhmmss+hhmm\n\ + YYYYMMDDhhmmss-hhmm\n\ + or None if there is no value set.\n\ +"; + +static PyObject* +crypto_X509_get_notAfter(crypto_X509Obj *self, PyObject *args) +{ + /* + * X509_get_notAfter returns a borrowed reference. + */ + return _get_asn1_time( + ":get_notAfter", X509_get_notAfter(self->x509), args); +} + + +static char crypto_X509_gmtime_adj_notBefore_doc[] = "\n\ +Change the timestamp for when the certificate starts being valid to the current\n\ +time plus an offset.\n \ +\n\ +@param amount: The number of seconds by which to adjust the starting validity\n\ + time.\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509_gmtime_adj_notBefore(crypto_X509Obj *self, PyObject *args) +{ + long amount; + + if (!PyArg_ParseTuple(args, "l:gmtime_adj_notBefore", &amount)) + return NULL; + + X509_gmtime_adj(X509_get_notBefore(self->x509), amount); + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_X509_gmtime_adj_notAfter_doc[] = "\n\ +Adjust the time stamp for when the certificate stops being valid\n\ +\n\ +@param amount: The number of seconds by which to adjust the ending validity\n\ + time.\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509_gmtime_adj_notAfter(crypto_X509Obj *self, PyObject *args) +{ + long amount; + + if (!PyArg_ParseTuple(args, "l:gmtime_adj_notAfter", &amount)) + return NULL; + + X509_gmtime_adj(X509_get_notAfter(self->x509), amount); + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_X509_sign_doc[] = "\n\ +Sign the certificate using the supplied key and digest\n\ +\n\ +@param pkey: The key to sign with\n\ +@param digest: The message digest to use\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509_sign(crypto_X509Obj *self, PyObject *args) +{ + crypto_PKeyObj *pkey; + char *digest_name; + const EVP_MD *digest; + + if (!PyArg_ParseTuple(args, "O!s:sign", &crypto_PKey_Type, &pkey, + &digest_name)) + return NULL; + + if (pkey->only_public) { + PyErr_SetString(PyExc_ValueError, "Key has only public part"); + return NULL; + } + + if (!pkey->initialized) { + PyErr_SetString(PyExc_ValueError, "Key is uninitialized"); + return NULL; + } + + if ((digest = EVP_get_digestbyname(digest_name)) == NULL) + { + PyErr_SetString(PyExc_ValueError, "No such digest method"); + return NULL; + } + + if (!X509_sign(self->x509, pkey->pkey, digest)) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_X509_has_expired_doc[] = "\n\ +Check whether the certificate has expired.\n\ +\n\ +@return: True if the certificate has expired, false otherwise\n\ +"; + +static PyObject * +crypto_X509_has_expired(crypto_X509Obj *self, PyObject *args) +{ + time_t tnow; + + if (!PyArg_ParseTuple(args, ":has_expired")) + return NULL; + + tnow = time(NULL); + if (ASN1_UTCTIME_cmp_time_t(X509_get_notAfter(self->x509), tnow) < 0) + return PyLong_FromLong(1L); + else + return PyLong_FromLong(0L); +} + +static char crypto_X509_subject_name_hash_doc[] = "\n\ +Return the hash of the X509 subject.\n\ +\n\ +@return: The hash of the subject\n\ +"; + +static PyObject * +crypto_X509_subject_name_hash(crypto_X509Obj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":subject_name_hash")) + return NULL; + + return PyLong_FromLongLong(X509_subject_name_hash(self->x509)); +} + +static char crypto_X509_digest_doc[] = "\n\ +Return the digest of the X509 object.\n\ +\n\ +@return: The digest of the object\n\ +"; + +static PyObject * +crypto_X509_digest(crypto_X509Obj *self, PyObject *args) +{ + unsigned char fp[EVP_MAX_MD_SIZE]; + char *tmp; + char *digest_name; + unsigned int len,i; + PyObject *ret; + const EVP_MD *digest; + + if (!PyArg_ParseTuple(args, "s:digest", &digest_name)) + return NULL; + + if ((digest = EVP_get_digestbyname(digest_name)) == NULL) + { + PyErr_SetString(PyExc_ValueError, "No such digest method"); + return NULL; + } + + if (!X509_digest(self->x509,digest,fp,&len)) + { + exception_from_error_queue(crypto_Error); + } + tmp = malloc(3*len+1); + memset(tmp, 0, 3*len+1); + for (i = 0; i < len; i++) { + sprintf(tmp+i*3,"%02X:",fp[i]); + } + tmp[3*len-1] = 0; + ret = PyBytes_FromStringAndSize(tmp,3*len-1); + free(tmp); + return ret; +} + + +static char crypto_X509_add_extensions_doc[] = "\n\ +Add extensions to the certificate.\n\ +\n\ +@param extensions: a sequence of X509Extension objects\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509_add_extensions(crypto_X509Obj *self, PyObject *args) +{ + PyObject *extensions, *seq; + crypto_X509ExtensionObj *ext; + int nr_of_extensions, i; + + if (!PyArg_ParseTuple(args, "O:add_extensions", &extensions)) + return NULL; + + seq = PySequence_Fast(extensions, "Expected a sequence"); + if (seq == NULL) + return NULL; + + nr_of_extensions = PySequence_Fast_GET_SIZE(seq); + + for (i = 0; i < nr_of_extensions; i++) + { + ext = (crypto_X509ExtensionObj *)PySequence_Fast_GET_ITEM(seq, i); + if (!crypto_X509Extension_Check(ext)) + { + Py_DECREF(seq); + PyErr_SetString(PyExc_ValueError, + "One of the elements is not an X509Extension"); + return NULL; + } + if (!X509_add_ext(self->x509, ext->x509_extension, -1)) + { + Py_DECREF(seq); + exception_from_error_queue(crypto_Error); + return NULL; + } + } + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_X509_get_extension_count_doc[] = "\n\ +Get the number of extensions on the certificate.\n\ +\n\ +@return: Number of extensions as a Python integer\n\ +"; + +static PyObject * +crypto_X509_get_extension_count(crypto_X509Obj *self, PyObject *args) { + if (!PyArg_ParseTuple(args, ":get_extension_count")) { + return NULL; + } + + return PyLong_FromLong((long)X509_get_ext_count(self->x509)); +} + +static char crypto_X509_get_extension_doc[] = "\n\ +Get a specific extension of the certificate by index.\n\ +\n\ +@param index: The index of the extension to retrieve.\n\ +@return: The X509Extension object at the specified index.\n\ +"; + +static PyObject * +crypto_X509_get_extension(crypto_X509Obj *self, PyObject *args) { + crypto_X509ExtensionObj *extobj; + int loc; + X509_EXTENSION *ext; + + if (!PyArg_ParseTuple(args, "i:get_extension", &loc)) { + return NULL; + } + + /* will return NULL if loc is outside the range of extensions, + not registered as an error*/ + ext = X509_get_ext(self->x509, loc); + if (!ext) { + PyErr_SetString(PyExc_IndexError, "extension index out of bounds"); + return NULL; /* Should be reported as an IndexError ? */ + } + + extobj = PyObject_New(crypto_X509ExtensionObj, &crypto_X509Extension_Type); + extobj->x509_extension = X509_EXTENSION_dup(ext); + + return (PyObject*)extobj; +} + +/* + * ADD_METHOD(name) expands to a correct PyMethodDef declaration + * { 'name', (PyCFunction)crypto_X509_name, METH_VARARGS } + * for convenience + */ +#define ADD_METHOD(name) \ + { #name, (PyCFunction)crypto_X509_##name, METH_VARARGS, crypto_X509_##name##_doc } +static PyMethodDef crypto_X509_methods[] = +{ + ADD_METHOD(get_version), + ADD_METHOD(set_version), + ADD_METHOD(get_serial_number), + ADD_METHOD(set_serial_number), + ADD_METHOD(get_issuer), + ADD_METHOD(set_issuer), + ADD_METHOD(get_subject), + ADD_METHOD(set_subject), + ADD_METHOD(get_pubkey), + ADD_METHOD(set_pubkey), + ADD_METHOD(get_notBefore), + ADD_METHOD(set_notBefore), + ADD_METHOD(get_notAfter), + ADD_METHOD(set_notAfter), + ADD_METHOD(gmtime_adj_notBefore), + ADD_METHOD(gmtime_adj_notAfter), + ADD_METHOD(sign), + ADD_METHOD(has_expired), + ADD_METHOD(subject_name_hash), + ADD_METHOD(digest), + ADD_METHOD(add_extensions), + ADD_METHOD(get_extension), + ADD_METHOD(get_extension_count), + { NULL, NULL } +}; +#undef ADD_METHOD + + +/* + * Constructor for X509 objects, never called by Python code directly + * + * Arguments: cert - A "real" X509 certificate object + * dealloc - Boolean value to specify whether the destructor should + * free the "real" X509 object + * Returns: The newly created X509 object + */ +crypto_X509Obj * +crypto_X509_New(X509 *cert, int dealloc) +{ + crypto_X509Obj *self; + + self = PyObject_New(crypto_X509Obj, &crypto_X509_Type); + + if (self == NULL) + return NULL; + + self->x509 = cert; + self->dealloc = dealloc; + + return self; +} + + +static char crypto_X509_doc[] = "\n\ +X509() -> X509 instance\n\ +\n\ +Create a new X509 object.\n\ +\n\ +@returns: The X509 object\n\ +"; + +static PyObject * +crypto_X509_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) +{ + if (!PyArg_ParseTuple(args, ":X509")) { + return NULL; + } + + return (PyObject *)crypto_X509_New(X509_new(), 1); +} + + +/* + * Deallocate the memory used by the X509 object + * + * Arguments: self - The X509 object + * Returns: None + */ +static void +crypto_X509_dealloc(crypto_X509Obj *self) +{ + /* Sometimes we don't have to dealloc the "real" X509 pointer ourselves */ + if (self->dealloc) + X509_free(self->x509); + + PyObject_Del(self); +} + +PyTypeObject crypto_X509_Type = { + PyOpenSSL_HEAD_INIT(&PyType_Type, 0) + "X509", + sizeof(crypto_X509Obj), + 0, + (destructor)crypto_X509_dealloc, + NULL, /* print */ + NULL, /* getattr */ + NULL, /* setattr */ + NULL, /* compare */ + NULL, /* repr */ + NULL, /* as_number */ + NULL, /* as_sequence */ + NULL, /* as_mapping */ + NULL, /* hash */ + NULL, /* call */ + NULL, /* str */ + NULL, /* getattro */ + NULL, /* setattro */ + NULL, /* as_buffer */ + Py_TPFLAGS_DEFAULT, + crypto_X509_doc, /* doc */ + NULL, /* traverse */ + NULL, /* clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + crypto_X509_methods, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + crypto_X509_new, /* tp_new */ +}; + +/* + * Initialize the X509 part of the crypto sub module + * + * Arguments: module - The crypto module + * Returns: None + */ +int +init_crypto_x509(PyObject *module) +{ + if (PyType_Ready(&crypto_X509_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "X509", (PyObject *)&crypto_X509_Type) != 0) { + return 0; + } + + if (PyModule_AddObject(module, "X509Type", (PyObject *)&crypto_X509_Type) != 0) { + return 0; + } + + return 1; +} + diff --git a/OpenSSL/crypto/x509.h b/OpenSSL/crypto/x509.h new file mode 100644 index 0000000..f6cd190 --- /dev/null +++ b/OpenSSL/crypto/x509.h @@ -0,0 +1,34 @@ +/* + * x509.h + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * Export x509 functions and data structure. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + * + */ +#ifndef PyOpenSSL_crypto_X509_H_ +#define PyOpenSSL_crypto_X509_H_ + +#include +#include + +extern PyTypeObject crypto_X509_Type; + +#define crypto_X509_Check(v) ((v)->ob_type == &crypto_X509_Type) + +typedef struct { + PyObject_HEAD + X509 *x509; + int dealloc; +} crypto_X509Obj; + +PyObject* _set_asn1_time(char *format, ASN1_TIME* timestamp, PyObject *args); +PyObject* _get_asn1_time(char *format, ASN1_TIME* timestamp, PyObject *args); +extern int init_crypto_x509 (PyObject *); + + +#endif diff --git a/OpenSSL/crypto/x509ext.c b/OpenSSL/crypto/x509ext.c new file mode 100644 index 0000000..bdaac7d --- /dev/null +++ b/OpenSSL/crypto/x509ext.c @@ -0,0 +1,330 @@ +/* + * x509ext.c + * + * Copyright (C) Jean-Paul Calderone + * See LICENSE for details. + * + * Export X.509 extension functions and data structures. + * See the file RATIONALE for a short explanation of why this module was written. + * + */ + +#include +#define crypto_MODULE +#include "crypto.h" + +static char crypto_X509Extension_get_critical_doc[] = "\n\ +Returns the critical field of the X509Extension\n\ +\n\ +@return: The critical field.\n\ +"; + +static PyObject * +crypto_X509Extension_get_critical(crypto_X509ExtensionObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":get_critical")) + return NULL; + + return PyLong_FromLong(X509_EXTENSION_get_critical(self->x509_extension)); +} + +static char crypto_X509Extension_get_short_name_doc[] = "\n\ +Returns the short version of the type name of the X509Extension\n\ +\n\ +@return: The short type name.\n\ +"; + +static PyObject * +crypto_X509Extension_get_short_name(crypto_X509ExtensionObj *self, PyObject *args) { + ASN1_OBJECT *obj; + const char *extname; + + if (!PyArg_ParseTuple(args, ":get_short_name")) { + return NULL; + } + + /* Returns an internal pointer to x509_extension, not a copy */ + obj = X509_EXTENSION_get_object(self->x509_extension); + + extname = OBJ_nid2sn(OBJ_obj2nid(obj)); + return PyBytes_FromString(extname); +} + + +static char crypto_X509Extension_get_data_doc[] = "\n\ +Returns the data of the X509Extension\n\ +\n\ +@return: A C{str} giving the X509Extension's ASN.1 encoded data.\n\ +"; + +static PyObject * +crypto_X509Extension_get_data(crypto_X509ExtensionObj *self, PyObject *args) { + ASN1_OCTET_STRING *data; + PyObject *result; + + if (!PyArg_ParseTuple(args, ":get_data")) { + return NULL; + } + + data = X509_EXTENSION_get_data(self->x509_extension); + result = PyBytes_FromStringAndSize((const char*)data->data, data->length); + return result; +} + +/* + * ADD_METHOD(name) expands to a correct PyMethodDef declaration + * { 'name', (PyCFunction)crypto_X509Extension_name, METH_VARARGS } + * for convenience + */ +#define ADD_METHOD(name) \ +{ #name, (PyCFunction)crypto_X509Extension_##name, METH_VARARGS, crypto_X509Extension_##name##_doc } +static PyMethodDef crypto_X509Extension_methods[] = +{ + ADD_METHOD(get_critical), + ADD_METHOD(get_short_name), + ADD_METHOD(get_data), + { NULL, NULL } +}; +#undef ADD_METHOD + +/* + * Constructor for X509Extension, never called by Python code directly + * + * Arguments: type_name - ??? + * critical - ??? + * value - ??? + * subject - An x509v3 certificate which is the subject for this extension. + * issuer - An x509v3 certificate which is the issuer for this extension. + * Returns: The newly created X509Extension object + */ +crypto_X509ExtensionObj * +crypto_X509Extension_New(char *type_name, int critical, char *value, + crypto_X509Obj *subject, crypto_X509Obj *issuer) { + X509V3_CTX ctx; + crypto_X509ExtensionObj *self; + char* value_with_critical = NULL; + + + /* + * 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. + */ + X509V3_set_ctx(&ctx, NULL, NULL, NULL, NULL, 0); + + /* + * We have no configuration database - but perhaps we should (some + * extensions may require it). + */ + 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 incref subject or issuer. + */ + if (subject) { + ctx.subject_cert = subject->x509; + } + + if (issuer) { + ctx.issuer_cert = issuer->x509; + } + + self = PyObject_New(crypto_X509ExtensionObj, &crypto_X509Extension_Type); + + if (self == NULL) { + goto error; + } + + self->dealloc = 0; + + /* 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_with_critical = malloc(strlen("critical,") + strlen(value) + 1); + if (!value_with_critical) { + goto critical_malloc_error; + } + + if (critical) { + strcpy(value_with_critical, "critical,"); + strcpy(value_with_critical + strlen("critical,"), value); + } else { + strcpy(value_with_critical, value); + } + + self->x509_extension = X509V3_EXT_nconf( + NULL, &ctx, type_name, value_with_critical); + + free(value_with_critical); + + if (!self->x509_extension) { + goto nconf_error; + } + + self->dealloc = 1; + return self; + + nconf_error: + exception_from_error_queue(crypto_Error); + + critical_malloc_error: + Py_XDECREF(self); + + error: + return NULL; + +} + +static char crypto_X509Extension_doc[] = "\n\ +X509Extension(typename, critical, value[, subject][, issuer]) -> \n\ + X509Extension instance\n\ +\n\ +@param typename: The name of the extension to create.\n\ +@type typename: C{str}\n\ +@param critical: A flag indicating whether this is a critical extension.\n\ +@param value: The value of the extension.\n\ +@type value: C{str}\n\ +@param subject: Optional X509 cert to use as subject.\n\ +@type subject: C{X509}\n\ +@param issuer: Optional X509 cert to use as issuer.\n\ +@type issuer: C{X509}\n\ +@return: The X509Extension object\n\ +"; + +static PyObject * +crypto_X509Extension_new(PyTypeObject *subtype, PyObject *args, + PyObject *kwargs) { + char *type_name, *value; + int critical = 0; + crypto_X509Obj * subject = NULL; + crypto_X509Obj * issuer = NULL; + static char *kwlist[] = {"type_name", "critical", "value", "subject", + "issuer", NULL}; + + if (!PyArg_ParseTupleAndKeywords( + args, kwargs, + BYTESTRING_FMT "i" BYTESTRING_FMT "|O!O!:X509Extension", + kwlist, &type_name, &critical, &value, + &crypto_X509_Type, &subject, + &crypto_X509_Type, &issuer )) { + return NULL; + } + + return (PyObject *)crypto_X509Extension_New(type_name, critical, value, + subject, issuer); +} + +/* + * Deallocate the memory used by the X509Extension object + * + * Arguments: self - The X509Extension object + * Returns: None + */ +static void +crypto_X509Extension_dealloc(crypto_X509ExtensionObj *self) +{ + /* Sometimes we don't have to dealloc this */ + if (self->dealloc) + X509_EXTENSION_free(self->x509_extension); + + PyObject_Del(self); +} + +/* + * Print a nice text representation of the certificate request. + */ +static PyObject * +crypto_X509Extension_str(crypto_X509ExtensionObj *self) +{ + int str_len; + char *tmp_str; + PyObject *str; + BIO *bio = BIO_new(BIO_s_mem()); + + if (!X509V3_EXT_print(bio, self->x509_extension, 0, 0)) + { + BIO_free(bio); + exception_from_error_queue(crypto_Error); + return NULL; + } + + str_len = BIO_get_mem_data(bio, &tmp_str); + str = PyText_FromStringAndSize(tmp_str, str_len); + + BIO_free(bio); + + return str; +} + +PyTypeObject crypto_X509Extension_Type = { + PyOpenSSL_HEAD_INIT(&PyType_Type, 0) + "X509Extension", + sizeof(crypto_X509ExtensionObj), + 0, + (destructor)crypto_X509Extension_dealloc, + NULL, /* print */ + NULL, /* getattr */ + NULL, /* setattr (setattrfunc)crypto_X509Name_setattr, */ + NULL, /* compare */ + NULL, /* repr */ + NULL, /* as_number */ + NULL, /* as_sequence */ + NULL, /* as_mapping */ + NULL, /* hash */ + NULL, /* call */ + (reprfunc)crypto_X509Extension_str, /* str */ + NULL, /* getattro */ + NULL, /* setattro */ + NULL, /* as_buffer */ + Py_TPFLAGS_DEFAULT, + crypto_X509Extension_doc, /* doc */ + NULL, /* traverse */ + NULL, /* clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + crypto_X509Extension_methods, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + crypto_X509Extension_new, /* tp_new */ +}; + +/* + * Initialize the X509Extension part of the crypto module + * + * Arguments: dict - The crypto module + * Returns: None + */ +int +init_crypto_x509extension(PyObject *module) +{ + if (PyType_Ready(&crypto_X509Extension_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "X509Extension", + (PyObject *)&crypto_X509Extension_Type) != 0) { + return 0; + } + + if (PyModule_AddObject(module, "X509ExtensionType", + (PyObject *)&crypto_X509Extension_Type) != 0) { + return 0; + } + + return 1; +} diff --git a/OpenSSL/crypto/x509ext.h b/OpenSSL/crypto/x509ext.h new file mode 100644 index 0000000..3ddc716 --- /dev/null +++ b/OpenSSL/crypto/x509ext.h @@ -0,0 +1,33 @@ +/* + * x509ext.h + * + * Copyright (C) Awanim + * See LICENSE for details. + * + * Export X.509 extension functions and data structures. + * See the file RATIONALE for a short explanation of why this module was written. + * + */ +#ifndef PyOpenSSL_crypto_X509EXTENSION_H_ +#define PyOpenSSL_crypto_X509EXTENSION_H_ + +#include +#include +#include + +extern int init_crypto_x509extension (PyObject *); + +extern PyTypeObject crypto_X509Extension_Type; + +#define crypto_X509Extension_Check(v) ( \ + PyObject_TypeCheck((v), \ + &crypto_X509Extension_Type)) + +typedef struct { + PyObject_HEAD + X509_EXTENSION *x509_extension; + int dealloc; +} crypto_X509ExtensionObj; + +#endif + diff --git a/OpenSSL/crypto/x509name.c b/OpenSSL/crypto/x509name.c new file mode 100644 index 0000000..91f617a --- /dev/null +++ b/OpenSSL/crypto/x509name.c @@ -0,0 +1,533 @@ +/* + * x509name.c + * + * Copyright (C) AB Strakt + * Copyright (C) Jean-Paul Calderone + * See LICENSE for details. + * + * X.509 Name handling, mostly thin wrapping. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + */ +#include +#define crypto_MODULE +#include "crypto.h" + +static PyMethodDef crypto_X509Name_methods[4]; + +/* + * Constructor for X509Name, never called by Python code directly + * + * Arguments: name - A "real" X509_NAME object + * dealloc - Boolean value to specify whether the destructor should + * free the "real" X509_NAME object + * Returns: The newly created X509Name object + */ +crypto_X509NameObj * +crypto_X509Name_New(X509_NAME *name, int dealloc) +{ + crypto_X509NameObj *self; + + self = PyObject_GC_New(crypto_X509NameObj, &crypto_X509Name_Type); + + if (self == NULL) + return NULL; + + self->x509_name = name; + self->dealloc = dealloc; + self->parent_cert = NULL; + + PyObject_GC_Track(self); + return self; +} + + +static char crypto_X509Name_doc[] = "\n\ +X509Name(name) -> New X509Name object\n\ +\n\ +Create a new X509Name, copying the given X509Name instance.\n\ +\n\ +@param name: An X509Name object to copy\n\ +@return: The X509Name object\n\ +"; + +static PyObject * +crypto_X509Name_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) +{ + crypto_X509NameObj *name; + + if (!PyArg_ParseTuple(args, "O!:X509Name", &crypto_X509Name_Type, &name)) { + return NULL; + } + + return (PyObject *)crypto_X509Name_New(X509_NAME_dup(name->x509_name), 1); +} + + +/* + * Return a name string given a X509_NAME object and a name identifier. Used + * by the getattr function. + * + * Arguments: name - The X509_NAME object + * nid - The name identifier + * Returns: The name as a Python string object + */ +static int +get_name_by_nid(X509_NAME *name, int nid, char **utf8string) +{ + int entry_idx; + X509_NAME_ENTRY *entry; + ASN1_STRING *data; + int len; + + if ((entry_idx = X509_NAME_get_index_by_NID(name, nid, -1)) == -1) + { + return 0; + } + entry = X509_NAME_get_entry(name, entry_idx); + data = X509_NAME_ENTRY_get_data(entry); + if ((len = ASN1_STRING_to_UTF8((unsigned char **)utf8string, data)) < 0) + { + exception_from_error_queue(crypto_Error); + return -1; + } + + return len; +} + +/* + * Given a X509_NAME object and a name identifier, set the corresponding + * attribute to the given string. Used by the setattr function. + * + * Arguments: name - The X509_NAME object + * nid - The name identifier + * value - The string to set + * Returns: 0 for success, -1 on failure + */ +static int +set_name_by_nid(X509_NAME *name, int nid, char *utf8string) +{ + X509_NAME_ENTRY *ne; + int i, entry_count, temp_nid; + + /* If there's an old entry for this NID, remove it */ + entry_count = X509_NAME_entry_count(name); + for (i = 0; i < entry_count; i++) + { + ne = X509_NAME_get_entry(name, i); + temp_nid = OBJ_obj2nid(X509_NAME_ENTRY_get_object(ne)); + if (temp_nid == nid) + { + ne = X509_NAME_delete_entry(name, i); + X509_NAME_ENTRY_free(ne); + break; + } + } + + /* Add the new entry */ + if (!X509_NAME_add_entry_by_NID(name, nid, MBSTRING_UTF8, + (unsigned char *)utf8string, + -1, -1, 0)) + { + exception_from_error_queue(crypto_Error); + return -1; + } + return 0; +} + + +/* + * 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... + * + * Arguments: self - The X509Name object + * name - The attribute name + * Returns: A Python object for the attribute, or NULL if something went + * wrong + */ +static PyObject * +crypto_X509Name_getattro(crypto_X509NameObj *self, PyObject *nameobj) +{ + int nid, len; + char *utf8string; + char *name; +#ifdef PY3 + name = PyBytes_AsString(PyUnicode_AsASCIIString(nameobj)); +#else + name = PyBytes_AsString(nameobj); +#endif + + if ((nid = OBJ_txt2nid(name)) == 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. + */ + flush_error_queue(); + return PyObject_GenericGetAttr((PyObject*)self, nameobj); + } + + len = get_name_by_nid(self->x509_name, nid, &utf8string); + if (len < 0) + return NULL; + else if (len == 0) + { + Py_INCREF(Py_None); + return Py_None; + } + else { + PyObject* result = PyUnicode_Decode(utf8string, len, "utf-8", NULL); + OPENSSL_free(utf8string); + return result; + } +} + +/* + * Set attribute + * + * Arguments: self - The X509Name object + * name - The attribute name + * value - The value to set + */ +static int +crypto_X509Name_setattr(crypto_X509NameObj *self, char *name, PyObject *value) +{ + int nid; + int result; + char *buffer; + + if ((nid = OBJ_txt2nid(name)) == NID_undef) + { + PyErr_SetString(PyExc_AttributeError, "No such attribute"); + return -1; + } + + /* Something of a hack to get nice unicode behaviour */ + if (!PyArg_Parse(value, "es:setattr", "utf-8", &buffer)) + return -1; + + result = set_name_by_nid(self->x509_name, nid, buffer); + PyMem_Free(buffer); + return result; +} + +/* + * Compare two X509Name structures. + * + * Arguments: n - The first X509Name + * m - The second X509Name + * Returns: <0 if n < m, 0 if n == m and >0 if n > m + */ +static PyObject * +crypto_X509Name_richcompare(PyObject *n, PyObject *m, int op) { + int result; + + if (!crypto_X509Name_Check(n) || !crypto_X509Name_Check(m)) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + result = X509_NAME_cmp( + ((crypto_X509NameObj*)n)->x509_name, + ((crypto_X509NameObj*)m)->x509_name); + + switch (op) { + case Py_EQ: + result = (result == 0); + break; + + case Py_NE: + result = (result != 0); + break; + + case Py_LT: + result = (result < 0); + break; + + case Py_LE: + result = (result <= 0); + break; + + case Py_GT: + result = (result > 0); + break; + + case Py_GE: + result = (result >= 0); + break; + + default: + /* Should be impossible */ + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + if (result) { + Py_INCREF(Py_True); + return Py_True; + } else { + Py_INCREF(Py_False); + return Py_False; + } +} + +/* + * String representation of an X509Name + * + * Arguments: self - The X509Name object + * Returns: A string representation of the object + */ +static PyObject * +crypto_X509Name_repr(crypto_X509NameObj *self) +{ + char tmpbuf[512] = ""; + char realbuf[512+64]; + + if (X509_NAME_oneline(self->x509_name, tmpbuf, 512) == NULL) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + else + { + /* This is safe because tmpbuf is max 512 characters */ + sprintf(realbuf, "", tmpbuf); + return PyText_FromString(realbuf); + } +} + +static char crypto_X509Name_hash_doc[] = "\n\ +Return the hash value of this name\n\ +\n\ +@return: None\n\ +"; + +/* + * First four bytes of the MD5 digest of the DER form of an X509Name. + * + * Arguments: self - The X509Name object + * Returns: An integer giving the hash. + */ +static PyObject * +crypto_X509Name_hash(crypto_X509NameObj *self, PyObject* args) +{ + unsigned long hash; + + if (!PyArg_ParseTuple(args, ":hash")) { + return NULL; + } + hash = X509_NAME_hash(self->x509_name); + return PyLong_FromLong(hash); +} + +static char crypto_X509Name_der_doc[] = "\n\ +Return the DER encoding of this name\n\ +\n\ +@return: None\n\ +"; + +/* + * Arguments: self - The X509Name object + * Returns: The DER form of an X509Name. + */ +static PyObject * +crypto_X509Name_der(crypto_X509NameObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":der")) { + return NULL; + } + + i2d_X509_NAME(self->x509_name, 0); + return PyBytes_FromStringAndSize(self->x509_name->bytes->data, + self->x509_name->bytes->length); +} + + +static char crypto_X509Name_get_components_doc[] = "\n\ +Returns the split-up components of this name.\n\ +\n\ +@return: List of tuples (name, value).\n\ +"; + +static PyObject * +crypto_X509Name_get_components(crypto_X509NameObj *self, PyObject *args) +{ + int n, i; + X509_NAME *name = self->x509_name; + PyObject *list; + + if (!PyArg_ParseTuple(args, ":get_components")) + return NULL; + + n = X509_NAME_entry_count(name); + list = PyList_New(n); + for (i = 0; i < n; i++) + { + X509_NAME_ENTRY *ent; + ASN1_OBJECT *fname; + ASN1_STRING *fval; + int nid; + int l; + unsigned char *str; + PyObject *tuple; + + ent = X509_NAME_get_entry(name, i); + + fname = X509_NAME_ENTRY_get_object(ent); + fval = X509_NAME_ENTRY_get_data(ent); + + l = ASN1_STRING_length(fval); + str = ASN1_STRING_data(fval); + + nid = OBJ_obj2nid(fname); + + /* printf("fname is %s len=%d str=%s\n", OBJ_nid2sn(nid), l, str); */ + + tuple = PyTuple_New(2); + PyTuple_SetItem(tuple, 0, PyBytes_FromString(OBJ_nid2sn(nid))); + PyTuple_SetItem(tuple, 1, PyBytes_FromStringAndSize((char *)str, l)); + + PyList_SetItem(list, i, tuple); + } + + return list; +} + + +/* + * Call the visitproc on all contained objects. + * + * Arguments: self - The Connection object + * visit - Function to call + * arg - Extra argument to visit + * Returns: 0 if all goes well, otherwise the return code from the first + * call that gave non-zero result. + */ +static int +crypto_X509Name_traverse(crypto_X509NameObj *self, visitproc visit, void *arg) +{ + int ret = 0; + + if (ret == 0 && self->parent_cert != NULL) + ret = visit(self->parent_cert, arg); + return ret; +} + +/* + * Decref all contained objects and zero the pointers. + * + * Arguments: self - The Connection object + * Returns: Always 0. + */ +static int +crypto_X509Name_clear(crypto_X509NameObj *self) +{ + Py_XDECREF(self->parent_cert); + self->parent_cert = NULL; + return 0; +} + +/* + * Deallocate the memory used by the X509Name object + * + * Arguments: self - The X509Name object + * Returns: None + */ +static void +crypto_X509Name_dealloc(crypto_X509NameObj *self) +{ + PyObject_GC_UnTrack(self); + /* Sometimes we don't have to dealloc this */ + if (self->dealloc) + X509_NAME_free(self->x509_name); + + crypto_X509Name_clear(self); + + PyObject_GC_Del(self); +} + +/* + * ADD_METHOD(name) expands to a correct PyMethodDef declaration + * { 'name', (PyCFunction)crypto_X509_name, METH_VARARGS } + * for convenience + */ +#define ADD_METHOD(name) \ + { #name, (PyCFunction)crypto_X509Name_##name, METH_VARARGS, crypto_X509Name_##name##_doc } +static PyMethodDef crypto_X509Name_methods[] = +{ + ADD_METHOD(hash), + ADD_METHOD(der), + ADD_METHOD(get_components), + { NULL, NULL } +}; +#undef ADD_METHOD + +PyTypeObject crypto_X509Name_Type = { + PyOpenSSL_HEAD_INIT(&PyType_Type, 0) + "X509Name", + sizeof(crypto_X509NameObj), + 0, + (destructor)crypto_X509Name_dealloc, + NULL, /* print */ + NULL, /* getattr */ + (setattrfunc)crypto_X509Name_setattr, + NULL, /* reserved */ + (reprfunc)crypto_X509Name_repr, + NULL, /* as_number */ + NULL, /* as_sequence */ + NULL, /* as_mapping */ + NULL, /* hash */ + NULL, /* call */ + NULL, /* str */ + (getattrofunc)crypto_X509Name_getattro, /* getattro */ + NULL, /* setattro */ + NULL, /* as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, /* tp_flags */ + crypto_X509Name_doc, /* tp_doc */ + (traverseproc)crypto_X509Name_traverse, /* tp_traverse */ + (inquiry)crypto_X509Name_clear, /* tp_clear */ + crypto_X509Name_richcompare, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + crypto_X509Name_methods, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + crypto_X509Name_new, /* tp_new */ +}; + +/* + * Initialize the X509Name part of the crypto module + * + * Arguments: module - The crypto module + * Returns: None + */ +int +init_crypto_x509name(PyObject *module) +{ + if (PyType_Ready(&crypto_X509Name_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "X509Name", (PyObject *)&crypto_X509Name_Type) != 0) { + return 0; + } + + if (PyModule_AddObject(module, "X509NameType", (PyObject *)&crypto_X509Name_Type) != 0) { + return 0; + } + + return 1; +} diff --git a/OpenSSL/crypto/x509name.h b/OpenSSL/crypto/x509name.h new file mode 100644 index 0000000..bfc7628 --- /dev/null +++ b/OpenSSL/crypto/x509name.h @@ -0,0 +1,33 @@ +/* + * x509name.h + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * Export X.509 name functions and data structures. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + * + */ +#ifndef PyOpenSSL_crypto_X509NAME_H_ +#define PyOpenSSL_crypto_X509NAME_H_ + +#include +#include + +extern int init_crypto_x509name (PyObject *); + +extern PyTypeObject crypto_X509Name_Type; + +#define crypto_X509Name_Check(v) ((v)->ob_type == &crypto_X509Name_Type) + +typedef struct { + PyObject_HEAD + X509_NAME *x509_name; + int dealloc; + PyObject *parent_cert; +} crypto_X509NameObj; + + +#endif diff --git a/OpenSSL/crypto/x509req.c b/OpenSSL/crypto/x509req.c new file mode 100644 index 0000000..4d7467d --- /dev/null +++ b/OpenSSL/crypto/x509req.c @@ -0,0 +1,425 @@ +/* + * x509req.c + * + * Copyright (C) AB Strakt + * Copyright (C) Jean-Paul Calderone + * See LICENSE for details. + * + * X.509 Request handling, mostly thin wrapping. + * See the file RATIONALE for a short explanation of why this module was written. + */ +#include +#define crypto_MODULE +#include "crypto.h" + + +static char crypto_X509Req_get_subject_doc[] = "\n\ +Create an X509Name object for the subject of the certificate request\n\ +\n\ +@return: An X509Name object\n\ +"; + +static PyObject * +crypto_X509Req_get_subject(crypto_X509ReqObj *self, PyObject *args) +{ + crypto_X509NameObj *crypto_X509Name_New(X509_NAME *, int); + X509_NAME *name; + crypto_X509NameObj* pyname; + + if (!PyArg_ParseTuple(args, ":get_subject")) + return NULL; + + if ((name = X509_REQ_get_subject_name(self->x509_req)) == NULL) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + if ((pyname = crypto_X509Name_New(name, 0)) != NULL) { + pyname->parent_cert = (PyObject *)self; + Py_INCREF(self); + } + return (PyObject *)pyname; +} + +static char crypto_X509Req_get_pubkey_doc[] = "\n\ +Get the public key from the certificate request\n\ +\n\ +@return: The public key\n\ +"; + +static PyObject * +crypto_X509Req_get_pubkey(crypto_X509ReqObj *self, PyObject *args) +{ + crypto_PKeyObj *crypto_PKey_New(EVP_PKEY *, int); + EVP_PKEY *pkey; + crypto_PKeyObj *py_pkey; + + if (!PyArg_ParseTuple(args, ":get_pubkey")) + return NULL; + + if ((pkey = X509_REQ_get_pubkey(self->x509_req)) == NULL) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + py_pkey = crypto_PKey_New(pkey, 1); + if (py_pkey != NULL) { + py_pkey->only_public = 1; + } + return (PyObject *)py_pkey; +} + +static char crypto_X509Req_set_pubkey_doc[] = "\n\ +Set the public key of the certificate request\n\ +\n\ +@param pkey: The public key to use\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509Req_set_pubkey(crypto_X509ReqObj *self, PyObject *args) +{ + crypto_PKeyObj *pkey; + + if (!PyArg_ParseTuple(args, "O!:set_pubkey", &crypto_PKey_Type, &pkey)) + return NULL; + + if (!X509_REQ_set_pubkey(self->x509_req, pkey->pkey)) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_X509Req_sign_doc[] = "\n\ +Sign the certificate request using the supplied key and digest\n\ +\n\ +@param pkey: The key to sign with\n\ +@param digest: The message digest to use\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509Req_sign(crypto_X509ReqObj *self, PyObject *args) +{ + crypto_PKeyObj *pkey; + char *digest_name; + const EVP_MD *digest; + + if (!PyArg_ParseTuple(args, "O!s:sign", &crypto_PKey_Type, &pkey, + &digest_name)) + return NULL; + + if (pkey->only_public) { + PyErr_SetString(PyExc_ValueError, "Key has only public part"); + return NULL; + } + + if (!pkey->initialized) { + PyErr_SetString(PyExc_ValueError, "Key is uninitialized"); + return NULL; + } + + if ((digest = EVP_get_digestbyname(digest_name)) == NULL) + { + PyErr_SetString(PyExc_ValueError, "No such digest method"); + return NULL; + } + + if (!X509_REQ_sign(self->x509_req, pkey->pkey, digest)) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_X509Req_verify_doc[] = "\n\ +Verifies a certificate request using the supplied public key\n\ +\n\ +@param key: a public key\n\ +@return: True if the signature is correct.\n\ +@raise OpenSSL.crypto.Error: If the signature is invalid or there is a\n\ + problem verifying the signature.\n\ +"; + +PyObject * +crypto_X509Req_verify(crypto_X509ReqObj *self, PyObject *args) +{ + PyObject *obj; + crypto_PKeyObj *key; + int answer; + + if (!PyArg_ParseTuple(args, "O!:verify", &crypto_PKey_Type, &obj)) { + return NULL; + } + + key = (crypto_PKeyObj *)obj; + + if ((answer = X509_REQ_verify(self->x509_req, key->pkey)) <= 0) { + exception_from_error_queue(crypto_Error); + return NULL; + } + + return PyLong_FromLong(answer); +} + +static char crypto_X509Req_add_extensions_doc[] = "\n\ +Add extensions to the request.\n\ +\n\ +@param extensions: a sequence of X509Extension objects\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509Req_add_extensions(crypto_X509ReqObj *self, PyObject *args) +{ + PyObject *extensions; + crypto_X509ExtensionObj *ext; + STACK_OF(X509_EXTENSION) *exts; + int nr_of_extensions, i; + + if (!PyArg_ParseTuple(args, "O:add_extensions", &extensions)) + return NULL; + + if (!PySequence_Check(extensions)) + { + PyErr_SetString(PyExc_TypeError, "Expected a sequence"); + return NULL; + } + + /* Make a STACK_OF(X509_EXTENSION) from sequence */ + if ((exts = sk_X509_EXTENSION_new_null()) == NULL) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + /* Put the extensions in a stack */ + nr_of_extensions = PySequence_Length(extensions); + + for (i = 0; i < nr_of_extensions; i++) + { + ext = (crypto_X509ExtensionObj *)PySequence_GetItem(extensions, i); + if (!(crypto_X509Extension_Check(ext))) + { + PyErr_SetString(PyExc_ValueError, + "One of the elements is not an X509Extension"); + sk_X509_EXTENSION_free(exts); + return NULL; + } + sk_X509_EXTENSION_push(exts, ext->x509_extension); + } + + if (!X509_REQ_add_extensions(self->x509_req, exts)) + { + sk_X509_EXTENSION_free(exts); + exception_from_error_queue(crypto_Error); + return NULL; + } + + sk_X509_EXTENSION_free(exts); + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_X509Req_set_version_doc[] = "\n\ +Set the version subfield (RFC 2459, section 4.1.2.1) of the certificate\n\ +request.\n\ +\n\ +@param version: The version number\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509Req_set_version(crypto_X509ReqObj *self, PyObject *args) +{ + long version; + + if (!PyArg_ParseTuple(args, "l:set_version", &version)) { + return NULL; + } + + if (!X509_REQ_set_version(self->x509_req, version)) { + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + +static char crypto_X509Req_get_version_doc[] = "\n\ +Get the version subfield (RFC 2459, section 4.1.2.1) of the certificate\n\ +request.\n\ +\n\ +@return: an integer giving the value of the version subfield\n\ +"; + +static PyObject * +crypto_X509Req_get_version(crypto_X509ReqObj *self, PyObject *args) +{ + long version; + + if (!PyArg_ParseTuple(args, ":get_version")) { + return NULL; + } + + version = X509_REQ_get_version(self->x509_req); + + return PyLong_FromLong(version); +} + +/* + * ADD_METHOD(name) expands to a correct PyMethodDef declaration + * { 'name', (PyCFunction)crypto_X509Req_name, METH_VARARGS } + * for convenience + */ +#define ADD_METHOD(name) \ + { #name, (PyCFunction)crypto_X509Req_##name, METH_VARARGS, crypto_X509Req_##name##_doc } +static PyMethodDef crypto_X509Req_methods[] = +{ + ADD_METHOD(get_subject), + ADD_METHOD(get_pubkey), + ADD_METHOD(set_pubkey), + ADD_METHOD(sign), + ADD_METHOD(verify), + ADD_METHOD(add_extensions), + ADD_METHOD(set_version), + ADD_METHOD(get_version), + { NULL, NULL } +}; +#undef ADD_METHOD + + +/* + * Constructor for X509Req, never called by Python code directly + * + * Arguments: name - A "real" X509_REQ object + * dealloc - Boolean value to specify whether the destructor should + * free the "real" X509_REQ object + * Returns: The newly created X509Req object + */ +crypto_X509ReqObj * +crypto_X509Req_New(X509_REQ *req, int dealloc) +{ + crypto_X509ReqObj *self; + + self = PyObject_New(crypto_X509ReqObj, &crypto_X509Req_Type); + + if (self == NULL) + return NULL; + + self->x509_req = req; + self->dealloc = dealloc; + + return self; +} + + +static char crypto_X509Req_doc[] = "\n\ +X509Req() -> X509Req instance\n\ +\n\ +Create a new X509Req object.\n\ +\n\ +@return: The X509Req object\n\ +"; + +static PyObject * +crypto_X509Req_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) { + if (!PyArg_ParseTuple(args, ":X509Req")) { + return NULL; + } + + return (PyObject *)crypto_X509Req_New(X509_REQ_new(), 1); +} + + +/* + * Deallocate the memory used by the X509Req object + * + * Arguments: self - The X509Req object + * Returns: None + */ +static void +crypto_X509Req_dealloc(crypto_X509ReqObj *self) +{ + /* Sometimes we don't have to dealloc this */ + if (self->dealloc) + X509_REQ_free(self->x509_req); + + PyObject_Del(self); +} + + +PyTypeObject crypto_X509Req_Type = { + PyOpenSSL_HEAD_INIT(&PyType_Type, 0) + "X509Req", + sizeof(crypto_X509ReqObj), + 0, + (destructor)crypto_X509Req_dealloc, + NULL, /* print */ + NULL, /* getattr */ + NULL, /* setattr */ + NULL, /* compare */ + NULL, /* repr */ + NULL, /* as_number */ + NULL, /* as_sequence */ + NULL, /* as_mapping */ + NULL, /* hash */ + NULL, /* call */ + NULL, /* str */ + NULL, /* getattro */ + NULL, /* setattro */ + NULL, /* as_buffer */ + Py_TPFLAGS_DEFAULT, + crypto_X509Req_doc, /* doc */ + NULL, /* traverse */ + NULL, /* clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + crypto_X509Req_methods, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + crypto_X509Req_new, /* tp_new */ +}; + + +/* + * Initialize the X509Req part of the crypto module + * + * Arguments: module - The crypto module + * Returns: None + */ +int +init_crypto_x509req(PyObject *module) +{ + if (PyType_Ready(&crypto_X509Req_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "X509Req", (PyObject *)&crypto_X509Req_Type) != 0) { + return 0; + } + + if (PyModule_AddObject(module, "X509ReqType", (PyObject *)&crypto_X509Req_Type) != 0) { + return 0; + } + + return 1; +} diff --git a/OpenSSL/crypto/x509req.h b/OpenSSL/crypto/x509req.h new file mode 100644 index 0000000..5fe0524 --- /dev/null +++ b/OpenSSL/crypto/x509req.h @@ -0,0 +1,30 @@ +/* + * x509req.h + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * Export X509 request functions and data structures. + * See the file RATIONALE for a short explanation of why this module was written. + * + */ +#ifndef PyOpenSSL_SSL_X509REQ_H_ +#define PyOpenSSL_SSL_X509REQ_H_ + +#include +#include + +extern int init_crypto_x509req (PyObject *); + +extern PyTypeObject crypto_X509Req_Type; + +#define crypto_X509Req_Check(v) ((v)->ob_type == &crypto_X509Req_Type) + +typedef struct { + PyObject_HEAD + X509_REQ *x509_req; + int dealloc; +} crypto_X509ReqObj; + + +#endif diff --git a/OpenSSL/crypto/x509store.c b/OpenSSL/crypto/x509store.c new file mode 100644 index 0000000..c6fa10c --- /dev/null +++ b/OpenSSL/crypto/x509store.c @@ -0,0 +1,146 @@ +/* + * x509store.c + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * X.509 Store handling, mostly thin wrapping. + * See the file RATIONALE for a short explanation of why this module was written. + */ +#include +#define crypto_MODULE +#include "crypto.h" + +static char crypto_X509Store_add_cert_doc[] = "\n\ +Add a certificate\n\ +\n\ +@param cert: The certificate to add\n\ +@return: None\n\ +"; + +static PyObject * +crypto_X509Store_add_cert(crypto_X509StoreObj *self, PyObject *args) +{ + crypto_X509Obj *cert; + + if (!PyArg_ParseTuple(args, "O!:add_cert", &crypto_X509_Type, &cert)) + return NULL; + + if (!X509_STORE_add_cert(self->x509_store, cert->x509)) + { + exception_from_error_queue(crypto_Error); + return NULL; + } + + Py_INCREF(Py_None); + return Py_None; +} + + +/* + * ADD_METHOD(name) expands to a correct PyMethodDef declaration + * { 'name', (PyCFunction)crypto_X509Store_name, METH_VARARGS } + * for convenience + */ +#define ADD_METHOD(name) \ + { #name, (PyCFunction)crypto_X509Store_##name, METH_VARARGS, crypto_X509Store_##name##_doc } +static PyMethodDef crypto_X509Store_methods[] = +{ + ADD_METHOD(add_cert), + { NULL, NULL } +}; +#undef ADD_METHOD + + +/* + * Constructor for X509Store, never called by Python code directly + * + * Arguments: name - A "real" X509_STORE object + * dealloc - Boolean value to specify whether the destructor should + * free the "real" X509_STORE object + * Returns: The newly created X509Store object + */ +crypto_X509StoreObj * +crypto_X509Store_New(X509_STORE *store, int dealloc) +{ + crypto_X509StoreObj *self; + + self = PyObject_New(crypto_X509StoreObj, &crypto_X509Store_Type); + + if (self == NULL) + return NULL; + + self->x509_store = store; + self->dealloc = dealloc; + + return self; +} + +/* + * Deallocate the memory used by the X509Store object + * + * Arguments: self - The X509Store object + * Returns: None + */ +static void +crypto_X509Store_dealloc(crypto_X509StoreObj *self) +{ + /* Sometimes we don't have to dealloc this */ + if (self->dealloc) + X509_STORE_free(self->x509_store); + + PyObject_Del(self); +} + + +PyTypeObject crypto_X509Store_Type = { + PyOpenSSL_HEAD_INIT(&PyType_Type, 0) + "X509Store", + sizeof(crypto_X509StoreObj), + 0, + (destructor)crypto_X509Store_dealloc, + NULL, /* print */ + NULL, /* getattr */ + NULL, /* setattr */ + NULL, /* compare */ + NULL, /* repr */ + NULL, /* as_number */ + NULL, /* as_sequence */ + NULL, /* as_mapping */ + NULL, /* hash */ + NULL, /* call */ + NULL, /* str */ + NULL, /* getattro */ + NULL, /* setattro */ + NULL, /* as_buffer */ + Py_TPFLAGS_DEFAULT, + NULL, /* doc */ + NULL, /* traverse */ + NULL, /* clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + crypto_X509Store_methods, /* tp_methods */ +}; + + +/* + * Initialize the X509Store part of the crypto module + * + * Arguments: module - The crypto module + * Returns: None + */ +int +init_crypto_x509store(PyObject *module) +{ + if (PyType_Ready(&crypto_X509Store_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "X509StoreType", (PyObject *)&crypto_X509Store_Type) != 0) { + return 0; + } + + return 1; +} diff --git a/OpenSSL/crypto/x509store.h b/OpenSSL/crypto/x509store.h new file mode 100644 index 0000000..de3531d --- /dev/null +++ b/OpenSSL/crypto/x509store.h @@ -0,0 +1,30 @@ +/* + * x509store.h + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * Export X509 store functions and data structures. + * See the file RATIONALE for a short explanation of why this module was written. + * + */ +#ifndef PyOpenSSL_SSL_X509STORE_H_ +#define PyOpenSSL_SSL_X509STORE_H_ + +#include +#include + +extern int init_crypto_x509store (PyObject *); + +extern PyTypeObject crypto_X509Store_Type; + +#define crypto_X509Store_Check(v) ((v)->ob_type == &crypto_X509Store_Type) + +typedef struct { + PyObject_HEAD + X509_STORE *x509_store; + int dealloc; +} crypto_X509StoreObj; + + +#endif diff --git a/OpenSSL/py3k.h b/OpenSSL/py3k.h new file mode 100644 index 0000000..29da2f1 --- /dev/null +++ b/OpenSSL/py3k.h @@ -0,0 +1,55 @@ +#ifndef PyOpenSSL_PY3K_H_ +#define PyOpenSSL_PY3K_H_ + +#if (PY_VERSION_HEX >= 0x03000000) + +#define PY3 + +#define PyOpenSSL_MODINIT(name) \ +PyMODINIT_FUNC \ +PyInit_##name(void) + +#define PyText_CheckExact PyUnicode_CheckExact +#define PyText_FromString PyUnicode_FromString +#define PyText_FromStringAndSize PyUnicode_FromStringAndSize + +#define PyOpenSSL_HEAD_INIT(type, size) PyVarObject_HEAD_INIT(NULL, size) + +#define PyOpenSSL_Integer_Check(o) PyLong_Check(o) + +#define PyOpenSSL_MODRETURN(module) { return module; } + +#define BYTESTRING_FMT "y" + +#else /* (PY_VERSION_HEX >= 0x03000000) */ + +#define PyOpenSSL_MODRETURN(module) { return; } + +#define PyOpenSSL_HEAD_INIT(type, size) PyObject_HEAD_INIT(NULL) 0, + +#define PyBytes_FromStringAndSize PyString_FromStringAndSize + +#define PyOpenSSL_Integer_Check(o) (PyInt_Check(o) || PyLong_Check(o)) + +#define PyBytes_Size PyString_Size +#define PyBytes_Check PyString_Check +#define PyBytes_CheckExact PyString_CheckExact +#define PyBytes_AsString PyString_AsString +#define PyBytes_FromString PyString_FromString +#define PyBytes_FromStringAndSize PyString_FromStringAndSize +#define _PyBytes_Resize _PyString_Resize + +#define PyText_CheckExact PyString_CheckExact +#define PyText_FromString PyString_FromString +#define PyText_FromStringAndSize PyString_FromStringAndSize + +#define PyOpenSSL_MODINIT(name) \ +void \ +init##name(void) + +#define BYTESTRING_FMT "s" + +#endif /* (PY_VERSION_HEX >= 0x03000000) */ + +#endif /* PyOpenSSL_PY3K_H_ */ + diff --git a/OpenSSL/pymemcompat.h b/OpenSSL/pymemcompat.h new file mode 100644 index 0000000..24221ec --- /dev/null +++ b/OpenSSL/pymemcompat.h @@ -0,0 +1,86 @@ +/* The idea of this file is that you bundle it with your extension, + #include it, program to Python 2.3's memory API and have your + extension build with any version of Python from 1.5.2 through to + 2.3 (and hopefully beyond). */ + +#ifndef Py_PYMEMCOMPAT_H +#define Py_PYMEMCOMPAT_H + +#include "Python.h" + +/* There are three "families" of memory API: the "raw memory", "object + memory" and "object" families. (This is ignoring the matter of the + cycle collector, about which more is said below). + + Raw Memory: + + PyMem_Malloc, PyMem_Realloc, PyMem_Free + + Object Memory: + + PyObject_Malloc, PyObject_Realloc, PyObject_Free + + Object: + + PyObject_New, PyObject_NewVar, PyObject_Del + + The raw memory and object memory allocators both mimic the + malloc/realloc/free interface from ANSI C, but the object memory + allocator can (and, since 2.3, does by default) use a different + allocation strategy biased towards lots of lots of "small" + allocations. + + The object family is used for allocating Python objects, and the + initializers take care of some basic initialization (setting the + refcount to 1 and filling out the ob_type field) as well as having + a somewhat different interface. + + Do not mix the families! E.g. do not allocate memory with + PyMem_Malloc and free it with PyObject_Free. You may get away with + it quite a lot of the time, but there *are* scenarios where this + will break. You Have Been Warned. + + Also, in many versions of Python there are an insane amount of + memory interfaces to choose from. Use the ones described above. */ + +#if PY_VERSION_HEX < 0x01060000 +/* raw memory interface already present */ + +/* there is no object memory interface in 1.5.2 */ +#define PyObject_Malloc PyMem_Malloc +#define PyObject_Realloc PyMem_Realloc +#define PyObject_Free PyMem_Free + +/* the object interface is there, but the names have changed */ +#define PyObject_New PyObject_NEW +#define PyObject_NewVar PyObject_NEW_VAR +#define PyObject_Del PyMem_Free +#endif + +/* If your object is a container you probably want to support the + cycle collector, which was new in Python 2.0. + + Unfortunately, the interface to the collector that was present in + Python 2.0 and 2.1 proved to be tricky to use, and so changed in + 2.2 -- in a way that can't easily be papered over with macros. + + This file contains macros that let you program to the 2.2 GC API. + Your module will compile against any Python since version 1.5.2, + but the type will only participate in the GC in versions 2.2 and + up. Some work is still necessary on your part to only fill out the + tp_traverse and tp_clear fields when they exist and set tp_flags + appropriately. + + It is possible to support both the 2.0 and 2.2 GC APIs, but it's + not pretty and this comment block is too narrow to contain a + desciption of what's required... */ + +#if PY_VERSION_HEX < 0x020200B1 +#define PyObject_GC_New PyObject_New +#define PyObject_GC_NewVar PyObject_NewVar +#define PyObject_GC_Del PyObject_Del +#define PyObject_GC_Track(op) +#define PyObject_GC_UnTrack(op) +#endif + +#endif /* !Py_PYMEMCOMPAT_H */ diff --git a/OpenSSL/rand/rand.c b/OpenSSL/rand/rand.c new file mode 100644 index 0000000..bce5e89 --- /dev/null +++ b/OpenSSL/rand/rand.c @@ -0,0 +1,303 @@ +/* + * rand.c + * + * Copyright (C) AB Strakt + * See LICENSE file for details. + * + * PRNG management routines, thin wrappers. + * See the file RATIONALE for a short explanation of why this module was written. + * + */ +#include + +/* + * In order to get the RAND_screen definition from the rand.h + * WIN32 or WINDOWS needs to be defined, otherwise we get a + * warning. + */ +#ifdef MS_WINDOWS +# ifndef WIN32 +# define WIN32 +# endif +#endif +#include +#include "../util.h" + +PyObject *rand_Error; + +static char rand_doc[] = "\n\ +PRNG management routines, thin wrappers.\n\ +See the file RATIONALE for a short explanation of why this module was written.\n\ +"; + +static char rand_add_doc[] = "\n\ +Add data with a given entropy to the PRNG\n\ +\n\ +@param buffer: Buffer with random data\n\ +@param entropy: The entropy (in bytes) measurement of the buffer\n\ +@return: None\n\ +"; + +static PyObject * +rand_add(PyObject *spam, PyObject *args) +{ + char *buf; + int size; + double entropy; + + if (!PyArg_ParseTuple(args, BYTESTRING_FMT "#d:add", &buf, &size, &entropy)) + return NULL; + + RAND_add(buf, size, entropy); + + Py_INCREF(Py_None); + return Py_None; +} + +static char rand_seed_doc[] = "\n\ +Alias for rand_add, with entropy equal to length\n\ +\n\ +@param buffer: Buffer with random data\n\ +@return: None\n\ +"; + +static PyObject * +rand_seed(PyObject *spam, PyObject *args) +{ + char *buf; + int size; + + if (!PyArg_ParseTuple(args, BYTESTRING_FMT "#:seed", &buf, &size)) + return NULL; + + RAND_seed(buf, size); + + Py_INCREF(Py_None); + return Py_None; +} + +static char rand_status_doc[] = "\n\ +Retrieve the status of the PRNG\n\ +\n\ +@return: True if the PRNG is seeded enough, false otherwise\n\ +"; + +static PyObject * +rand_status(PyObject *spam, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":status")) + return NULL; + + return PyLong_FromLong((long)RAND_status()); +} + +#ifdef MS_WINDOWS +static char rand_screen_doc[] = "\n\ +Add the current contents of the screen to the PRNG state. Availability:\n\ +Windows.\n\ +\n\ +@return: None\n\ +"; + +static PyObject * +rand_screen(PyObject *spam, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":screen")) + return NULL; + + RAND_screen(); + Py_INCREF(Py_None); + return Py_None; +} +#endif + +static char rand_egd_doc[] = "\n\ +Query an entropy gathering daemon (EGD) for random data and add it to the\n\ +PRNG. I haven't found any problems when the socket is missing, the function\n\ +just returns 0.\n\ +\n\ +@param path: The path to the EGD socket\n\ +@param bytes: (optional) The number of bytes to read, default is 255\n\ +@returns: The number of bytes read (NB: a value of 0 isn't necessarily an\n\ + error, check rand.status())\n\ +"; + +static PyObject * +rand_egd(PyObject *spam, PyObject *args) +{ + char *path; + int bytes = 255; + + if (!PyArg_ParseTuple(args, "s|i:egd", &path, &bytes)) + return NULL; + + return PyLong_FromLong((long)RAND_egd_bytes(path, bytes)); +} + +static char rand_cleanup_doc[] = "\n\ +Erase the memory used by the PRNG.\n\ +\n\ +@return: None\n\ +"; + +static PyObject * +rand_cleanup(PyObject *spam, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":cleanup")) + return NULL; + + RAND_cleanup(); + + Py_INCREF(Py_None); + return Py_None; +} + +static char rand_load_file_doc[] = "\n\ +Seed the PRNG with data from a file\n\ +\n\ +@param filename: The file to read data from\n\ +@param maxbytes: (optional) The number of bytes to read, default is\n\ + to read the entire file\n\ +@return: The number of bytes read\n\ +"; + +static PyObject * +rand_load_file(PyObject *spam, PyObject *args) +{ + char *filename; + int maxbytes = -1; + + if (!PyArg_ParseTuple(args, "s|i:load_file", &filename, &maxbytes)) + return NULL; + + return PyLong_FromLong((long)RAND_load_file(filename, maxbytes)); +} + +static char rand_write_file_doc[] = "\n\ +Save PRNG state to a file\n\ +\n\ +@param filename: The file to write data to\n\ +@return: The number of bytes written\n\ +"; + +static PyObject * +rand_write_file(PyObject *spam, PyObject *args) +{ + char *filename; + + if (!PyArg_ParseTuple(args, "s:write_file", &filename)) + return NULL; + + return PyLong_FromLong((long)RAND_write_file(filename)); +} + +static char rand_bytes_doc[] = "\n\ +Get some randomm bytes as a string.\n\ +\n\ +@param num_bytes: The number of bytes to fetch\n\ +@return: A string of random bytes\n\ +"; + +#if PY_VERSION_HEX < 0x02050000 +#define Py_ssize_t int +#define PY_SSIZE_FMT "i" +#else +#define PY_SSIZE_FMT "n" +#endif + +static PyObject * +rand_bytes(PyObject *spam, PyObject *args, PyObject *keywds) { + Py_ssize_t num_bytes; + static char *kwlist[] = {"num_bytes", NULL}; + char *buf; + unsigned int rc; + PyObject *obj = NULL; + + if (!PyArg_ParseTupleAndKeywords( + args, keywds, PY_SSIZE_FMT ":bytes", kwlist, &num_bytes)) { + return NULL; + } + + if(num_bytes < 0) { + PyErr_SetString(PyExc_ValueError, "num_bytes must not be negative"); + return NULL; + } + buf = malloc(num_bytes); + if (buf == NULL) /* out of memory */ + return NULL; + rc = RAND_bytes((unsigned char *) buf, num_bytes); + if(rc != 1) { /* if unsuccessful */ + exception_from_error_queue(rand_Error); + goto done; + } + obj = PyBytes_FromStringAndSize(buf, (unsigned) num_bytes); + done: + free(buf); + return obj; +} + + +/* Methods in the OpenSSL.rand module */ +static PyMethodDef rand_methods[] = { + { "add", (PyCFunction)rand_add, METH_VARARGS, rand_add_doc }, + { "seed", (PyCFunction)rand_seed, METH_VARARGS, rand_seed_doc }, + { "status", (PyCFunction)rand_status, METH_VARARGS, rand_status_doc }, +#ifdef MS_WINDOWS + { "screen", (PyCFunction)rand_screen, METH_VARARGS, rand_screen_doc }, +#endif + { "egd", (PyCFunction)rand_egd, METH_VARARGS, rand_egd_doc }, + { "cleanup", (PyCFunction)rand_cleanup, METH_VARARGS, rand_cleanup_doc }, + { "load_file", (PyCFunction)rand_load_file, METH_VARARGS, rand_load_file_doc }, + { "write_file",(PyCFunction)rand_write_file, METH_VARARGS, rand_write_file_doc }, + { "bytes", (PyCFunction)rand_bytes, METH_VARARGS|METH_KEYWORDS, rand_bytes_doc }, + { NULL, NULL } +}; + + +#ifdef PY3 +static struct PyModuleDef randmodule = { + PyModuleDef_HEAD_INIT, + "rand", + rand_doc, + -1, + rand_methods +}; +#endif + +/* + * Initialize the rand sub module + * + * Arguments: None + * Returns: None + */ +PyOpenSSL_MODINIT(rand) { + PyObject *module; + +#ifdef PY3 + module = PyModule_Create(&randmodule); +#else + module = Py_InitModule3("rand", rand_methods, rand_doc); +#endif + if (module == NULL) { + PyOpenSSL_MODRETURN(NULL); + } + + rand_Error = PyErr_NewException("OpenSSL.rand.Error", NULL, NULL); + + if (rand_Error == NULL) { + goto error; + } + + if (PyModule_AddObject(module, "Error", rand_Error) != 0) { + goto error; + } + + ERR_load_RAND_strings(); + + PyOpenSSL_MODRETURN(module); + +error: + PyOpenSSL_MODRETURN(NULL); + ; +} + diff --git a/OpenSSL/ssl/connection.c b/OpenSSL/ssl/connection.c new file mode 100755 index 0000000..bef8c2f --- /dev/null +++ b/OpenSSL/ssl/connection.c @@ -0,0 +1,1445 @@ +/* + * connection.c + * + * Copyright (C) AB Strakt + * Copyright (C) Jean-Paul Calderone + * See LICENSE for details. + * + * SSL Connection objects and methods. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + */ +#include + +#ifndef MS_WINDOWS +# include +# include +# if !(defined(__BEOS__) || defined(__CYGWIN__)) +# include +# endif +#else +# include +# include +#endif + +#define SSL_MODULE +#include +#include +#include "ssl.h" + +/** + * If we are on UNIX, fine, just use PyErr_SetFromErrno. If we are on Windows, + * apply some black winsock voodoo. This is basically just copied from Python's + * socketmodule.c + * + * Arguments: None + * Returns: None + */ +static void +syscall_from_errno(void) +{ +#ifdef MS_WINDOWS + int errnum = WSAGetLastError(); + if (errnum) + { + static struct { int num; const char *msg; } *msgp, msgs[] = { + { WSAEINTR, "Interrupted system call" }, + { WSAEBADF, "Bad file descriptor" }, + { WSAEACCES, "Permission denied" }, + { WSAEFAULT, "Bad address" }, + { WSAEINVAL, "Invalid argument" }, + { WSAEMFILE, "Too many open files" }, + { WSAEWOULDBLOCK, "The socket operation could not complete " + "without blocking" }, + { WSAEINPROGRESS, "Operation now in progress" }, + { WSAEALREADY, "Operation already in progress" }, + { WSAENOTSOCK, "Socket operation on non-socket" }, + { WSAEDESTADDRREQ, "Destination address required" }, + { WSAEMSGSIZE, "Message too long" }, + { WSAEPROTOTYPE, "Protocol wrong type for socket" }, + { WSAENOPROTOOPT, "Protocol not available" }, + { WSAEPROTONOSUPPORT, "Protocol not supported" }, + { WSAESOCKTNOSUPPORT, "Socket type not supported" }, + { WSAEOPNOTSUPP, "Operation not supported" }, + { WSAEPFNOSUPPORT, "Protocol family not supported" }, + { WSAEAFNOSUPPORT, "Address family not supported" }, + { WSAEADDRINUSE, "Address already in use" }, + { WSAEADDRNOTAVAIL, "Can't assign requested address" }, + { WSAENETDOWN, "Network is down" }, + { WSAENETUNREACH, "Network is unreachable" }, + { WSAENETRESET, "Network dropped connection on reset" }, + { WSAECONNABORTED, "Software caused connection abort" }, + { WSAECONNRESET, "Connection reset by peer" }, + { WSAENOBUFS, "No buffer space available" }, + { WSAEISCONN, "Socket is already connected" }, + { WSAENOTCONN, "Socket is not connected" }, + { WSAESHUTDOWN, "Can't send after socket shutdown" }, + { WSAETOOMANYREFS, "Too many references: can't splice" }, + { WSAETIMEDOUT, "Operation timed out" }, + { WSAECONNREFUSED, "Connection refused" }, + { WSAELOOP, "Too many levels of symbolic links" }, + { WSAENAMETOOLONG, "File name too long" }, + { WSAEHOSTDOWN, "Host is down" }, + { WSAEHOSTUNREACH, "No route to host" }, + { WSAENOTEMPTY, "Directory not empty" }, + { WSAEPROCLIM, "Too many processes" }, + { WSAEUSERS, "Too many users" }, + { WSAEDQUOT, "Disc quota exceeded" }, + { WSAESTALE, "Stale NFS file handle" }, + { WSAEREMOTE, "Too many levels of remote in path" }, + { WSASYSNOTREADY, "Network subsystem is unvailable" }, + { WSAVERNOTSUPPORTED, "WinSock version is not supported" }, + { WSANOTINITIALISED, "Successful WSAStartup() not yet performed" }, + { WSAEDISCON, "Graceful shutdown in progress" }, + /* Resolver errors */ + { WSAHOST_NOT_FOUND, "No such host is known" }, + { WSATRY_AGAIN, "Host not found, or server failed" }, + { WSANO_RECOVERY, "Unexpected server error encountered" }, + { WSANO_DATA, "Valid name without requested data" }, + { WSANO_ADDRESS, "No address, look for MX record" }, + { 0, NULL } + }; + PyObject *v; + const char *msg = "winsock error"; + + for (msgp = msgs; msgp->msg; msgp++) + { + if (errnum == msgp->num) + { + msg = msgp->msg; + break; + } + } + + v = Py_BuildValue("(is)", errnum, msg); + if (v != NULL) + { + PyErr_SetObject(ssl_SysCallError, v); + Py_DECREF(v); + } + return; + } +#else + PyErr_SetFromErrno(ssl_SysCallError); +#endif +} + +/* + * Handle errors raised by BIO functions. + * + * Arguments: bio - The BIO object + * ret - The return value of the BIO_ function. + * Returns: None, the calling function should return NULL; + */ +static void +handle_bio_errors(BIO* bio, int ret) +{ + if (BIO_should_retry(bio)) { + if (BIO_should_read(bio)) { + PyErr_SetNone(ssl_WantReadError); + } else if (BIO_should_write(bio)) { + PyErr_SetNone(ssl_WantWriteError); + } else if (BIO_should_io_special(bio)) { + /* + * It's somewhat unclear what this means. From the OpenSSL source, + * it seems like it should not be triggered by the memory BIO, so + * for the time being, this case shouldn't come up. The SSL BIO + * (which I think should be named the socket BIO) may trigger this + * case if its socket is not yet connected or it is busy doing + * something related to x509. + */ + PyErr_SetString(PyExc_ValueError, "BIO_should_io_special"); + } else { + /* + * I hope this is dead code. The BIO documentation suggests that + * one of the above three checks should always be true. + */ + PyErr_SetString(PyExc_ValueError, "unknown bio failure"); + } + } else { + /* + * If we aren't to retry, it's really an error, so fall back to the + * normal error reporting code. However, the BIO interface does not + * specify a uniform error reporting mechanism. We can only hope that + * the code which triggered the error also kindly pushed something onto + * the error stack. + */ + exception_from_error_queue(ssl_Error); + } +} + +/* + * Handle errors raised by SSL I/O functions. NOTE: Not SSL_shutdown ;) + * + * Arguments: ssl - The SSL object + * err - The return code from SSL_get_error + * ret - The return code from the SSL I/O function + * Returns: None, the calling function should return NULL + */ +static void +handle_ssl_errors(SSL *ssl, int err, int ret) +{ + switch (err) + { + /* + * Strange as it may seem, ZeroReturn is not an error per se. It means + * that the SSL Connection has been closed correctly (note, not the + * transport layer!), i.e. closure alerts have been exchanged. This is + * an exception since + * + There's an SSL "error" code for it + * + You have to deal with it in any case, close the transport layer + * etc + */ + case SSL_ERROR_ZERO_RETURN: + PyErr_SetNone(ssl_ZeroReturnError); + break; + + /* + * The WantXYZ exceptions don't mean that there's an error, just that + * nothing could be read/written just now, maybe because the transport + * layer would block on the operation, or that there's not enough data + * available to fill an entire SSL record. + */ + case SSL_ERROR_WANT_READ: + PyErr_SetNone(ssl_WantReadError); + break; + + case SSL_ERROR_WANT_WRITE: + PyErr_SetNone(ssl_WantWriteError); + break; + + case SSL_ERROR_WANT_X509_LOOKUP: + PyErr_SetNone(ssl_WantX509LookupError); + break; + + case SSL_ERROR_SYSCALL: + if (ERR_peek_error() == 0) + { + if (ret < 0) + { + syscall_from_errno(); + } + else + { + PyObject *v; + + v = Py_BuildValue("(is)", -1, "Unexpected EOF"); + if (v != NULL) + { + PyErr_SetObject(ssl_SysCallError, v); + Py_DECREF(v); + } + } + break; + } + + /* NOTE: Fall-through here, we don't want to duplicate code, right? */ + + case SSL_ERROR_SSL: + ; + default: + exception_from_error_queue(ssl_Error); + break; + } +} + +/* + * Here be member methods of the Connection "class" + */ + +static char ssl_Connection_get_context_doc[] = "\n\ +Get session context\n\ +\n\ +@return: A Context object\n\ +"; +static PyObject * +ssl_Connection_get_context(ssl_ConnectionObj *self, PyObject *args) { + if (!PyArg_ParseTuple(args, ":get_context")) { + return NULL; + } + + Py_INCREF(self->context); + return (PyObject *)self->context; +} + +static char ssl_Connection_pending_doc[] = "\n\ +Get the number of bytes that can be safely read from the connection\n\ +\n\ +@return: The number of bytes available in the receive buffer.\n\ +"; +static PyObject * +ssl_Connection_pending(ssl_ConnectionObj *self, PyObject *args) { + int ret; + + if (!PyArg_ParseTuple(args, ":pending")) { + return NULL; + } + + ret = SSL_pending(self->ssl); + return PyLong_FromLong((long)ret); +} + +static char ssl_Connection_bio_write_doc[] = "\n\ +When using non-socket connections this function sends\n\ +\"dirty\" data that would have traveled in on the network.\n\ +\n\ +@param buf: The string to put into the memory BIO.\n\ +@return: The number of bytes written\n\ +"; +static PyObject * +ssl_Connection_bio_write(ssl_ConnectionObj *self, PyObject *args) +{ + char *buf; + int len, ret; + + if (self->into_ssl == NULL) + { + PyErr_SetString(PyExc_TypeError, "Connection sock was not None"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s#|i:bio_write", &buf, &len)) + return NULL; + + ret = BIO_write(self->into_ssl, buf, len); + + if (PyErr_Occurred()) + { + flush_error_queue(); + return NULL; + } + + if (ret <= 0) { + /* + * There was a problem with the BIO_write of some sort. + */ + handle_bio_errors(self->into_ssl, ret); + return NULL; + } + + return PyLong_FromLong((long)ret); +} + +static char ssl_Connection_send_doc[] = "\n\ +Send data on the connection. NOTE: If you get one of the WantRead,\n\ +WantWrite or WantX509Lookup exceptions on this, you have to call the\n\ +method again with the SAME buffer.\n\ +\n\ +@param buf: The string to send\n\ +@param flags: (optional) Included for compatibility with the socket\n\ + API, the value is ignored\n\ +@return: The number of bytes written\n\ +"; +static PyObject * +ssl_Connection_send(ssl_ConnectionObj *self, PyObject *args) { + int len, ret, err, flags; + char *buf; + +#if PY_VERSION_HEX >= 0x02060000 + Py_buffer pbuf; + + if (!PyArg_ParseTuple(args, "s*|i:send", &pbuf, &flags)) + return NULL; + + buf = pbuf.buf; + len = pbuf.len; +#else + + if (!PyArg_ParseTuple(args, "s#|i:send", &buf, &len, &flags)) + return NULL; +#endif + + MY_BEGIN_ALLOW_THREADS(self->tstate) + ret = SSL_write(self->ssl, buf, len); + MY_END_ALLOW_THREADS(self->tstate) + +#if PY_VERSION_HEX >= 0x02060000 + PyBuffer_Release(&pbuf); +#endif + + if (PyErr_Occurred()) + { + flush_error_queue(); + return NULL; + } + + err = SSL_get_error(self->ssl, ret); + if (err == SSL_ERROR_NONE) + { + return PyLong_FromLong((long)ret); + } + else + { + handle_ssl_errors(self->ssl, err, ret); + return NULL; + } +} + +static char ssl_Connection_sendall_doc[] = "\n\ +Send \"all\" data on the connection. This calls send() repeatedly until\n\ +all data is sent. If an error occurs, it's impossible to tell how much data\n\ +has been sent.\n\ +\n\ +@param buf: The string to send\n\ +@param flags: (optional) Included for compatibility with the socket\n\ + API, the value is ignored\n\ +@return: The number of bytes written\n\ +"; +static PyObject * +ssl_Connection_sendall(ssl_ConnectionObj *self, PyObject *args) +{ + char *buf; + int len, ret, err, flags; + PyObject *pyret = Py_None; + +#if PY_VERSION_HEX >= 0x02060000 + Py_buffer pbuf; + + if (!PyArg_ParseTuple(args, "s*|i:sendall", &pbuf, &flags)) + return NULL; + + buf = pbuf.buf; + len = pbuf.len; +#else + if (!PyArg_ParseTuple(args, "s#|i:sendall", &buf, &len, &flags)) + return NULL; +#endif + + do { + MY_BEGIN_ALLOW_THREADS(self->tstate) + ret = SSL_write(self->ssl, buf, len); + MY_END_ALLOW_THREADS(self->tstate) + if (PyErr_Occurred()) + { + flush_error_queue(); + pyret = NULL; + break; + } + err = SSL_get_error(self->ssl, ret); + if (err == SSL_ERROR_NONE) + { + buf += ret; + len -= ret; + } + else if (err == SSL_ERROR_SSL || err == SSL_ERROR_SYSCALL || + err == SSL_ERROR_ZERO_RETURN) + { + handle_ssl_errors(self->ssl, err, ret); + pyret = NULL; + break; + } + } while (len > 0); + +#if PY_VERSION_HEX >= 0x02060000 + PyBuffer_Release(&pbuf); +#endif + + Py_XINCREF(pyret); + return pyret; +} + +static char ssl_Connection_recv_doc[] = "\n\ +Receive data on the connection. NOTE: If you get one of the WantRead,\n\ +WantWrite or WantX509Lookup exceptions on this, you have to call the\n\ +method again with the SAME buffer.\n\ +\n\ +@param bufsiz: The maximum number of bytes to read\n\ +@param flags: (optional) Included for compatibility with the socket\n\ + API, the value is ignored\n\ +@return: The string read from the Connection\n\ +"; +static PyObject * +ssl_Connection_recv(ssl_ConnectionObj *self, PyObject *args) +{ + int bufsiz, ret, err, flags; + PyObject *buf; + + if (!PyArg_ParseTuple(args, "i|i:recv", &bufsiz, &flags)) + return NULL; + + buf = PyBytes_FromStringAndSize(NULL, bufsiz); + if (buf == NULL) + return NULL; + + MY_BEGIN_ALLOW_THREADS(self->tstate) + ret = SSL_read(self->ssl, PyBytes_AsString(buf), bufsiz); + MY_END_ALLOW_THREADS(self->tstate) + + if (PyErr_Occurred()) + { + Py_DECREF(buf); + flush_error_queue(); + return NULL; + } + + err = SSL_get_error(self->ssl, ret); + if (err == SSL_ERROR_NONE) + { + if (ret != bufsiz && _PyBytes_Resize(&buf, ret) < 0) + return NULL; + return buf; + } + else + { + handle_ssl_errors(self->ssl, err, ret); + Py_DECREF(buf); + return NULL; + } +} + +static char ssl_Connection_bio_read_doc[] = "\n\ +When using non-socket connections this function reads\n\ +the \"dirty\" data that would have traveled away on the network.\n\ +\n\ +@param bufsiz: The maximum number of bytes to read\n\ +@return: The string read.\n\ +"; +static PyObject * +ssl_Connection_bio_read(ssl_ConnectionObj *self, PyObject *args) +{ + int bufsiz, ret; + PyObject *buf; + + if (self->from_ssl == NULL) + { + PyErr_SetString(PyExc_TypeError, "Connection sock was not None"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "i:bio_read", &bufsiz)) + return NULL; + + buf = PyBytes_FromStringAndSize(NULL, bufsiz); + if (buf == NULL) + return NULL; + + ret = BIO_read(self->from_ssl, PyBytes_AsString(buf), bufsiz); + + if (PyErr_Occurred()) + { + Py_DECREF(buf); + flush_error_queue(); + return NULL; + } + + if (ret <= 0) { + /* + * There was a problem with the BIO_read of some sort. + */ + handle_bio_errors(self->from_ssl, ret); + Py_DECREF(buf); + return NULL; + } + + /* + * Shrink the string to match the number of bytes we actually read. + */ + if (ret != bufsiz && _PyBytes_Resize(&buf, ret) < 0) + { + Py_DECREF(buf); + return NULL; + } + return buf; +} + +static char ssl_Connection_renegotiate_doc[] = "\n\ +Renegotiate the session\n\ +\n\ +@return: True if the renegotiation can be started, false otherwise\n\ +"; +static PyObject * +ssl_Connection_renegotiate(ssl_ConnectionObj *self, PyObject *args) { + int ret; + + if (!PyArg_ParseTuple(args, ":renegotiate")) { + return NULL; + } + + MY_BEGIN_ALLOW_THREADS(self->tstate); + ret = SSL_renegotiate(self->ssl); + MY_END_ALLOW_THREADS(self->tstate); + + if (PyErr_Occurred()) { + flush_error_queue(); + return NULL; + } + + return PyLong_FromLong((long)ret); +} + +static char ssl_Connection_do_handshake_doc[] = "\n\ +Perform an SSL handshake (usually called after renegotiate() or one of\n\ +set_*_state()). This can raise the same exceptions as send and recv.\n\ +\n\ +@return: None.\n\ +"; +static PyObject * +ssl_Connection_do_handshake(ssl_ConnectionObj *self, PyObject *args) +{ + int ret, err; + + if (!PyArg_ParseTuple(args, ":do_handshake")) + return NULL; + + MY_BEGIN_ALLOW_THREADS(self->tstate); + ret = SSL_do_handshake(self->ssl); + MY_END_ALLOW_THREADS(self->tstate); + + if (PyErr_Occurred()) + { + flush_error_queue(); + return NULL; + } + + err = SSL_get_error(self->ssl, ret); + if (err == SSL_ERROR_NONE) + { + Py_INCREF(Py_None); + return Py_None; + } + else + { + handle_ssl_errors(self->ssl, err, ret); + return NULL; + } +} + +#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x00907000L +static char ssl_Connection_renegotiate_pending_doc[] = "\n\ +Check if there's a renegotiation in progress, it will return false once\n\ +a renegotiation is finished.\n\ +\n\ +@return: Whether there's a renegotiation in progress\n\ +"; +static PyObject * +ssl_Connection_renegotiate_pending(ssl_ConnectionObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":renegotiate_pending")) + return NULL; + + return PyLong_FromLong((long)SSL_renegotiate_pending(self->ssl)); +} +#endif + +static char ssl_Connection_total_renegotiations_doc[] = "\n\ +Find out the total number of renegotiations.\n\ +\n\ +@return: The number of renegotiations.\n\ +"; +static PyObject * +ssl_Connection_total_renegotiations(ssl_ConnectionObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":total_renegotiations")) + return NULL; + + return PyLong_FromLong(SSL_total_renegotiations(self->ssl)); +} + +static char ssl_Connection_set_accept_state_doc[] = "\n\ +Set the connection to work in server mode. The handshake will be handled\n\ +automatically by read/write.\n\ +\n\ +@return: None\n\ +"; +static PyObject * +ssl_Connection_set_accept_state(ssl_ConnectionObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":set_accept_state")) + return NULL; + + SSL_set_accept_state(self->ssl); + + Py_INCREF(Py_None); + return Py_None; +} + +static char ssl_Connection_set_connect_state_doc[] = "\n\ +Set the connection to work in client mode. The handshake will be handled\n\ +automatically by read/write.\n\ +\n\ +@return: None\n\ +"; +static PyObject * +ssl_Connection_set_connect_state(ssl_ConnectionObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":set_connect_state")) + return NULL; + + SSL_set_connect_state(self->ssl); + + Py_INCREF(Py_None); + return Py_None; +} + +static char ssl_Connection_connect_doc[] = "\n\ +Connect to remote host and set up client-side SSL\n\ +\n\ +@param addr: A remote address\n\ +@return: What the socket's connect method returns\n\ +"; +static PyObject * +ssl_Connection_connect(ssl_ConnectionObj *self, PyObject *args) +{ + PyObject *meth, *ret; + + if ((meth = PyObject_GetAttrString(self->socket, "connect")) == NULL) + return NULL; + + SSL_set_connect_state(self->ssl); + + ret = PyEval_CallObject(meth, args); + Py_DECREF(meth); + if (ret == NULL) + return NULL; + + return ret; +} + +static char ssl_Connection_connect_ex_doc[] = "\n\ +Connect to remote host and set up client-side SSL. Note that if the socket's\n\ +connect_ex method doesn't return 0, SSL won't be initialized.\n\ +\n\ +@param addr: A remove address\n\ +@return: What the socket's connect_ex method returns\n\ +"; +static PyObject * +ssl_Connection_connect_ex(ssl_ConnectionObj *self, PyObject *args) +{ + PyObject *meth, *ret; + + if ((meth = PyObject_GetAttrString(self->socket, "connect_ex")) == NULL) + return NULL; + + SSL_set_connect_state(self->ssl); + + ret = PyEval_CallObject(meth, args); + Py_DECREF(meth); + return ret; +} + +static char ssl_Connection_accept_doc[] = "\n\ +Accept incoming connection and set up SSL on it\n\ +\n\ +@return: A (conn,addr) pair where conn is a Connection and addr is an\n\ + address\n\ +"; +static PyObject * +ssl_Connection_accept(ssl_ConnectionObj *self, PyObject *args) +{ + PyObject *tuple, *socket, *address, *meth; + ssl_ConnectionObj *conn; + + if ((meth = PyObject_GetAttrString(self->socket, "accept")) == NULL) + return NULL; + tuple = PyEval_CallObject(meth, args); + Py_DECREF(meth); + if (tuple == NULL) + return NULL; + + socket = PyTuple_GetItem(tuple, 0); + Py_INCREF(socket); + address = PyTuple_GetItem(tuple, 1); + Py_INCREF(address); + Py_DECREF(tuple); + + conn = ssl_Connection_New(self->context, socket); + Py_DECREF(socket); + if (conn == NULL) + { + Py_DECREF(address); + return NULL; + } + + SSL_set_accept_state(conn->ssl); + + tuple = Py_BuildValue("(OO)", conn, address); + + Py_DECREF(conn); + Py_DECREF(address); + + return tuple; +} + +static char ssl_Connection_bio_shutdown_doc[] = "\n\ +When using non-socket connections this function signals end of\n\ +data on the input for this connection.\n\ +\n\ +@return: None\n\ +"; + +static PyObject * +ssl_Connection_bio_shutdown(ssl_ConnectionObj *self, PyObject *args) +{ + if (self->from_ssl == NULL) + { + PyErr_SetString(PyExc_TypeError, "Connection sock was not None"); + return NULL; + } + + BIO_set_mem_eof_return(self->into_ssl, 0); + Py_INCREF(Py_None); + return Py_None; +} + + + +static char ssl_Connection_shutdown_doc[] = "\n\ +Send closure alert\n\ +\n\ +@return: True if the shutdown completed successfully (i.e. both sides\n\ + have sent closure alerts), false otherwise (i.e. you have to\n\ + wait for a ZeroReturnError on a recv() method call\n\ +"; +static PyObject * +ssl_Connection_shutdown(ssl_ConnectionObj *self, PyObject *args) +{ + int ret; + + if (!PyArg_ParseTuple(args, ":shutdown")) + return NULL; + + MY_BEGIN_ALLOW_THREADS(self->tstate) + ret = SSL_shutdown(self->ssl); + MY_END_ALLOW_THREADS(self->tstate) + + if (PyErr_Occurred()) + { + flush_error_queue(); + return NULL; + } + + if (ret < 0) + { + exception_from_error_queue(ssl_Error); + return NULL; + } + else if (ret > 0) + { + Py_INCREF(Py_True); + return Py_True; + } + else + { + Py_INCREF(Py_False); + return Py_False; + } +} + +static char ssl_Connection_get_cipher_list_doc[] = "\n\ +Get the session cipher list\n\ +\n\ +@return: A list of cipher strings\n\ +"; +static PyObject * +ssl_Connection_get_cipher_list(ssl_ConnectionObj *self, PyObject *args) +{ + int idx = 0; + const char *ret; + PyObject *lst, *item; + + if (!PyArg_ParseTuple(args, ":get_cipher_list")) + return NULL; + + lst = PyList_New(0); + while ((ret = SSL_get_cipher_list(self->ssl, idx)) != NULL) + { + item = PyText_FromString(ret); + PyList_Append(lst, item); + Py_DECREF(item); + idx++; + } + return lst; +} + +static char ssl_Connection_get_client_ca_list_doc[] = "\n\ +Get CAs whose certificates are suggested for client authentication.\n\ +\n\ +@return: If this is a server connection, a list of X509Names representing\n\ + the acceptable CAs as set by L{OpenSSL.SSL.Context.set_client_ca_list} or\n\ + L{OpenSSL.SSL.Context.add_client_ca}. If this is a client connection,\n\ + the list of such X509Names sent by the server, or an empty list if that\n\ + has not yet happened.\n\ +"; + +static PyObject * +ssl_Connection_get_client_ca_list(ssl_ConnectionObj *self, PyObject *args) { + STACK_OF(X509_NAME) *CANames; + PyObject *CAList; + int i, n; + + if (!PyArg_ParseTuple(args, ":get_client_ca_list")) { + return NULL; + } + CANames = SSL_get_client_CA_list(self->ssl); + if (CANames == NULL) { + return PyList_New(0); + } + n = sk_X509_NAME_num(CANames); + CAList = PyList_New(n); + if (CAList == NULL) { + return NULL; + } + for (i = 0; i < n; i++) { + X509_NAME *CAName; + PyObject *CA; + + CAName = X509_NAME_dup(sk_X509_NAME_value(CANames, i)); + if (CAName == NULL) { + Py_DECREF(CAList); + exception_from_error_queue(ssl_Error); + return NULL; + } + CA = (PyObject *)new_x509name(CAName, 1); + if (CA == NULL) { + X509_NAME_free(CAName); + Py_DECREF(CAList); + return NULL; + } + if (PyList_SetItem(CAList, i, CA)) { + Py_DECREF(CA); + Py_DECREF(CAList); + return NULL; + } + } + return CAList; +} + +static char ssl_Connection_makefile_doc[] = "\n\ +The makefile() method is not implemented, since there is no dup semantics\n\ +for SSL connections\n\ +\n\ +@raise NotImplementedError\n\ +"; +static PyObject * +ssl_Connection_makefile(ssl_ConnectionObj *self, PyObject *args) +{ + PyErr_SetString(PyExc_NotImplementedError, "Cannot make file object of SSL.Connection"); + return NULL; +} + +static char ssl_Connection_get_app_data_doc[] = "\n\ +Get application data\n\ +\n\ +@return: The application data\n\ +"; +static PyObject * +ssl_Connection_get_app_data(ssl_ConnectionObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":get_app_data")) + return NULL; + + Py_INCREF(self->app_data); + return self->app_data; +} + +static char ssl_Connection_set_app_data_doc[] = "\n\ +Set application data\n\ +\n\ +@param data - The application data\n\ +@return: None\n\ +"; +static PyObject * +ssl_Connection_set_app_data(ssl_ConnectionObj *self, PyObject *args) +{ + PyObject *data; + + if (!PyArg_ParseTuple(args, "O:set_app_data", &data)) + return NULL; + + Py_DECREF(self->app_data); + Py_INCREF(data); + self->app_data = data; + + Py_INCREF(Py_None); + return Py_None; +} + +static char ssl_Connection_get_shutdown_doc[] = "\n\ +Get shutdown state\n\ +\n\ +@return: The shutdown state, a bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN.\n\ +"; +static PyObject * +ssl_Connection_get_shutdown(ssl_ConnectionObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":get_shutdown")) + return NULL; + + return PyLong_FromLong((long)SSL_get_shutdown(self->ssl)); +} + +static char ssl_Connection_set_shutdown_doc[] = "\n\ +Set shutdown state\n\ +\n\ +@param state - bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN.\n\ +@return: None\n\ +"; +static PyObject * +ssl_Connection_set_shutdown(ssl_ConnectionObj *self, PyObject *args) +{ + int shutdown; + + if (!PyArg_ParseTuple(args, "i:set_shutdown", &shutdown)) + return NULL; + + SSL_set_shutdown(self->ssl, shutdown); + Py_INCREF(Py_None); + return Py_None; +} + +static char ssl_Connection_state_string_doc[] = "\n\ +Get a verbose state description\n\ +\n\ +@return: A string representing the state\n\ +"; +static PyObject * +ssl_Connection_state_string(ssl_ConnectionObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":state_string")) + return NULL; + + return PyText_FromString(SSL_state_string_long(self->ssl)); +} + +static char ssl_Connection_client_random_doc[] = "\n\ +Get a copy of the client hello nonce.\n\ +\n\ +@return: A string representing the state\n\ +"; +static PyObject * +ssl_Connection_client_random(ssl_ConnectionObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":client_random")) + return NULL; + + if (self->ssl->session == NULL) { + Py_INCREF(Py_None); + return Py_None; + } + return PyBytes_FromStringAndSize( (const char *) self->ssl->s3->client_random, SSL3_RANDOM_SIZE); +} + +static char ssl_Connection_server_random_doc[] = "\n\ +Get a copy of the server hello nonce.\n\ +\n\ +@return: A string representing the state\n\ +"; +static PyObject * +ssl_Connection_server_random(ssl_ConnectionObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":server_random")) + return NULL; + + if (self->ssl->session == NULL) { + Py_INCREF(Py_None); + return Py_None; + } + return PyBytes_FromStringAndSize( (const char *) self->ssl->s3->server_random, SSL3_RANDOM_SIZE); +} + +static char ssl_Connection_master_key_doc[] = "\n\ +Get a copy of the master key.\n\ +\n\ +@return: A string representing the state\n\ +"; +static PyObject * +ssl_Connection_master_key(ssl_ConnectionObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":master_key")) + return NULL; + + if (self->ssl->session == NULL) { + Py_INCREF(Py_None); + return Py_None; + } + return PyBytes_FromStringAndSize( (const char *) self->ssl->session->master_key, self->ssl->session->master_key_length); +} + +static char ssl_Connection_sock_shutdown_doc[] = "\n\ +See shutdown(2)\n\ +\n\ +@return: What the socket's shutdown() method returns\n\ +"; +static PyObject * +ssl_Connection_sock_shutdown(ssl_ConnectionObj *self, PyObject *args) +{ + PyObject *meth, *ret; + + if ((meth = PyObject_GetAttrString(self->socket, "shutdown")) == NULL) + return NULL; + ret = PyEval_CallObject(meth, args); + Py_DECREF(meth); + return ret; +} + +static char ssl_Connection_get_peer_certificate_doc[] = "\n\ +Retrieve the other side's certificate (if any)\n\ +\n\ +@return: The peer's certificate\n\ +"; +static PyObject * +ssl_Connection_get_peer_certificate(ssl_ConnectionObj *self, PyObject *args) +{ + X509 *cert; + + if (!PyArg_ParseTuple(args, ":get_peer_certificate")) + return NULL; + + cert = SSL_get_peer_certificate(self->ssl); + if (cert != NULL) + { + return (PyObject *)new_x509(cert, 1); + } + else + { + Py_INCREF(Py_None); + return Py_None; + } +} + +static char ssl_Connection_want_read_doc[] = "\n\ +Checks if more data has to be read from the transport layer to complete an\n\ +operation.\n\ +\n\ +@return: True iff more data has to be read\n\ +"; +static PyObject * +ssl_Connection_want_read(ssl_ConnectionObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":want_read")) + return NULL; + + return PyLong_FromLong((long)SSL_want_read(self->ssl)); +} + +static char ssl_Connection_want_write_doc[] = "\n\ +Checks if there is data to write to the transport layer to complete an\n\ +operation.\n\ +\n\ +@return: True iff there is data to write\n\ +"; +static PyObject * +ssl_Connection_want_write(ssl_ConnectionObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":want_write")) + return NULL; + + return PyLong_FromLong((long)SSL_want_write(self->ssl)); +} + +/* + * Member methods in the Connection object + * ADD_METHOD(name) expands to a correct PyMethodDef declaration + * { 'name', (PyCFunction)ssl_Connection_name, METH_VARARGS } + * for convenience + * ADD_ALIAS(name,real) creates an "alias" of the ssl_Connection_real + * function with the name 'name' + */ +#define ADD_METHOD(name) \ + { #name, (PyCFunction)ssl_Connection_##name, METH_VARARGS, ssl_Connection_##name##_doc } +#define ADD_ALIAS(name,real) \ + { #name, (PyCFunction)ssl_Connection_##real, METH_VARARGS, ssl_Connection_##real##_doc } +static PyMethodDef ssl_Connection_methods[] = +{ + ADD_METHOD(get_context), + ADD_METHOD(pending), + ADD_METHOD(send), + ADD_ALIAS (write, send), + ADD_METHOD(sendall), + ADD_METHOD(recv), + ADD_ALIAS (read, recv), + ADD_METHOD(bio_read), + ADD_METHOD(bio_write), + ADD_METHOD(renegotiate), + ADD_METHOD(do_handshake), +#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x00907000L + ADD_METHOD(renegotiate_pending), +#endif + ADD_METHOD(total_renegotiations), + ADD_METHOD(connect), + ADD_METHOD(connect_ex), + ADD_METHOD(accept), + ADD_METHOD(bio_shutdown), + ADD_METHOD(shutdown), + ADD_METHOD(get_cipher_list), + ADD_METHOD(get_client_ca_list), + ADD_METHOD(makefile), + ADD_METHOD(get_app_data), + ADD_METHOD(set_app_data), + ADD_METHOD(get_shutdown), + ADD_METHOD(set_shutdown), + ADD_METHOD(state_string), + ADD_METHOD(server_random), + ADD_METHOD(client_random), + ADD_METHOD(master_key), + ADD_METHOD(sock_shutdown), + ADD_METHOD(get_peer_certificate), + ADD_METHOD(want_read), + ADD_METHOD(want_write), + ADD_METHOD(set_accept_state), + ADD_METHOD(set_connect_state), + { NULL, NULL } +}; +#undef ADD_ALIAS +#undef ADD_METHOD + +static char ssl_Connection_doc[] = "\n\ +Connection(context, socket) -> Connection instance\n\ +\n\ +Create a new Connection object, using the given OpenSSL.SSL.Context instance\n\ +and socket.\n\ +\n\ +@param context: An SSL Context to use for this connection\n\ +@param socket: The socket to use for transport layer\n\ +"; + +/* + * Initializer used by ssl_Connection_new and ssl_Connection_New. *Not* + * tp_init. This takes an already allocated ssl_ConnectionObj, a context, and + * a optionally a socket, and glues them all together. + */ +static ssl_ConnectionObj* +ssl_Connection_init(ssl_ConnectionObj *self, ssl_ContextObj *ctx, PyObject *sock) { + int fd; + + Py_INCREF(ctx); + self->context = ctx; + + Py_INCREF(sock); + self->socket = sock; + + self->ssl = NULL; + self->from_ssl = NULL; + self->into_ssl = NULL; + + Py_INCREF(Py_None); + self->app_data = Py_None; + + self->tstate = NULL; + + self->ssl = SSL_new(self->context->ctx); + SSL_set_app_data(self->ssl, self); + + if (self->socket == Py_None) + { + /* If it's not a socket or file, treat it like a memory buffer, + * so crazy people can do things like EAP-TLS. */ + self->into_ssl = BIO_new(BIO_s_mem()); + self->from_ssl = BIO_new(BIO_s_mem()); + if (self->into_ssl == NULL || self->from_ssl == NULL) + goto error; + SSL_set_bio(self->ssl, self->into_ssl, self->from_ssl); + } + else + { + fd = PyObject_AsFileDescriptor(self->socket); + if (fd < 0) + { + Py_DECREF(self); + return NULL; + } + else + { + SSL_set_fd(self->ssl, (SOCKET_T)fd); + } + } + return self; + +error: + BIO_free(self->into_ssl); /* NULL safe */ + BIO_free(self->from_ssl); /* NULL safe */ + Py_DECREF(self); + return NULL; +} + +/* + * Constructor for Connection objects + * + * Arguments: ctx - An SSL Context to use for this connection + * sock - The socket to use for transport layer + * Returns: The newly created Connection object + */ +ssl_ConnectionObj * +ssl_Connection_New(ssl_ContextObj *ctx, PyObject *sock) { + ssl_ConnectionObj *self; + + self = PyObject_GC_New(ssl_ConnectionObj, &ssl_Connection_Type); + if (self == NULL) { + return NULL; + } + self = ssl_Connection_init(self, ctx, sock); + if (self == NULL) { + return NULL; + } + PyObject_GC_Track((PyObject *)self); + return self; +} + +static PyObject* +ssl_Connection_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) { + ssl_ConnectionObj *self; + ssl_ContextObj *ctx; + PyObject *sock; + static char *kwlist[] = {"context", "socket", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "O!O:Connection", kwlist, + &ssl_Context_Type, &ctx, &sock)) { + return NULL; + } + + self = (ssl_ConnectionObj *)subtype->tp_alloc(subtype, 1); + if (self == NULL) { + return NULL; + } + + return (PyObject *)ssl_Connection_init(self, ctx, sock); +} + +/* + * Find attribute + * + * Arguments: self - The Connection object + * name - The attribute name + * Returns: A Python object for the attribute, or NULL if something went + * wrong + */ +static PyObject * +ssl_Connection_getattro(ssl_ConnectionObj *self, PyObject *nameobj) { + PyObject *meth; + + meth = PyObject_GenericGetAttr((PyObject*)self, nameobj); + if (PyErr_Occurred() && PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + /* Try looking it up in the "socket" instead. */ + meth = PyObject_GenericGetAttr(self->socket, nameobj); + } + + return meth; +} + +/* + * Call the visitproc on all contained objects. + * + * Arguments: self - The Connection object + * visit - Function to call + * arg - Extra argument to visit + * Returns: 0 if all goes well, otherwise the return code from the first + * call that gave non-zero result. + */ +static int +ssl_Connection_traverse(ssl_ConnectionObj *self, visitproc visit, void *arg) +{ + int ret = 0; + + if (ret == 0 && self->context != NULL) + ret = visit((PyObject *)self->context, arg); + if (ret == 0 && self->socket != NULL) + ret = visit(self->socket, arg); + if (ret == 0 && self->app_data != NULL) + ret = visit(self->app_data, arg); + return ret; +} + +/* + * Decref all contained objects and zero the pointers. + * + * Arguments: self - The Connection object + * Returns: Always 0. + */ +static int +ssl_Connection_clear(ssl_ConnectionObj *self) +{ + Py_XDECREF(self->context); + self->context = NULL; + Py_XDECREF(self->socket); + self->socket = NULL; + Py_XDECREF(self->app_data); + self->app_data = NULL; + self->into_ssl = NULL; /* was cleaned up by SSL_free() */ + self->from_ssl = NULL; /* was cleaned up by SSL_free() */ + return 0; +} + +/* + * Deallocate the memory used by the Connection object + * + * Arguments: self - The Connection object + * Returns: None + */ +static void +ssl_Connection_dealloc(ssl_ConnectionObj *self) +{ + PyObject_GC_UnTrack(self); + if (self->ssl != NULL) + SSL_free(self->ssl); + ssl_Connection_clear(self); + PyObject_GC_Del(self); +} + +PyTypeObject ssl_Connection_Type = { + PyOpenSSL_HEAD_INIT(&PyType_Type, 0) + "OpenSSL.SSL.Connection", + sizeof(ssl_ConnectionObj), + 0, + (destructor)ssl_Connection_dealloc, + NULL, /* print */ + NULL, /* tp_getattr */ + NULL, /* setattr */ + NULL, /* compare */ + NULL, /* repr */ + NULL, /* as_number */ + NULL, /* as_sequence */ + NULL, /* as_mapping */ + NULL, /* hash */ + NULL, /* call */ + NULL, /* str */ + (getattrofunc)ssl_Connection_getattro, /* getattro */ + NULL, /* setattro */ + NULL, /* as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, + ssl_Connection_doc, /* doc */ + (traverseproc)ssl_Connection_traverse, + (inquiry)ssl_Connection_clear, + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + ssl_Connection_methods, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + ssl_Connection_new, /* tp_new */ +}; + + +/* + * Initiailze the Connection part of the SSL sub module + * + * Arguments: dict - The OpenSSL.SSL module + * Returns: 1 for success, 0 otherwise + */ +int +init_ssl_connection(PyObject *module) { + + if (PyType_Ready(&ssl_Connection_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "Connection", (PyObject *)&ssl_Connection_Type) != 0) { + return 0; + } + + if (PyModule_AddObject(module, "ConnectionType", (PyObject *)&ssl_Connection_Type) != 0) { + return 0; + } + + return 1; +} + diff --git a/OpenSSL/ssl/connection.h b/OpenSSL/ssl/connection.h new file mode 100644 index 0000000..59f659b --- /dev/null +++ b/OpenSSL/ssl/connection.h @@ -0,0 +1,53 @@ +/* + * connection.h + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * Export SSL Connection data structures and functions. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + * + */ +#ifndef PyOpenSSL_SSL_CONNECTION_H_ +#define PyOpenSSL_SSL_CONNECTION_H_ + +#include +#include + +/* shamelessly stolen from socketmodule.c */ +#ifdef MS_WINDOWS +# include +typedef SOCKET SOCKET_T; +# ifdef MS_WIN64 +# define SIZEOF_SOCKET_T 8 +# else +# define SIZEOF_SOCKET_T 4 +# endif +#else +typedef int SOCKET_T; +# define SIZEOF_SOCKET_T SIZEOF_INT +#endif + + +extern int init_ssl_connection (PyObject *); + +extern PyTypeObject ssl_Connection_Type; + +#define ssl_Connection_Check(v) ((v)->ob_type == &ssl_Connection_Type) + +typedef struct { + PyObject_HEAD + SSL *ssl; + ssl_ContextObj *context; + PyObject *socket; + PyThreadState *tstate; /* This field is no longer used. */ + PyObject *app_data; + BIO *into_ssl, *from_ssl; /* for connections without file descriptors */ +} ssl_ConnectionObj; + + + +#endif + diff --git a/OpenSSL/ssl/context.c b/OpenSSL/ssl/context.c new file mode 100644 index 0000000..ea7847f --- /dev/null +++ b/OpenSSL/ssl/context.c @@ -0,0 +1,1323 @@ +/* + * context.c + * + * Copyright (C) AB Strakt + * Copyright (C) Jean-Paul Calderone + * See LICENSE for details. + * + * SSL Context objects and their methods. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + */ +#include + +#if PY_VERSION_HEX >= 0x02050000 +# define PYARG_PARSETUPLE_FORMAT const char +# define PYOBJECT_GETATTRSTRING_TYPE const char* +#else +# define PYARG_PARSETUPLE_FORMAT char +# define PYOBJECT_GETATTRSTRING_TYPE char* +#endif + +#ifndef MS_WINDOWS +# include +# include +# if !(defined(__BEOS__) || defined(__CYGWIN__)) +# include +# endif +#else +# include +# include +#endif + +#define SSL_MODULE +#include "ssl.h" + +/* + * CALLBACKS + * + * Callbacks work like this: We provide a "global" callback in C which + * transforms the arguments into a Python argument tuple and calls the + * corresponding Python callback, and then parsing the return value back into + * things the C function can return. + * + * Three caveats: + * + How do we find the Context object where the Python callbacks are stored? + * + What about multithreading and execution frames? + * + What about Python callbacks that raise exceptions? + * + * The solution to the first issue is trivial if the callback provides + * "userdata" functionality. Since the only callbacks that don't provide + * userdata do provide a pointer to an SSL structure, we can associate an SSL + * object and a Connection one-to-one via the SSL_set/get_app_data() + * functions. + * + * The solution to the other issue is to rewrite the Py_BEGIN_ALLOW_THREADS + * macro allowing it (or rather a new macro) to specify where to save the + * thread state (in our case, as a member of the Connection/Context object) so + * we can retrieve it again before calling the Python callback. + */ + +/* + * Globally defined passphrase callback. This is called from OpenSSL + * internally. The GIL will not be held when this function is invoked. It + * must not be held when the function returns. + * + * Arguments: buf - Buffer to store the returned passphrase in + * maxlen - Maximum length of the passphrase + * verify - If true, the passphrase callback should ask for a + * password twice and verify they're equal. If false, only + * ask once. + * arg - User data, always a Context object + * Returns: The length of the password if successful, 0 otherwise + */ +static int +global_passphrase_callback(char *buf, int maxlen, int verify, void *arg) +{ + /* + * Initialize len here because we're always going to return it, and we + * might jump to the return before it gets initialized in any other way. + */ + int len = 0; + char *str; + PyObject *argv, *ret = NULL; + ssl_ContextObj *ctx = (ssl_ContextObj *)arg; + + /* + * GIL isn't held yet. First things first - acquire it, or any Python API + * we invoke might segfault or blow up the sun. The reverse will be done + * before returning. + */ + MY_END_ALLOW_THREADS(ctx->tstate); + + /* The Python callback is called with a (maxlen,verify,userdata) tuple */ + argv = Py_BuildValue("(iiO)", maxlen, verify, ctx->passphrase_userdata); + + /* + * XXX Didn't check argv to see if it was NULL. -exarkun + */ + ret = PyEval_CallObject(ctx->passphrase_callback, argv); + Py_DECREF(argv); + + if (ret == NULL) { + /* + * The callback raised an exception. It will be raised by whatever + * Python API triggered this callback. + */ + goto out; + } + + if (!PyObject_IsTrue(ret)) { + /* + * Returned "", or None, or something. Treat it as no passphrase. + */ + Py_DECREF(ret); + goto out; + } + + if (!PyBytes_Check(ret)) { + /* + * XXX Returned something that wasn't a string. This is bogus. We'll + * return 0 and OpenSSL will treat it as an error, resulting in an + * exception from whatever Python API triggered this callback. + */ + Py_DECREF(ret); + goto out; + } + + len = PyBytes_Size(ret); + if (len > maxlen) { + /* + * Returned more than we said they were allowed to return. Just + * truncate it. Might be better to raise an exception, + * instead. -exarkun + */ + len = maxlen; + } + + str = PyBytes_AsString(ret); + strncpy(buf, str, len); + Py_XDECREF(ret); + + out: + /* + * This function is returning into OpenSSL. Release the GIL again. + */ + MY_BEGIN_ALLOW_THREADS(ctx->tstate); + return len; +} + +/* + * Globally defined verify callback + * + * Arguments: ok - True everything is OK "so far", false otherwise + * x509_ctx - Contains the certificate being checked, the current + * error number and depth, and the Connection we're + * dealing with + * Returns: True if everything is okay, false otherwise + */ +static int +global_verify_callback(int ok, X509_STORE_CTX *x509_ctx) +{ + PyObject *argv, *ret; + SSL *ssl; + ssl_ConnectionObj *conn; + crypto_X509Obj *cert; + int errnum, errdepth, c_ret; + + // Get Connection object to check thread state + ssl = (SSL *)X509_STORE_CTX_get_app_data(x509_ctx); + conn = (ssl_ConnectionObj *)SSL_get_app_data(ssl); + + MY_END_ALLOW_THREADS(conn->tstate); + + cert = new_x509(X509_STORE_CTX_get_current_cert(x509_ctx), 0); + errnum = X509_STORE_CTX_get_error(x509_ctx); + errdepth = X509_STORE_CTX_get_error_depth(x509_ctx); + + argv = Py_BuildValue("(OOiii)", (PyObject *)conn, (PyObject *)cert, + errnum, errdepth, ok); + Py_DECREF(cert); + ret = PyEval_CallObject(conn->context->verify_callback, argv); + Py_DECREF(argv); + + if (ret != NULL && PyObject_IsTrue(ret)) { + X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); + Py_DECREF(ret); + c_ret = 1; + } else { + c_ret = 0; + } + + MY_BEGIN_ALLOW_THREADS(conn->tstate); + return c_ret; +} + +/* + * Globally defined info callback. This is called from OpenSSL internally. + * The GIL will not be held when this function is invoked. It must not be held + * when the function returns. + * + * Arguments: ssl - The Connection + * where - The part of the SSL code that called us + * _ret - The return code of the SSL function that called us + * Returns: None + */ +static void +global_info_callback(const SSL *ssl, int where, int _ret) +{ + ssl_ConnectionObj *conn = (ssl_ConnectionObj *)SSL_get_app_data(ssl); + PyObject *argv, *ret; + + /* + * GIL isn't held yet. First things first - acquire it, or any Python API + * we invoke might segfault or blow up the sun. The reverse will be done + * before returning. + */ + MY_END_ALLOW_THREADS(conn->tstate); + + argv = Py_BuildValue("(Oii)", (PyObject *)conn, where, _ret); + ret = PyEval_CallObject(conn->context->info_callback, argv); + Py_DECREF(argv); + + if (ret == NULL) { + /* + * XXX - This should be reported somehow. -exarkun + */ + PyErr_Clear(); + } else { + Py_DECREF(ret); + } + + /* + * This function is returning into OpenSSL. Release the GIL again. + */ + MY_BEGIN_ALLOW_THREADS(conn->tstate); + return; +} + + +static char ssl_Context_doc[] = "\n\ +Context(method) -> Context instance\n\ +\n\ +OpenSSL.SSL.Context instances define the parameters for setting up new SSL\n\ +connections.\n\ +\n\ +@param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, or\n\ + TLSv1_METHOD.\n\ +"; + +static char ssl_Context_load_verify_locations_doc[] = "\n\ +Let SSL know where we can find trusted certificates for the certificate\n\ +chain\n\ +\n\ +@param cafile: In which file we can find the certificates\n\ +@param capath: In which directory we can find the certificates\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_load_verify_locations(ssl_ContextObj *self, PyObject *args) { + char *cafile = NULL; + char *capath = NULL; + + if (!PyArg_ParseTuple(args, "z|z:load_verify_locations", &cafile, &capath)) { + return NULL; + } + + if (!SSL_CTX_load_verify_locations(self->ctx, cafile, capath)) + { + exception_from_error_queue(ssl_Error); + return NULL; + } + else + { + Py_INCREF(Py_None); + return Py_None; + } +} + +static char ssl_Context_set_default_verify_paths_doc[] = "\n\ +Use the platform-specific CA certificate locations\n\ +\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_set_default_verify_paths(ssl_ContextObj *self, PyObject *args) { + if (!PyArg_ParseTuple(args, ":set_default_verify_paths")) { + return NULL; + } + + /* + * XXX Error handling for SSL_CTX_set_default_verify_paths is untested. + * -exarkun + */ + if (!SSL_CTX_set_default_verify_paths(self->ctx)) { + exception_from_error_queue(ssl_Error); + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +}; + + +static char ssl_Context_set_passwd_cb_doc[] = "\n\ +Set the passphrase callback\n\ +\n\ +@param callback: The Python callback to use\n\ +@param userdata: (optional) A Python object which will be given as\n\ + argument to the callback\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_set_passwd_cb(ssl_ContextObj *self, PyObject *args) +{ + PyObject *callback = NULL, *userdata = Py_None; + + if (!PyArg_ParseTuple(args, "O|O:set_passwd_cb", &callback, &userdata)) + return NULL; + + if (!PyCallable_Check(callback)) + { + PyErr_SetString(PyExc_TypeError, "expected PyCallable"); + return NULL; + } + + Py_DECREF(self->passphrase_callback); + Py_INCREF(callback); + self->passphrase_callback = callback; + SSL_CTX_set_default_passwd_cb(self->ctx, global_passphrase_callback); + + Py_DECREF(self->passphrase_userdata); + Py_INCREF(userdata); + self->passphrase_userdata = userdata; + SSL_CTX_set_default_passwd_cb_userdata(self->ctx, (void *)self); + + Py_INCREF(Py_None); + return Py_None; +} + +static PyTypeObject * +type_modified_error(const char *name) { + PyErr_Format(PyExc_RuntimeError, + "OpenSSL.crypto's '%s' attribute has been modified", + name); + return NULL; +} + +static PyTypeObject * +import_crypto_type(const char *name, size_t objsize) { + PyObject *module, *type, *name_attr; + PyTypeObject *res; + int right_name; + + module = PyImport_ImportModule("OpenSSL.crypto"); + if (module == NULL) { + return NULL; + } + type = PyObject_GetAttrString(module, (PYOBJECT_GETATTRSTRING_TYPE)name); + Py_DECREF(module); + if (type == NULL) { + return NULL; + } + if (!(PyType_Check(type))) { + Py_DECREF(type); + return type_modified_error(name); + } + name_attr = PyObject_GetAttrString(type, "__name__"); + if (name_attr == NULL) { + Py_DECREF(type); + return NULL; + } + +#ifdef PY3 + { + PyObject* asciiname = PyUnicode_AsASCIIString(name_attr); + Py_DECREF(name_attr); + name_attr = asciiname; + } +#endif + right_name = (PyBytes_CheckExact(name_attr) && + strcmp(name, PyBytes_AsString(name_attr)) == 0); + Py_DECREF(name_attr); + res = (PyTypeObject *)type; + if (!right_name || res->tp_basicsize != objsize) { + Py_DECREF(type); + return type_modified_error(name); + } + return res; +} + +static crypto_X509Obj * +parse_certificate_argument(const char* format, PyObject* args) { + static PyTypeObject *crypto_X509_type = NULL; + crypto_X509Obj *cert; + + if (!crypto_X509_type) { + crypto_X509_type = import_crypto_type("X509", sizeof(crypto_X509Obj)); + if (!crypto_X509_type) { + return NULL; + } + } + if (!PyArg_ParseTuple(args, (PYARG_PARSETUPLE_FORMAT *)format, + crypto_X509_type, &cert)) { + return NULL; + } + return cert; +} + +static char ssl_Context_add_extra_chain_cert_doc[] = "\n\ +Add certificate to chain\n\ +\n\ +@param certobj: The X509 certificate object to add to the chain\n\ +@return: None\n\ +"; + +static PyObject * +ssl_Context_add_extra_chain_cert(ssl_ContextObj *self, PyObject *args) +{ + X509* cert_original; + crypto_X509Obj *cert = parse_certificate_argument( + "O!:add_extra_chain_cert", args); + if (cert == NULL) + { + return NULL; + } + if (!(cert_original = X509_dup(cert->x509))) + { + /* exception_from_error_queue(ssl_Error); */ + PyErr_SetString(PyExc_RuntimeError, "X509_dup failed"); + return NULL; + } + if (!SSL_CTX_add_extra_chain_cert(self->ctx, cert_original)) + { + X509_free(cert_original); + exception_from_error_queue(ssl_Error); + return NULL; + } + else + { + Py_INCREF(Py_None); + return Py_None; + } +} + + +static char ssl_Context_use_certificate_chain_file_doc[] = "\n\ +Load a certificate chain from a file\n\ +\n\ +@param certfile: The name of the certificate chain file\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_use_certificate_chain_file(ssl_ContextObj *self, PyObject *args) +{ + char *certfile; + + if (!PyArg_ParseTuple(args, "s:use_certificate_chain_file", &certfile)) + return NULL; + + if (!SSL_CTX_use_certificate_chain_file(self->ctx, certfile)) + { + exception_from_error_queue(ssl_Error); + return NULL; + } + else + { + Py_INCREF(Py_None); + return Py_None; + } +} + + +static char ssl_Context_use_certificate_file_doc[] = "\n\ +Load a certificate from a file\n\ +\n\ +@param certfile: The name of the certificate file\n\ +@param filetype: (optional) The encoding of the file, default is PEM\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_use_certificate_file(ssl_ContextObj *self, PyObject *args) +{ + char *certfile; + int filetype = SSL_FILETYPE_PEM; + + if (!PyArg_ParseTuple(args, "s|i:use_certificate_file", &certfile, &filetype)) + return NULL; + + if (!SSL_CTX_use_certificate_file(self->ctx, certfile, filetype)) + { + exception_from_error_queue(ssl_Error); + return NULL; + } + else + { + Py_INCREF(Py_None); + return Py_None; + } +} + +static char ssl_Context_use_certificate_doc[] = "\n\ +Load a certificate from a X509 object\n\ +\n\ +@param cert: The X509 object\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_use_certificate(ssl_ContextObj *self, PyObject *args) +{ + crypto_X509Obj *cert = parse_certificate_argument( + "O!:use_certificate", args); + if (cert == NULL) { + return NULL; + } + + if (!SSL_CTX_use_certificate(self->ctx, cert->x509)) + { + exception_from_error_queue(ssl_Error); + return NULL; + } + else + { + Py_INCREF(Py_None); + return Py_None; + } +} + +static char ssl_Context_use_privatekey_file_doc[] = "\n\ +Load a private key from a file\n\ +\n\ +@param keyfile: The name of the key file\n\ +@param filetype: (optional) The encoding of the file, default is PEM\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_use_privatekey_file(ssl_ContextObj *self, PyObject *args) +{ + char *keyfile; + int filetype = SSL_FILETYPE_PEM, ret; + + if (!PyArg_ParseTuple(args, "s|i:use_privatekey_file", &keyfile, &filetype)) + return NULL; + + MY_BEGIN_ALLOW_THREADS(self->tstate); + ret = SSL_CTX_use_PrivateKey_file(self->ctx, keyfile, filetype); + MY_END_ALLOW_THREADS(self->tstate); + + if (PyErr_Occurred()) + { + flush_error_queue(); + return NULL; + } + + if (!ret) + { + exception_from_error_queue(ssl_Error); + return NULL; + } + else + { + Py_INCREF(Py_None); + return Py_None; + } +} + +static char ssl_Context_use_privatekey_doc[] = "\n\ +Load a private key from a PKey object\n\ +\n\ +@param pkey: The PKey object\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_use_privatekey(ssl_ContextObj *self, PyObject *args) { + static PyTypeObject *crypto_PKey_type = NULL; + crypto_PKeyObj *pkey; + + if (!crypto_PKey_type) { + crypto_PKey_type = import_crypto_type("PKey", sizeof(crypto_PKeyObj)); + if (!crypto_PKey_type) { + return NULL; + } + } + if (!PyArg_ParseTuple(args, "O!:use_privatekey", crypto_PKey_type, &pkey)) { + return NULL; + } + + if (!SSL_CTX_use_PrivateKey(self->ctx, pkey->pkey)) { + exception_from_error_queue(ssl_Error); + return NULL; + } else { + Py_INCREF(Py_None); + return Py_None; + } +} + +static char ssl_Context_check_privatekey_doc[] = "\n\ +Check that the private key and certificate match up\n\ +\n\ +@return: None (raises an exception if something's wrong)\n\ +"; +static PyObject * +ssl_Context_check_privatekey(ssl_ContextObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":check_privatekey")) + return NULL; + + if (!SSL_CTX_check_private_key(self->ctx)) + { + exception_from_error_queue(ssl_Error); + return NULL; + } + else + { + Py_INCREF(Py_None); + return Py_None; + } +} + +static char ssl_Context_load_client_ca_doc[] = "\n\ +Load the trusted certificates that will be sent to the client (basically\n \ +telling the client \"These are the guys I trust\"). Does not actually\n\ +imply any of the certificates are trusted; that must be configured\n\ +separately.\n\ +\n\ +@param cafile: The name of the certificates file\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_load_client_ca(ssl_ContextObj *self, PyObject *args) +{ + char *cafile; + + if (!PyArg_ParseTuple(args, "s:load_client_ca", &cafile)) + return NULL; + + SSL_CTX_set_client_CA_list(self->ctx, SSL_load_client_CA_file(cafile)); + + Py_INCREF(Py_None); + return Py_None; +} + +static char ssl_Context_set_session_id_doc[] = "\n\ +Set the session identifier, this is needed if you want to do session\n\ +resumption (which, ironically, isn't implemented yet)\n\ +\n\ +@param buf: A Python object that can be safely converted to a string\n\ +@returns: None\n\ +"; +static PyObject * +ssl_Context_set_session_id(ssl_ContextObj *self, PyObject *args) +{ + unsigned char *buf; + unsigned int len; + + if (!PyArg_ParseTuple(args, "s#:set_session_id", &buf, &len)) + return NULL; + + if (!SSL_CTX_set_session_id_context(self->ctx, buf, len)) + { + exception_from_error_queue(ssl_Error); + return NULL; + } + else + { + Py_INCREF(Py_None); + return Py_None; + } +} + +static char ssl_Context_set_verify_doc[] = "\n\ +Set the verify mode and verify callback\n\ +\n\ +@param mode: The verify mode, this is either VERIFY_NONE or\n\ + VERIFY_PEER combined with possible other flags\n\ +@param callback: The Python callback to use\n\ +@return: None\n\ +\n\ +See SSL_CTX_set_verify(3SSL) for further details.\n\ +"; +static PyObject * +ssl_Context_set_verify(ssl_ContextObj *self, PyObject *args) +{ + int mode; + PyObject *callback = NULL; + + if (!PyArg_ParseTuple(args, "iO:set_verify", &mode, &callback)) + return NULL; + + if (!PyCallable_Check(callback)) + { + PyErr_SetString(PyExc_TypeError, "expected PyCallable"); + return NULL; + } + + Py_DECREF(self->verify_callback); + Py_INCREF(callback); + self->verify_callback = callback; + SSL_CTX_set_verify(self->ctx, mode, global_verify_callback); + + Py_INCREF(Py_None); + return Py_None; +} + +static char ssl_Context_set_verify_depth_doc[] = "\n\ +Set the verify depth\n\ +\n\ +@param depth: An integer specifying the verify depth\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_set_verify_depth(ssl_ContextObj *self, PyObject *args) +{ + int depth; + + if (!PyArg_ParseTuple(args, "i:set_verify_depth", &depth)) + return NULL; + + SSL_CTX_set_verify_depth(self->ctx, depth); + Py_INCREF(Py_None); + return Py_None; +} + +static char ssl_Context_get_verify_mode_doc[] = "\n\ +Get the verify mode\n\ +\n\ +@return: The verify mode\n\ +"; +static PyObject * +ssl_Context_get_verify_mode(ssl_ContextObj *self, PyObject *args) +{ + int mode; + + if (!PyArg_ParseTuple(args, ":get_verify_mode")) + return NULL; + + mode = SSL_CTX_get_verify_mode(self->ctx); + return PyLong_FromLong((long)mode); +} + +static char ssl_Context_get_verify_depth_doc[] = "\n\ +Get the verify depth\n\ +\n\ +@return: The verify depth\n\ +"; +static PyObject * +ssl_Context_get_verify_depth(ssl_ContextObj *self, PyObject *args) +{ + int depth; + + if (!PyArg_ParseTuple(args, ":get_verify_depth")) + return NULL; + + depth = SSL_CTX_get_verify_depth(self->ctx); + return PyLong_FromLong((long)depth); +} + +static char ssl_Context_load_tmp_dh_doc[] = "\n\ +Load parameters for Ephemeral Diffie-Hellman\n\ +\n\ +@param dhfile: The file to load EDH parameters from\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_load_tmp_dh(ssl_ContextObj *self, PyObject *args) +{ + char *dhfile; + BIO *bio; + DH *dh; + + if (!PyArg_ParseTuple(args, "s:load_tmp_dh", &dhfile)) + return NULL; + + bio = BIO_new_file(dhfile, "r"); + if (bio == NULL) { + exception_from_error_queue(ssl_Error); + return NULL; + } + + dh = PEM_read_bio_DHparams(bio, NULL, NULL, NULL); + SSL_CTX_set_tmp_dh(self->ctx, dh); + DH_free(dh); + BIO_free(bio); + + Py_INCREF(Py_None); + return Py_None; +} + +static char ssl_Context_set_cipher_list_doc[] = "\n\ +Change the cipher list\n\ +\n\ +@param cipher_list: A cipher list, see ciphers(1)\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_set_cipher_list(ssl_ContextObj *self, PyObject *args) +{ + char *cipher_list; + + if (!PyArg_ParseTuple(args, "s:set_cipher_list", &cipher_list)) + return NULL; + + if (!SSL_CTX_set_cipher_list(self->ctx, cipher_list)) + { + exception_from_error_queue(ssl_Error); + return NULL; + } + else + { + Py_INCREF(Py_None); + return Py_None; + } +} + +static char ssl_Context_set_client_ca_list_doc[] = "\n\ +Set the list of preferred client certificate signers for this server context.\n\ +\n\ +This list of certificate authorities will be sent to the client when the\n\ +server requests a client certificate.\n\ +\n\ +@param certificate_authorities: a sequence of X509Names.\n\ +@return: None\n\ +"; + +static PyObject * +ssl_Context_set_client_ca_list(ssl_ContextObj *self, PyObject *args) +{ + static PyTypeObject *X509NameType; + PyObject *sequence, *tuple, *item; + crypto_X509NameObj *name; + X509_NAME *sslname; + STACK_OF(X509_NAME) *CANames; + Py_ssize_t length; + int i; + + if (X509NameType == NULL) { + X509NameType = import_crypto_type("X509Name", sizeof(crypto_X509NameObj)); + if (X509NameType == NULL) { + return NULL; + } + } + if (!PyArg_ParseTuple(args, "O:set_client_ca_list", &sequence)) { + return NULL; + } + tuple = PySequence_Tuple(sequence); + if (tuple == NULL) { + return NULL; + } + length = PyTuple_Size(tuple); + if (length >= INT_MAX) { + PyErr_SetString(PyExc_ValueError, "client CA list is too long"); + Py_DECREF(tuple); + return NULL; + } + CANames = sk_X509_NAME_new_null(); + if (CANames == NULL) { + Py_DECREF(tuple); + exception_from_error_queue(ssl_Error); + return NULL; + } + for (i = 0; i < length; i++) { + item = PyTuple_GetItem(tuple, i); + if (item->ob_type != X509NameType) { + PyErr_Format(PyExc_TypeError, + "client CAs must be X509Name objects, not %s objects", + item->ob_type->tp_name); + sk_X509_NAME_free(CANames); + Py_DECREF(tuple); + return NULL; + } + name = (crypto_X509NameObj *)item; + sslname = X509_NAME_dup(name->x509_name); + if (sslname == NULL) { + sk_X509_NAME_free(CANames); + Py_DECREF(tuple); + exception_from_error_queue(ssl_Error); + return NULL; + } + if (!sk_X509_NAME_push(CANames, sslname)) { + X509_NAME_free(sslname); + sk_X509_NAME_free(CANames); + Py_DECREF(tuple); + exception_from_error_queue(ssl_Error); + return NULL; + } + } + Py_DECREF(tuple); + SSL_CTX_set_client_CA_list(self->ctx, CANames); + Py_INCREF(Py_None); + return Py_None; +} + +static char ssl_Context_add_client_ca_doc[] = "\n\ +Add the CA certificate to the list of preferred signers for this context.\n\ +\n\ +The list of certificate authorities will be sent to the client when the\n\ +server requests a client certificate.\n\ +\n\ +@param certificate_authority: certificate authority's X509 certificate.\n\ +@return: None\n\ +"; + +static PyObject * +ssl_Context_add_client_ca(ssl_ContextObj *self, PyObject *args) +{ + crypto_X509Obj *cert; + + cert = parse_certificate_argument("O!:add_client_ca", args); + if (cert == NULL) { + return NULL; + } + if (!SSL_CTX_add_client_CA(self->ctx, cert->x509)) { + exception_from_error_queue(ssl_Error); + return NULL; + } + Py_INCREF(Py_None); + return Py_None; +} + +static char ssl_Context_set_timeout_doc[] = "\n\ +Set session timeout\n\ +\n\ +@param timeout: The timeout in seconds\n\ +@return: The previous session timeout\n\ +"; +static PyObject * +ssl_Context_set_timeout(ssl_ContextObj *self, PyObject *args) +{ + long t, ret; + + if (!PyArg_ParseTuple(args, "l:set_timeout", &t)) + return NULL; + + ret = SSL_CTX_set_timeout(self->ctx, t); + return PyLong_FromLong(ret); +} + +static char ssl_Context_get_timeout_doc[] = "\n\ +Get the session timeout\n\ +\n\ +@return: The session timeout\n\ +"; +static PyObject * +ssl_Context_get_timeout(ssl_ContextObj *self, PyObject *args) +{ + long ret; + + if (!PyArg_ParseTuple(args, ":get_timeout")) + return NULL; + + ret = SSL_CTX_get_timeout(self->ctx); + return PyLong_FromLong(ret); +} + +static char ssl_Context_set_info_callback_doc[] = "\n\ +Set the info callback\n\ +\n\ +@param callback: The Python callback to use\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_set_info_callback(ssl_ContextObj *self, PyObject *args) +{ + PyObject *callback; + + if (!PyArg_ParseTuple(args, "O:set_info_callback", &callback)) + return NULL; + + if (!PyCallable_Check(callback)) + { + PyErr_SetString(PyExc_TypeError, "expected PyCallable"); + return NULL; + } + + Py_DECREF(self->info_callback); + Py_INCREF(callback); + self->info_callback = callback; + SSL_CTX_set_info_callback(self->ctx, global_info_callback); + + Py_INCREF(Py_None); + return Py_None; +} + +static char ssl_Context_get_app_data_doc[] = "\n\ +Get the application data (supplied via set_app_data())\n\ +\n\ +@return: The application data\n\ +"; +static PyObject * +ssl_Context_get_app_data(ssl_ContextObj *self, PyObject *args) +{ + if (!PyArg_ParseTuple(args, ":get_app_data")) + return NULL; + + Py_INCREF(self->app_data); + return self->app_data; +} + +static char ssl_Context_set_app_data_doc[] = "\n\ +Set the application data (will be returned from get_app_data())\n\ +\n\ +@param data: Any Python object\n\ +@return: None\n\ +"; +static PyObject * +ssl_Context_set_app_data(ssl_ContextObj *self, PyObject *args) +{ + PyObject *data; + + if (!PyArg_ParseTuple(args, "O:set_app_data", &data)) + return NULL; + + Py_DECREF(self->app_data); + Py_INCREF(data); + self->app_data = data; + + Py_INCREF(Py_None); + return Py_None; +} + +static char ssl_Context_get_cert_store_doc[] = "\n\ +Get the certificate store for the context\n\ +\n\ +@return: A X509Store object\n\ +"; +static PyObject * +ssl_Context_get_cert_store(ssl_ContextObj *self, PyObject *args) +{ + X509_STORE *store; + + if (!PyArg_ParseTuple(args, ":get_cert_store")) + return NULL; + + if ((store = SSL_CTX_get_cert_store(self->ctx)) == NULL) + { + Py_INCREF(Py_None); + return Py_None; + } + else + { + return (PyObject *)new_x509store(store, 0); + } +} + +static char ssl_Context_set_options_doc[] = "\n\ +Add options. Options set before are not cleared!\n\ +\n\ +@param options: The options to add.\n\ +@return: The new option bitmask.\n\ +"; +static PyObject * +ssl_Context_set_options(ssl_ContextObj *self, PyObject *args) +{ + long options; + + if (!PyArg_ParseTuple(args, "l:set_options", &options)) + return NULL; + + return PyLong_FromLong(SSL_CTX_set_options(self->ctx, options)); +} + + +/* + * Member methods in the Context object + * ADD_METHOD(name) expands to a correct PyMethodDef declaration + * { 'name', (PyCFunction)ssl_Context_name, METH_VARARGS } + * for convenience + * ADD_ALIAS(name,real) creates an "alias" of the ssl_Context_real + * function with the name 'name' + */ +#define ADD_METHOD(name) { #name, (PyCFunction)ssl_Context_##name, METH_VARARGS, ssl_Context_##name##_doc } +static PyMethodDef ssl_Context_methods[] = { + ADD_METHOD(load_verify_locations), + ADD_METHOD(set_passwd_cb), + ADD_METHOD(set_default_verify_paths), + ADD_METHOD(use_certificate_chain_file), + ADD_METHOD(use_certificate_file), + ADD_METHOD(use_certificate), + ADD_METHOD(add_extra_chain_cert), + ADD_METHOD(use_privatekey_file), + ADD_METHOD(use_privatekey), + ADD_METHOD(check_privatekey), + ADD_METHOD(load_client_ca), + ADD_METHOD(set_session_id), + ADD_METHOD(set_verify), + ADD_METHOD(set_verify_depth), + ADD_METHOD(get_verify_mode), + ADD_METHOD(get_verify_depth), + ADD_METHOD(load_tmp_dh), + ADD_METHOD(set_cipher_list), + ADD_METHOD(set_client_ca_list), + ADD_METHOD(add_client_ca), + ADD_METHOD(set_timeout), + ADD_METHOD(get_timeout), + ADD_METHOD(set_info_callback), + ADD_METHOD(get_app_data), + ADD_METHOD(set_app_data), + ADD_METHOD(get_cert_store), + ADD_METHOD(set_options), + { NULL, NULL } +}; +#undef ADD_METHOD + +/* + * Despite the name which might suggest otherwise, this is not the tp_init for + * the Context type. It's just the common initialization code shared by the + * two _{Nn}ew functions below. + */ +static ssl_ContextObj* +ssl_Context_init(ssl_ContextObj *self, int i_method) { + SSL_METHOD *method; + + switch (i_method) { + case ssl_SSLv2_METHOD: + method = SSLv2_method(); + break; + case ssl_SSLv23_METHOD: + method = SSLv23_method(); + break; + case ssl_SSLv3_METHOD: + method = SSLv3_method(); + break; + case ssl_TLSv1_METHOD: + method = TLSv1_method(); + break; + default: + PyErr_SetString(PyExc_ValueError, "No such protocol"); + return NULL; + } + + self->ctx = SSL_CTX_new(method); + Py_INCREF(Py_None); + self->passphrase_callback = Py_None; + Py_INCREF(Py_None); + self->verify_callback = Py_None; + Py_INCREF(Py_None); + self->info_callback = Py_None; + + Py_INCREF(Py_None); + self->passphrase_userdata = Py_None; + + Py_INCREF(Py_None); + self->app_data = Py_None; + + /* Some initialization that's required to operate smoothly in Python */ + 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->tstate = NULL; + + return self; +} + +/* + * This one is exposed in the CObject API. I want to deprecate it. + */ +ssl_ContextObj* +ssl_Context_New(int i_method) { + ssl_ContextObj *self; + + self = PyObject_GC_New(ssl_ContextObj, &ssl_Context_Type); + if (self == NULL) { + return (ssl_ContextObj *)PyErr_NoMemory(); + } + self = ssl_Context_init(self, i_method); + PyObject_GC_Track((PyObject *)self); + return self; +} + + +/* + * This one is the tp_new of the Context type. It's great. + */ +static PyObject* +ssl_Context_new(PyTypeObject *subtype, PyObject *args, PyObject *kwargs) { + int i_method; + ssl_ContextObj *self; + static char *kwlist[] = {"method", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i:Context", kwlist, &i_method)) { + return NULL; + } + + self = (ssl_ContextObj *)subtype->tp_alloc(subtype, 1); + if (self == NULL) { + return NULL; + } + + return (PyObject *)ssl_Context_init(self, i_method); +} + +/* + * Call the visitproc on all contained objects. + * + * Arguments: self - The Context object + * visit - Function to call + * arg - Extra argument to visit + * Returns: 0 if all goes well, otherwise the return code from the first + * call that gave non-zero result. + */ +static int +ssl_Context_traverse(ssl_ContextObj *self, visitproc visit, void *arg) +{ + int ret = 0; + + if (ret == 0 && self->passphrase_callback != NULL) + ret = visit((PyObject *)self->passphrase_callback, arg); + if (ret == 0 && self->passphrase_userdata != NULL) + ret = visit((PyObject *)self->passphrase_userdata, arg); + if (ret == 0 && self->verify_callback != NULL) + ret = visit((PyObject *)self->verify_callback, arg); + if (ret == 0 && self->info_callback != NULL) + ret = visit((PyObject *)self->info_callback, arg); + if (ret == 0 && self->app_data != NULL) + ret = visit(self->app_data, arg); + return ret; +} + +/* + * Decref all contained objects and zero the pointers. + * + * Arguments: self - The Context object + * Returns: Always 0. + */ +static int +ssl_Context_clear(ssl_ContextObj *self) +{ + Py_XDECREF(self->passphrase_callback); + self->passphrase_callback = NULL; + Py_XDECREF(self->passphrase_userdata); + self->passphrase_userdata = NULL; + Py_XDECREF(self->verify_callback); + self->verify_callback = NULL; + Py_XDECREF(self->info_callback); + self->info_callback = NULL; + Py_XDECREF(self->app_data); + self->app_data = NULL; + return 0; +} + +/* + * Deallocate the memory used by the Context object + * + * Arguments: self - The Context object + * Returns: None + */ +static void +ssl_Context_dealloc(ssl_ContextObj *self) +{ + PyObject_GC_UnTrack((PyObject *)self); + SSL_CTX_free(self->ctx); + ssl_Context_clear(self); + PyObject_GC_Del(self); +} + + +PyTypeObject ssl_Context_Type = { + PyOpenSSL_HEAD_INIT(&PyType_Type, 0) + "OpenSSL.SSL.Context", + sizeof(ssl_ContextObj), + 0, + (destructor)ssl_Context_dealloc, /* tp_dealloc */ + NULL, /* print */ + NULL, /* tp_getattr */ + NULL, /* setattr */ + NULL, /* compare */ + NULL, /* repr */ + NULL, /* as_number */ + NULL, /* as_sequence */ + NULL, /* as_mapping */ + NULL, /* hash */ + NULL, /* call */ + NULL, /* str */ + NULL, /* getattro */ + NULL, /* setattro */ + NULL, /* as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE, /* tp_flags */ + ssl_Context_doc, /* tp_doc */ + (traverseproc)ssl_Context_traverse, /* tp_traverse */ + (inquiry)ssl_Context_clear, /* tp_clear */ + NULL, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + NULL, /* tp_iter */ + NULL, /* tp_iternext */ + ssl_Context_methods, /* tp_methods */ + NULL, /* tp_members */ + NULL, /* tp_getset */ + NULL, /* tp_base */ + NULL, /* tp_dict */ + NULL, /* tp_descr_get */ + NULL, /* tp_descr_set */ + 0, /* tp_dictoffset */ + NULL, /* tp_init */ + NULL, /* tp_alloc */ + ssl_Context_new, /* tp_new */ +}; + + +/* + * Initialize the Context part of the SSL sub module + * + * Arguments: dict - The OpenSSL.SSL module + * Returns: 1 for success, 0 otherwise + */ +int +init_ssl_context(PyObject *module) { + + if (PyType_Ready(&ssl_Context_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "Context", (PyObject *)&ssl_Context_Type) < 0) { + return 0; + } + + if (PyModule_AddObject(module, "ContextType", (PyObject *)&ssl_Context_Type) < 0) { + return 0; + } + + return 1; +} + diff --git a/OpenSSL/ssl/context.h b/OpenSSL/ssl/context.h new file mode 100644 index 0000000..21407f3 --- /dev/null +++ b/OpenSSL/ssl/context.h @@ -0,0 +1,42 @@ +/* + * context.h + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * Export SSL Context object data structures and functions. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + * + */ +#ifndef PyOpenSSL_SSL_CONTEXT_H_ +#define PyOpenSSL_SSL_CONTEXT_H_ + +#include +#include + +extern int init_ssl_context (PyObject *); + +extern PyTypeObject ssl_Context_Type; + +#define ssl_Context_Check(v) ((v)->ob_type == &ssl_Context_Type) + +typedef struct { + PyObject_HEAD + SSL_CTX *ctx; + PyObject *passphrase_callback, + *passphrase_userdata, + *verify_callback, + *info_callback, + *app_data; + PyThreadState *tstate; +} ssl_ContextObj; + +#define ssl_SSLv2_METHOD (1) +#define ssl_SSLv3_METHOD (2) +#define ssl_SSLv23_METHOD (3) +#define ssl_TLSv1_METHOD (4) + + +#endif diff --git a/OpenSSL/ssl/ssl.c b/OpenSSL/ssl/ssl.c new file mode 100644 index 0000000..50651a9 --- /dev/null +++ b/OpenSSL/ssl/ssl.c @@ -0,0 +1,252 @@ +/* + * ssl.c + * + * Copyright (C) AB Strakt + * Copyright (C) Jean-Paul Calderone + * See LICENSE for details. + * + * Main file of the SSL sub module. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + */ +#include + +#ifndef MS_WINDOWS +# include +# include +# if !(defined(__BEOS__) || defined(__CYGWIN__)) +# include +# endif +#else +# include +# include +#endif + +#define SSL_MODULE +#include "ssl.h" + +static char ssl_doc[] = "\n\ +Main file of the SSL sub module.\n\ +See the file RATIONALE for a short explanation of why this module was written.\n\ +"; + +crypto_X509Obj* (*new_x509)(X509*, int); +crypto_X509NameObj* (*new_x509name)(X509_NAME*, int); +crypto_X509StoreObj* (*new_x509store)(X509_STORE*, int); + + +#ifndef PY3 +void **crypto_API; +#endif + +int _pyOpenSSL_tstate_key; + +/* Exceptions defined by the SSL submodule */ +PyObject *ssl_Error, /* Base class */ + *ssl_ZeroReturnError, /* Used with SSL_get_error */ + *ssl_WantReadError, /* ... */ + *ssl_WantWriteError, /* ... */ + *ssl_WantX509LookupError, /* ... */ + *ssl_SysCallError; /* Uses (errno,errstr) */ + + +/* Methods in the OpenSSL.SSL module */ +static PyMethodDef ssl_methods[] = { + { NULL, NULL } +}; + +#ifdef PY3 +static struct PyModuleDef sslmodule = { + PyModuleDef_HEAD_INIT, + "SSL", + ssl_doc, + -1, + ssl_methods +}; +#endif + +/* + * Initialize SSL sub module + * + * Arguments: None + * Returns: None + */ +PyOpenSSL_MODINIT(SSL) { + PyObject *module; +#ifndef PY3 + static void *ssl_API[ssl_API_pointers]; + PyObject *ssl_api_object; + + import_crypto(); + + new_x509 = crypto_X509_New; + new_x509name = crypto_X509Name_New; + new_x509store = crypto_X509Store_New; +#else +# ifdef _WIN32 + HMODULE crypto = GetModuleHandle("crypto.pyd"); + if (crypto == NULL) { + PyErr_SetString(PyExc_RuntimeError, "Unable to get crypto module"); + PyOpenSSL_MODRETURN(NULL); + } + + new_x509 = (crypto_X509Obj* (*)(X509*, int))GetProcAddress(crypto, "crypto_X509_New"); + new_x509name = (crypto_X509NameObj* (*)(X509_NAME*, int))GetProcAddress(crypto, "crypto_X509Name_New"); + new_x509store = (crypto_X509StoreObj* (*)(X509_STORE*, int))GetProcAddress(crypto, "crypto_X509Store_New"); +# else + new_x509 = crypto_X509_New; + new_x509name = crypto_X509Name_New; + new_x509store = crypto_X509Store_New; +# endif +#endif + + SSL_library_init(); + ERR_load_SSL_strings(); + +#ifdef PY3 + module = PyModule_Create(&sslmodule); +#else + module = Py_InitModule3("SSL", ssl_methods, ssl_doc); +#endif + if (module == NULL) { + PyOpenSSL_MODRETURN(NULL); + } + +#ifndef PY3 + /* Initialize the C API pointer array */ + ssl_API[ssl_Context_New_NUM] = (void *)ssl_Context_New; + ssl_API[ssl_Connection_New_NUM] = (void *)ssl_Connection_New; + ssl_api_object = PyCObject_FromVoidPtr((void *)ssl_API, NULL); + if (ssl_api_object != NULL) + PyModule_AddObject(module, "_C_API", ssl_api_object); +#endif + + /* Exceptions */ +/* + * ADD_EXCEPTION(dict,name,base) expands to a correct Exception declaration, + * inserting OpenSSL.SSL.name into dict, derviving the exception from base. + */ +#define ADD_EXCEPTION(_name, _base) \ +do { \ + ssl_##_name = PyErr_NewException("OpenSSL.SSL."#_name, _base, NULL);\ + if (ssl_##_name == NULL) \ + goto error; \ + if (PyModule_AddObject(module, #_name, ssl_##_name) != 0) \ + goto error; \ +} while (0) + + ssl_Error = PyErr_NewException("OpenSSL.SSL.Error", NULL, NULL); + if (ssl_Error == NULL) + goto error; + if (PyModule_AddObject(module, "Error", ssl_Error) != 0) + goto error; + + ADD_EXCEPTION(ZeroReturnError, ssl_Error); + ADD_EXCEPTION(WantReadError, ssl_Error); + ADD_EXCEPTION(WantWriteError, ssl_Error); + ADD_EXCEPTION(WantX509LookupError, ssl_Error); + ADD_EXCEPTION(SysCallError, ssl_Error); +#undef ADD_EXCEPTION + + /* Method constants */ + PyModule_AddIntConstant(module, "SSLv2_METHOD", ssl_SSLv2_METHOD); + PyModule_AddIntConstant(module, "SSLv3_METHOD", ssl_SSLv3_METHOD); + PyModule_AddIntConstant(module, "SSLv23_METHOD", ssl_SSLv23_METHOD); + PyModule_AddIntConstant(module, "TLSv1_METHOD", ssl_TLSv1_METHOD); + + /* Verify constants */ + PyModule_AddIntConstant(module, "VERIFY_NONE", SSL_VERIFY_NONE); + PyModule_AddIntConstant(module, "VERIFY_PEER", SSL_VERIFY_PEER); + PyModule_AddIntConstant(module, "VERIFY_FAIL_IF_NO_PEER_CERT", + SSL_VERIFY_FAIL_IF_NO_PEER_CERT); + PyModule_AddIntConstant(module, "VERIFY_CLIENT_ONCE", + SSL_VERIFY_CLIENT_ONCE); + + /* File type constants */ + PyModule_AddIntConstant(module, "FILETYPE_PEM", SSL_FILETYPE_PEM); + PyModule_AddIntConstant(module, "FILETYPE_ASN1", SSL_FILETYPE_ASN1); + + /* SSL option constants */ + PyModule_AddIntConstant(module, "OP_SINGLE_DH_USE", SSL_OP_SINGLE_DH_USE); + PyModule_AddIntConstant(module, "OP_EPHEMERAL_RSA", SSL_OP_EPHEMERAL_RSA); + PyModule_AddIntConstant(module, "OP_NO_SSLv2", SSL_OP_NO_SSLv2); + PyModule_AddIntConstant(module, "OP_NO_SSLv3", SSL_OP_NO_SSLv3); + PyModule_AddIntConstant(module, "OP_NO_TLSv1", SSL_OP_NO_TLSv1); + + /* More SSL option constants */ + PyModule_AddIntConstant(module, "OP_MICROSOFT_SESS_ID_BUG", SSL_OP_MICROSOFT_SESS_ID_BUG); + PyModule_AddIntConstant(module, "OP_NETSCAPE_CHALLENGE_BUG", SSL_OP_NETSCAPE_CHALLENGE_BUG); + PyModule_AddIntConstant(module, "OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG", SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG); + PyModule_AddIntConstant(module, "OP_SSLREF2_REUSE_CERT_TYPE_BUG", SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG); + PyModule_AddIntConstant(module, "OP_MICROSOFT_BIG_SSLV3_BUFFER", SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER); + PyModule_AddIntConstant(module, "OP_MSIE_SSLV2_RSA_PADDING", SSL_OP_MSIE_SSLV2_RSA_PADDING); + PyModule_AddIntConstant(module, "OP_SSLEAY_080_CLIENT_DH_BUG", SSL_OP_SSLEAY_080_CLIENT_DH_BUG); + PyModule_AddIntConstant(module, "OP_TLS_D5_BUG", SSL_OP_TLS_D5_BUG); + PyModule_AddIntConstant(module, "OP_TLS_BLOCK_PADDING_BUG", SSL_OP_TLS_BLOCK_PADDING_BUG); + PyModule_AddIntConstant(module, "OP_DONT_INSERT_EMPTY_FRAGMENTS", SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS); + PyModule_AddIntConstant(module, "OP_ALL", SSL_OP_ALL); + PyModule_AddIntConstant(module, "OP_CIPHER_SERVER_PREFERENCE", SSL_OP_CIPHER_SERVER_PREFERENCE); + PyModule_AddIntConstant(module, "OP_TLS_ROLLBACK_BUG", SSL_OP_TLS_ROLLBACK_BUG); + PyModule_AddIntConstant(module, "OP_PKCS1_CHECK_1", SSL_OP_PKCS1_CHECK_1); + PyModule_AddIntConstant(module, "OP_PKCS1_CHECK_2", SSL_OP_PKCS1_CHECK_2); + PyModule_AddIntConstant(module, "OP_NETSCAPE_CA_DN_BUG", SSL_OP_NETSCAPE_CA_DN_BUG); + PyModule_AddIntConstant(module, "OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG", SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG); + + /* DTLS related options. The first two of these were introduced in + * 2005, the third in 2007. To accomodate systems which are still using + * older versions, make them optional. */ +#ifdef SSL_OP_NO_QUERY_MTU + PyModule_AddIntConstant(module, "OP_NO_QUERY_MTU", SSL_OP_NO_QUERY_MTU); +#endif +#ifdef SSL_OP_COOKIE_EXCHANGE + PyModule_AddIntConstant(module, "OP_COOKIE_EXCHANGE", SSL_OP_COOKIE_EXCHANGE); +#endif +#ifdef SSL_OP_NO_TICKET + PyModule_AddIntConstant(module, "OP_NO_TICKET", SSL_OP_NO_TICKET); +#endif + + /* For SSL_set_shutdown */ + PyModule_AddIntConstant(module, "SENT_SHUTDOWN", SSL_SENT_SHUTDOWN); + PyModule_AddIntConstant(module, "RECEIVED_SHUTDOWN", SSL_RECEIVED_SHUTDOWN); + + /* For set_info_callback */ + PyModule_AddIntConstant(module, "SSL_ST_CONNECT", SSL_ST_CONNECT); + PyModule_AddIntConstant(module, "SSL_ST_ACCEPT", SSL_ST_ACCEPT); + PyModule_AddIntConstant(module, "SSL_ST_MASK", SSL_ST_MASK); + PyModule_AddIntConstant(module, "SSL_ST_INIT", SSL_ST_INIT); + PyModule_AddIntConstant(module, "SSL_ST_BEFORE", SSL_ST_BEFORE); + PyModule_AddIntConstant(module, "SSL_ST_OK", SSL_ST_OK); + PyModule_AddIntConstant(module, "SSL_ST_RENEGOTIATE", SSL_ST_RENEGOTIATE); + PyModule_AddIntConstant(module, "SSL_CB_LOOP", SSL_CB_LOOP); + PyModule_AddIntConstant(module, "SSL_CB_EXIT", SSL_CB_EXIT); + PyModule_AddIntConstant(module, "SSL_CB_READ", SSL_CB_READ); + PyModule_AddIntConstant(module, "SSL_CB_WRITE", SSL_CB_WRITE); + PyModule_AddIntConstant(module, "SSL_CB_ALERT", SSL_CB_ALERT); + PyModule_AddIntConstant(module, "SSL_CB_READ_ALERT", SSL_CB_READ_ALERT); + PyModule_AddIntConstant(module, "SSL_CB_WRITE_ALERT", SSL_CB_WRITE_ALERT); + PyModule_AddIntConstant(module, "SSL_CB_ACCEPT_LOOP", SSL_CB_ACCEPT_LOOP); + PyModule_AddIntConstant(module, "SSL_CB_ACCEPT_EXIT", SSL_CB_ACCEPT_EXIT); + PyModule_AddIntConstant(module, "SSL_CB_CONNECT_LOOP", SSL_CB_CONNECT_LOOP); + PyModule_AddIntConstant(module, "SSL_CB_CONNECT_EXIT", SSL_CB_CONNECT_EXIT); + PyModule_AddIntConstant(module, "SSL_CB_HANDSHAKE_START", SSL_CB_HANDSHAKE_START); + PyModule_AddIntConstant(module, "SSL_CB_HANDSHAKE_DONE", SSL_CB_HANDSHAKE_DONE); + + if (!init_ssl_context(module)) + goto error; + if (!init_ssl_connection(module)) + goto error; + +#ifdef WITH_THREAD + /* + * Initialize this module's threading support structures. + */ + _pyOpenSSL_tstate_key = PyThread_create_key(); +#endif + + PyOpenSSL_MODRETURN(module); + +error: + PyOpenSSL_MODRETURN(NULL); + ; +} diff --git a/OpenSSL/ssl/ssl.h b/OpenSSL/ssl/ssl.h new file mode 100644 index 0000000..6a0a57e --- /dev/null +++ b/OpenSSL/ssl/ssl.h @@ -0,0 +1,76 @@ +/* + * ssl.h + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * Export functions and exceptions from the SSL sub module. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + * + */ +#ifndef PyOpenSSL_SSL_H_ +#define PyOpenSSL_SSL_H_ + +#include +#include +#include "context.h" +#include "connection.h" +#include "../util.h" +#include "../crypto/crypto.h" + +extern PyObject *ssl_Error, /* Base class */ + *ssl_ZeroReturnError, /* Used with SSL_get_erorr */ + *ssl_WantReadError, /* ... */ + *ssl_WantWriteError, /* ... */ + *ssl_WantX509LookupError, /* ... */ + *ssl_SysCallError; /* Uses (errno,errstr) */ + +#define ssl_Context_New_NUM 0 +#define ssl_Context_New_RETURN ssl_ContextObj * +#define ssl_Context_New_PROTO (int method) + +#define ssl_Connection_New_NUM 1 +#define ssl_Connection_New_RETURN ssl_ConnectionObj * +#define ssl_Connection_New_PROTO (ssl_ContextObj *ctx, PyObject *sock) + +#define ssl_API_pointers 2 + +#ifdef WITH_THREAD +extern int _pyOpenSSL_tstate_key; +#endif /* WITH_THREAD */ + +#ifdef SSL_MODULE + +extern ssl_Context_New_RETURN ssl_Context_New ssl_Context_New_PROTO; +extern ssl_Connection_New_RETURN ssl_Connection_New ssl_Connection_New_PROTO; + +extern crypto_X509Obj* (*new_x509)(X509*, int); +extern crypto_X509NameObj* (*new_x509name)(X509_NAME*, int); +extern crypto_X509StoreObj* (*new_x509store)(X509_STORE*, int); + +#else /* SSL_MODULE */ + +extern void **ssl_API; + +#define ssl_Context_New \ + (*(ssl_Context_New_RETURN (*)ssl_Context_New_PROTO) ssl_API[ssl_Context_New_NUM]) +#define ssl_Connection_New \ + (*(ssl_Connection_New_RETURN (*)ssl_Connection_New_PROTO) ssl_API[ssl_Connection_New_NUM]) + +#define import_SSL() \ +{ \ + PyObject *module = PyImport_ImportModule("OpenSSL.SSL"); \ + if (module != NULL) { \ + PyObject *module_dict = PyModule_GetDict(module); \ + PyObject *c_api_object = PyDict_GetItemString(module_dict, "_C_API"); \ + if (PyCObject_Check(c_api_object)) { \ + ssl_API = (void **)PyCObject_AsVoidPtr(c_api_object); \ + } \ + } \ +} + +#endif /* SSL_MODULE */ + +#endif /* PyOpenSSL_SSL_H_ */ diff --git a/OpenSSL/test/__init__.py b/OpenSSL/test/__init__.py new file mode 100644 index 0000000..ccb4e9a --- /dev/null +++ b/OpenSSL/test/__init__.py @@ -0,0 +1,6 @@ +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. + +""" +Package containing unit tests for L{OpenSSL}. +""" diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py new file mode 100644 index 0000000..661ee53 --- /dev/null +++ b/OpenSSL/test/test_crypto.py @@ -0,0 +1,2580 @@ +# Copyright (c) Jean-Paul Calderone +# See LICENSE file for details. + +""" +Unit tests for L{OpenSSL.crypto}. +""" + +from unittest import main + +import os, re +from subprocess import PIPE, Popen +from datetime import datetime, timedelta + +from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType +from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType +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 +from OpenSSL.test.util import TestCase, bytes, b + + +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----- +""") + +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 = 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----- +""") + +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 = 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 = 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----- +""") + +crlData = b("""\ +-----BEGIN X509 CRL----- +MIIBWzCBxTANBgkqhkiG9w0BAQQFADBYMQswCQYDVQQGEwJVUzELMAkGA1UECBMC +SUwxEDAOBgNVBAcTB0NoaWNhZ28xEDAOBgNVBAoTB1Rlc3RpbmcxGDAWBgNVBAMT +D1Rlc3RpbmcgUm9vdCBDQRcNMDkwNzI2MDQzNDU2WhcNMTIwOTI3MDI0MTUyWjA8 +MBUCAgOrGA8yMDA5MDcyNTIzMzQ1NlowIwICAQAYDzIwMDkwNzI1MjMzNDU2WjAM +MAoGA1UdFQQDCgEEMA0GCSqGSIb3DQEBBAUAA4GBAEBt7xTs2htdD3d4ErrcGAw1 +4dKcVnIWTutoI7xxen26Wwvh8VCsT7i/UeP+rBl9rC/kfjWjzQk3/zleaarGTpBT +0yp4HXRFFoRhhSE/hP+eteaPXRgrsNRLHe9ZDd69wmh7J1wMDb0m81RG7kqcbsid +vrzEeLDRiiPl92dyyWmu +-----END X509 CRL----- +""") + +class X509ExtTests(TestCase): + """ + Tests for L{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). + """ + # 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 test_str(self): + """ + The string representation of L{X509Extension} instances as returned by + C{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): + """ + L{X509Extension} and L{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): + """ + L{X509Extension} accepts an extension type name, a critical flag, + and an extension value and returns an L{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): + """ + L{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): + """ + L{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): + """ + L{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): + """ + L{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): + """ + L{X509Extension.get_data} raises L{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 C{subject} parameter to L{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 C{subject} parameter to + L{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 C{subject} parameter is + given no value, something happens. + """ + self.assertRaises( + Error, X509Extension, b('subjectKeyIdentifier'), False, b('hash')) + + + def test_invalid_subject(self): + """ + If the C{subject} parameter is given a value which is not an L{X509} + instance, L{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 C{issuer} parameter to L{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 a issuer, the C{issuer} parameter to + L{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 C{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 C{issuer} parameter is given a value which is not an L{X509} + instance, L{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 L{OpenSSL.crypto.PKey}. + """ + def test_type(self): + """ + L{PKey} and L{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): + """ + L{PKey} takes no arguments and returns a new L{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): + """ + L{PKeyType.bits} and L{PKeyType.type} return C{0} before the key is + generated. + """ + key = PKey() + self.assertEqual(key.type(), 0) + self.assertEqual(key.bits(), 0) + + + def test_failedGeneration(self): + """ + L{PKeyType.generate_key} takes two arguments, the first giving the key + type as one of L{TYPE_RSA} or L{TYPE_DSA} and the second giving the + number of bits to generate. If an invalid type is specified or + generation fails, L{Error} is raised. If an invalid number of bits is + specified, L{ValueError} or L{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): + """ + L{PKeyType.generate_key} generates an RSA key when passed + L{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) + + + def test_dsaGeneration(self): + """ + L{PKeyType.generate_key} generates a DSA key when passed + L{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) + + + def test_regeneration(self): + """ + L{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) + + + +class X509NameTests(TestCase): + """ + Unit tests for L{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 L{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_attributes(self): + """ + L{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): + """ + L{X509Name} creates a new L{X509NameType} instance with all the same + attributes as an existing L{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): + """ + L{repr} passed an L{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): + """ + L{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): + """ + L{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): + """ + L{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): + """ + L{X509Name.get_components} returns a C{list} of two-tuples of C{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"))]) + + +class _PKeyInteractionTestsMixin: + """ + Tests which involve another thing and a PKey. + """ + def signable(self): + """ + Return something with a C{set_pubkey}, C{set_pubkey}, and C{sign} method. + """ + raise NotImplementedError() + + + def test_signWithUngenerated(self): + """ + L{X509Req.sign} raises L{ValueError} when pass a L{PKey} with no parts. + """ + request = self.signable() + key = PKey() + self.assertRaises(ValueError, request.sign, key, 'MD5') + + + def test_signWithPublicKey(self): + """ + L{X509Req.sign} raises L{ValueError} when pass a L{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, 'MD5') + + + def test_signWithUnknownDigest(self): + """ + L{X509Req.sign} raises L{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, "monkeys") + + + def test_sign(self): + """ + L{X509Req.sign} succeeds when passed a private key object and a valid + digest function. C{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, 'MD5') + # 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 L{OpenSSL.crypto.X509Req}. + """ + def signable(self): + """ + Create and return a new L{X509Req}. + """ + return X509Req() + + + def test_type(self): + """ + L{X509Req} and L{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): + """ + L{X509Req} takes no arguments and returns an L{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): + """ + L{X509ReqType.set_version} sets the X.509 version of the certificate + request. L{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): + """ + L{X509ReqType.set_version} raises L{TypeError} if called with the wrong + number of arguments or with a non-C{int} argument. + L{X509ReqType.get_version} raises L{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): + """ + L{X509ReqType.get_subject} returns an L{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): + """ + L{X509ReqType.get_subject} raises L{TypeError} if called with any + arguments. + """ + request = X509Req() + self.assertRaises(TypeError, request.get_subject, None) + + + def test_add_extensions(self): + """ + L{X509Req.add_extensions} accepts a C{list} of L{X509Extension} + instances and adds them to the X509 request. + """ + request = X509Req() + request.add_extensions([ + X509Extension(b('basicConstraints'), True, b('CA:false'))]) + # XXX Add get_extensions so the rest of this unit test can be written. + + + def test_add_extensions_wrong_args(self): + """ + L{X509Req.add_extensions} raises L{TypeError} if called with the wrong + number of arguments or with a non-C{list}. Or it raises L{ValueError} + if called with a C{list} containing objects other than L{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) + + + +class X509Tests(TestCase, _PKeyInteractionTestsMixin): + """ + Tests for L{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 L{X509}. + """ + return X509() + + + def test_type(self): + """ + L{X509} and L{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): + """ + L{X509} takes no arguments and returns an instance of L{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): + """ + L{X509.get_version} raises L{TypeError} if invoked with any arguments. + """ + cert = X509() + self.assertRaises(TypeError, cert.get_version, None) + + + def test_set_version_wrong_args(self): + """ + L{X509.set_version} raises L{TypeError} if invoked with the wrong number + of arguments or an argument not of type C{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): + """ + L{X509.set_version} sets the certificate version number. + L{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): + """ + L{X509.get_serial_number} raises L{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 L{X509Type} can be retrieved and modified with + L{X509Type.get_serial_number} and L{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): + """ + L{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): + """ + L{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): + """ + L{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): + """ + L{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): + """ + L{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): + """ + L{X509Type.gmtime_adj_notBefore} raises L{TypeError} if called with the + wrong number of arguments or a non-C{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): + """ + L{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): + """ + L{X509Type.gmtime_adj_notAfter} raises L{TypeError} if called with the + wrong number of arguments or a non-C{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): + """ + L{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): + """ + L{X509Type.has_expired} raises L{TypeError} if called with any + arguments. + """ + cert = X509() + self.assertRaises(TypeError, cert.has_expired, None) + + + def test_has_expired(self): + """ + L{X509Type.has_expired} returns C{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): + """ + L{X509Type.has_expired} returns C{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): + """ + L{X509.digest} returns a string giving ":"-separated hex-encoded words + of the digest of the certificate. + """ + cert = X509() + self.assertEqual( + 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): + """ + L{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): + """ + L{X509.get_extension} takes an integer and returns an L{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_invalid_digest_algorithm(self): + """ + L{X509.digest} raises L{ValueError} if called with an unrecognized hash + algorithm. + """ + cert = X509() + self.assertRaises(ValueError, cert.digest, "monkeys") + + + def test_get_subject_wrong_args(self): + """ + L{X509.get_subject} raises L{TypeError} if called with any arguments. + """ + cert = X509() + self.assertRaises(TypeError, cert.get_subject, None) + + + def test_get_subject(self): + """ + L{X509.get_subject} returns an L{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): + """ + L{X509.set_subject} raises a L{TypeError} if called with the wrong + number of arguments or an argument not of type L{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): + """ + L{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): + """ + L{X509.get_issuer} raises L{TypeError} if called with any arguments. + """ + cert = X509() + self.assertRaises(TypeError, cert.get_issuer, None) + + + def test_get_issuer(self): + """ + L{X509.get_issuer} returns an L{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): + """ + L{X509.set_issuer} raises a L{TypeError} if called with the wrong + number of arguments or an argument not of type L{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): + """ + L{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, L{X509.get_pubkey} + raises L{OpenSSL.crypto.Error}. + """ + cert = X509() + self.assertRaises(Error, cert.get_pubkey) + + + def test_subject_name_hash_wrong_args(self): + """ + L{X509.subject_name_hash} raises L{TypeError} if called with any + arguments. + """ + cert = X509() + self.assertRaises(TypeError, cert.subject_name_hash, None) + + + def test_subject_name_hash(self): + """ + L{X509.subject_name_hash} returns the hash of the certificate's subject + name. + """ + cert = load_certificate(FILETYPE_PEM, self.pemData) + self.assertEquals(cert.subject_name_hash(), 3350047874) + + + +class PKCS12Tests(TestCase): + """ + Test for L{OpenSSL.crypto.PKCS12} and L{OpenSSL.crypto.load_pkcs12}. + """ + pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM + + def test_type(self): + """ + L{PKCS12Type} is a type object. + """ + self.assertIdentical(PKCS12, PKCS12Type) + self.assertConsistentType(PKCS12, 'PKCS12') + + + def test_empty_construction(self): + """ + L{PKCS12} returns a new instance of L{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 L{PKCS12} setter functions (C{set_certificate}, C{set_privatekey}, + C{set_ca_certificates}, and C{set_friendlyname}) raise L{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 L{PKCS12} with only a private key can be exported using + L{PKCS12.export} and loaded again using L{load_pkcs12}. + """ + passwd = '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 L{PKCS12} with only a certificate can be exported using + L{PKCS12.export} and loaded again using L{load_pkcs12}. + """ + passwd = '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='', + extra=()): + """ + Use openssl program to confirm three components are recoverable from a + PKCS12 string. + """ + if key: + recovered_key = _runopenssl( + p12_str, "pkcs12", '-nocerts', '-nodes', '-passin', + 'pass:' + passwd, *extra) + self.assertEqual(recovered_key[-len(key):], key) + if cert: + recovered_cert = _runopenssl( + p12_str, "pkcs12", '-clcerts', '-nodes', '-passin', + 'pass:' + passwd, '-nokeys', *extra) + self.assertEqual(recovered_cert[-len(cert):], cert) + if ca: + recovered_cert = _runopenssl( + p12_str, "pkcs12", '-cacerts', '-nodes', '-passin', + 'pass:' + passwd, '-nokeys', *extra) + self.assertEqual(recovered_cert[-len(ca):], ca) + + + def test_load_pkcs12(self): + """ + A PKCS12 string generated using the openssl command line can be loaded + with L{load_pkcs12} and its components extracted and examined. + """ + passwd = 'whatever' + pem = client_key_pem + client_cert_pem + p12_str = _runopenssl( + pem, "pkcs12", '-export', '-clcerts', '-passout', 'pass:' + passwd) + p12 = load_pkcs12(p12_str, passwd) + # verify + self.assertTrue(isinstance(p12, PKCS12)) + cert_pem = dump_certificate(FILETYPE_PEM, p12.get_certificate()) + self.assertEqual(cert_pem, client_cert_pem) + key_pem = dump_privatekey(FILETYPE_PEM, p12.get_privatekey()) + self.assertEqual(key_pem, client_key_pem) + self.assertEqual(None, p12.get_ca_certificates()) + + + def test_load_pkcs12_garbage(self): + """ + L{load_pkcs12} raises L{OpenSSL.crypto.Error} when passed a string + which is not a PKCS12 dump. + """ + passwd = 'whatever' + e = self.assertRaises(Error, load_pkcs12, '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): + """ + L{PKCS12.set_certificate} replaces the certificate in a PKCS12 cluster. + L{PKCS12.set_privatekey} replaces the private key. + L{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 I{friendlyName} of a PKCS12 can be set and retrieved via + L{PKCS12.get_friendlyname} and L{PKCS12_set_friendlyname}, and a + L{PKCS12} with a friendly name set can be dumped with L{PKCS12.export}. + """ + passwd = '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 = '' + 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 C{None} to L{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 C{maciter} of C{-1} excludes the MAC + entirely. + """ + passwd = '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=('-nomacver',)) + + + def test_load_without_mac(self): + """ + Loading a PKCS12 without a MAC does something other than crash. + """ + passwd = '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 L{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='') + + + def test_key_cert_mismatch(self): + """ + L{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(r'(\\*)"') +_cmdLineQuoteRe2 = re.compile(r'(\\+)\Z') +def cmdLineQuote(s): + """ + Internal method for quoting a single command-line argument. + + @type: C{str} + @param s: A single unquoted string to quote for something that is expecting + cmd.exe-style quoting + + @rtype: C{str} + @return: A cmd.exe-style quoted string + + @see: U{http://www.perlmonks.org/?node_id=764004} + """ + s = _cmdLineQuoteRe2.sub(r"\1\1", _cmdLineQuoteRe.sub(r'\1\1\\"', s)) + return '"%s"' % s + + + +def quoteArguments(arguments): + """ + Quote an iterable of command-line arguments for passing to CreateProcess or + a similar API. This allows the list passed to C{reactor.spawnProcess} to + match the child process's C{sys.argv} properly. + + @type arguments: C{iterable} of C{str} + @param arguments: An iterable of unquoted arguments to quote + + @rtype: C{str} + @return: A space-delimited string containing quoted versions of L{arguments} + """ + return ' '.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 = "openssl " + " ".join([ + "'%s'" % (arg.replace("'", "'\\''"),) for arg in args]) + else: + command = "openssl " + quoteArguments(args) + proc = Popen(command, shell=True, stdin=PIPE, stdout=PIPE) + proc.stdin.write(pem) + proc.stdin.close() + return proc.stdout.read() + + + +class FunctionTests(TestCase): + """ + Tests for free-functions in the L{OpenSSL.crypto} module. + """ + + def test_load_privatekey_invalid_format(self): + """ + L{load_privatekey} raises L{ValueError} if passed an unknown filetype. + """ + self.assertRaises(ValueError, load_privatekey, 100, root_key_pem) + + + def test_load_privatekey_invalid_passphrase_type(self): + """ + L{load_privatekey} raises L{TypeError} if passed a passphrase that is + neither a c{str} nor a callable. + """ + self.assertRaises( + TypeError, + load_privatekey, + FILETYPE_PEM, encryptedPrivateKeyPEMPassphrase, object()) + + + def test_load_privatekey_wrong_args(self): + """ + L{load_privatekey} raises L{TypeError} if called with the wrong number + of arguments. + """ + self.assertRaises(TypeError, load_privatekey) + + + def test_load_privatekey_wrongPassphrase(self): + """ + L{load_privatekey} raises L{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_passphrase(self): + """ + L{load_privatekey} can create a L{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_wrongPassphraseCallback(self): + """ + L{load_privatekey} raises L{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 "quack" + self.assertRaises( + Error, + load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb) + self.assertTrue(called) + + + def test_load_privatekey_passphraseCallback(self): + """ + L{load_privatekey} can create a L{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_exception(self): + """ + An exception raised by the passphrase callback passed to + L{load_privatekey} causes L{OpenSSL.crypto.Error} to be raised. + + This isn't as nice as just letting the exception pass through. The + behavior might be changed to that eventually. + """ + def broken(ignored): + raise RuntimeError("This is not working.") + self.assertRaises( + Error, + load_privatekey, + FILETYPE_PEM, encryptedPrivateKeyPEM, broken) + + + def test_dump_privatekey_wrong_args(self): + """ + L{dump_privatekey} raises L{TypeError} if called with the wrong number + of arguments. + """ + self.assertRaises(TypeError, dump_privatekey) + + + def test_dump_privatekey_unknown_cipher(self): + """ + L{dump_privatekey} raises L{ValueError} if called with an unrecognized + cipher name. + """ + key = PKey() + key.generate_key(TYPE_RSA, 512) + self.assertRaises( + ValueError, dump_privatekey, + FILETYPE_PEM, key, "zippers", "passphrase") + + + def test_dump_privatekey_invalid_passphrase_type(self): + """ + L{dump_privatekey} raises L{TypeError} if called with a passphrase which + is neither a C{str} nor a callable. + """ + key = PKey() + key.generate_key(TYPE_RSA, 512) + self.assertRaises( + TypeError, + dump_privatekey, FILETYPE_PEM, key, "blowfish", object()) + + + def test_dump_privatekey_invalid_filetype(self): + """ + L{dump_privatekey} raises L{ValueError} if called with an unrecognized + filetype. + """ + key = PKey() + key.generate_key(TYPE_RSA, 512) + self.assertRaises(ValueError, dump_privatekey, 100, key) + + + def test_dump_privatekey_passphrase(self): + """ + L{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, "blowfish", passphrase) + self.assertTrue(isinstance(pem, bytes)) + 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_certificate(self): + """ + L{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, "x509", "-outform", "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, "x509", "-noout", "-text") + self.assertEqual(dumped_text, good_text) + + + def test_dump_privatekey(self): + """ + L{dump_privatekey} writes a PEM, DER, and text. + """ + key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + dumped_pem = dump_privatekey(FILETYPE_PEM, key) + self.assertEqual(dumped_pem, cleartextPrivateKeyPEM) + dumped_der = dump_privatekey(FILETYPE_ASN1, key) + # XXX This OpenSSL call writes "writing RSA key" to standard out. Sad. + good_der = _runopenssl(dumped_pem, "rsa", "-outform", "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) + dumped_text = dump_privatekey(FILETYPE_TEXT, key) + good_text = _runopenssl(dumped_pem, "rsa", "-noout", "-text") + self.assertEqual(dumped_text, good_text) + + + def test_dump_certificate_request(self): + """ + L{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, "req", "-outform", "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, "req", "-noout", "-text") + self.assertEqual(dumped_text, good_text) + self.assertRaises(ValueError, dump_certificate_request, 100, req) + + + def test_dump_privatekey_passphraseCallback(self): + """ + L{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, "blowfish", cb) + self.assertTrue(isinstance(pem, bytes)) + 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_load_pkcs7_data(self): + """ + L{load_pkcs7_data} accepts a PKCS#7 string and returns an instance of + L{PKCS7Type}. + """ + pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) + self.assertTrue(isinstance(pkcs7, PKCS7Type)) + + + +class PKCS7Tests(TestCase): + """ + Tests for L{PKCS7Type}. + """ + def test_type(self): + """ + L{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): + """ + L{PKCS7Type.type_is_signed} raises L{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): + """ + L{PKCS7Type.type_is_signed} returns C{True} if the PKCS7 object is of + the type I{signed}. + """ + pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) + self.assertTrue(pkcs7.type_is_signed()) + + + def test_type_is_enveloped_wrong_args(self): + """ + L{PKCS7Type.type_is_enveloped} raises L{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): + """ + L{PKCS7Type.type_is_enveloped} returns C{False} if the PKCS7 object is + not of the type I{enveloped}. + """ + pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) + self.assertFalse(pkcs7.type_is_enveloped()) + + + def test_type_is_signedAndEnveloped_wrong_args(self): + """ + L{PKCS7Type.type_is_signedAndEnveloped} raises L{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): + """ + L{PKCS7Type.type_is_signedAndEnveloped} returns C{False} if the PKCS7 + object is not of the type I{signed and enveloped}. + """ + pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) + self.assertFalse(pkcs7.type_is_signedAndEnveloped()) + + + def test_type_is_data(self): + """ + L{PKCS7Type.type_is_data} returns C{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): + """ + L{PKCS7Type.type_is_data} raises L{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): + """ + L{PKCS7Type.get_type_name} raises L{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): + """ + L{PKCS7Type.get_type_name} returns a C{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 L{PKCS7Type}, L{AttributeError} is raised. + """ + pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) + self.assertRaises(AttributeError, getattr, pkcs7, "foo") + + + +class NetscapeSPKITests(TestCase, _PKeyInteractionTestsMixin): + """ + Tests for L{OpenSSL.crypto.NetscapeSPKI}. + """ + def signable(self): + """ + Return a new L{NetscapeSPKI} for use with signing tests. + """ + return NetscapeSPKI() + + + def test_type(self): + """ + L{NetscapeSPKI} and L{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): + """ + L{NetscapeSPKI} returns an instance of L{NetscapeSPKIType}. + """ + nspki = NetscapeSPKI() + self.assertTrue(isinstance(nspki, NetscapeSPKIType)) + + + def test_invalid_attribute(self): + """ + Accessing a non-existent attribute of a L{NetscapeSPKI} instance causes + an L{AttributeError} to be raised. + """ + nspki = NetscapeSPKI() + self.assertRaises(AttributeError, lambda: nspki.foo) + + + def test_b64_encode(self): + """ + L{NetscapeSPKI.b64_encode} encodes the certificate to a base64 blob. + """ + nspki = NetscapeSPKI() + blob = nspki.b64_encode() + self.assertTrue(isinstance(blob, bytes)) + + + +class RevokedTests(TestCase): + """ + Tests for L{OpenSSL.crypto.Revoked} + """ + def test_construction(self): + """ + Confirm we can create L{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 L{OpenSSL.crypto.Revoked} with any arguments results + in a L{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 + L{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 + L{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 + L{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 L{OpenSSL.crypto.Revoked.set_reason} with other than + one argument, or an argument which isn't a valid reason, + results in L{TypeError} or L{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 L{OpenSSL.crypto.Revoked.get_reason} with any + arguments results in L{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 L{OpenSSL.crypto.CRL} + """ + cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) + pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) + + def test_construction(self): + """ + Confirm we can create L{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 L{OpenSSL.crypto.CRL} with any number of arguments + results in a L{TypeError} being raised. + """ + self.assertRaises(TypeError, CRL, 1) + self.assertRaises(TypeError, CRL, "") + self.assertRaises(TypeError, CRL, None) + + + def test_export(self): + """ + Use python to create a simple CRL with a revocation, and export + the CRL in formats of PEM, DER and text. Those outputs are verified + with the openssl program. + """ + 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) + + # PEM format + dumped_crl = crl.export(self.cert, self.pkey, days=20) + text = _runopenssl(dumped_crl, "crl", "-noout", "-text") + 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')) + + # DER format + dumped_crl = crl.export(self.cert, self.pkey, FILETYPE_ASN1) + text = _runopenssl(dumped_crl, "crl", "-noout", "-text", "-inform", "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')) + + # text format + dumped_text = crl.export(self.cert, self.pkey, type=FILETYPE_TEXT) + self.assertEqual(text, dumped_text) + + + def test_add_revoked_keyword(self): + """ + L{OpenSSL.CRL.add_revoked} accepts its single argument as the + I{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 L{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 L{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, "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 L{OpenSSL.CRL.export} with a file type other than + L{FILETYPE_PEM}, L{FILETYPE_ASN1}, or L{FILETYPE_TEXT} results + in a L{ValueError} being raised. + """ + crl = CRL() + self.assertRaises(ValueError, crl.export, self.cert, self.pkey, 100, 10) + + + def test_get_revoked(self): + """ + Use python to create a simple CRL with two revocations. + Get back the L{Revoked} using L{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 L{OpenSSL.CRL.get_revoked} with any arguments results + in a L{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 L{OpenSSL.CRL.add_revoked} with other than one + argument results in a L{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, "crl", "-outform", "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 L{OpenSSL.crypto.load_crl} with other than two + arguments results in a L{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 L{OpenSSL.crypto.load_crl} with an unknown file type + raises a L{ValueError}. + """ + self.assertRaises(ValueError, load_crl, 100, crlData) + + + def test_load_crl_bad_data(self): + """ + Calling L{OpenSSL.crypto.load_crl} with file data which can't + be loaded raises a L{OpenSSL.crypto.Error}. + """ + self.assertRaises(Error, load_crl, FILETYPE_PEM, "hello, world") + + +class SignVerifyTests(TestCase): + """ + Tests for L{OpenSSL.crypto.sign} and L{OpenSSL.crypto.verify}. + """ + def test_sign_verify(self): + """ + L{sign} generates a cryptographic signature which L{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") + + +if __name__ == '__main__': + main() diff --git a/OpenSSL/test/test_rand.py b/OpenSSL/test/test_rand.py new file mode 100644 index 0000000..00fc6d1 --- /dev/null +++ b/OpenSSL/test/test_rand.py @@ -0,0 +1,182 @@ +# Copyright (c) Frederick Dean +# See LICENSE for details. + +""" +Unit tests for L{OpenSSL.rand}. +""" + +from unittest import main +import os +import stat + +from OpenSSL.test.util import TestCase, b +from OpenSSL import rand + + +class RandTests(TestCase): + def test_bytes_wrong_args(self): + """ + L{OpenSSL.rand.bytes} raises L{TypeError} if called with the wrong + number of arguments or with a non-C{int} argument. + """ + self.assertRaises(TypeError, rand.bytes) + self.assertRaises(TypeError, rand.bytes, None) + self.assertRaises(TypeError, rand.bytes, 3, None) + + # XXX Test failure of the malloc() in rand_bytes. + + 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 C{str} and C{int}, L{OpenSSL.rand.add} raises L{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): + """ + L{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-C{str} + argument, L{OpenSSL.rand.seed} raises L{TypeError}. + """ + self.assertRaises(TypeError, rand.seed) + self.assertRaises(TypeError, rand.seed, None) + self.assertRaises(TypeError, rand.seed, b("foo"), None) + + + def test_seed(self): + """ + L{OpenSSL.rand.seed} adds entropy to the PRNG. + """ + rand.seed(b('milk shake')) + + + def test_status_wrong_args(self): + """ + L{OpenSSL.rand.status} raises L{TypeError} when called with any + arguments. + """ + self.assertRaises(TypeError, rand.status, None) + + + def test_status(self): + """ + L{OpenSSL.rand.status} returns C{True} if the PRNG has sufficient + entropy, C{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): + """ + L{OpenSSL.rand.egd} raises L{TypeError} when called with the wrong + number of arguments or with arguments not of type C{str} and C{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): + """ + L{OpenSSL.rand.egd} returns C{0} or C{-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_cleanup_wrong_args(self): + """ + L{OpenSSL.rand.cleanup} raises L{TypeError} when called with any + arguments. + """ + self.assertRaises(TypeError, rand.cleanup, None) + + + def test_cleanup(self): + """ + L{OpenSSL.rand.cleanup} releases the memory used by the PRNG and returns + C{None}. + """ + self.assertIdentical(rand.cleanup(), None) + + + def test_load_file_wrong_args(self): + """ + L{OpenSSL.rand.load_file} raises L{TypeError} when called the wrong + number of arguments or arguments not of type C{str} and C{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): + """ + L{OpenSSL.rand.write_file} raises L{TypeError} when called with the + wrong number of arguments or a non-C{str} argument. + """ + self.assertRaises(TypeError, rand.write_file) + self.assertRaises(TypeError, rand.write_file, None) + self.assertRaises(TypeError, rand.write_file, "foo", None) + + + def test_files(self): + """ + Test reading and writing of files via rand functions. + """ + # Write random bytes to a file + tmpfile = self.mktemp() + # Make sure it exists (so cleanup definitely succeeds) + fObj = open(tmpfile, 'w') + fObj.close() + try: + rand.write_file(tmpfile) + # Verify length of written file + size = os.stat(tmpfile)[stat.ST_SIZE] + self.assertEquals(size, 1024) + # Read random bytes from file + rand.load_file(tmpfile) + rand.load_file(tmpfile, 4) # specify a length + finally: + # Cleanup + os.unlink(tmpfile) + + +if __name__ == '__main__': + main() diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py new file mode 100644 index 0000000..487266d --- /dev/null +++ b/OpenSSL/test/test_ssl.py @@ -0,0 +1,1710 @@ +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. + +""" +Unit tests for L{OpenSSL.SSL}. +""" + +from errno import ECONNREFUSED, EINPROGRESS, EWOULDBLOCK +from sys import platform +from socket import error, socket +from os import makedirs +from os.path import join +from unittest import main + +from OpenSSL.crypto import TYPE_RSA, FILETYPE_PEM, FILETYPE_ASN1 +from OpenSSL.crypto import PKey, X509, X509Extension +from OpenSSL.crypto import dump_privatekey, load_privatekey +from OpenSSL.crypto import dump_certificate, load_certificate + +from OpenSSL.SSL import SENT_SHUTDOWN, RECEIVED_SHUTDOWN +from OpenSSL.SSL import SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD +from OpenSSL.SSL import OP_NO_SSLv2, OP_NO_SSLv3, OP_SINGLE_DH_USE +from OpenSSL.SSL import VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_CLIENT_ONCE +from OpenSSL.SSL import Error, SysCallError, WantReadError, ZeroReturnError +from OpenSSL.SSL import Context, ContextType, Connection, ConnectionType + +from OpenSSL.test.util import TestCase, bytes, b +from OpenSSL.test.test_crypto import cleartextCertificatePEM, cleartextPrivateKeyPEM +from OpenSSL.test.test_crypto import client_cert_pem, client_key_pem +from OpenSSL.test.test_crypto import 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 + +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 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) + + +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 _loopback(self): + (server, client) = socket_pair() + + 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, server) + server.set_accept_state() + client = Connection(Context(TLSv1_METHOD), client) + client.set_connect_state() + + 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 L{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 C{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: + bytes = 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, bytes) + + 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) + + + +class ContextTests(TestCase, _LoopbackMixin): + """ + Unit tests for L{OpenSSL.SSL.Context}. + """ + def test_method(self): + """ + L{Context} can be instantiated with one of L{SSLv2_METHOD}, + L{SSLv3_METHOD}, L{SSLv23_METHOD}, or L{TLSv1_METHOD}. + """ + for meth in [SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD]: + Context(meth) + self.assertRaises(TypeError, Context, "") + self.assertRaises(ValueError, Context, 10) + + + def test_type(self): + """ + L{Context} and L{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): + """ + L{Context.use_privatekey} takes an L{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_set_app_data_wrong_args(self): + """ + L{Context.set_app_data} raises L{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): + """ + L{Context.get_app_data} raises L{TypeError} if called with any + arguments. + """ + context = Context(TLSv1_METHOD) + self.assertRaises(TypeError, context.get_app_data, None) + + + def test_app_data(self): + """ + L{Context.set_app_data} stores an object for later retrieval using + L{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): + """ + L{Context.set_options} raises L{TypeError} if called with the wrong + number of arguments or a non-C{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_timeout_wrong_args(self): + """ + L{Context.set_timeout} raises L{TypeError} if called with the wrong + number of arguments or a non-C{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): + """ + L{Context.get_timeout} raises L{TypeError} if called with any arguments. + """ + context = Context(TLSv1_METHOD) + self.assertRaises(TypeError, context.get_timeout, None) + + + def test_timeout(self): + """ + L{Context.set_timeout} sets the session timeout for all connections + created using the context object. L{Context.get_timeout} retrieves this + value. + """ + context = Context(TLSv1_METHOD) + context.set_timeout(1234) + self.assertEquals(context.get_timeout(), 1234) + + + def test_set_verify_depth_wrong_args(self): + """ + L{Context.set_verify_depth} raises L{TypeError} if called with the wrong + number of arguments or a non-C{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): + """ + L{Context.get_verify_depth} raises L{TypeError} if called with any arguments. + """ + context = Context(TLSv1_METHOD) + self.assertRaises(TypeError, context.get_verify_depth, None) + + + def test_verify_depth(self): + """ + L{Context.set_verify_depth} sets the number of certificates in a chain + to follow before giving up. The value can be retrieved with + L{Context.get_verify_depth}. + """ + context = Context(TLSv1_METHOD) + context.set_verify_depth(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): + """ + L{Context.set_passwd_cb} raises L{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): + """ + L{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): + """ + L{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): + """ + L{Context.use_privatekey_file} raises L{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 None + + context = Context(TLSv1_METHOD) + context.set_passwd_cb(passphraseCallback) + self.assertRaises(Error, context.use_privatekey_file, pemFile) + + + def test_passwd_callback_non_string(self): + """ + L{Context.use_privatekey_file} raises L{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(Error, 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): + """ + L{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() + + while not called: + for ssl in clientSSL, serverSSL: + try: + ssl.do_handshake() + except WantReadError: + pass + + # Kind of lame. Just make sure it got called somehow. + self.assertTrue(called) + + + def _load_verify_locations_test(self, *args): + """ + Create a client context which will verify the peer certificate and call + its C{load_verify_locations} method with C{*args}. 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 test_load_verify_file(self): + """ + L{Context.load_verify_locations} accepts a file name and uses the + certificates within for verification purposes. + """ + cafile = self.mktemp() + fObj = open(cafile, 'w') + fObj.write(cleartextCertificatePEM.decode('ascii')) + fObj.close() + + self._load_verify_locations_test(cafile) + + + def test_load_verify_invalid_file(self): + """ + L{Context.load_verify_locations} raises L{Error} when passed a + non-existent cafile. + """ + clientContext = Context(TLSv1_METHOD) + self.assertRaises( + Error, clientContext.load_verify_locations, self.mktemp()) + + + def test_load_verify_directory(self): + """ + L{Context.load_verify_locations} accepts a directory name and uses + the certificates within for verification purposes. + """ + capath = self.mktemp() + makedirs(capath) + # Hash value computed manually with c_rehash to avoid depending on + # c_rehash in the test suite. + cafile = join(capath, 'c7adac82.0') + fObj = open(cafile, 'w') + fObj.write(cleartextCertificatePEM.decode('ascii')) + fObj.close() + + self._load_verify_locations_test(None, capath) + + + def test_load_verify_locations_wrong_args(self): + """ + L{Context.load_verify_locations} raises L{TypeError} if called with + the wrong number of arguments or with non-C{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): + """ + L{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 TLSv1 + context = Context(SSLv3_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('GET / HTTP/1.0\r\n\r\n') + self.assertTrue(clientSSL.recv(1024)) + + + def test_set_default_verify_paths_signature(self): + """ + L{Context.set_default_verify_paths} takes no arguments and raises + L{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): + """ + L{Context.add_extra_chain_cert} raises L{TypeError} if called with + other than one argument or if called with an object which is not an + instance of L{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 _create_certificate_chain(self): + """ + 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)] + + + 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_add_extra_chain_cert(self): + """ + L{Context.add_extra_chain_cert} accepts an L{X509} instance to add to + the certificate chain. + + See L{_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 = self._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('ca.pem') + + # Try it out. + self._handshake_test(serverContext, clientContext) + + + def test_use_certificate_chain_file(self): + """ + L{Context.use_certificate_chain_file} reads a certificate chain from + the 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 = self._create_certificate_chain() + [(cakey, cacert), (ikey, icert), (skey, scert)] = chain + + # Write out the chain file. + chainFile = self.mktemp() + fObj = open(chainFile, 'w') + # Most specific to least general. + fObj.write(dump_certificate(FILETYPE_PEM, scert).decode('ascii')) + fObj.write(dump_certificate(FILETYPE_PEM, icert).decode('ascii')) + fObj.write(dump_certificate(FILETYPE_PEM, cacert).decode('ascii')) + fObj.close() + + serverContext = Context(TLSv1_METHOD) + serverContext.use_certificate_chain_file(chainFile) + serverContext.use_privatekey(skey) + + fObj = open('ca.pem', 'w') + fObj.write(dump_certificate(FILETYPE_PEM, cacert).decode('ascii')) + fObj.close() + + clientContext = Context(TLSv1_METHOD) + clientContext.set_verify( + VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) + clientContext.load_verify_locations('ca.pem') + + self._handshake_test(serverContext, clientContext) + + # XXX load_client_ca + # XXX set_session_id + + def test_get_verify_mode_wrong_args(self): + """ + L{Context.get_verify_mode} raises L{TypeError} if called with any + arguments. + """ + context = Context(TLSv1_METHOD) + self.assertRaises(TypeError, context.get_verify_mode, None) + + + def test_get_verify_mode(self): + """ + L{Context.get_verify_mode} returns the verify mode flags previously + passed to L{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) + + + def test_load_tmp_dh_wrong_args(self): + """ + L{Context.load_tmp_dh} raises L{TypeError} if called with the wrong + number of arguments or with a non-C{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): + """ + L{Context.load_tmp_dh} raises L{OpenSSL.SSL.Error} if the specified file + does not exist. + """ + context = Context(TLSv1_METHOD) + self.assertRaises(Error, context.load_tmp_dh, "hello") + + + def test_load_tmp_dh(self): + """ + L{Context.load_tmp_dh} loads Diffie-Hellman parameters from the + specified file. + """ + context = Context(TLSv1_METHOD) + dhfilename = self.mktemp() + dhfile = open(dhfilename, "w") + dhfile.write(dhparam) + dhfile.close() + context.load_tmp_dh(dhfilename) + # XXX What should I assert here? -exarkun + + + def test_set_cipher_list(self): + """ + L{Context.set_cipher_list} accepts a C{str} naming the ciphers which + connections created with the context object will be able to choose from. + """ + context = Context(TLSv1_METHOD) + context.set_cipher_list("hello world:EXP-RC4-MD5") + conn = Connection(context, None) + self.assertEquals(conn.get_cipher_list(), ["EXP-RC4-MD5"]) + + + +class ConnectionTests(TestCase, _LoopbackMixin): + """ + Unit tests for L{OpenSSL.SSL.Connection}. + """ + # XXX want_write + # XXX want_read + # 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): + """ + L{Connection} and L{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): + """ + L{Connection.get_context} returns the L{Context} instance used to + construct the L{Connection} instance. + """ + context = Context(TLSv1_METHOD) + connection = Connection(context, None) + self.assertIdentical(connection.get_context(), context) + + + def test_get_context_wrong_args(self): + """ + L{Connection.get_context} raises L{TypeError} if called with any + arguments. + """ + connection = Connection(Context(TLSv1_METHOD), None) + self.assertRaises(TypeError, connection.get_context, None) + + + def test_pending(self): + """ + L{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): + """ + L{Connection.pending} raises L{TypeError} if called with any arguments. + """ + connection = Connection(Context(TLSv1_METHOD), None) + self.assertRaises(TypeError, connection.pending, None) + + + def test_connect_wrong_args(self): + """ + L{Connection.connect} raises L{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): + """ + L{Connection.connect} raises L{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): + """ + L{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, L{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): + """ + L{Connection.accept} raises L{TypeError} if called with any arguments. + """ + connection = Connection(Context(TLSv1_METHOD), socket()) + self.assertRaises(TypeError, connection.accept, None) + + + def test_accept(self): + """ + L{Connection.accept} accepts a pending connection attempt and returns a + tuple of a new L{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): + """ + L{Connection.shutdown} raises L{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): + """ + L{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_set_shutdown(self): + """ + L{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) + + + def test_app_data_wrong_args(self): + """ + L{Connection.set_app_data} raises L{TypeError} if called with other than + one argument. L{Connection.get_app_data} raises L{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 + L{Connection.set_app_data} and later retrieved with + L{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): + """ + L{Connection.makefile} is not implemented and calling that method raises + L{NotImplementedError}. + """ + conn = Connection(Context(TLSv1_METHOD), None) + self.assertRaises(NotImplementedError, conn.makefile) + + + +class ConnectionGetCipherListTests(TestCase): + """ + Tests for L{Connection.get_cipher_list}. + """ + def test_wrong_args(self): + """ + L{Connection.get_cipher_list} raises L{TypeError} if called with any + arguments. + """ + connection = Connection(Context(TLSv1_METHOD), None) + self.assertRaises(TypeError, connection.get_cipher_list, None) + + + def test_result(self): + """ + L{Connection.get_cipher_list} returns a C{list} of C{str} 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 L{Connection.send} + """ + def test_wrong_args(self): + """ + When called with arguments other than a single string, + L{Connection.send} raises L{TypeError}. + """ + connection = Connection(Context(TLSv1_METHOD), None) + self.assertRaises(TypeError, connection.send) + self.assertRaises(TypeError, connection.send, object()) + self.assertRaises(TypeError, connection.send, "foo", "bar") + + + def test_short_bytes(self): + """ + When passed a short byte string, L{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')) + + 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, + L{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')) + + + +class ConnectionSendallTests(TestCase, _LoopbackMixin): + """ + Tests for L{Connection.sendall}. + """ + def test_wrong_args(self): + """ + When called with arguments other than a single string, + L{Connection.sendall} raises L{TypeError}. + """ + connection = Connection(Context(TLSv1_METHOD), None) + self.assertRaises(TypeError, connection.sendall) + self.assertRaises(TypeError, connection.sendall, object()) + self.assertRaises(TypeError, connection.sendall, "foo", "bar") + + + def test_short(self): + """ + L{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')) + + + 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, + L{Connection.sendall} transmits all of them. + """ + server, client = self._loopback() + server.sendall(memoryview(b('x'))) + self.assertEquals(client.recv(1), b('x')) + + + def test_long(self): + """ + L{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, L{Connection.sendall} propagates the + write error from the low level write call. + """ + server, client = self._loopback() + server.sock_shutdown(2) + self.assertRaises(SysCallError, server.sendall, "hello, world") + + + +class ConnectionRenegotiateTests(TestCase, _LoopbackMixin): + """ + Tests for SSL renegotiation APIs. + """ + def test_renegotiate_wrong_args(self): + """ + L{Connection.renegotiate} raises L{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): + """ + L{Connection.total_renegotiations} raises L{TypeError} if called with + any arguments. + """ + connection = Connection(Context(TLSv1_METHOD), None) + self.assertRaises(TypeError, connection.total_renegotiations, None) + + + def test_total_renegotiations(self): + """ + L{Connection.total_renegotiations} returns C{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 L{OpenSSL.SSL.Error}. + """ + def test_type(self): + """ + L{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 L{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 L{OpenSSL.SSL.OP_NO_QUERY_MTU} is 0x1000, the value of + I{SSL_OP_NO_QUERY_MTU} defined by I{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 L{OpenSSL.SSL.OP_COOKIE_EXCHANGE} is 0x2000, the value + of I{SSL_OP_COOKIE_EXCHANGE} defined by I{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 L{OpenSSL.SSL.OP_NO_TICKET} is 0x4000, the value of + I{SSL_OP_NO_TICKET} defined by I{openssl/ssl.h}. + """ + self.assertEqual(OP_NO_TICKET, 0x4000) + else: + "OP_NO_TICKET unavailable - OpenSSL version may be too old" + + + +class MemoryBIOTests(TestCase, _LoopbackMixin): + """ + Tests for L{OpenSSL.SSL.Connection} using a memory BIO. + """ + def _server(self, sock): + """ + Create a new server-side SSL L{Connection} object wrapped around + C{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 L{Connection} object wrapped around + C{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 L{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 L{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 L{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 L{OpenSSL.SSL.bio_read} and L{OpenSSL.SSL.bio_write} don't + work on L{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 + L{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("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): + """ + L{Connection.bio_shutdown} signals the end of the data stream from + which the L{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 _check_client_ca_list(self, func): + """ + Verify the return value of the C{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 C{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): + """ + L{Context.set_client_ca_list} raises a L{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, L{Context.set_client_ca_list} configures the + context to send no CA names to the client and, on both the server and + client sides, L{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, + L{Context.set_client_ca_list} configures the context to send that CA + name to the client and, on both the server and client sides, + L{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, + L{Context.set_client_ca_list} configures the context to send those CA + names to the client and, on both the server and client sides, + L{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 L{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 L{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): + """ + L{Context.add_client_ca} raises L{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 L{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 + L{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 L{Context.set_client_ca_list} followed by a call to + L{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 L{Context.set_client_ca_list} after a call to + L{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 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() diff --git a/OpenSSL/test/util.py b/OpenSSL/test/util.py new file mode 100644 index 0000000..f6e9291 --- /dev/null +++ b/OpenSSL/test/util.py @@ -0,0 +1,150 @@ +# 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 os, os.path +from tempfile import mktemp +from unittest import TestCase +import sys + +from OpenSSL.crypto import Error, _exception_from_error_queue + +if sys.version_info < (3, 0): + def b(s): + return s + bytes = str +else: + def b(s): + return s.encode("charmap") + bytes = bytes + + +class TestCase(TestCase): + """ + L{TestCase} adds useful testing functionality beyond what is available + from the standard library L{unittest.TestCase}. + """ + def tearDown(self): + """ + Clean up any files or directories created using L{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() + except Error: + e = sys.exc_info()[1] + if e.args != ([],): + self.fail("Left over errors in OpenSSL error queue: " + repr(e)) + + + def failUnlessIdentical(self, first, second, msg=None): + """ + Fail the test if C{first} is not C{second}. This is an + obect-identity-equality test, not an object equality + (i.e. C{__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 + + + def failIfIdentical(self, first, second, msg=None): + """ + Fail the test if C{first} is C{second}. This is an + obect-identity-equality test, not an object equality + (i.e. C{__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 + + + def failUnlessRaises(self, exception, f, *args, **kwargs): + """ + Fail the test unless calling the function C{f} with the given + C{args} and C{kwargs} raises C{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 = mktemp(dir=".") + self._temporaryFiles.append(temp) + return temp + + + # Python 2.3 compatibility. + def assertTrue(self, *a, **kw): + return self.failUnless(*a, **kw) + + + def assertFalse(self, *a, **kw): + return self.failIf(*a, **kw) + + + # Other stuff + def assertConsistentType(self, theType, name, *constructionArgs): + """ + Perform various assertions about C{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 C{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) diff --git a/OpenSSL/tsafe.py b/OpenSSL/tsafe.py new file mode 100644 index 0000000..fe4b75f --- /dev/null +++ b/OpenSSL/tsafe.py @@ -0,0 +1,28 @@ +from OpenSSL import SSL +_ssl = SSL +del SSL + +import threading +_RLock = threading.RLock +del threading + +class Connection: + def __init__(self, *args): + self._ssl_conn = apply(_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', '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)) + diff --git a/OpenSSL/util.c b/OpenSSL/util.c new file mode 100644 index 0000000..ca60ccf --- /dev/null +++ b/OpenSSL/util.c @@ -0,0 +1,96 @@ +/* + * util.c + * + * Copyright (C) AB Strakt + * Copyright (C) Jean-Paul Calderone + * See LICENSE for details. + * + * Utility functions. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + */ +#include +#include "util.h" + +/* + * Flush OpenSSL's error queue and return a list of errors (a (library, + * function, reason) string tuple) + * + * Arguments: None + * Returns: A list of errors (new reference) + */ +PyObject * +error_queue_to_list(void) { + PyObject *errlist, *tuple; + long err; + + errlist = PyList_New(0); + + while ((err = ERR_get_error()) != 0) { + tuple = Py_BuildValue("(sss)", ERR_lib_error_string(err), + ERR_func_error_string(err), + ERR_reason_error_string(err)); + PyList_Append(errlist, tuple); + Py_DECREF(tuple); + } + + return errlist; +} + +void exception_from_error_queue(PyObject *the_Error) { + PyObject *errlist = error_queue_to_list(); + PyErr_SetObject(the_Error, errlist); + Py_DECREF(errlist); +} + +/* + * Flush OpenSSL's error queue and ignore the result + * + * Arguments: None + * Returns: None + */ +void +flush_error_queue(void) { + /* + * Make sure to save the errors to a local. Py_DECREF might expand such + * that it evaluates its argument more than once, which would lead to + * very nasty things if we just invoked it with error_queue_to_list(). + */ + PyObject *list = error_queue_to_list(); + Py_DECREF(list); +} + +#if (PY_VERSION_HEX < 0x02600000) +PyObject* PyOpenSSL_LongToHex(PyObject *o) { + PyObject *hex = NULL; + PyObject *format = NULL; + PyObject *format_args = NULL; + + if ((format_args = Py_BuildValue("(O)", o)) == NULL) { + goto err; + } + + if ((format = PyString_FromString("%x")) == NULL) { + goto err; + } + + if ((hex = PyString_Format(format, format_args)) == NULL) { + goto err; + } + + return hex; + + err: + if (format_args) { + Py_DECREF(format_args); + } + if (format) { + Py_DECREF(format); + } + if (hex) { + Py_DECREF(hex); + } + return NULL; +} +#endif diff --git a/OpenSSL/util.h b/OpenSSL/util.h new file mode 100644 index 0000000..4cef481 --- /dev/null +++ b/OpenSSL/util.h @@ -0,0 +1,140 @@ +/* + * util.h + * + * Copyright (C) AB Strakt + * See LICENSE for details. + * + * Export utility functions and macros. + * See the file RATIONALE for a short explanation of why this module was written. + * + * Reviewed 2001-07-23 + * + */ +#ifndef PyOpenSSL_UTIL_H_ +#define PyOpenSSL_UTIL_H_ + +#include +#include + +/* + * pymemcompat written by Michael Hudson and lets you program to the + * Python 2.3 memory API while keeping backwards compatibility. + */ +#include "pymemcompat.h" + +/* + * py3k defines macros that help with Python 2.x/3.x compatibility. + */ +#include "py3k.h" + + +extern PyObject *error_queue_to_list(void); +extern void exception_from_error_queue(PyObject *the_Error); +extern void flush_error_queue(void); + +/* + * These are needed because there is no "official" way to specify + * WHERE to save the thread state. + */ +#ifdef WITH_THREAD + +/* + * Get the current Python threadstate and put it somewhere any code running + * in this thread can get it, if it needs to restore the threadstate to run + * some Python. + */ +# define MY_BEGIN_ALLOW_THREADS(ignored) \ + PyThread_delete_key_value(_pyOpenSSL_tstate_key); \ + PyThread_set_key_value(_pyOpenSSL_tstate_key, PyEval_SaveThread()); + +/* + * Get the previous Python threadstate and restore it. + */ +# define MY_END_ALLOW_THREADS(ignored) \ + PyEval_RestoreThread(PyThread_get_key_value(_pyOpenSSL_tstate_key)); + +#else +# define MY_BEGIN_ALLOW_THREADS(st) +# define MY_END_ALLOW_THREADS(st) { st = NULL; } +#endif + +#if !defined(PY_MAJOR_VERSION) || PY_VERSION_HEX < 0x02000000 +static int +PyModule_AddObject(PyObject *m, char *name, PyObject *o) +{ + PyObject *dict; + if (!PyModule_Check(m) || o == NULL) + return -1; + dict = PyModule_GetDict(m); + if (dict == NULL) + return -1; + if (PyDict_SetItemString(dict, name, o)) + return -1; + Py_DECREF(o); + return 0; +} + +static int +PyModule_AddIntConstant(PyObject *m, char *name, long value) +{ + return PyModule_AddObject(m, name, PyInt_FromLong(value)); +} + +static int PyObject_AsFileDescriptor(PyObject *o) +{ + int fd; + PyObject *meth; + + if (PyInt_Check(o)) { + fd = PyInt_AsLong(o); + } + else if (PyLong_Check(o)) { + fd = PyLong_AsLong(o); + } + else if ((meth = PyObject_GetAttrString(o, "fileno")) != NULL) + { + PyObject *fno = PyEval_CallObject(meth, NULL); + Py_DECREF(meth); + if (fno == NULL) + return -1; + + if (PyInt_Check(fno)) { + fd = PyInt_AsLong(fno); + Py_DECREF(fno); + } + else if (PyLong_Check(fno)) { + fd = PyLong_AsLong(fno); + Py_DECREF(fno); + } + else { + PyErr_SetString(PyExc_TypeError, "fileno() returned a non-integer"); + Py_DECREF(fno); + return -1; + } + } + else { + PyErr_SetString(PyExc_TypeError, "argument must be an int, or have a fileno() method."); + return -1; + } + + if (fd < 0) { + PyErr_Format(PyExc_ValueError, "file descriptor cannot be a negative integer (%i)", fd); + return -1; + } + return fd; +} +#endif + +#if !defined(PY_SSIZE_T_MIN) +typedef int Py_ssize_t; +#define PY_SSIZE_T_MAX INT_MAX +#define PY_SSIZE_T_MIN INT_MIN +#endif + +#if (PY_VERSION_HEX < 0x02600000) +extern PyObject* PyOpenSSL_LongToHex(PyObject *o); +#else +#define PyOpenSSL_LongToHex(o) PyNumber_ToBase(o, 16) +#endif + +#endif diff --git a/OpenSSL/version.py b/OpenSSL/version.py new file mode 100644 index 0000000..b7255e9 --- /dev/null +++ b/OpenSSL/version.py @@ -0,0 +1,9 @@ +# Copyright (C) AB Strakt +# Copyright (C) Jean-Paul Calderone +# See LICENSE for details. + +""" +pyOpenSSL - A simple wrapper around the OpenSSL library +""" + +__version__ = '0.12' diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..cb77461 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,15 @@ +Metadata-Version: 1.0 +Name: pyOpenSSL +Version: 0.12 +Summary: Python wrapper module around the OpenSSL library +Home-page: http://pyopenssl.sourceforge.net/ +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 diff --git a/README b/README new file mode 100644 index 0000000..191fa5b --- /dev/null +++ b/README @@ -0,0 +1,8 @@ + +pyOpenSSL - A Python wrapper around the OpenSSL library +------------------------------------------------------------------------------ + +See the file INSTALL for installation instructions. + +I appreciate bug reports and patches. Please visit +. diff --git a/TODO b/TODO new file mode 100644 index 0000000..cbcf642 --- /dev/null +++ b/TODO @@ -0,0 +1,8 @@ +TODO 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? diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..07aabdc --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,20 @@ +PAPER = a4 +MKHOWTO = python tools/mkhowto --$(PAPER) + +default: html + +all: ps html text dvi + +dvi ps text: pyOpenSSL.tex + $(MKHOWTO) --$@ $^ + +html: pyOpenSSL.tex + $(MKHOWTO) --html --iconserver . $^ + -rm -rf html + mv pyOpenSSL html + +clean: + rm -rf html pyOpenSSL.dvi pyOpenSSL.ps pyOpenSSL.txt \ + pyOpenSSL.l2h pyOpenSSL.how + +.PHONY: default all html dvi ps text clean diff --git a/doc/pyOpenSSL.tex b/doc/pyOpenSSL.tex new file mode 100644 index 0000000..7c8bfca --- /dev/null +++ b/doc/pyOpenSSL.tex @@ -0,0 +1,1402 @@ +\documentclass{howto} + +\title{Python OpenSSL Manual} + +\release{0.11} + +\author{Jean-Paul Calderone} +\authoraddress{\email{exarkun@twistedmatrix.com}} + +\usepackage[english]{babel} +\usepackage[T1]{fontenc} + +\begin{document} + +\maketitle + +\begin{abstract} +\noindent +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. +\end{abstract} + +\tableofcontents + + +\section{Introduction \label{intro}} + +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\footnote{See \url{http://chandlerproject.org/Projects/MeTooCrypto}}) +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. + +\section{Building and Installing \label{building}} + +These instructions can also be found in the file \verb|INSTALL|. + +I have tested this on Debian Linux systems (woody and sid), Solaris 2.6 and +2.7. Others have successfully compiled it on Windows and NT. + +\subsection{Building the Module on a Unix System \label{building-unix}} + +pyOpenSSL uses distutils, so there really shouldn't be any problems. To build +the library: +\begin{verbatim} +python setup.py build +\end{verbatim} + +If your OpenSSL header files aren't in \verb|/usr/include|, you may need to +supply the \verb|-I| flag to let the setup script know where to look. The same +goes for the libraries of course, use the \verb|-L| flag. Note that +\verb|build| won't accept these flags, so you have to run first +\verb|build_ext| and then \verb|build|! Example: +\begin{verbatim} +python setup.py build_ext -I/usr/local/ssl/include -L/usr/local/ssl/lib +python setup.py build +\end{verbatim} + +Now you should have a directory called \verb|OpenSSL| that contains e.g. +\verb|SSL.so| and \verb|__init__.py| somewhere in the build dicrectory, +so just: +\begin{verbatim} +python setup.py install +\end{verbatim} + +If you, for some arcane reason, don't want the module to appear in the +\verb|site-packages| directory, use the \verb|--prefix| option. + +You can, of course, do +\begin{verbatim} +python setup.py --help +\end{verbatim} + +to find out more about how to use the script. + +\subsection{Building the Module on a Windows System \label{building-windows}} + +Big thanks to Itamar Shtull-Trauring and Oleg Orlov for their help with +Windows build instructions. Same as for Unix systems, we have to separate +the \verb|build_ext| and the \verb|build|. + +Building the library: + +\begin{verbatim} +setup.py build_ext -I ...\openssl\inc32 -L ...\openssl\out32dll +setup.py build +\end{verbatim} + +Where \verb|...\openssl| is of course the location of your OpenSSL installation. + +Installation is the same as for Unix systems: +\begin{verbatim} +setup.py install +\end{verbatim} + +And similarily, you can do +\begin{verbatim} +setup.py --help +\end{verbatim} + +to get more information. + + +\section{\module{OpenSSL} --- Python interface to OpenSSL \label{openssl}} + +\declaremodule{extension}{OpenSSL} +\modulesynopsis{Python interface to OpenSSL} + +This package provides a high-level interface to the functions in the +OpenSSL library. The following modules are defined: + +\begin{datadesc}{crypto} +Generic cryptographic module. Note that if anything is incomplete, this module is! +\end{datadesc} + +\begin{datadesc}{rand} +An interface to the OpenSSL pseudo random number generator. +\end{datadesc} + +\begin{datadesc}{SSL} +An interface to the SSL-specific parts of OpenSSL. +\end{datadesc} + + +% % % crypto moduleOpenSSL + +\subsection{\module{crypto} --- Generic cryptographic module \label{openssl-crypto}} + +\declaremodule{extension}{crypto} +\modulesynopsis{Generic cryptographic module} + +\begin{datadesc}{X509Type} +See \class{X509}. +\end{datadesc} + +\begin{classdesc}{X509}{} +A class representing X.509 certificates. +\end{classdesc} + +\begin{datadesc}{X509NameType} +See \class{X509Name}. +\end{datadesc} + +\begin{classdesc}{X509Name}{x509name} +A class representing X.509 Distinguished Names. + +This constructor creates a copy of \var{x509name} which should be an +instance of \class{X509Name}. +\end{classdesc} + +\begin{datadesc}{X509ReqType} +See \class{X509Req}. +\end{datadesc} + +\begin{classdesc}{X509Req}{} +A class representing X.509 certificate requests. +\end{classdesc} + +\begin{datadesc}{X509StoreType} +A Python type object representing the X509Store object type. +\end{datadesc} + +\begin{datadesc}{PKeyType} +See \class{PKey}. +\end{datadesc} + +\begin{classdesc}{PKey}{} +A class representing DSA or RSA keys. +\end{classdesc} + +\begin{datadesc}{PKCS7Type} +A Python type object representing the PKCS7 object type. +\end{datadesc} + +\begin{datadesc}{PKCS12Type} +A Python type object representing the PKCS12 object type. +\end{datadesc} + +\begin{datadesc}{X509ExtensionType} +See \class{X509Extension}. +\end{datadesc} + +\begin{classdesc}{X509Extension}{typename, critical, value\optional{, subject}\optional{, issuer}} +A class representing an X.509 v3 certificate extensions. +See \url{http://openssl.org/docs/apps/x509v3_config.html\#STANDARD_EXTENSIONS} +for \var{typename} strings and their options. +Optional parameters \var{subject} and \var{issuer} must be X509 objects. +\end{classdesc} + +\begin{datadesc}{NetscapeSPKIType} +See \class{NetscapeSPKI}. +\end{datadesc} + +\begin{classdesc}{NetscapeSPKI}{\optional{enc}} +A class representing Netscape SPKI objects. + +If the \var{enc} argument is present, it should be a base64-encoded string +representing a NetscapeSPKI object, as returned by the \method{b64_encode} +method. +\end{classdesc} + +\begin{classdesc}{CRL}{} +A class representing Certifcate Revocation List objects. +\end{classdesc} + +\begin{classdesc}{Revoked}{} +A class representing Revocation objects of CRL. +\end{classdesc} + +\begin{datadesc}{FILETYPE_PEM} +\dataline{FILETYPE_ASN1} +File type constants. +\end{datadesc} + +\begin{datadesc}{TYPE_RSA} +\dataline{TYPE_DSA} +Key type constants. +\end{datadesc} + +\begin{excdesc}{Error} +Generic exception used in the \module{crypto} module. +\end{excdesc} + +\begin{funcdesc}{dump_certificate}{type, cert} +Dump the certificate \var{cert} into a buffer string encoded with the type +\var{type}. +\end{funcdesc} + +\begin{funcdesc}{dump_certificate_request}{type, req} +Dump the certificate request \var{req} into a buffer string encoded with the +type \var{type}. +\end{funcdesc} + +\begin{funcdesc}{dump_privatekey}{type, pkey\optional{, cipher, passphrase}} +Dump the private key \var{pkey} into a buffer string encoded with the type +\var{type}, optionally (if \var{type} is \constant{FILETYPE_PEM}) encrypting it +using \var{cipher} and \var{passphrase}. + +\var{passphrase} must be either a string or a callback for providing the +pass phrase. +\end{funcdesc} + +\begin{funcdesc}{load_certificate}{type, buffer} +Load a certificate (X509) from the string \var{buffer} encoded with the +type \var{type}. +\end{funcdesc} + +\begin{funcdesc}{load_certificate_request}{type, buffer} +Load a certificate request (X509Req) from the string \var{buffer} encoded with +the type \var{type}. +\end{funcdesc} + +\begin{funcdesc}{load_privatekey}{type, buffer\optional{, passphrase}} +Load a private key (PKey) from the string \var{buffer} encoded with +the type \var{type} (must be one of \constant{FILETYPE_PEM} and +\constant{FILETYPE_ASN1}). + +\var{passphrase} must be either a string or a callback for providing the +pass phrase. +\end{funcdesc} + +\begin{funcdesc}{load_crl}{type, buffer} +Load Certificate Revocation List (CRL) data from a string \var{buffer}. +\var{buffer} encoded with the type \var{type}. The type \var{type} +must either \constant{FILETYPE_PEM} or \constant{FILETYPE_ASN1}). +\end{funcdesc} + +\begin{funcdesc}{load_pkcs7_data}{type, buffer} +Load pkcs7 data from the string \var{buffer} encoded with the type \var{type}. +\end{funcdesc} + +\begin{funcdesc}{load_pkcs12}{buffer\optional{, passphrase}} +Load pkcs12 data from the string \var{buffer}. If the pkcs12 structure is +encrypted, a \var{passphrase} must be included. The MAC is always +checked and thus required. + +See also the man page for the C function \function{PKCS12_parse}. +\end{funcdesc} + +\begin{funcdesc}{sign}{key, data, digest} +Sign a data string using the given key and message digest. + +\var{key} is a \code{PKey} instance. \var{data} is a \code{str} instance. +\var{digest} is a \code{str} naming a supported message digest type, for example +\code{``sha1''}. +\versionadded{0.11} +\end{funcdesc} + +\begin{funcdesc}{verify}{certificate, signature, data, digest} +Verify the signature for a data string. + +\var{certificate} is a \code{X509} instance corresponding to the private key +which generated the signature. \var{signature} is a \var{str} instance giving +the signature itself. \var{data} is a \var{str} instance giving the data to +which the signature applies. \var{digest} is a \var{str} instance naming the +message digest type of the signature, for example \code{``sha1''}. +\versionadded{0.11} +\end{funcdesc} + +\subsubsection{X509Extension objects \label{openssl-x509ext}} + +X509Extension objects have the following methods: + +\begin{methoddesc}[X509Extension]{get_short_name}{} +Retrieve the short descriptive name for this extension. + +The result is a byte string like \code{``basicConstraints''}. +\versionadded{0.12} +\end{methoddesc} + +\begin{methoddesc}[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} +\end{methoddesc} + +\subsubsection{X509 objects \label{openssl-x509}} + +X509 objects have the following methods: + +\begin{methoddesc}[X509]{get_issuer}{} +Return an X509Name object representing the issuer of the certificate. +\end{methoddesc} + +\begin{methoddesc}[X509]{get_pubkey}{} +Return a PKey object representing the public key of the certificate. +\end{methoddesc} + +\begin{methoddesc}[X509]{get_serial_number}{} +Return the certificate serial number. +\end{methoddesc} + +\begin{methoddesc}[X509]{get_subject}{} +Return an X509Name object representing the subject of the certificate. +\end{methoddesc} + +\begin{methoddesc}[X509]{get_version}{} +Return the certificate version. +\end{methoddesc} + +\begin{methoddesc}[X509]{get_notBefore}{} +Return a string giving the time before which the certificate is not valid. The +string is formatted as an ASN1 GENERALIZEDTIME: +\begin{verbatim} + YYYYMMDDhhmmssZ + YYYYMMDDhhmmss+hhmm + YYYYMMDDhhmmss-hhmm +\end{verbatim} +If no value exists for this field, \code{None} is returned. +\end{methoddesc} + +\begin{methoddesc}[X509]{get_notAfter}{} +Return a string giving the time after which the certificate is not valid. The +string is formatted as an ASN1 GENERALIZEDTIME: +\begin{verbatim} + YYYYMMDDhhmmssZ + YYYYMMDDhhmmss+hhmm + YYYYMMDDhhmmss-hhmm +\end{verbatim} +If no value exists for this field, \code{None} is returned. +\end{methoddesc} + +\begin{methoddesc}[X509]{set_notBefore}{when} +Change the time before which the certificate is not valid. \var{when} is a +string formatted as an ASN1 GENERALIZEDTIME: +\begin{verbatim} + YYYYMMDDhhmmssZ + YYYYMMDDhhmmss+hhmm + YYYYMMDDhhmmss-hhmm +\end{verbatim} +\end{methoddesc} + +\begin{methoddesc}[X509]{set_notAfter}{when} +Change the time after which the certificate is not valid. \var{when} is a +string formatted as an ASN1 GENERALIZEDTIME: +\begin{verbatim} + YYYYMMDDhhmmssZ + YYYYMMDDhhmmss+hhmm + YYYYMMDDhhmmss-hhmm +\end{verbatim} +\end{methoddesc} + +\begin{methoddesc}[X509]{gmtime_adj_notBefore}{time} +Adjust the timestamp (in GMT) when the certificate starts being valid. +\end{methoddesc} + +\begin{methoddesc}[X509]{gmtime_adj_notAfter}{time} +Adjust the timestamp (in GMT) when the certificate stops being valid. +\end{methoddesc} + +\begin{methoddesc}[X509]{has_expired}{} +Checks the certificate's time stamp against current time. Returns true if the +certificate has expired and false otherwise. +\end{methoddesc} + +\begin{methoddesc}[X509]{set_issuer}{issuer} +Set the issuer of the certificate to \var{issuer}. +\end{methoddesc} + +\begin{methoddesc}[X509]{set_pubkey}{pkey} +Set the public key of the certificate to \var{pkey}. +\end{methoddesc} + +\begin{methoddesc}[X509]{set_serial_number}{serialno} +Set the serial number of the certificate to \var{serialno}. +\end{methoddesc} + +\begin{methoddesc}[X509]{set_subject}{subject} +Set the subject of the certificate to \var{subject}. +\end{methoddesc} + +\begin{methoddesc}[X509]{set_version}{version} +Set the certificate version to \var{version}. +\end{methoddesc} + +\begin{methoddesc}[X509]{sign}{pkey, digest} +Sign the certificate, using the key \var{pkey} and the message digest algorithm +identified by the string \var{digest}. +\end{methoddesc} + +\begin{methoddesc}[X509]{subject_name_hash}{} +Return the hash of the certificate subject. +\end{methoddesc} + +\begin{methoddesc}[X509]{digest}{digest_name} +Return a digest of the certificate, using the \var{digest_name} method. +\var{digest_name} must be a string describing a digest algorithm supported +by OpenSSL (by EVP_get_digestbyname, specifically). For example, +\constant{"md5"} or \constant{"sha1"}. +\end{methoddesc} + +\begin{methoddesc}[X509]{add_extensions}{extensions} +Add the extensions in the sequence \var{extensions} to the certificate. +\end{methoddesc} + +\begin{methoddesc}[X509]{get_extension_count}{} +Return the number of extensions on this certificate. +\versionadded{0.12} +\end{methoddesc} + +\begin{methoddesc}[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 X509Extension +instance. +\versionadded{0.12} +\end{methoddesc} + +\subsubsection{X509Name objects \label{openssl-x509name}} + +X509Name objects have the following methods: + +\begin{methoddesc}[X509Name]{hash}{} +Return an integer giving the first four bytes of the MD5 digest of the DER +representation of the name. +\end{methoddesc} + +\begin{methoddesc}[X509Name]{der}{} +Return a string giving the DER representation of the name. +\end{methoddesc} + +\begin{methoddesc}[X509Name]{get_components}{} +Return a list of two-tuples of strings giving the components of the name. +\end{methoddesc} + +X509Name objects have the following members: + +\begin{memberdesc}[X509Name]{countryName} +The country of the entity. \code{C} may be used as an alias for +\code{countryName}. +\end{memberdesc} + +\begin{memberdesc}[X509Name]{stateOrProvinceName} +The state or province of the entity. \code{ST} may be used as an alias for +\code{stateOrProvinceName}· +\end{memberdesc} + +\begin{memberdesc}[X509Name]{localityName} +The locality of the entity. \code{L} may be used as an alias for +\code{localityName}. +\end{memberdesc} + +\begin{memberdesc}[X509Name]{organizationName} +The organization name of the entity. \code{O} may be used as an alias for +\code{organizationName}. +\end{memberdesc} + +\begin{memberdesc}[X509Name]{organizationalUnitName} +The organizational unit of the entity. \code{OU} may be used as an alias for +\code{organizationalUnitName}. +\end{memberdesc} + +\begin{memberdesc}[X509Name]{commonName} +The common name of the entity. \code{CN} may be used as an alias for +\code{commonName}. +\end{memberdesc} + +\begin{memberdesc}[X509Name]{emailAddress} +The e-mail address of the entity. +\end{memberdesc} + +\subsubsection{X509Req objects \label{openssl-x509req}} + +X509Req objects have the following methods: + +\begin{methoddesc}[X509Req]{get_pubkey}{} +Return a PKey object representing the public key of the certificate request. +\end{methoddesc} + +\begin{methoddesc}[X509Req]{get_subject}{} +Return an X509Name object representing the subject of the certificate. +\end{methoddesc} + +\begin{methoddesc}[X509Req]{set_pubkey}{pkey} +Set the public key of the certificate request to \var{pkey}. +\end{methoddesc} + +\begin{methoddesc}[X509Req]{sign}{pkey, digest} +Sign the certificate request, using the key \var{pkey} and the message digest +algorithm identified by the string \var{digest}. +\end{methoddesc} + +\begin{methoddesc}[X509Req]{verify}{pkey} +Verify a certificate request using the public key \var{pkey}. +\end{methoddesc} + +\begin{methoddesc}[X509Req]{set_version}{version} +Set the version (RFC 2459, 4.1.2.1) of the certificate request to +\var{version}. +\end{methoddesc} + +\begin{methoddesc}[X509Req]{get_version}{} +Get the version (RFC 2459, 4.1.2.1) of the certificate request. +\end{methoddesc} + +\subsubsection{X509Store objects \label{openssl-x509store}} + +The X509Store object has currently just one method: + +\begin{methoddesc}[X509Store]{add_cert}{cert} +Add the certificate \var{cert} to the certificate store. +\end{methoddesc} + +\subsubsection{PKey objects \label{openssl-pkey}} + +The PKey object has the following methods: + +\begin{methoddesc}[PKey]{bits}{} +Return the number of bits of the key. +\end{methoddesc} + +\begin{methoddesc}[PKey]{generate_key}{type, bits} +Generate a public/private key pair of the type \var{type} (one of +\constant{TYPE_RSA} and \constant{TYPE_DSA}) with the size \var{bits}. +\end{methoddesc} + +\begin{methoddesc}[PKey]{type}{} +Return the type of the key. +\end{methoddesc} + +\subsubsection{PKCS7 objects \label{openssl-pkcs7}} + +PKCS7 objects have the following methods: + +\begin{methoddesc}[PKCS7]{type_is_signed}{} +FIXME +\end{methoddesc} + +\begin{methoddesc}[PKCS7]{type_is_enveloped}{} +FIXME +\end{methoddesc} + +\begin{methoddesc}[PKCS7]{type_is_signedAndEnveloped}{} +FIXME +\end{methoddesc} + +\begin{methoddesc}[PKCS7]{type_is_data}{} +FIXME +\end{methoddesc} + +\begin{methoddesc}[PKCS7]{get_type_name}{} +Get the type name of the PKCS7. +\end{methoddesc} + +\subsubsection{PKCS12 objects \label{openssl-pkcs12}} + +PKCS12 objects have the following methods: + +\begin{methoddesc}[PKCS12]{export}{\optional{passphrase=None}\optional{, iter=2048}\optional{, maciter=1}} +Returns a PKCS12 object as a string. + +The optional \var{passphrase} must be a string not a callback. + +See also the man page for the C function \function{PKCS12_create}. +\end{methoddesc} + +\begin{methoddesc}[PKCS12]{get_ca_certificates}{} +Return CA certificates within the PKCS12 object as a tuple. Returns +\constant{None} if no CA certificates are present. +\end{methoddesc} + +\begin{methoddesc}[PKCS12]{get_certificate}{} +Return certificate portion of the PKCS12 structure. +\end{methoddesc} + +\begin{methoddesc}[PKCS12]{get_friendlyname}{} +Return friendlyName portion of the PKCS12 structure. +\end{methoddesc} + +\begin{methoddesc}[PKCS12]{get_privatekey}{} +Return private key portion of the PKCS12 structure +\end{methoddesc} + +\begin{methoddesc}[PKCS12]{set_ca_certificates}{cacerts} +Replace or set the CA certificates within the PKCS12 object with the sequence \var{cacerts}. + +Set \var{cacerts} to \constant{None} to remove all CA certificates. +\end{methoddesc} + +\begin{methoddesc}[PKCS12]{set_certificate}{cert} +Replace or set the certificate portion of the PKCS12 structure. +\end{methoddesc} + +\begin{methoddesc}[PKCS12]{set_friendlyname}{name} +Replace or set the friendlyName portion of the PKCS12 structure. +\end{methoddesc} + +\begin{methoddesc}[PKCS12]{set_privatekey}{pkey} +Replace or set private key portion of the PKCS12 structure +\end{methoddesc} + +\subsubsection{X509Extension objects \label{openssl-509ext}} + +X509Extension objects have several methods: + +\begin{methoddesc}[X509Extension]{get_critical}{} +Return the critical field of the extension object. +\end{methoddesc} + +\begin{methoddesc}[X509Extension]{get_short_name}{} +Return the short type name of the extension object. +\end{methoddesc} + +\subsubsection{NetscapeSPKI objects \label{openssl-netscape-spki}} + +NetscapeSPKI objects have the following methods: + +\begin{methoddesc}[NetscapeSPKI]{b64_encode}{} +Return a base64-encoded string representation of the object. +\end{methoddesc} + +\begin{methoddesc}[NetscapeSPKI]{get_pubkey}{} +Return the public key of object. +\end{methoddesc} + +\begin{methoddesc}[NetscapeSPKI]{set_pubkey}{key} +Set the public key of the object to \var{key}. +\end{methoddesc} + +\begin{methoddesc}[NetscapeSPKI]{sign}{key, digest_name} +Sign the NetscapeSPKI object using the given \var{key} and +\var{digest_name}. \var{digest_name} must be a string describing a digest +algorithm supported by OpenSSL (by EVP_get_digestbyname, specifically). For +example, \constant{"md5"} or \constant{"sha1"}. +\end{methoddesc} + +\begin{methoddesc}[NetscapeSPKI]{verify}{key} +Verify the NetscapeSPKI object using the given \var{key}. +\end{methoddesc} + +\subsubsection{CRL objects \label{crl}} + +CRL objects have the following methods: + +\begin{methoddesc}[CRL]{add_revoked}{revoked} +Add a Revoked object to the CRL, by value not reference. +\end{methoddesc} + +\begin{methoddesc}[CRL]{export}{cert, key\optional{, type=FILETYPE_PEM}\optional{, days=100}} +Use \var{cert} and \var{key} to sign the CRL and return the CRL as a string. +\var{days} is the number of days before the next CRL is due. +\end{methoddesc} + +\begin{methoddesc}[CRL]{get_revoked}{} +Return a tuple of Revoked objects, by value not reference. +\end{methoddesc} + +\subsubsection{Revoked objects \label{revoked}} + +Revoked objects have the following methods: + +\begin{methoddesc}[Revoked]{all_reasons}{} +Return a list of all supported reasons. +\end{methoddesc} + +\begin{methoddesc}[Revoked]{get_reason}{} +Return the revocation reason as a str. Can be +None, which differs from "Unspecified". +\end{methoddesc} + +\begin{methoddesc}[Revoked]{get_rev_date}{} +Return the revocation date as a str. +The string is formatted as an ASN1 GENERALIZEDTIME. +\end{methoddesc} + +\begin{methoddesc}[Revoked]{get_serial}{} +Return a str containing a hex number of the serial of the revoked certificate. +\end{methoddesc} + +\begin{methoddesc}[Revoked]{set_reason}{reason} +Set the revocation reason. \var{reason} must +be None or a string, but the values are limited. +Spaces and case are ignored. See \method{all_reasons}. +\end{methoddesc} + +\begin{methoddesc}[Revoked]{set_rev_date}{date} +Set the revocation date. +The string is formatted as an ASN1 GENERALIZEDTIME. +\end{methoddesc} + +\begin{methoddesc}[Revoked]{set_serial}{serial} +\var{serial} is a string containing a hex number of the serial of the revoked certificate. +\end{methoddesc} + + +% % % rand module + +\subsection{\module{rand} --- An interface to the OpenSSL pseudo random number generator \label{openssl-rand}} + +\declaremodule{extension}{rand} +\modulesynopsis{An interface to the OpenSSL pseudo random number generator} + +This module handles the OpenSSL pseudo random number generator (PRNG) and +declares the following: + +\begin{funcdesc}{add}{string, entropy} +Mix bytes from \var{string} into the PRNG state. The \var{entropy} argument is +(the lower bound of) an estimate of how much randomness is contained in +\var{string}, measured in bytes. For more information, see e.g. \rfc{1750}. +\end{funcdesc} + +\begin{funcdesc}{bytes}{num_bytes} +Get some random bytes from the PRNG as a string. + +This is a wrapper for the C function \function{RAND_bytes}. +\end{funcdesc} + +\begin{funcdesc}{cleanup}{} +Erase the memory used by the PRNG. + +This is a wrapper for the C function \function{RAND_cleanup}. +\end{funcdesc} + +\begin{funcdesc}{egd}{path\optional{, bytes}} +Query the Entropy Gathering Daemon\footnote{See +\url{http://www.lothar.com/tech/crypto/}} on socket \var{path} for \var{bytes} +bytes of random data and and uses \function{add} to seed the PRNG. The default +value of \var{bytes} is 255. +\end{funcdesc} + +\begin{funcdesc}{load_file}{path\optional{, bytes}} +Read \var{bytes} bytes (or all of it, if \var{bytes} is negative) of data from +the file \var{path} to seed the PRNG. The default value of \var{bytes} is -1. +\end{funcdesc} + +\begin{funcdesc}{screen}{} +Add the current contents of the screen to the PRNG state. +Availability: Windows. +\end{funcdesc} + +\begin{funcdesc}{seed}{string} +This is equivalent to calling \function{add} with \var{entropy} as the length +of the string. +\end{funcdesc} + +\begin{funcdesc}{status}{} +Returns true if the PRNG has been seeded with enough data, and false otherwise. +\end{funcdesc} + +\begin{funcdesc}{write_file}{path} +Write a number of random bytes (currently 1024) to the file \var{path}. This +file can then be used with \function{load_file} to seed the PRNG again. +\end{funcdesc} + +\begin{excdesc}{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 \code{(\var{lib}, +\var{function}, \var{reason})}. Here \var{lib}, \var{function} and \var{reason} +are all strings, describing where and what the problem is. See \manpage{err}{3} +for more information. +\end{excdesc} + + +% % % SSL module + +\subsection{\module{SSL} --- An interface to the SSL-specific parts of OpenSSL \label{openssl-ssl}} + +\declaremodule{extension}{SSL} +\modulesynopsis{An interface to the SSL-specific parts of OpenSSL} + +This module handles things specific to SSL. There are two objects defined: +Context, Connection. + +\begin{datadesc}{SSLv2_METHOD} +\dataline{SSLv3_METHOD} +\dataline{SSLv23_METHOD} +\dataline{TLSv1_METHOD} +These constants represent the different SSL methods to use when creating a +context object. +\end{datadesc} + +\begin{datadesc}{VERIFY_NONE} +\dataline{VERIFY_PEER} +\dataline{VERIFY_FAIL_IF_NO_PEER_CERT} +These constants represent the verification mode used by the Context +object's \method{set_verify} method. +\end{datadesc} + +\begin{datadesc}{FILETYPE_PEM} +\dataline{FILETYPE_ASN1} +File type constants used with the \method{use_certificate_file} and +\method{use_privatekey_file} methods of Context objects. +\end{datadesc} + +\begin{datadesc}{OP_SINGLE_DH_USE} +\dataline{OP_EPHEMERAL_RSA} +\dataline{OP_NO_SSLv2} +\dataline{OP_NO_SSLv3} +\dataline{OP_NO_TLSv1} +Constants used with \method{set_options} of Context objects. +\constant{OP_SINGLE_DH_USE} means to always create a new key when using ephemeral +Diffie-Hellman. \constant{OP_EPHEMERAL_RSA} means to always use ephemeral RSA keys +when doing RSA operations. \constant{OP_NO_SSLv2}, \constant{OP_NO_SSLv3} and +\constant{OP_NO_TLSv1} means to disable those specific protocols. This is +interesting if you're using e.g. \constant{SSLv23_METHOD} to get an SSLv2-compatible +handshake, but don't want to use SSLv2. +\end{datadesc} + +\begin{datadesc}{ContextType} +See \class{Context}. +\end{datadesc} + +\begin{classdesc}{Context}{method} +A class representing SSL contexts. Contexts define the parameters of one or +more SSL connections. + +\var{method} should be \constant{SSLv2_METHOD}, \constant{SSLv3_METHOD}, +\constant{SSLv23_METHOD} or \constant{TLSv1_METHOD}. +\end{classdesc} + +\begin{datadesc}{ConnectionType} +See \class{Connection}. +\end{datadesc} + +\begin{classdesc}{Connection}{context, socket} +A class representing SSL connections. + +\var{context} should be an instance of \class{Context} and \var{socket} +should be a socket \footnote{Actually, all that is required is an object +that \emph{behaves} like a socket, you could even use files, even though +it'd be tricky to get the handshakes right!} object. \var{socket} may be +\var{None}; in this case, the Connection is created with a memory BIO: see +the \method{bio_read}, \method{bio_write}, and \method{bio_shutdown} +methods. +\end{classdesc} + +\begin{excdesc}{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 \code{(\var{lib}, +\var{function}, \var{reason})}. Here \var{lib}, \var{function} and \var{reason} +are all strings, describing where and what the problem is. See \manpage{err}{3} +for more information. +\end{excdesc} + +\begin{excdesc}{ZeroReturnError} +This exception matches the error return code \code{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 +\code{SSL_ERROR} code, and is very convenient. +\end{excdesc} + +\begin{excdesc}{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 \emph{dirty} data sent over the network, not the +\emph{clean} data inside the tunnel. For a socket based SSL connection, +\emph{read} means data coming at us over the network. Until that read +succeeds, the attempted \method{OpenSSL.SSL.Connection.recv}, +\method{OpenSSL.SSL.Connection.send}, or +\method{OpenSSL.SSL.Connection.do_handshake} is prevented or incomplete. You +probably want to \method{select()} on the socket before trying again. +\end{excdesc} + +\begin{excdesc}{WantWriteError} +See \exception{WantReadError}. The socket send buffer may be too full to +write more data. +\end{excdesc} + +\begin{excdesc}{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. +\end{excdesc} + +\begin{excdesc}{SysCallError} +The \exception{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 \code{(\var{errnum}, +\var{errstr})}. +\end{excdesc} + + +\subsubsection{Context objects \label{openssl-context}} + +Context objects have the following methods: + +\begin{methoddesc}[Context]{check_privatekey}{} +Check if the private key (loaded with \method{use_privatekey\optional{_file}}) +matches the certificate (loaded with \method{use_certificate\optional{_file}}). +Returns \code{None} if they match, raises \exception{Error} otherwise. +\end{methoddesc} + +\begin{methoddesc}[Context]{get_app_data}{} +Retrieve application data as set by \method{set_app_data}. +\end{methoddesc} + +\begin{methoddesc}[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. +\method{load_verify_locations()} method. +\end{methoddesc} + +\begin{methoddesc}[Context]{get_timeout}{} +Retrieve session timeout, as set by \method{set_timeout}. The default is 300 +seconds. +\end{methoddesc} + +\begin{methoddesc}[Context]{get_verify_depth}{} +Retrieve the Context object's verify depth, as set by +\method{set_verify_depth}. +\end{methoddesc} + +\begin{methoddesc}[Context]{get_verify_mode}{} +Retrieve the Context object's verify mode, as set by \method{set_verify}. +\end{methoddesc} + +\begin{methoddesc}[Context]{load_client_ca}{pemfile} +Read a file with PEM-formatted certificates that will be sent to the client +when requesting a client certificate. +\end{methoddesc} + +\begin{methoddesc}[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 +\var{certificate_authorities} sequence of \class{OpenSSL.crypto.X509Name}s. + +\versionadded{0.10} +\end{methoddesc} + +\begin{methoddesc}[Context]{add_client_ca}{certificate_authority} +Extract a \class{OpenSSL.crypto.X509Name} from the \var{certificate_authority} +\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} +\end{methoddesc} + +\begin{methoddesc}[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 +\code{c_rehash} tool included with OpenSSL. Either, but not both, of +\var{pemfile} or \var{capath} may be \code{None}. +\end{methoddesc} + +\begin{methoddesc}[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. +\end{methoddesc} + +\begin{methoddesc}[Context]{load_tmp_dh}{dhfile} +Load parameters for Ephemeral Diffie-Hellman from \var{dhfile}. +\end{methoddesc} + +\begin{methoddesc}[Context]{set_app_data}{data} +Associate \var{data} with this Context object. \var{data} can be retrieved +later using the \method{get_app_data} method. +\end{methoddesc} + +\begin{methoddesc}[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. ciphers(1)) +\end{methoddesc} + +\begin{methoddesc}[Context]{set_info_callback}{callback} +Set the information callback to \var{callback}. This function will be called +from time to time during SSL handshakes. +\var{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. +\end{methoddesc} + +\begin{methoddesc}[Context]{set_options}{options} +Add SSL options. Options you have set before are not cleared! +This method should be used with the \constant{OP_*} constants. +\end{methoddesc} + +\begin{methoddesc}[Context]{set_passwd_cb}{callback\optional{, userdata}} +Set the passphrase callback to \var{callback}. This function will be called +when a private key with a passphrase is loaded. \var{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 +\var{userdata} parameter to \method{set_passwd_cb}. If an error occurs, +\var{callback} should return a false value (e.g. an empty string). +\end{methoddesc} + +\begin{methoddesc}[Context]{set_session_id}{name} +Set the context \var{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. +\var{name} may be any binary data. +\end{methoddesc} + +\begin{methoddesc}[Context]{set_timeout}{timeout} +Set the timeout for newly created sessions for this Context object to +\var{timeout}. \var{timeout} must be given in (whole) seconds. The default +value is 300 seconds. See the OpenSSL manual for more information (e.g. +SSL_CTX_set_timeout(3)). +\end{methoddesc} + +\begin{methoddesc}[Context]{set_verify}{mode, callback} +Set the verification flags for this Context object to \var{mode} and specify +that \var{callback} should be used for verification callbacks. \var{mode} +should be one of \constant{VERIFY_NONE} and \constant{VERIFY_PEER}. If +\constant{VERIFY_PEER} is used, \var{mode} can be OR:ed with +\constant{VERIFY_FAIL_IF_NO_PEER_CERT} and \constant{VERIFY_CLIENT_ONCE} to +further control the behaviour. +\var{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. \var{callback} should return true if verification passes +and false otherwise. +\end{methoddesc} + +\begin{methoddesc}[Context]{set_verify_depth}{depth} +Set the maximum depth for the certificate chain verification that shall be +allowed for this Context object. +\end{methoddesc} + +\begin{methoddesc}[Context]{use_certificate}{cert} +Use the certificate \var{cert} which has to be a X509 object. +\end{methoddesc} + +\begin{methoddesc}[Context]{add_extra_chain_cert}{cert} +Adds the certificate \var{cert}, which has to be a X509 object, to the +certificate chain presented together with the certificate. +\end{methoddesc} + +\begin{methoddesc}[Context]{use_certificate_chain_file}{file} +Load a certificate chain from \var{file} which must be PEM encoded. +\end{methoddesc} + +\begin{methoddesc}[Context]{use_privatekey}{pkey} +Use the private key \var{pkey} which has to be a PKey object. +\end{methoddesc} + +\begin{methoddesc}[Context]{use_certificate_file}{file\optional{, format}} +Load the first certificate found in \var{file}. The certificate must be in the +format specified by \var{format}, which is either \constant{FILETYPE_PEM} or +\constant{FILETYPE_ASN1}. The default is \constant{FILETYPE_PEM}. +\end{methoddesc} + +\begin{methoddesc}[Context]{use_privatekey_file}{file\optional{, format}} +Load the first private key found in \var{file}. The private key must be in the +format specified by \var{format}, which is either \constant{FILETYPE_PEM} or +\constant{FILETYPE_ASN1}. The default is \constant{FILETYPE_PEM}. +\end{methoddesc} + + +\subsubsection{Connection objects \label{openssl-connection}} + +Connection objects have the following methods: + +\begin{methoddesc}[Connection]{accept}{} +Call the \method{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 \code{(\var{conn}, \var{address})}. where \var{conn} +is the new Connection object created, and \var{address} is as returned by the +socket's \method{accept}. +\end{methoddesc} + +\begin{methoddesc}[Connection]{bind}{address} +Call the \method{bind} method of the underlying socket. +\end{methoddesc} + +\begin{methoddesc}[Connection]{close}{} +Call the \method{close} method of the underlying socket. Note: If you want +correct SSL closure, you need to call the \method{shutdown} method first. +\end{methoddesc} + +\begin{methoddesc}[Connection]{connect}{address} +Call the \method{connect} method of the underlying socket and set up SSL on the +socket, using the Context object supplied to this Connection object at +creation. +\end{methoddesc} + +\begin{methoddesc}[Connection]{connect_ex}{address} +Call the \method{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 \method{connect_ex} method of the socket doesn't +return 0, SSL won't be initialized. +\end{methoddesc} + +\begin{methoddesc}[Connection]{do_handshake}{} +Perform an SSL handshake (usually called after \method{renegotiate} or one of +\method{set_accept_state} or \method{set_accept_state}). This can raise the +same exceptions as \method{send} and \method{recv}. +\end{methoddesc} + +\begin{methoddesc}[Connection]{fileno}{} +Retrieve the file descriptor number for the underlying socket. +\end{methoddesc} + +\begin{methoddesc}[Connection]{listen}{backlog} +Call the \method{listen} method of the underlying socket. +\end{methoddesc} + +\begin{methoddesc}[Connection]{get_app_data}{} +Retrieve application data as set by \method{set_app_data}. +\end{methoddesc} + +\begin{methoddesc}[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. +\end{methoddesc} + +\begin{methoddesc}[Connection]{get_client_ca_list}{} +Retrieve the list of preferred client certificate issuers sent by the server +as \class{OpenSSL.crypto.X509Name} objects. + +If this is a client \class{Connection}, the list will be empty until the +connection with the server is established. + +If this is a server \class{Connection}, return the list of certificate +authorities that will be sent or has been sent to the client, as controlled +by this \class{Connection}'s \class{Context}. + +\versionadded{0.10} +\end{methoddesc} + +\begin{methoddesc}[Connection]{get_context}{} +Retrieve the Context object associated with this Connection. +\end{methoddesc} + +\begin{methoddesc}[Connection]{get_peer_certificate}{} +Retrieve the other side's certificate (if any) +\end{methoddesc} + +\begin{methoddesc}[Connection]{getpeername}{} +Call the \method{getpeername} method of the underlying socket. +\end{methoddesc} + +\begin{methoddesc}[Connection]{getsockname}{} +Call the \method{getsockname} method of the underlying socket. +\end{methoddesc} + +\begin{methoddesc}[Connection]{getsockopt}{level, optname\optional{, buflen}} +Call the \method{getsockopt} method of the underlying socket. +\end{methoddesc} + +\begin{methoddesc}[Connection]{pending}{} +Retrieve the number of bytes that can be safely read from the SSL buffer +(\emph{not} the underlying transport buffer). +\end{methoddesc} + +\begin{methoddesc}[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 \var{bufsize}. +\end{methoddesc} + +\begin{methoddesc}[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 \method{recv}). +\end{methoddesc} + +\begin{methoddesc}[Connection]{renegotiate}{} +Renegotiate the SSL session. Call this if you wish to change cipher suites or +anything like that. +\end{methoddesc} + +\begin{methoddesc}[Connection]{send}{string} +Send the \var{string} data to the Connection. +\end{methoddesc} + +\begin{methoddesc}[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. +\end{methoddesc} + +\begin{methoddesc}[Connection]{sendall}{string} +Send all of the \var{string} data to the Connection. This calls \method{send} +repeatedly until all data is sent. If an error occurs, it's impossible to tell +how much data has been sent. +\end{methoddesc} + +\begin{methoddesc}[Connection]{set_accept_state}{} +Set the connection to work in server mode. The handshake will be handled +automatically by read/write. +\end{methoddesc} + +\begin{methoddesc}[Connection]{set_app_data}{data} +Associate \var{data} with this Connection object. \var{data} can be retrieved +later using the \method{get_app_data} method. +\end{methoddesc} + +\begin{methoddesc}[Connection]{set_connect_state}{} +Set the connection to work in client mode. The handshake will be handled +automatically by read/write. +\end{methoddesc} + +\begin{methoddesc}[Connection]{setblocking}{flag} +Call the \method{setblocking} method of the underlying socket. +\end{methoddesc} + +\begin{methoddesc}[Connection]{setsockopt}{level, optname, value} +Call the \method{setsockopt} method of the underlying socket. +\end{methoddesc} + +\begin{methoddesc}[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 +\method{recv()} or \method{send()} when the connection becomes +readable/writeable. +\end{methoddesc} + +\begin{methoddesc}[Connection]{get_shutdown}{} +Get the shutdown state of the Connection. Returns a bitvector of either or +both of \var{SENT_SHUTDOWN} and \var{RECEIVED_SHUTDOWN}. +\end{methoddesc} + +\begin{methoddesc}[Connection]{set_shutdown}{state} +Set the shutdown state of the Connection. \var{state} is a bitvector of +either or both of \var{SENT_SHUTDOWN} and \var{RECEIVED_SHUTDOWN}. +\end{methoddesc} + +\begin{methoddesc}[Connection]{sock_shutdown}{how} +Call the \method{shutdown} method of the underlying socket. +\end{methoddesc} + +\begin{methoddesc}[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. +\end{methoddesc} + +\begin{methoddesc}[Connection]{state_string}{} +Retrieve a verbose string detailing the state of the Connection. +\end{methoddesc} + +\begin{methoddesc}[Connection]{client_random}{} +Retrieve the random value used with the client hello message. +\end{methoddesc} + +\begin{methoddesc}[Connection]{server_random}{} +Retrieve the random value used with the server hello message. +\end{methoddesc} + +\begin{methoddesc}[Connection]{master_key}{} +Retrieve the value of the master key for this session. +\end{methoddesc} + +\begin{methoddesc}[Connection]{want_read}{} +Checks if more data has to be read from the transport layer to complete an +operation. +\end{methoddesc} + +\begin{methoddesc}[Connection]{want_write}{} +Checks if there is data to write to the transport layer to complete an +operation. +\end{methoddesc} + + + +\section{Internals \label{internals}} + +We ran into three main problems developing this: Exceptions, callbacks and +accessing socket methods. This is what this chapter is about. + +\subsection{Exceptions \label{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 +\exception{SSL.ZeroReturnError}, \exception{SSL.WantReadError}, +\exception{SSL.WantWriteError}, \exception{SSL.WantX509LookupError} and +\exception{SSL.SysCallError}. + +For more information about this, see section \ref{openssl-ssl}. + + +\subsection{Callbacks \label{callbacks}} + +There are a number of problems with callbacks. First of all, OpenSSL is written +as a C library, it's not meant to have Python callbacks, so a way around that +is needed. Another problem is thread support. A lot of the OpenSSL I/O +functions can block if the socket is in blocking mode, and then you want other +Python threads to be able to do other things. The real trouble is if you've +released the global CPython interpreter lock to do a potentially blocking +operation, and the operation calls a callback. Then we must take the GIL back, +since calling Python APIs without holding it is not allowed. + +There are two solutions to the first problem, both of which are necessary. The +first solution to use is if the C callback allows ''userdata'' to be passed to +it (an arbitrary pointer normally). This is great! We can set our Python +function object as the real userdata and emulate userdata for the Python +function in another way. The other solution can be used if an object with an +''app_data'' system always is passed to the callback. For example, the SSL +object in OpenSSL has app_data functions and in e.g. the verification +callbacks, you can retrieve the related SSL object. What we do is to set our +wrapper \class{Connection} object as app_data for the SSL object, and we can +easily find the Python callback. + +The other problem is solved using thread local variables. Whenever the GIL is +released before calling into an OpenSSL API, the PyThreadState pointer returned +by \cfunction{PyEval_SaveState} is stored in a global thread local variable +(using Python's own TLS API, \cfunction{PyThread_set_key_value}). When it is +necessary to re-acquire the GIL, either after the OpenSSL API returns or in a C +callback invoked by that OpenSSL API, the value of the thread local variable is +retrieved (\cfunction{PyThread_get_key_value}) and used to re-acquire the GIL. +This allows Python threads to execute while OpenSSL APIs are running and allows +use of any particular pyOpenSSL object from any Python thread, since there is +no per-thread state associated with any of these objects and since OpenSSL is +threadsafe (as long as properly initialized, as pyOpenSSL initializes it). + + +\subsection{Acessing Socket Methods \label{socket-methods}} + +We quickly saw the benefit of wrapping socket methods in the +\class{SSL.Connection} class, for an easy transition into using SSL. The +problem here is that the \module{socket} module lacks a C API, and all the +methods are declared static. One approach would be to have \module{OpenSSL} as +a submodule to the \module{socket} module, placing all the code in +\file{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 \module{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 ``\class{socket}-like'' transport +object to the \class{SSL.Connection}. The only requirement of this object is +that it has a \method{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 \method{connect()} or \method{accept()} methods of the +\class{SSL.Connection} object, the transport object has to supply such +methods too. Apart from them, any method lookups in the \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 \method{fileno()} methods, have \method{read()} and \method{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 \method{fileno()} method of \class{SSL.Connection} +becomes virtually useless. Also, should the method resolution be used on the +read-transport or the write-transport? + + +\end{document} diff --git a/doc/tools/anno-api.py b/doc/tools/anno-api.py new file mode 100755 index 0000000..0d355d2 --- /dev/null +++ b/doc/tools/anno-api.py @@ -0,0 +1,71 @@ +#! /usr/bin/env python +"""Add reference count annotations to the Python/C API Reference.""" +__version__ = '$Revision: 1.1.1.1 $' + +import getopt +import os +import sys + +import refcounts + + +PREFIX_1 = r"\begin{cfuncdesc}{PyObject*}{" +PREFIX_2 = r"\begin{cfuncdesc}{PyVarObject*}{" + + +def main(): + rcfile = os.path.join(os.path.dirname(refcounts.__file__), os.pardir, + "api", "refcounts.dat") + outfile = "-" + opts, args = getopt.getopt(sys.argv[1:], "o:r:", ["output=", "refcounts="]) + for opt, arg in opts: + if opt in ("-o", "--output"): + outfile = arg + elif opt in ("-r", "--refcounts"): + rcfile = arg + rcdict = refcounts.load(rcfile) + if outfile == "-": + output = sys.stdout + else: + output = open(outfile, "w") + if not args: + args = ["-"] + for infile in args: + if infile == "-": + input = sys.stdin + else: + input = open(infile) + while 1: + line = input.readline() + if not line: + break + prefix = None + if line.startswith(PREFIX_1): + prefix = PREFIX_1 + elif line.startswith(PREFIX_2): + prefix = PREFIX_2 + if prefix: + s = line[len(prefix):].split('}', 1)[0] + try: + info = rcdict[s] + except KeyError: + sys.stderr.write("No refcount data for %s\n" % s) + else: + if info.result_type in ("PyObject*", "PyVarObject*"): + if info.result_refs is None: + rc = "Always \NULL{}" + else: + rc = info.result_refs and "New" or "Borrowed" + rc = rc + " reference" + line = (r"\begin{cfuncdesc}[%s]{%s}{" + % (rc, info.result_type)) \ + + line[len(prefix):] + output.write(line) + if infile != "-": + input.close() + if outfile != "-": + output.close() + + +if __name__ == "__main__": + main() diff --git a/doc/tools/buildindex.py b/doc/tools/buildindex.py new file mode 100755 index 0000000..5a41c0e --- /dev/null +++ b/doc/tools/buildindex.py @@ -0,0 +1,353 @@ +#! /usr/bin/env python + +__version__ = '$Revision: 1.1.1.1 $' + +import os +import re +import string +import sys + + +class Node: + __rmjunk = re.compile("<#\d+#>") + + continuation = 0 + + def __init__(self, link, str, seqno): + self.links = [link] + self.seqno = seqno + # remove <#\d+#> left in by moving the data out of LaTeX2HTML + str = self.__rmjunk.sub('', str) + # build up the text + self.text = split_entry_text(str) + self.key = split_entry_key(str) + + def __cmp__(self, other): + """Comparison operator includes sequence number, for use with + list.sort().""" + return self.cmp_entry(other) or cmp(self.seqno, other.seqno) + + def cmp_entry(self, other): + """Comparison 'operator' that ignores sequence number.""" + c = 0 + for i in range(min(len(self.key), len(other.key))): + c = (cmp_part(self.key[i], other.key[i]) + or cmp_part(self.text[i], other.text[i])) + if c: + break + return c or cmp(self.key, other.key) or cmp(self.text, other.text) + + def __repr__(self): + return "" % (string.join(self.text, '!'), self.seqno) + + def __str__(self): + return string.join(self.key, '!') + + def dump(self): + return "%s\1%s###%s\n" \ + % (string.join(self.links, "\1"), + string.join(self.text, '!'), + self.seqno) + + +def cmp_part(s1, s2): + result = cmp(s1, s2) + if result == 0: + return 0 + l1 = string.lower(s1) + l2 = string.lower(s2) + minlen = min(len(s1), len(s2)) + if len(s1) < len(s2) and l1 == l2[:len(s1)]: + result = -1 + elif len(s2) < len(s1) and l2 == l1[:len(s2)]: + result = 1 + else: + result = cmp(l1, l2) or cmp(s1, s2) + return result + + +def split_entry(str, which): + stuff = [] + parts = string.split(str, '!') + parts = map(string.split, parts, ['@'] * len(parts)) + for entry in parts: + if len(entry) != 1: + key = entry[which] + else: + key = entry[0] + stuff.append(key) + return stuff + + +_rmtt = re.compile(r"""(.*)(.*)(.*)$""", + re.IGNORECASE) +_rmparens = re.compile(r"\(\)") + +def split_entry_key(str): + parts = split_entry(str, 1) + for i in range(len(parts)): + m = _rmtt.match(parts[i]) + if m: + parts[i] = string.join(m.group(1, 2, 3), '') + else: + parts[i] = string.lower(parts[i]) + # remove '()' from the key: + parts[i] = _rmparens.sub('', parts[i]) + return map(trim_ignored_letters, parts) + + +def split_entry_text(str): + if '<' in str: + m = _rmtt.match(str) + if m: + str = string.join(m.group(1, 2, 3), '') + return split_entry(str, 1) + + +def load(fp): + nodes = [] + rx = re.compile("(.*)\1(.*)###(.*)$") + while 1: + line = fp.readline() + if not line: + break + m = rx.match(line) + if m: + link, str, seqno = m.group(1, 2, 3) + nodes.append(Node(link, str, seqno)) + return nodes + + +def trim_ignored_letters(s): + # ignore $ to keep environment variables with the + # leading letter from the name + s = string.lower(s) + if s[0] == "$": + return s[1:] + else: + return s + +def get_first_letter(s): + return string.lower(trim_ignored_letters(s)[0]) + + +def split_letters(nodes): + letter_groups = [] + if nodes: + group = [] + append = group.append + letter = get_first_letter(nodes[0].text[0]) + letter_groups.append((letter, group)) + for node in nodes: + nletter = get_first_letter(node.text[0]) + if letter != nletter: + letter = nletter + group = [] + letter_groups.append((letter, group)) + append = group.append + append(node) + return letter_groups + + +# need a function to separate the nodes into columns... +def split_columns(nodes, columns=1): + if columns <= 1: + return [nodes] + # This is a rough height; we may have to increase to avoid breaks before + # a subitem. + colheight = len(nodes) / columns + numlong = len(nodes) % columns + if numlong: + colheight = colheight + 1 + else: + numlong = columns + cols = [] + for i in range(numlong): + start = i * colheight + end = start + colheight + cols.append(nodes[start:end]) + del nodes[:end] + colheight = colheight - 1 + try: + numshort = len(nodes) / colheight + except ZeroDivisionError: + cols = cols + (columns - len(cols)) * [[]] + else: + for i in range(numshort): + start = i * colheight + end = start + colheight + cols.append(nodes[start:end]) + # + # If items continue across columns, make sure they are marked + # as continuations so the user knows to look at the previous column. + # + for i in range(len(cols) - 1): + try: + prev = cols[i][-1] + next = cols[i + 1][0] + except IndexError: + return cols + else: + n = min(len(prev.key), len(next.key)) + for j in range(n): + if prev.key[j] != next.key[j]: + break + next.continuation = j + 1 + return cols + + +DL_LEVEL_INDENT = " " + +def format_column(nodes): + strings = ["
"] + append = strings.append + level = 0 + previous = [] + for node in nodes: + current = node.text + count = 0 + for i in range(min(len(current), len(previous))): + if previous[i] != current[i]: + break + count = i + 1 + if count > level: + append("
" * (count - level) + "\n") + level = count + elif level > count: + append("\n") + append(level * DL_LEVEL_INDENT) + append("
" * (level - count)) + level = count + # else: level == count + for i in range(count, len(current) - 1): + term = node.text[i] + level = level + 1 + if node.continuation > i: + extra = " (continued)" + else: + extra = "" + append("\n
%s%s\n
\n%s
" + % (term, extra, level * DL_LEVEL_INDENT)) + append("\n%s
%s%s" + % (level * DL_LEVEL_INDENT, node.links[0], node.text[-1])) + for link in node.links[1:]: + append(",\n%s %s[Link]" % (level * DL_LEVEL_INDENT, link)) + previous = current + append("\n") + append("
" * (level + 1)) + return string.join(strings, '') + + +def format_nodes(nodes, columns=1): + strings = [] + append = strings.append + if columns > 1: + colnos = range(columns) + colheight = len(nodes) / columns + if len(nodes) % columns: + colheight = colheight + 1 + colwidth = 100 / columns + append('') + for col in split_columns(nodes, columns): + append('") + append("\n
\n' % colwidth) + append(format_column(col)) + append("\n
") + else: + append(format_column(nodes)) + append("\n

\n") + return string.join(strings, '') + + +def format_letter(letter): + if letter == '.': + lettername = ". (dot)" + elif letter == '_': + lettername = "_ (underscore)" + else: + lettername = string.upper(letter) + return "\n


\n

%s

\n\n" \ + % (letter, lettername) + + +def format_html_letters(nodes, columns=1): + letter_groups = split_letters(nodes) + items = [] + for letter, nodes in letter_groups: + s = "%s" % (letter, letter) + items.append(s) + s = ["
\n%s
\n" % string.join(items, " |\n")] + for letter, nodes in letter_groups: + s.append(format_letter(letter)) + s.append(format_nodes(nodes, columns)) + return string.join(s, '') + +def format_html(nodes, columns): + return format_nodes(nodes, columns) + + +def collapse(nodes): + """Collapse sequences of nodes with matching keys into a single node. + Destructive.""" + if len(nodes) < 2: + return + prev = nodes[0] + i = 1 + while i < len(nodes): + node = nodes[i] + if not node.cmp_entry(prev): + prev.links.append(node.links[0]) + del nodes[i] + else: + i = i + 1 + prev = node + + +def dump(nodes, fp): + for node in nodes: + fp.write(node.dump()) + + +def process_nodes(nodes, columns, letters): + nodes.sort() + collapse(nodes) + if letters: + return format_html_letters(nodes, columns) + else: + return format_html(nodes, columns) + + +def main(): + import getopt + ifn = "-" + ofn = "-" + columns = 1 + letters = 0 + opts, args = getopt.getopt(sys.argv[1:], "c:lo:", + ["columns=", "letters", "output="]) + for opt, val in opts: + if opt in ("-o", "--output"): + ofn = val + elif opt in ("-c", "--columns"): + columns = string.atoi(val) + elif opt in ("-l", "--letters"): + letters = 1 + if not args: + args = [ifn] + nodes = [] + for fn in args: + nodes = nodes + load(open(fn)) + num_nodes = len(nodes) + html = process_nodes(nodes, columns, letters) + program = os.path.basename(sys.argv[0]) + if ofn == "-": + sys.stdout.write(html) + sys.stderr.write("\n%s: %d index nodes" % (program, num_nodes)) + else: + open(ofn, "w").write(html) + print + print "%s: %d index nodes" % (program, num_nodes) + + +if __name__ == "__main__": + main() diff --git a/doc/tools/checkargs.pm b/doc/tools/checkargs.pm new file mode 100644 index 0000000..de52f69 --- /dev/null +++ b/doc/tools/checkargs.pm @@ -0,0 +1,112 @@ +#!/uns/bin/perl + +package checkargs; +require 5.004; # uses "for my $var" +require Exporter; +@ISA = qw(Exporter); +@EXPORT = qw(check_args check_args_range check_args_at_least); +use strict; +use Carp; + +=head1 NAME + +checkargs -- Provide rudimentary argument checking for perl5 functions + +=head1 SYNOPSIS + + check_args(cArgsExpected, @_) + check_args_range(cArgsMin, cArgsMax, @_) + check_args_at_least(cArgsMin, @_) +where "@_" should be supplied literally. + +=head1 DESCRIPTION + +As the first line of user-written subroutine foo, do one of the following: + + my ($arg1, $arg2) = check_args(2, @_); + my ($arg1, @rest) = check_args_range(1, 4, @_); + my ($arg1, @rest) = check_args_at_least(1, @_); + my @args = check_args_at_least(0, @_); + +These functions may also be called for side effect (put a call to one +of the functions near the beginning of the subroutine), but using the +argument checkers to set the argument list is the recommended usage. + +The number of arguments and their definedness are checked; if the wrong +number are received, the program exits with an error message. + +=head1 AUTHOR + +Michael D. Ernst > + +=cut + +## Need to check that use of caller(1) really gives desired results. +## Need to give input chunk information. +## Is this obviated by Perl 5.003's declarations? Not entirely, I think. + +sub check_args ( $@ ) +{ + my ($num_formals, @args) = @_; + my ($pack, $file_arg, $line_arg, $subname, $hasargs, $wantarr) = caller(1); + if (@_ < 1) { croak "check_args needs at least 7 args, got ", scalar(@_), ": @_\n "; } + if ((!wantarray) && ($num_formals != 0)) + { croak "check_args called in scalar context"; } + # Can't use croak below here: it would only go out to caller, not its caller + my $num_actuals = @args; + if ($num_actuals != $num_formals) + { die "$file_arg:$line_arg: function $subname expected $num_formals argument", + (($num_formals == 1) ? "" : "s"), + ", got $num_actuals", + (($num_actuals == 0) ? "" : ": @args"), + "\n"; } + for my $index (0..$#args) + { if (!defined($args[$index])) + { die "$file_arg:$line_arg: function $subname undefined argument ", $index+1, ": @args[0..$index-1]\n"; } } + return @args; +} + +sub check_args_range ( $$@ ) +{ + my ($min_formals, $max_formals, @args) = @_; + my ($pack, $file_arg, $line_arg, $subname, $hasargs, $wantarr) = caller(1); + if (@_ < 2) { croak "check_args_range needs at least 8 args, got ", scalar(@_), ": @_"; } + if ((!wantarray) && ($max_formals != 0) && ($min_formals !=0) ) + { croak "check_args_range called in scalar context"; } + # Can't use croak below here: it would only go out to caller, not its caller + my $num_actuals = @args; + if (($num_actuals < $min_formals) || ($num_actuals > $max_formals)) + { die "$file_arg:$line_arg: function $subname expected $min_formals-$max_formals arguments, got $num_actuals", + ($num_actuals == 0) ? "" : ": @args", "\n"; } + for my $index (0..$#args) + { if (!defined($args[$index])) + { die "$file_arg:$line_arg: function $subname undefined argument ", $index+1, ": @args[0..$index-1]\n"; } } + return @args; +} + +sub check_args_at_least ( $@ ) +{ + my ($min_formals, @args) = @_; + my ($pack, $file_arg, $line_arg, $subname, $hasargs, $wantarr) = caller(1); + # Don't do this, because we want every sub to start with a call to check_args* + # if ($min_formals == 0) + # { die "Isn't it pointless to check for at least zero args to $subname?\n"; } + if (scalar(@_) < 1) + { croak "check_args_at_least needs at least 1 arg, got ", scalar(@_), ": @_"; } + if ((!wantarray) && ($min_formals != 0)) + { croak "check_args_at_least called in scalar context"; } + # Can't use croak below here: it would only go out to caller, not its caller + my $num_actuals = @args; + if ($num_actuals < $min_formals) + { die "$file_arg:$line_arg: function $subname expected at least $min_formals argument", + ($min_formals == 1) ? "" : "s", + ", got $num_actuals", + ($num_actuals == 0) ? "" : ": @args", "\n"; } + for my $index (0..$#args) + { if (!defined($args[$index])) + { warn "$file_arg:$line_arg: function $subname undefined argument ", $index+1, ": @args[0..$index-1]\n"; last; } } + return @args; +} + +1; # successful import +__END__ diff --git a/doc/tools/cklatex b/doc/tools/cklatex new file mode 100755 index 0000000..396e914 --- /dev/null +++ b/doc/tools/cklatex @@ -0,0 +1,26 @@ +#! /bin/sh +# -*- ksh -*- + +# This script *helps* locate lines of normal content that end in '}'; +# this is useful since LaTeX2HTML (at least the old version that we +# use) breaks on many lines that end that way. +# +# Usage: cklatex files... | less +# +# *Read* the output looking for suspicious lines! + +grep -n "[^ ]}\$" $@ | \ + grep -v '\\begin{' | \ + grep -v '\\end{' | \ + grep -v '\\input{' | \ + grep -v '\\documentclass{' | \ + grep -v '\\title{' | \ + grep -v '\\chapter{' | \ + grep -v '\\chapter\*{' | \ + grep -v '\\section{' | \ + grep -v '\\subsection{' | \ + grep -v '\\subsubsection{' | \ + grep -v '\\sectionauthor{' | \ + grep -v '\\moduleauthor{' + +exit $? diff --git a/doc/tools/custlib.py b/doc/tools/custlib.py new file mode 100644 index 0000000..9958451 --- /dev/null +++ b/doc/tools/custlib.py @@ -0,0 +1,73 @@ +# Generate custlib.tex, which is a site-specific library document. + +# Phase I: list all the things that can be imported + +import glob, os, sys, string +modules={} + +for modname in sys.builtin_module_names: + modules[modname]=modname + +for dir in sys.path: + # Look for *.py files + filelist=glob.glob(os.path.join(dir, '*.py')) + for file in filelist: + path, file = os.path.split(file) + base, ext=os.path.splitext(file) + modules[string.lower(base)]=base + + # Look for shared library files + filelist=(glob.glob(os.path.join(dir, '*.so')) + + glob.glob(os.path.join(dir, '*.sl')) + + glob.glob(os.path.join(dir, '*.o')) ) + for file in filelist: + path, file = os.path.split(file) + base, ext=os.path.splitext(file) + if base[-6:]=='module': base=base[:-6] + modules[string.lower(base)]=base + +# Minor oddity: the types module is documented in libtypes2.tex +if modules.has_key('types'): + del modules['types'] ; modules['types2']=None + +# Phase II: find all documentation files (lib*.tex) +# and eliminate modules that don't have one. + +docs={} +filelist=glob.glob('lib*.tex') +for file in filelist: + modname=file[3:-4] + docs[modname]=modname + +mlist=modules.keys() +mlist=filter(lambda x, docs=docs: docs.has_key(x), mlist) +mlist.sort() +mlist=map(lambda x, docs=docs: docs[x], mlist) + +modules=mlist + +# Phase III: write custlib.tex + +# Write the boilerplate +# XXX should be fancied up. +print """\documentstyle[twoside,11pt,myformat]{report} +\\title{Python Library Reference} +\\input{boilerplate} +\\makeindex % tell \\index to actually write the .idx file +\\begin{document} +\\pagenumbering{roman} +\\maketitle +\\input{copyright} +\\begin{abstract} +\\noindent This is a customized version of the Python Library Reference. +\\end{abstract} +\\pagebreak +{\\parskip = 0mm \\tableofcontents} +\\pagebreak\\pagenumbering{arabic}""" + +for modname in mlist: + print "\\input{lib%s}" % (modname,) + +# Write the end +print """\\input{custlib.ind} % Index +\\end{document}""" diff --git a/doc/tools/cvsinfo.py b/doc/tools/cvsinfo.py new file mode 100644 index 0000000..58a32c2 --- /dev/null +++ b/doc/tools/cvsinfo.py @@ -0,0 +1,81 @@ +"""Utility class and function to get information about the CVS repository +based on checked-out files. +""" + +import os + + +def get_repository_list(paths): + d = {} + for name in paths: + if os.path.isfile(name): + dir = os.path.dirname(name) + else: + dir = name + rootfile = os.path.join(name, "CVS", "Root") + root = open(rootfile).readline().strip() + if not d.has_key(root): + d[root] = RepositoryInfo(dir), [name] + else: + d[root][1].append(name) + return d.values() + + +class RepositoryInfo: + """Record holding information about the repository we want to talk to.""" + cvsroot_path = None + branch = None + + # type is '', ':ext', or ':pserver:' + type = "" + + def __init__(self, dir=None): + if dir is None: + dir = os.getcwd() + dir = os.path.join(dir, "CVS") + root = open(os.path.join(dir, "Root")).readline().strip() + if root.startswith(":pserver:"): + self.type = ":pserver:" + root = root[len(":pserver:"):] + elif ":" in root: + if root.startswith(":ext:"): + root = root[len(":ext:"):] + self.type = ":ext:" + self.repository = root + if ":" in root: + host, path = root.split(":", 1) + self.cvsroot_path = path + else: + self.cvsroot_path = root + fn = os.path.join(dir, "Tag") + if os.path.isfile(fn): + self.branch = open(fn).readline().strip()[1:] + + def get_cvsroot(self): + return self.type + self.repository + + _repository_dir_cache = {} + + def get_repository_file(self, path): + filename = os.path.abspath(path) + if os.path.isdir(path): + dir = path + join = 0 + else: + dir = os.path.dirname(path) + join = 1 + try: + repodir = self._repository_dir_cache[dir] + except KeyError: + repofn = os.path.join(dir, "CVS", "Repository") + repodir = open(repofn).readline().strip() + repodir = os.path.join(self.cvsroot_path, repodir) + self._repository_dir_cache[dir] = repodir + if join: + fn = os.path.join(repodir, os.path.basename(path)) + else: + fn = repodir + return fn[len(self.cvsroot_path)+1:] + + def __repr__(self): + return "" % `self.get_cvsroot()` diff --git a/doc/tools/findacks b/doc/tools/findacks new file mode 100755 index 0000000..c13b00f --- /dev/null +++ b/doc/tools/findacks @@ -0,0 +1,161 @@ +#!/usr/bin/env python +"""Script to locate email addresses in the CVS logs.""" +__version__ = '$Revision: 1.1.1.1 $' + +import os +import re +import sys +import UserDict + +import cvsinfo + + +class Acknowledgements(UserDict.UserDict): + def add(self, email, name, path): + d = self.data + d.setdefault(email, {})[path] = name + + +def open_cvs_log(info, paths=None): + cvsroot = info.get_cvsroot() + cmd = "cvs -q -d%s log " % cvsroot + if paths: + cmd += " ".join(paths) + return os.popen(cmd, "r") + + +email_rx = re.compile("<([a-z][-a-z0-9._]*@[-a-z0-9.]+)>", re.IGNORECASE) + +def find_acks(f, acks): + prev = '' + filename = None + MAGIC_WORDS = ('van', 'von') + while 1: + line = f.readline() + if not line: + break + if line.startswith("Working file: "): + filename = line.split(None, 2)[2].strip() + prev = line + continue + m = email_rx.search(line) + if m: + words = prev.split() + line[:m.start()].split() + L = [] + while words \ + and (words[-1][0].isupper() or words[-1] in MAGIC_WORDS): + L.insert(0, words.pop()) + name = " ".join(L) + email = m.group(1).lower() + acks.add(email, name, filename) + prev = line + + +def load_cvs_log_acks(acks, args): + repolist = cvsinfo.get_repository_list(args or [""]) + for info, paths in repolist: + print >>sys.stderr, "Repository:", info.get_cvsroot() + f = open_cvs_log(info, paths) + find_acks(f, acks) + f.close() + + +def load_tex_source_acks(acks, args): + for path in args: + path = path or os.curdir + if os.path.isfile(path): + read_acks_from_tex_file(acks, path) + else: + read_acks_from_tex_dir(acks, path) + + +def read_acks_from_tex_file(acks, path): + f = open(path) + while 1: + line = f.readline() + if not line: + break + if line.startswith(r"\sectionauthor{"): + line = line[len(r"\sectionauthor"):] + name, line = extract_tex_group(line) + email, line = extract_tex_group(line) + acks.add(email, name, path) + + +def read_acks_from_tex_dir(acks, path): + stack = [path] + while stack: + p = stack.pop() + for n in os.listdir(p): + n = os.path.join(p, n) + if os.path.isdir(n): + stack.insert(0, n) + elif os.path.normpath(n).endswith(".tex"): + read_acks_from_tex_file(acks, n) + + +def extract_tex_group(s): + c = 0 + for i in range(len(s)): + if s[i] == '{': + c += 1 + elif s[i] == '}': + c -= 1 + if c == 0: + return s[1:i], s[i+1:] + + +def print_acks(acks): + first = 1 + for email, D in acks.items(): + if first: + first = 0 + else: + print + L = D.items() + L.sort() + prefname = L[0][1] + for file, name in L[1:]: + if name != prefname: + prefname = "" + break + if prefname: + print prefname, "<%s>:" % email + else: + print email + ":" + for file, name in L: + if name == prefname: + print " " + file + else: + print " %s (as %s)" % (file, name) + + +def print_ack_names(acks): + names = [] + for email, D in acks.items(): + L = D.items() + L.sort() + prefname = L[0][1] + for file, name in L[1:]: + prefname = prefname or name + names.append(prefname or email) + def f(s1, s2): + s1 = s1.lower() + s2 = s2.lower() + return cmp((s1.split()[-1], s1), + (s2.split()[-1], s2)) + names.sort(f) + for name in names: + print name + + +def main(): + args = sys.argv[1:] + acks = Acknowledgements() + load_cvs_log_acks(acks, args) + load_tex_source_acks(acks, args) + print_ack_names(acks) + + +if __name__ == "__main__": + main() diff --git a/doc/tools/findmodrefs b/doc/tools/findmodrefs new file mode 100755 index 0000000..8c5f93f --- /dev/null +++ b/doc/tools/findmodrefs @@ -0,0 +1,63 @@ +#! /usr/bin/env python +# -*- Python -*- + +import fileinput +import getopt +import glob +import os +import re +import sys + + +declare_rx = re.compile( + r"\\declaremodule(?:\[[a-zA-Z0-9]*\]*)?{[a-zA-Z_0-9]+}{([a-zA-Z_0-9]+)}") + +module_rx = re.compile(r"\\module{([a-zA-Z_0-9]+)}") + +def main(): + try: + just_list = 0 + print_lineno = 0 + opts, args = getopt.getopt(sys.argv[1:], "ln", ["list", "number"]) + for opt, arg in opts: + if opt in ("-l", "--list"): + just_list = 1 + elif opt in ("-n", "--number"): + print_lineno = 1 + files = args + if not files: + files = glob.glob("*.tex") + files.sort() + modulename = None + for line in fileinput.input(files): + if line[:9] == r"\section{": + modulename = None + continue + if line[:16] == r"\modulesynopsys{": + continue + m = declare_rx.match(line) + if m: + modulename = m.group(1) + continue + if not modulename: + continue + m = module_rx.search(line) + if m: + name = m.group(1) + if name != modulename: + filename = fileinput.filename() + if just_list: + print filename + fileinput.nextfile() + modulename = None + elif print_lineno: + print "%s(%d):%s" \ + % (filename, fileinput.filelineno(), line[:-1]) + else: + print "%s:%s" % (filename, line[:-1]) + except KeyboardInterrupt: + sys.exit(1) + + +if __name__ == "__main__": + main() diff --git a/doc/tools/fix_hack b/doc/tools/fix_hack new file mode 100755 index 0000000..8dad111 --- /dev/null +++ b/doc/tools/fix_hack @@ -0,0 +1,2 @@ +#!/bin/sh +sed -e 's/{\\ptt[ ]*\\char[ ]*'"'"'137}/_/g' <"$1" > "@$1" && mv "@$1" $1 diff --git a/doc/tools/fix_libaux.sed b/doc/tools/fix_libaux.sed new file mode 100755 index 0000000..fb33cc5 --- /dev/null +++ b/doc/tools/fix_libaux.sed @@ -0,0 +1,3 @@ +#! /bin/sed -f +s/{\\tt \\hackscore {}\\hackscore {}/\\sectcode{__/ +s/\\hackscore {}\\hackscore {}/__/ diff --git a/doc/tools/fixinfo.el b/doc/tools/fixinfo.el new file mode 100644 index 0000000..267a7e3 --- /dev/null +++ b/doc/tools/fixinfo.el @@ -0,0 +1,15 @@ +(defun fix-python-texinfo () + (goto-char (point-min)) + (replace-regexp "\\(@setfilename \\)\\([-a-z]*\\)$" + "\\1python-\\2.info") + (replace-string "@node Front Matter\n@chapter Abstract\n" + "@node Abstract\n@section Abstract\n") + (mark-whole-buffer) + (texinfo-master-menu 'update-all-nodes) + (save-buffer) + ) ;; fix-python-texinfo + +;; now really do it: +(find-file (car command-line-args-left)) +(fix-python-texinfo) +(kill-emacs) diff --git a/doc/tools/getpagecounts b/doc/tools/getpagecounts new file mode 100755 index 0000000..179ced1 --- /dev/null +++ b/doc/tools/getpagecounts @@ -0,0 +1,88 @@ +#! /usr/bin/env python +# -*- Python -*- + +"""Generate a page count report of the PostScript version of the manuals.""" + +__version__ = '$Revision: 1.1.1.1 $' + + +class PageCounter: + def __init__(self): + self.doclist = [] + self.total = 0 + self.title_width = 0 + + def add_document(self, prefix, title): + count = count_pages(prefix + ".ps") + self.doclist.append((title, prefix, count)) + self.title_width = max(self.title_width, len(title)) + self.total = self.total + count + + def dump(self): + fmt = "%%-%ds (%%s.ps, %%d pages)" % self.title_width + for item in self.doclist: + print fmt % item + print + print " Total page count: %d" % self.total + + def run(self): + for prefix, title in [ + ("api", "Python/C API"), + ("ext", "Extending and Embedding the Python Interpreter"), + ("lib", "Python Library Reference"), + ("mac", "Macintosh Module Reference"), + ("ref", "Python Reference Manual"), + ("tut", "Python Tutorial"), + ("doc", "Documenting Python"), + ("inst", "Installing Python Modules"), + ("dist", "Distributing Python Modules"), + ]: + self.add_document(prefix, title) + print self.PREFIX + self.dump() + print self.SUFFIX + + PREFIX = """\ +This is the PostScript version of the standard Python documentation. +If you plan to print this, be aware that some of the documents are +long. It is formatted for printing on two-sided paper; if you do plan +to print this, *please* print two-sided if you have a printer capable +of it! To locate published copies of the larger manuals, or other +Python reference material, consult the PSA Online Bookstore at: + + http://www.python.org/psa/bookstore/ + +The following manuals are included: +""" + SUFFIX = """\ + + +If you have any questions, comments, or suggestions regarding these +documents, please send them via email to python-docs@python.org. + +If you would like to support the development and maintenance of +documentation for Python, please consider joining the Python Software +Activity (PSA; see http://www.python.org/psa/), or urging your +organization to join the PSA or the Python Consortium (see +http://www.python.org/consortium/). +""" + +def count_pages(filename): + fp = open(filename) + count = 0 + while 1: + lines = fp.readlines(1024*40) + if not lines: + break + for line in lines: + if line[:7] == "%%Page:": + count = count + 1 + fp.close() + return count + + +def main(): + PageCounter().run() + +if __name__ == "__main__": + main() diff --git a/doc/tools/html/about.dat b/doc/tools/html/about.dat new file mode 100644 index 0000000..e6f8b55 --- /dev/null +++ b/doc/tools/html/about.dat @@ -0,0 +1,24 @@ +

This document was generated using the + LaTeX2HTML translator. +

+ +

+ LaTeX2HTML is Copyright © + 1993, 1994, 1995, 1996, 1997, Nikos + Drakos, Computer Based Learning Unit, University of + Leeds, and Copyright © 1997, 1998, Ross + Moore, Mathematics Department, Macquarie University, + Sydney. +

+ +

The application of + LaTeX2HTML to the Python + documentation has been heavily tailored by Fred L. Drake, + Jr. Original navigation icons were contributed by Christopher + Petrilli. +

diff --git a/doc/tools/html/about.html b/doc/tools/html/about.html new file mode 100644 index 0000000..3203faf --- /dev/null +++ b/doc/tools/html/about.html @@ -0,0 +1,74 @@ + + + + About the Python Documentation + + + + + + +
+ +

About the Python Documentation

+ +

The Python documentation was originally written by Guido van + Rossum, but has increasingly become a community effort over the + past several years. This growing collection of documents is + available in several formats, including typeset versions in PDF + and PostScript for printing, from the Python Web site. + +

A list of contributors is available. + +

Comments and Questions

+ +

General comments and questions regarding this document should + be sent by email to python-docs@python.org. If you find specific errors in + this document, please report the bug at the Python Bug + Tracker at SourceForge. +

+ +

Questions regarding how to use the information in this + document should be sent to the Python news group, comp.lang.python, or the Python mailing list (which is gated to the newsgroup and + carries the same content). +

+ +

For any of these channels, please be sure not to send HTML email. + Thanks. +

+ +
+ + diff --git a/doc/tools/html/icons/blank.gif b/doc/tools/html/icons/blank.gif new file mode 100644 index 0000000000000000000000000000000000000000..2e31f4ed50ee9a3dc57f62d88392e7928d262c07 GIT binary patch literal 1958 zcmd^;Id0QY5XQ&xnu0s{08ottNHkO+&OieNQ*j77iu8!N21s0jxd3hAE%Cm@juXej z&#$6Ofg-WI%J0q0H{as_%U3U+K6%@gwtSSssZ=UQM@Q9awN|Uu>-9#X(QGzbt=94J zF;x{Y5f=%OEO%8g6;}zBEM+w@6E_KyEZyp2F76U8S%x*lLOdiwvS4Y7rFcrDWPw%z ziBWnfayuMTb=6SKvY5M>x@nkZ;X&D4-8EdZ(4yNyJv2hIuw>X$JvCCZkVXWop$RXD zu$fh01I9G%)Op~zS(s%}$YSp97H(Mt!-KMiMOYU7(4yPZA}xy@xP?9VfdME+BAO5Z zYiPm?B5Vd4Y`~bNojMPkaL-~T3R%oO!m~JwV0ciD^ejdb3prpsPGc=zVjgZ`4}M?( zijfFuuwV^MctM2CK!Xh!)3j6Pfs-XOtVAJ;xo3%x{0W?diIfZpjg*Lm955cIu@*0} z1C|Wq2L_-RiI4^h*3g6(MA!^8*nlxjJ9Qp7r>Cc9XJ?&Gr`zrJdcA(XKNt+o&(AL| zE{4P5Xfzs+$CJrqI-SmDv-y0!SS*&ygsB{-Ckc`-`w2ncDvi# z+x>nI^25UyaQGIUw^F{o`~Ku>LWeEA=LKALIe|!wxvrN0~ja~uAbC*@nW(^ z6HX>_CjJ4Q_HN>lukuZyd>2@M-AUSJc4pqZ_a^;3Ix^JJ-q|i99ogZ`D}E!5PdLbT zX6efxk)Vve7#}1J1OiP>O~GKWxw$zM3bnMfw6?Z}!{N5JHmXWQix?3r;(V@@XeCC8 zRpNYEY0+AY7OTbiZe>IpF-EKr=ZBRQZN*rzR-DIDPP7x_#5!>vS_w#u(o2!75n3su zlvT?4#pGHmqm|Xlc^;ItQN}21l=HOc)>av-tX0mlWLP_8oU%?iPZ|-hh9|?r7Lz-FLhjGOxiBZ?h8&G6aTe}?@i>jO zc!?dbWEej%0L4gzG+3~PCcGfRW}v|ajA`1b^FXYwuCA@Et*@_dY;0_9ZdNLlt*x!? z?d_eNo!#Bty}iBt{r!W3gTuqaYPEWFbaZ@tT&vYiPEJlwPtVTI&d<*;E-o%FFR!ky z>h=2d_4Uoo&F$?i$OHYIe;faS{PUrVd)6k?OI;$r znkJ%wAC+Y~2FinP%40LS{2@16l)lH0o@A$Tlf{?0$x`86BoU2Adn4&|Ppr2m_B2wM zEzOk|`=%D>%B4cII2WCme?K!_juvL7zWsi<0$i7eD@{)4%JbmGa)neXR!pSQI0C0IOr??&Iv!8t68U27&i6V~cNY($TQaJwElDoXb{P z{iKTTfktP#y~XC0jUJp#lZSML7^`tb6&=(bvdv16}%BLs@B+A z<>Fo^)a#KlQRA*qPm^bZhl&`J&_aPqkqo6nizOUfc$PGUJ}H>O;jEeB*fqn|dMO8! zrs_UrMq4YPw9CrO%&LwxjNF18D$I3^j?G$mjZSThY=S(D8Tp#iV;HS@RXc3sEVQ@; zix|u2GxjW6Be+hTuaqq?ZXLJ41ncEWOc6>OSQ6GZED$(c#b3i5(CxI2U4WC7>!J#q zKr~mE8p{M#*464y?Owimt;fVYla;rNrHhN5mzRZ$m+K4Tw;x|>oe~<_m>GGN*%Tb{ z^3*ZpS)XVu;yquLCFGH5iiE=A71_)dg*pK(DU58~cGgxo1rJhA^>SBrR78A=Vq({F yzjww`;hH^D;28~#=PK%FS+o}ac$3N2$Xvl$m3PG4WR({a-;E8oZz-`bSOWl(s*7p> literal 0 HcmV?d00001 diff --git a/doc/tools/html/icons/contents.png b/doc/tools/html/icons/contents.png new file mode 100644 index 0000000000000000000000000000000000000000..3429be0c1d45d9b1f0816da78621c029f512de1e GIT binary patch literal 649 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy2?0JKuK)l4pLyng|DmsKJKxl- ze^k8eX6Bsp@so~*b?))0-{f4j(kgd>LGm=U$R62%W?{EVF53bI<20adB0;ac!CIhF z#w2fd7oqAsIUu&Ir;B4q#jUfG?oK+az|q>>#C1&m-v3l_L(kpjZ++9#jb~Q4+3_XR z9B6<3v7g}&LrNuo%Gvh}Ijg4ntJ`I`J0-45WM0x7CTzi@Ju7TZTjSpMLMyILf7xB8 zzpLlj9fsp zeuu2_;)6`NKZ_-)w2oSBzJ<5*SaeU1+jXReym>iyYq)M&223n3t=3{GA}JF@a0-?ty6= z4|59K_IAJPaC-WhpW+ApmRY3ca0~EUaO{{dzu`0k-;rwzOJ=t`j&$K^?=t zlYFakZp7OHL&~QrB%&lZKdq!Zu_%>+q2gBYg@9{c&iMNHfA#e_?{hB9;G*#*uns|DeZt?IkVPWWX<(OZvdC7C2 sc5#sQfW&mLlZ?$)8JJx%yI{t^z%`5Ur+fKfQJ@kAPgg&ebxsLQ0It{kg8%>k literal 0 HcmV?d00001 diff --git a/doc/tools/html/icons/index.gif b/doc/tools/html/icons/index.gif new file mode 100644 index 0000000000000000000000000000000000000000..32eecfb4fd4bca98f6601e677fa0e667c3a1efe3 GIT binary patch literal 289 zcmZ?wbhEHbRA5kGIP#z2|Ns9p&;0K{^tEm0o0|2HikIEYoO3>Y($TQaJwElDoXb{P z{iKTTfktP#y~XC0jUJp#lX5OL7^`tb6&=(bvdu+v3=SX7;-^cMjqsd~%GA9>CT~(zvt(6O}bg@iq3s31#ImX!PTkxTXwIj7u zuu$NF+mpyDJ&b7+7!9W=aq$;v&*J5q z+r!Bh!kW~b&dV*pn6%1`o1b~Z)}XD7+$;+i`8kYcZalJe4dd!&Zf5_Q%c_bD)&L0t BTp0iW literal 0 HcmV?d00001 diff --git a/doc/tools/html/icons/index.png b/doc/tools/html/icons/index.png new file mode 100644 index 0000000000000000000000000000000000000000..cd918afe76ca2913a4de7ac7663211878b0c263f GIT binary patch literal 529 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy2?0JKuK)l4pLyng|DmsKJKxl- ze^k8eX6Bsp@so~*b?))0-{f4j(kgd>LGm=U$R62%W?{EVF53bI<20adB0;ac!CIhF z#w2fd7oqAsIUsFIJzX3_DsFAPbdm3{0tf4bMXs#u)BgXzdFjnr%UL?B*Ux+KwT(CL z+_(CGTf7E`B|d1|N*Rg2lJF1kHncD~*Ko{GLECVG&NY_%=9 z{C8Qq4_rQDCxf4A&_I-XbVGq#3K2;$RCBgY=CFO}l zsSFGiw~8+WT>Emy*T?^>ug`g(b72M-jV~DoZP;?f+~BIYL5PRmDIL9)B`4PE=$-Vr z=B<0pTT}PMdF_kd=gzY-eDM})_;UA19MD{2u(_G(d5I;Z5QB|P{fzyM{7ijK%?(U_ zOiT@p{I+=*d;1vs7`C(C@5(ZCKKbLh*2~7a755D*S literal 0 HcmV?d00001 diff --git a/doc/tools/html/icons/modules.gif b/doc/tools/html/icons/modules.gif new file mode 100644 index 0000000000000000000000000000000000000000..f5860b6bb9c4ce21e25ca74b0fb90faaaf231993 GIT binary patch literal 385 zcmZ?wbhEHbRA5kGIP#z2|Ns9p&;0K{^tEm0o0|2HikIEYoO3>Y($TQaJwElDoXb{P z{iKTTfktP#y~XC0jUJp#lZSML7^`tb6&=(bvdv173iEV$-Q4T zQKc)wfV=U<1&+oZeq4N_(o)_}U;aU1zf@uM5%DzVY0=106wfVe;%z`}1(`G1DaWJ*(c(i+zDK$Cj z^Moj`3SCwmZ=u4vY3n8yH_mu&o=`4ce|8p@LutIck+YRq1(*d`IXGBm@G|i-bFx}l zDNRZ5x8vvKvv-;@g^lCp?V@{p>rQjJvfg{nX!ZJLkpS-zZdI#-i?7+9-Q&ys#nDy3 w&^OzT`5Kp=zzM&F4LO^OLLGm=U$R62%W?{EVF53bI<20adB0;ac!CIhF z#w2fd7oqAsIUsGXJY5_^DsH{Kbg<}%0tedzR@WmO6TjErOqz6e*=(Qco5{sL_|KUi z`tyvjzNWwa1E0qd*Ke$h5l^47ePi1*Lp&q*YYFS4R?A6IY+q+fpXp4$#$@n@>w@pL zH5)yBb~ilI&fDp~yXn~?`hzbNux0)FaWKvLDLKtM7)-DE-S)yE@JP zqv`*DVqYF!Kf^RB@g?Uhu?KT3kMCP`rk360(t@aBc2{2~6_L5_!foskS63vV}1V#`3H2Y)7~&VTJeCz+m2D>7h8tgf@=yhm)_9gPYwR#5E##Q zl4sT2ou>~2eeF{f5>XPIpH@ zSIiBrnj3_8=$+EhTUl~qt&ZMFpKIQ_*Ss}#Pn_4j=zZ=yE5jFWp@uJakHi7ZH3plT znVy$eQVKEHdy|2wx3RaOk%@<)k(rsdmyfZpsfm%nHWSY+2IgB07#O-=aOh>eQs4n< s7YAt%NK6Mi$=Ga_f!QUq3uX)qT(cN|x|bgo1u9|iboFyt=akR{0EAiRtN;K2 literal 0 HcmV?d00001 diff --git a/doc/tools/html/icons/next.gif b/doc/tools/html/icons/next.gif new file mode 100644 index 0000000000000000000000000000000000000000..5dcaff8b2429e50aad5de34344459cebef9e0957 GIT binary patch literal 253 zcmZ?wbhEHbRA5kGIP#z2|Ns9p&;0K{^tEm0o0|2HikIEYoO3>Y($TQaJwElDoXb{P zg2lA{-J z-=104^TCI&GE5IMJ{UMjoDw|98R+85{_3GrKtS>JHenB~pz|5ZMN{&Yi!WMP{Z5Bv z)0aPH6Hc6$Pq1Q2%5rpKPP60nbYk&!EAV3Px8n$`b>n9Xw3*px?BbW*&Sh@K)f2O7 O^_sQo)~hNqSOWl>6;&qy literal 0 HcmV?d00001 diff --git a/doc/tools/html/icons/next.png b/doc/tools/html/icons/next.png new file mode 100644 index 0000000000000000000000000000000000000000..cfe5e51ca619b357fc22d618fa2cb132036da31f GIT binary patch literal 511 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy2?0JKuK)l4pLyng|DmsKJKxl- ze^k8eX6Bsp@so~*b?))0-{f4j(kgd>LGm=U$R62%W-i+T2IDlKR^mbIJX?@mj7i?^ zE`pD?+U^2!CVRR#hE&`-JNcl{Ap;JVWQUcVoIn4spW0L=b9UR~wbf63)8v=FESgZ_ z<}j=MBX{YVz$S-xeqVU3Cs=(vpn9`0a6>DLV#H>agBAR_!G;P^{>fTwJ##~tEBGa@ ze_3bKe}lW+CX7wYQpJkVmuvmBgNq{KnG&uFeP*qF_hOBCi9zXVd)9q@PuUkd`W|8T z?D?OO|E6o##^(5>XPIpH@SIiBrnj3_8=$+EhTUl~qt&ZMFpKIQ_*Ss}#Pn_4j=zZ=yE5jFWp@uJa zkHi7ZH3plTnVy$eQVKEH*vQy$^A;Zyb7ON8Z$m>9BQrlAW5ewRranf71|B}fEDSYy z>=#b%J8=Z4T^yu6ATb^6BxAEx240W+V6sUy3)78&qol`;+0AXLa A&;S4c literal 0 HcmV?d00001 diff --git a/doc/tools/html/icons/previous.gif b/doc/tools/html/icons/previous.gif new file mode 100644 index 0000000000000000000000000000000000000000..de1da16023d2b5c20e6f1c8546c04e434136ac6b GIT binary patch literal 252 zcmZ?wbhEHbRA5kGIP#z2|Ns9p&;0K{^tEm0o0|2HikIEYoO3>Y($TQaJwElDoXb{P z{iKTTfktP#y~XC0jUJp#lYH_pwO3+IWJ??x}4Yh3Ut`rlkXq( z(F?rq%re*Gz=y9gjt>tc_L#67Vqm-!=z}!6qV*&EihG9 zIC1R#*_Ia#?|<_sbJf*)D!C>zhnM8L2ZS@1*%o)PWN>wvWF&MoCAS9h#`UH0n43+X Qv2xYwHEY*tD>7IE016OT9smFU literal 0 HcmV?d00001 diff --git a/doc/tools/html/icons/previous.png b/doc/tools/html/icons/previous.png new file mode 100644 index 0000000000000000000000000000000000000000..497def42a0cf47e012bcd69599c2d3c405c3bca4 GIT binary patch literal 511 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy2?0JKuK)l4pLyng|DmsKJKxl- ze^k8eX6Bsp@so~*b?))0-{f4j(kgd>LGm=U$R62%W?{EVF53bI<20adB0;ac!CIhF z#w2fd7oqAsIUsG5JzX3_DsEk!wRyY zCEWY>_i=7wnwQ?Thv7!lk3Q3b_hn3_6cyBDCqA^@uz$wKgFRYIje)6~-oBPwCCtL8 zRn{-bS5|{;$vbCgx;QBrpAU_J-m#4jD3wv4E??ROxPGE zh;n@Sv(s`4P`fxtdq843*h$7_s|?I8nO!hrVBng?_|v`ouqaRogQu&X%Q~loCID!* Bwsrsj literal 0 HcmV?d00001 diff --git a/doc/tools/html/icons/up.gif b/doc/tools/html/icons/up.gif new file mode 100644 index 0000000000000000000000000000000000000000..a9d3e13e47a031bc4ab5e0bf8485fc844233e2a0 GIT binary patch literal 316 zcmZ?wbhEHbRA5kGIP#z2|Ns9p&;0K{^tEm0o0|2HikIEYoO3>Y($TQaJwElDoXb{P z{iKTTfktP#y~XC0jUJp#lU(nL7^|jk}GZ1DyGA0K4@74E;;hU z!=beHi6QfP@kW7$Rm>AP92GsgpI-VXvUSO&qs0z~y#x=;y`FtQwrwxVN5NFNRPPfD z9dcN9?Y-S~&zMcXg_DsfL(pD3%aqTL%cVWZl+_@Y$B&WMWs;JYso{($6>rrne#16Z zW|t6E?lPlP)f!WMRi+L8ou*BpT0Ab>_o!9~G9T1jbWD|{^rR}A%Yw5?I|X-Nnm+To e62Hr|OFp+4Hwmu0-cb3FmGd>{tcS-G8LR>07;De~ literal 0 HcmV?d00001 diff --git a/doc/tools/html/icons/up.png b/doc/tools/html/icons/up.png new file mode 100644 index 0000000000000000000000000000000000000000..a90e0284436f3823439ae6c00a5fdd692a0663ff GIT binary patch literal 577 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE3?yBabR7dy2?0JKuK)l4pLyng|DmsKJKxl- ze^k8eX6Bsp@so~*b?))0-{f4j(kgd>LGm=U$R62%W?{EVF53bI<20adB0;ac!CIhF z#w2fd7oqAsIUsFUJzX3_DsI(Ix-HhKz~M64NIU=IfBR~Y$X4|dxhzrZ1U2!`f+hZ^ zm&+UyX=thNZFqZ3$(*HZ)#JUq4DD;ALk>J%yo5m@;6#0cY2xz7{28yk-^xCS`nzWO zo266P9IBt@>~+1oES71*pOcQcJRLiv0-~2c`um6}H-$A|S=%gM7riSdo=lopdpYOb zOw|LQ!vq)gGckPg+5M@o!H)g&n<$?iejfqORSc|K{2H#e@a5>fFx*hi?#O5&Ec=1Q zmZ^v}f$4(1dGdiQHI;%r$1^TG)Y&BLaGk%ULbzhlI&Nd22YsqSB1(eu(@M${i&7aF zDsB~D2)Op;jIWRXS6`p=KIg&=E*f7l4%)Egin+m6bAu2My;C}RD@#tS)zLfYbIn`# znzyFziSybQz0aLzW%%MP)bQo*kvO2a#$a@ip=GH1RMs z+iK#o&Bw&V#KXj3tBH{z6GLwqhx7L6v&}&5;vnq-iRoY`8Jn##FuP=S!Hj`{YZl{A T_wvJ{KqU;Gu6{1-oD!M + + Python @RELEASE@ Documentation - @DATE@ + + + + + + +
+

Python Documentation

+ +

+ Release @RELEASE@ +
+ @DATE@ +

+
+ + + + + + + + + + + +
+ + + +
+   + + +   + +
+

+ +

+
+ See About the Python Documentation + for information on suggesting changes. +
+ + diff --git a/doc/tools/html/stdabout.dat b/doc/tools/html/stdabout.dat new file mode 100644 index 0000000..a9b2718 --- /dev/null +++ b/doc/tools/html/stdabout.dat @@ -0,0 +1,48 @@ +

This document was generated using the + LaTeX2HTML translator. +

+ +

+ LaTeX2HTML is Copyright © + 1993, 1994, 1995, 1996, 1997, Nikos + Drakos, Computer Based Learning Unit, University of + Leeds, and Copyright © 1997, 1998, Ross + Moore, Mathematics Department, Macquarie University, + Sydney. +

+ +

The application of + LaTeX2HTML to the Python + documentation has been heavily tailored by Fred L. Drake, + Jr. Original navigation icons were contributed by Christopher + Petrilli. +

+ +
+ +

Comments and Questions

+ +

General comments and questions regarding this document should + be sent by email to python-docs@python.org. If you find specific errors in + this document, please report the bug at the Python Bug + Tracker at SourceForge. +

+ +

Questions regarding how to use the information in this + document should be sent to the Python news group, comp.lang.python, or the Python mailing list (which is gated to the newsgroup and + carries the same content). +

+ +

For any of these channels, please be sure not to send HTML email. + Thanks. +

diff --git a/doc/tools/html/style.css b/doc/tools/html/style.css new file mode 100644 index 0000000..767cf74 --- /dev/null +++ b/doc/tools/html/style.css @@ -0,0 +1,88 @@ +/* + * The first part of this is the standard CSS generated by LaTeX2HTML, + * with the "empty" declarations removed. + */ + +/* Century Schoolbook font is very similar to Computer Modern Math: cmmi */ +.math { font-family: "Century Schoolbook", serif; } +.math i { font-family: "Century Schoolbook", serif; + font-weight: bold } +.boldmath { font-family: "Century Schoolbook", serif; + font-weight: bold } + +/* Implement both fixed-size and relative sizes: */ +small.xtiny { font-size : xx-small } +small.tiny { font-size : x-small } +small.scriptsize { font-size : smaller } +small.footnotesize { font-size : small } +big.xlarge { font-size : large } +big.xxlarge { font-size : x-large } +big.huge { font-size : larger } +big.xhuge { font-size : xx-large } + +/* + * Document-specific styles come next; + * these are added for the Python documentation. + * + * Note that the size specifications for the H* elements are because + * Netscape on Solaris otherwise doesn't get it right; they all end up + * the normal text size. + */ + +body { color: #000000; + background-color: #ffffff; } + +a:active { color: #ff0000; } +a:visited { color: #551a8b; } +a:link { color: #0000bb; } + +h1, h2, h3, h4, h5, h6 { font-family: avantgarde, sans-serif; + font-weight: bold } +h1 { font-size: 180% } +h2 { font-size: 150% } +h3, h4 { font-size: 120% } +code, tt { font-family: monospace } +var { font-family: times, serif; + font-style: italic; + font-weight: normal } + +.navigation td { background-color: #99ccff; + font-weight: bold; + font-family: avantgarde, sans-serif; + font-size: 110% } + +.release-info { font-style: italic; } + +.titlegraphic { vertical-align: top; } + +.verbatim { color: #00008b } + +.email { font-family: avantgarde, sans-serif } +.mimetype { font-family: avantgarde, sans-serif } +.newsgroup { font-family: avantgarde, sans-serif } +.url { font-family: avantgarde, sans-serif } +.file { font-family: avantgarde, sans-serif } + +.tableheader { background-color: #99ccff; + font-family: avantgarde, sans-serif; } + +.refcount-info { font-style: italic } +.refcount-info .value { font-weight: bold; + color: #006600 } + +/* + * Some decoration for the "See also:" blocks, in part inspired by some of + * the styling on Lars Marius Garshol's XSA pages. + * (The blue in the navigation bars is #99CCFF.) + */ +.seealso { background-color: #fffaf0; + border: thin solid black; + padding: 4pt } + +.seealso .heading { font-size: 110% } + +/* + * Class 'availability' is used for module availability statements at + * the top of modules. + */ +.availability .platform { font-weight: bold } diff --git a/doc/tools/html2texi.pl b/doc/tools/html2texi.pl new file mode 100755 index 0000000..be050b1 --- /dev/null +++ b/doc/tools/html2texi.pl @@ -0,0 +1,1750 @@ +#! /usr/bin/env perl +# html2texi.pl -- Convert HTML documentation to Texinfo format +# Michael Ernst +# Time-stamp: <1999-01-12 21:34:27 mernst> + +# This program converts HTML documentation trees into Texinfo format. +# Given the name of a main (or contents) HTML file, it processes that file, +# and other files (transitively) referenced by it, into a Texinfo file +# (whose name is chosen from the file or directory name of the argument). +# For instance: +# html2texi.pl api/index.html +# produces file "api.texi". + +# Texinfo format can be easily converted to Info format (for browsing in +# Emacs or the standalone Info browser), to a printed manual, or to HTML. +# Thus, html2texi.pl permits conversion of HTML files to Info format, and +# secondarily enables producing printed versions of Web page hierarchies. + +# Unlike HTML, Info format is searchable. Since Info is integrated into +# Emacs, one can read documentation without starting a separate Web +# browser. Additionally, Info browsers (including Emacs) contain +# convenient features missing from Web browsers, such as easy index lookup +# and mouse-free browsing. + +# Limitations: +# html2texi.pl is currently tuned to latex2html output (and it corrects +# several latex2html bugs), but should be extensible to arbitrary HTML +# documents. It will be most useful for HTML with a hierarchical structure +# and an index, and it recognizes those features as created by latex2html +# (and possibly by some other tools). The HTML tree to be traversed must +# be on local disk, rather than being accessed via HTTP. +# This script requires the use of "checkargs.pm". To eliminate that +# dependence, replace calls to check_args* by @_ (which is always the last +# argument to those functions). +# Also see the "to do" section, below. +# Comments, suggestions, bug fixes, and enhancements are welcome. + +# Troubleshooting: +# Malformed HTML can cause this program to abort, so +# you should check your HTML files to make sure they are legal. + + +### +### Typical usage for the Python documentation: +### + +# (Actually, most of this is in a Makefile instead.) +# The resulting Info format Python documentation is currently available at +# ftp://ftp.cs.washington.edu/homes/mernst/python-info.tar.gz + +# Fix up HTML problems, eg
should be
. + +# html2texi.pl /homes/fish/mernst/tmp/python-doc/html/api/index.html +# html2texi.pl /homes/fish/mernst/tmp/python-doc/html/ext/index.html +# html2texi.pl /homes/fish/mernst/tmp/python-doc/html/lib/index.html +# html2texi.pl /homes/fish/mernst/tmp/python-doc/html/mac/index.html +# html2texi.pl /homes/fish/mernst/tmp/python-doc/html/ref/index.html +# html2texi.pl /homes/fish/mernst/tmp/python-doc/html/tut/index.html + +# Edit the generated .texi files: +# * change @setfilename to prefix "python-" +# * fix up any sectioning, such as for Abstract +# * make Texinfo menus +# * perhaps remove the @detailmenu ... @end detailmenu +# In Emacs, to do all this: +# (progn (goto-char (point-min)) (replace-regexp "\\(@setfilename \\)\\([-a-z]*\\)$" "\\1python-\\2.info") (replace-string "@node Front Matter\n@chapter Abstract\n" "@node Abstract\n@section Abstract\n") (progn (mark-whole-buffer) (texinfo-master-menu 'update-all-nodes)) (save-buffer)) + +# makeinfo api.texi +# makeinfo ext.texi +# makeinfo lib.texi +# makeinfo mac.texi +# makeinfo ref.texi +# makeinfo tut.texi + + +### +### Structure of the code +### + +# To be written... + + +### +### Design decisions +### + +# Source and destination languages +# -------------------------------- +# +# The goal is Info files; I create Texinfo, so I don't have to worry about +# the finer details of Info file creation. (I'm not even sure of its exact +# format.) +# +# Why not start from LaTeX rather than HTML? +# I could hack latex2html itself to produce Texinfo instead, or fix up +# partparse.py (which already translates LaTeX to Teinfo). +# Pros: +# * has high-level information such as index entries, original formatting +# Cons: +# * those programs are complicated to read and understand +# * those programs try to handle arbitrary LaTeX input, track catcodes, +# and more: I don't want to go to that effort. HTML isn't as powerful +# as LaTeX, so there are fewer subtleties. +# * the result wouldn't work for arbitrary HTML documents; it would be +# nice to eventually extend this program to HTML produced from Docbook, +# Frame, and more. + +# Parsing +# ------- +# +# I don't want to view the text as a linear stream; I'd rather parse the +# whole thing and then do pattern matching over the parsed representation (to +# find idioms such as indices, lists of child nodes, etc.). +# * Perl provides HTML::TreeBuilder, which does just what I want. +# * libwww-perl: http://www.linpro.no/lwp/ +# * TreeBuilder: HTML-Tree-0.51.tar.gz +# * Python Parsers, Formatters, and Writers don't really provide the right +# interface (and the version in Grail doesn't correspond to another +# distributed version, so I'm confused about which to be using). I could +# write something in Python that creates a parse tree, but why bother? + +# Other implementation language issues: +# * Python lacks variable declarations, reasonable scoping, and static +# checking tools. I've written some of the latter for myself that make +# my Perl programming a lot safer than my Python programming will be until +# I have a similar suite for that language. + + +########################################################################### +### To do +### + +# Section names: +# Fix the problem with multiple sections in a single file (eg, Abstract in +# Front Matter section). +# Deal with cross-references, as in /homes/fish/mernst/tmp/python-doc/html/ref/types.html:310 +# Index: +# Perhaps double-check that every tag mentioned in the index is found +# in the text. +# Python: email to python-docs@python.org, to get their feedback. +# Compare to existing lib/ Info manual +# Write the hooks into info-look; replace pyliblookup1-1.tar.gz. +# Postpass to remove extra quotation marks around typography already in +# a different font (to avoid double delimiters as in "`code'"); or +# perhaps consider using only font-based markup so that we don't get +# the extra *bold* and `code' markup in Info. + +## Perhaps don't rely on automatic means for adding up, next, prev; I have +## all that info available to me already, so it's not so much trouble to +## add it. (Right?) But it is *so* easy to use Emacs instead... + + +########################################################################### +### Strictures +### + +# man HTML::TreeBuilder +# man HTML::Parser +# man HTML::Element + +# require HTML::ParserWComment; +require HTML::Parser; +require HTML::TreeBuilder; +require HTML::Element; + +use File::Basename; + +use strict; +# use Carp; + +use checkargs; + + +########################################################################### +### Variables +### + +my @section_stack = (); # elements are chapter/section/subsec nodetitles (I think) +my $current_ref_tdf; # for the file currently being processed; + # used in error messages +my $html_directory; +my %footnotes; + +# First element should not be used. +my @sectionmarker = ("manual", "chapter", "section", "subsection", "subsubsection"); + +my %inline_markup = ("b" => "strong", + "code" => "code", + "i" => "emph", + "kbd" => "kbd", + "samp" => "samp", + "strong" => "strong", + "tt" => "code", + "var" => "var"); + +my @deferred_index_entries = (); + +my @index_titles = (); # list of (filename, type) lists +my %index_info = ("Index" => ["\@blindex", "bl"], + "Concept Index" => ["\@cindex", "cp"], + "Module Index" => ["\@mdindex", "md"]); + + +########################################################################### +### Main/contents page +### + +# Process first-level page on its own, or just a contents page? Well, I do +# want the title, author, etc., and the front matter... For now, just add +# that by hand at the end. + + +# data structure possibilities: +# * tree-like (need some kind of stack when processing (or parent pointers)) +# * list of name and depth; remember old and new depths. + +# Each element is a reference to a list of (nodetitle, depth, filename). +my @contents_list = (); + +# The problem with doing fixups on the fly is that some sections may have +# already been processed (and no longer available) by the time we notice +# others with the same name. It's probably better to fully construct the +# contents list (reading in all files of interest) upfront; that will also +# let me do a better job with cross-references, because again, all files +# will already be read in. +my %contents_hash = (); +my %contents_fixups = (); + +my @current_contents_list = (); + +# Merge @current_contents_list into @contents_list, +# and set @current_contents_list to be empty. +sub merge_contents_lists ( ) +{ check_args(0, @_); + + # Three possibilities: + # * @contents_list is empty: replace it by @current_contents_list. + # * prefixes of the two lists are identical: do nothing + # * @current_contents_list is all at lower level than $contents_list[0]; + # prefix @contents_list by @current_contents_list + + if (scalar(@current_contents_list) == 0) + { die "empty current_contents_list"; } + + # if (scalar(@contents_list) == 0) + # { @contents_list = @current_contents_list; + # @current_contents_list = (); + # return; } + + # if (($ {$contents_list[0]}[1]) < ($ {$current_contents_list[0]}[1])) + # { unshift @contents_list, @current_contents_list; + # @current_contents_list = (); + # return; } + + for (my $i=0; $i= scalar(@contents_list)) + { push @contents_list, $ref_c_tdf; + my $title = $ {$ref_c_tdf}[0]; + if (defined $contents_hash{$title}) + { $contents_fixups{$title} = 1; } + else + { $contents_hash{$title} = 1; } + next; } + my $ref_tdf = $contents_list[$i]; + my ($title, $depth, $file) = @{$ref_tdf}; + my ($c_title, $c_depth, $c_file) = @{$ref_c_tdf}; + + if (($title ne $c_title) + && ($depth < $c_depth) + && ($file ne $c_file)) + { splice @contents_list, $i, 0, $ref_c_tdf; + if (defined $contents_hash{$c_title}) + { $contents_fixups{$c_title} = 1; } + else + { $contents_hash{$c_title} = 1; } + next; } + + if (($title ne $c_title) + || ($depth != $c_depth) + || ($file ne $c_file)) + { die ("while processing $ {$current_ref_tdf}[2] at depth $ {$current_ref_tdf}[1], mismatch at index $i:", + "\n main: <<<$title>>> $depth $file", + "\n curr: <<<$c_title>>> $c_depth $c_file"); } + } + @current_contents_list = (); +} + + + +# Set @current_contents_list to a list of (title, href, sectionlevel); +# then merge that list into @contents_list. +# Maybe this function should also produce a map +# from title (or href) to sectionlevel (eg "chapter"?). +sub process_child_links ( $ ) +{ my ($he) = check_args(1, @_); + + # $he->dump(); + if (scalar(@current_contents_list) != 0) + { die "current_contents_list nonempty: @current_contents_list"; } + $he->traverse(\&increment_current_contents_list, 'ignore text'); + + # Normalize the depths; for instance, convert 1,3,5 into 0,1,2. + my %depths = (); + for my $ref_tdf (@current_contents_list) + { $depths{$ {$ref_tdf}[1]} = 1; } + my @sorted_depths = sort keys %depths; + my $current_depth = scalar(@section_stack)-1; + my $current_depth_2 = $ {$current_ref_tdf}[1]; + if ($current_depth != $current_depth_2) + { die "mismatch in current depths: $current_depth $current_depth_2; ", join(", ", @section_stack); } + for (my $i=0; $i 1) + && ($ {$current_contents_list[1]}[0] eq "Contents")) + { my $ref_first_tdf = shift @current_contents_list; + $current_contents_list[0] = $ref_first_tdf; } + + for (my $i=0; $itag eq "li") + { my @li_content = @{$he->content}; + if ($li_content[0]->tag ne "a") + { die "first element of
  • should be "; } + my ($name, $href, @content) = anchor_info($li_content[0]); + # unused $name + my $title = join("", collect_texts($li_content[0])); + $title = texi_remove_punctuation($title); + # The problem with these is that they are formatted differently in + # @menu and @node! + $title =~ s/``/\"/g; + $title =~ s/''/\"/g; + $title =~ s/ -- / /g; + push @current_contents_list, [ $title, $depth, $href ]; } + return 1; +} + +# Simple version for section titles +sub html_to_texi ( $ ) +{ my ($he) = check_args(1, @_); + if (!ref $he) + { return $he; } + + my $tag = $he->tag; + if (exists $inline_markup{$tag}) + { my $result = "\@$inline_markup{$tag}\{"; + for my $elt (@{$he->content}) + { $result .= html_to_texi($elt); } + $result .= "\}"; + return $result; } + else + { $he->dump(); + die "html_to_texi confused by <$tag>"; } +} + + + +sub print_contents_list () +{ check_args(0, @_); + print STDERR "Contents list:\n"; + for my $ref_tdf (@contents_list) + { my ($title, $depth, $file) = @{$ref_tdf}; + print STDERR "$title $depth $file\n"; } +} + + + +########################################################################### +### Index +### + +my $l2h_broken_link_name = "l2h-"; + + +# map from file to (map from anchor name to (list of index texts)) +# (The list is needed when a single LaTeX command like \envvar +# expands to multiple \index commands.) +my %file_index_entries = (); +my %this_index_entries; # map from anchor name to (list of index texts) + +my %file_index_entries_broken = (); # map from file to (list of index texts) +my @this_index_entries_broken; + +my $index_prefix = ""; +my @index_prefixes = (); + +my $this_indexing_command; + +sub print_index_info () +{ check_args(0, @_); + my ($key, $val); + for my $file (sort keys %file_index_entries) + { my %index_entries = %{$file_index_entries{$file}}; + print STDERR "file: $file\n"; + for my $aname (sort keys %index_entries) + { my @entries = @{$index_entries{$aname}}; + if (scalar(@entries) == 1) + { print STDERR " $aname : $entries[0]\n"; } + else + { print STDERR " $aname : ", join("\n " . (" " x length($aname)), @entries), "\n"; } } } + for my $file (sort keys %file_index_entries_broken) + { my @entries = @{$file_index_entries_broken{$file}}; + print STDERR "file: $file\n"; + for my $entry (@entries) + { print STDERR " $entry\n"; } + } +} + + +sub process_index_file ( $$ ) +{ my ($file, $indexing_command) = check_args(2, @_); + # print "process_index_file $file $indexing_command\n"; + + my $he = file_to_tree($html_directory . $file); + # $he->dump(); + + $this_indexing_command = $indexing_command; + $he->traverse(\&process_if_index_dl_compact, 'ignore text'); + undef $this_indexing_command; + # print "process_index_file done\n"; +} + + +sub process_if_index_dl_compact ( $$$ ) +{ my ($he, $startflag) = (check_args(3, @_))[0,1]; # ignore depth argument + if (!$startflag) + { return; } + + if (($he->tag() eq "dl") && (defined $he->attr('compact'))) + { process_index_dl_compact($he); + return 0; } + else + { return 1; } +} + + +# The elements of a
    list from a LaTeX2HTML index: +# * a single space: text to be ignored +# *
    elements with an optional
    element following each one +# Two types of
    elements: +# * Followed by a
    element: the
    contains a single +# string, and the
    contains a whitespace string to be ignored, a +#
    to be recursively processed (with the
    string as a +# prefix), and a whitespace string to be ignored. +# * Not followed by a
    element: contains a list of anchors +# and texts (ignore the texts, which are only whitespace and commas). +# Optionally contains a
    to be recursively processed (with +# the
    string as a prefix) +sub process_index_dl_compact ( $ ) +{ my ($h) = check_args(1, @_); + my @content = @{$h->content()}; + for (my $i = 0; $i < scalar(@content); $i++) + { my $this_he = $content[$i]; + if ($this_he->tag ne "dt") + { $this_he->dump(); + die "Expected
    tag: " . $this_he->tag; } + if (($i < scalar(@content) - 1) && ($content[$i+1]->tag eq "dd")) + { process_index_dt_and_dd($this_he, $content[$i+1]); + $i++; } + else + { process_index_lone_dt($this_he); } } } + + + +# Argument is a
    element. If it contains more than one anchor, then +# the texts of all subsequent ones are "[Link]". Example: +#
    +# +# "$PATH" +# ", " +# +# "[Link]" +# Optionally contains a
    as well. Example: +#
    +# +# "attribute" +#
    +#
    +# +# "assignment" +# ", " +# +# "[Link]" +#
    +# +# "assignment, class" + +sub process_index_lone_dt ( $ ) +{ my ($dt) = check_args(1, @_); + my @dtcontent = @{$dt->content()}; + my $acontent; + my $acontent_suffix; + for my $a (@dtcontent) + { if ($a eq ", ") + { next; } + if (!ref $a) + { $dt->dump; + die "Unexpected
    string element: $a"; } + + if ($a->tag eq "dl") + { push @index_prefixes, $index_prefix; + if (!defined $acontent_suffix) + { die "acontent_suffix not yet defined"; } + $index_prefix .= $acontent_suffix . ", "; + process_index_dl_compact($a); + $index_prefix = pop(@index_prefixes); + return; } + + if ($a->tag ne "a") + { $dt->dump; + $a->dump; + die "Expected anchor in lone
    "; } + + my ($aname, $ahref, @acontent) = anchor_info($a); + # unused $aname + if (scalar(@acontent) != 1) + { die "Expected just one content of in
    : @acontent"; } + if (ref $acontent[0]) + { $acontent[0]->dump; + die "Expected string content of in
    : $acontent[0]"; } + if (!defined($acontent)) + { $acontent = $index_prefix . $acontent[0]; + $acontent_suffix = $acontent[0]; } + elsif (($acontent[0] ne "[Link]") && ($acontent ne ($index_prefix . $acontent[0]))) + { die "Differing content: <<<$acontent>>>, <<<$acontent[0]>>>"; } + + if (!defined $ahref) + { $dt->dump; + die "no HREF in nachor in
    "; } + my ($ahref_file, $ahref_name) = split(/\#/, $ahref); + if (!defined $ahref_name) + { # Reference to entire file + $ahref_name = ""; } + + if ($ahref_name eq $l2h_broken_link_name) + { if (!exists $file_index_entries_broken{$ahref_file}) + { $file_index_entries_broken{$ahref_file} = []; } + push @{$file_index_entries_broken{$ahref_file}}, "$this_indexing_command $acontent"; + next; } + + if (!exists $file_index_entries{$ahref_file}) + { $file_index_entries{$ahref_file} = {}; } + # Don't do this! It appears to make a copy, which is not desired. + # my %index_entries = %{$file_index_entries{$ahref_file}}; + if (!exists $ {$file_index_entries{$ahref_file}}{$ahref_name}) + { $ {$file_index_entries{$ahref_file}}{$ahref_name} = []; } + # { my $oldcontent = $ {$file_index_entries{$ahref_file}}{$ahref_name}; + # if ($acontent eq $oldcontent) + # { die "Multiple identical index entries?"; } + # die "Trying to add $acontent, but already have index entry pointing at $ahref_file\#$ahref_name: ${$file_index_entries{$ahref_file}}{$ahref_name}"; } + + push @{$ {$file_index_entries{$ahref_file}}{$ahref_name}}, "$this_indexing_command $acontent"; + # print STDERR "keys: ", keys %{$file_index_entries{$ahref_file}}, "\n"; + } +} + +sub process_index_dt_and_dd ( $$ ) +{ my ($dt, $dd) = check_args(2, @_); + my $dtcontent; + { my @dtcontent = @{$dt->content()}; + if ((scalar(@dtcontent) != 1) || (ref $dtcontent[0])) + { $dd->dump; + $dt->dump; + die "Expected single string (actual size = " . scalar(@dtcontent) . ") in content of
    : @dtcontent"; } + $dtcontent = $dtcontent[0]; + $dtcontent =~ s/ +$//; } + my $ddcontent; + { my @ddcontent = @{$dd->content()}; + if (scalar(@ddcontent) != 1) + { die "Expected single
    content, got ", scalar(@ddcontent), " elements:\n", join("\n", @ddcontent), "\n "; } + $ddcontent = $ddcontent[0]; } + if ($ddcontent->tag ne "dl") + { die "Expected
    as content of
    , but saw: $ddcontent"; } + + push @index_prefixes, $index_prefix; + $index_prefix .= $dtcontent . ", "; + process_index_dl_compact($ddcontent); + $index_prefix = pop(@index_prefixes); +} + + +########################################################################### +### Ordinary sections +### + +sub process_section_file ( $$$ ) +{ my ($file, $depth, $nodetitle) = check_args(3, @_); + my $he = file_to_tree(($file =~ /^\//) ? $file : $html_directory . $file); + + # print STDERR "process_section_file: $file $depth $nodetitle\n"; + + # Equivalently: + # while ($depth >= scalar(@section_stack)) { pop(@section_stack); } + @section_stack = @section_stack[0..$depth-1]; + + # Not a great nodename fixup scheme; need a more global view + if ((defined $contents_fixups{$nodetitle}) + && (scalar(@section_stack) > 0)) + { my $up_title = $section_stack[$#section_stack]; + # hack for Python Standard Library + $up_title =~ s/^(Built-in|Standard) Module //g; + my ($up_first_word) = split(/ /, $up_title); + $nodetitle = "$up_first_word $nodetitle"; + } + + push @section_stack, $nodetitle; + # print STDERR "new section_stack: ", join(", ", @section_stack), "\n"; + + $he->traverse(\&process_if_child_links, 'ignore text'); + %footnotes = (); + # $he->dump; + $he->traverse(\&process_if_footnotes, 'ignore text'); + + # $he->dump; + + if (exists $file_index_entries{$file}) + { %this_index_entries = %{$file_index_entries{$file}}; + # print STDERR "this_index_entries:\n ", join("\n ", keys %this_index_entries), "\n"; + } + else + { # print STDERR "Warning: no index entries for file $file\n"; + %this_index_entries = (); } + + if (exists $file_index_entries_broken{$file}) + { @this_index_entries_broken = @{$file_index_entries_broken{$file}}; } + else + { # print STDERR "Warning: no index entries for file $file\n"; + @this_index_entries_broken = (); } + + + if ($he->tag() ne "html") + { die "Expected at top level"; } + my @content = @{$he->content()}; + if ((!ref $content[0]) or ($content[0]->tag ne "head")) + { $he->dump; + die " not first element of "; } + if ((!ref $content[1]) or ($content[1]->tag ne "body")) + { $he->dump; + die " not second element of "; } + + $content[1]->traverse(\&output_body); +} + +# stack of things we're inside that are preventing indexing from occurring now. +# These are "h1", "h2", "h3", "h4", "h5", "h6", "dt" (and possibly others?) +my @index_deferrers = (); + +sub push_or_pop_index_deferrers ( $$ ) +{ my ($tag, $startflag) = check_args(2, @_); + if ($startflag) + { push @index_deferrers, $tag; } + else + { my $old_deferrer = pop @index_deferrers; + if ($tag ne $old_deferrer) + { die "Expected $tag at top of index_deferrers but saw $old_deferrer; remainder = ", join(" ", @index_deferrers); } + do_deferred_index_entries(); } +} + + +sub label_add_index_entries ( $;$ ) +{ my ($label, $he) = check_args_range(1, 2, @_); + # print ((exists $this_index_entries{$label}) ? "*" : " "), " label_add_index_entries $label\n"; + # $he is the anchor element + if (exists $this_index_entries{$label}) + { push @deferred_index_entries, @{$this_index_entries{$label}}; + return; } + + if ($label eq $l2h_broken_link_name) + { # Try to find some text to use in guessing which links should point here + # I should probably only look at the previous element, or if that is + # all punctuation, the one before it; collecting all the previous texts + # is a bit of overkill. + my @anchor_texts = collect_texts($he); + my @previous_texts = collect_texts($he->parent, $he); + # 4 elements is arbitrary; ought to filter out punctuation and small words + # first, then perhaps keep fewer. Perhaps also filter out formatting so + # that we can see a larger chunk of text? (Probably not.) + # Also perhaps should do further chunking into words, in case the + # index term isn't a chunk of its own (eg, was in .... + my @candidate_texts = (@anchor_texts, (reverse(@previous_texts))[0..min(3,$#previous_texts)]); + + my $guessed = 0; + for my $text (@candidate_texts) + { # my $orig_text = $text; + if ($text =~ /^[\"\`\'().?! ]*$/) + { next; } + if (length($text) <= 2) + { next; } + # hack for Python manual; maybe defer until failure first time around? + $text =~ s/^sys\.//g; + for my $iterm (@this_index_entries_broken) + { # I could test for zero: LaTeX2HTML's failures in the Python + # documentation are only for items of the form "... (built-in...)" + if (index($iterm, $text) != -1) + { push @deferred_index_entries, $iterm; + # print STDERR "Guessing index term `$iterm' for text `$orig_text'\n"; + $guessed = 1; + } } } + if (!$guessed) + { # print STDERR "No guess in `", join("'; `", @this_index_entries_broken), "' for texts:\n `", join("'\n `", @candidate_texts), "'\n"; + } + } +} + + +# Need to add calls to this at various places. +# Perhaps add HTML::Element argument and do the check for appropriateness +# here (ie, no action if inside

    , etc.). +sub do_deferred_index_entries () +{ check_args(0, @_); + if ((scalar(@deferred_index_entries) > 0) + && (scalar(@index_deferrers) == 0)) + { print TEXI "\n", join("\n", @deferred_index_entries), "\n"; + @deferred_index_entries = (); } +} + +my $table_columns; # undefined if not in a table +my $table_first_column; # boolean + +sub output_body ( $$$ ) +{ my ($he, $startflag) = (check_args(3, @_))[0,1]; # ignore depth argument + + if (!ref $he) + { my $space_index = index($he, " "); + if ($space_index != -1) + { # Why does + # print TEXI texi_quote(substr($he, 0, $space_index+1)); + # give: Can't locate object method "TEXI" via package "texi_quote" + # (Because the definition texi_quote hasn't been seen yet.) + print TEXI &texi_quote(substr($he, 0, $space_index+1)); + do_deferred_index_entries(); + print TEXI &texi_quote(substr($he, $space_index+1)); } + else + { print TEXI &texi_quote($he); } + return; } + + my $tag = $he->tag(); + + # Ordinary text markup first + if (exists $inline_markup{$tag}) + { if ($startflag) + { print TEXI "\@$inline_markup{$tag}\{"; } + else + { print TEXI "\}"; } } + elsif ($tag eq "a") + { my ($name, $href, @content) = anchor_info($he); + if (!$href) + { # This anchor is only here for indexing/cross referencing purposes. + if ($startflag) + { label_add_index_entries($name, $he); } + } + elsif ($href =~ "^(ftp|http|news):") + { if ($startflag) + { # Should avoid second argument if it's identical to the URL. + print TEXI "\@uref\{$href, "; } + else + { print TEXI "\}"; } + } + elsif ($href =~ /^\#(foot[0-9]+)$/) + { # Footnote + if ($startflag) + { # Could double-check name and content, but I'm not + # currently storing that information. + print TEXI "\@footnote\{"; + $footnotes{$1}->traverse(\&output_body); + print TEXI "\}"; + return 0; } } + else + { if ($startflag) + { # cross-references are not active Info links, but no text is lost + print STDERR "Can't deal with internal HREF anchors yet:\n"; + $he->dump; } + } + } + elsif ($tag eq "br") + { print TEXI "\@\n"; } + elsif ($tag eq "body") + { } + elsif ($tag eq "center") + { if (has_single_content_string($he) + && ($ {$he->content}[0] =~ /^ *$/)) + { return 0; } + if ($startflag) + { print TEXI "\n\@center\n"; } + else + { print TEXI "\n\@end center\n"; } + } + elsif ($tag eq "div") + { my $align = $he->attr('align'); + if (defined($align) && ($align eq "center")) + { if (has_single_content_string($he) + && ($ {$he->content}[0] =~ /^ *$/)) + { return 0; } + if ($startflag) + { print TEXI "\n\@center\n"; } + else + { print TEXI "\n\@end center\n"; } } + } + elsif ($tag eq "dl") + { # Recognize "
     ... 
    " paradigm for "@example" + if (has_single_content_with_tag($he, "dd")) + { my $he_dd = $ {$he->content}[0]; + if (has_single_content_with_tag($he_dd, "pre")) + { my $he_pre = $ {$he_dd->content}[0]; + print_pre($he_pre); + return 0; } } + if ($startflag) + { # Could examine the elements, to be cleverer about formatting. + # (Also to use ftable, vtable...) + print TEXI "\n\@table \@asis\n"; } + else + { print TEXI "\n\@end table\n"; } + } + elsif ($tag eq "dt") + { push_or_pop_index_deferrers($tag, $startflag); + if ($startflag) + { print TEXI "\n\@item "; } + else + { } } + elsif ($tag eq "dd") + { if ($startflag) + { print TEXI "\n"; } + else + { } + if (scalar(@index_deferrers) != 0) + { $he->dump; + die "Unexpected <$tag> while inside: (" . join(" ", @index_deferrers) . "); bad HTML?"; } + do_deferred_index_entries(); + } + elsif ($tag =~ /^(font|big|small)$/) + { # Do nothing for now. + } + elsif ($tag =~ /^h[1-6]$/) + { # We don't need this because we never recursively enter the heading content. + # push_or_pop_index_deferrers($tag, $startflag); + my $secname = ""; + my @seclabels = (); + for my $elt (@{$he->content}) + { if (!ref $elt) + { $secname .= $elt; } + elsif ($elt->tag eq "br") + { } + elsif ($elt->tag eq "a") + { my ($name, $href, @acontent) = anchor_info($elt); + if ($href) + { $he->dump; + $elt->dump; + die "Nonsimple anchor in <$tag>"; } + if (!defined $name) + { die "No NAME for anchor in $tag"; } + push @seclabels, $name; + for my $subelt (@acontent) + { $secname .= html_to_texi($subelt); } } + else + { $secname .= html_to_texi($elt); } } + if ($secname eq "") + { die "No section name in <$tag>"; } + if (scalar(@section_stack) == 1) + { if ($section_stack[-1] ne "Top") + { die "Not top? $section_stack[-1]"; } + print TEXI "\@settitle $secname\n"; + print TEXI "\@c %**end of header\n"; + print TEXI "\n"; + print TEXI "\@node Top\n"; + print TEXI "\n"; } + else + { print TEXI "\n\@node $section_stack[-1]\n"; + print TEXI "\@$sectionmarker[scalar(@section_stack)-1] ", texi_remove_punctuation($secname), "\n"; } + for my $seclabel (@seclabels) + { label_add_index_entries($seclabel); } + # This should only happen once per file. + label_add_index_entries(""); + if (scalar(@index_deferrers) != 0) + { $he->dump; + die "Unexpected <$tag> while inside: (" . join(" ", @index_deferrers) . "); bad HTML?"; } + do_deferred_index_entries(); + return 0; + } + elsif ($tag eq "hr") + { } + elsif ($tag eq "ignore") + { # Hack for ignored elements + return 0; + } + elsif ($tag eq "li") + { if ($startflag) + { print TEXI "\n\n\@item\n"; + do_deferred_index_entries(); } } + elsif ($tag eq "ol") + { if ($startflag) + { print TEXI "\n\@enumerate \@bullet\n"; } + else + { print TEXI "\n\@end enumerate\n"; } } + elsif ($tag eq "p") + { if ($startflag) + { print TEXI "\n\n"; } + if (scalar(@index_deferrers) != 0) + { $he->dump; + die "Unexpected <$tag> while inside: (" . join(" ", @index_deferrers) . "); bad HTML?"; } + do_deferred_index_entries(); } + elsif ($tag eq "pre") + { print_pre($he); + return 0; } + elsif ($tag eq "table") + { # Could also indicate common formatting for first column, or + # determine relative widths for columns (or determine a prototype row) + if ($startflag) + { if (defined $table_columns) + { $he->dump; + die "Can't deal with table nested inside $table_columns-column table"; } + $table_columns = table_columns($he); + if ($table_columns < 2) + { $he->dump; + die "Column with $table_columns columns?"; } + elsif ($table_columns == 2) + { print TEXI "\n\@table \@asis\n"; } + else + { print TEXI "\n\@multitable \@columnfractions"; + for (my $i=0; $i<$table_columns; $i++) + { print TEXI " ", 1.0/$table_columns; } + print TEXI "\n"; } } + else + { if ($table_columns == 2) + { print TEXI "\n\@end table\n"; } + else + { print TEXI "\n\@end multitable\n"; } + undef $table_columns; } } + elsif (($tag eq "td") || ($tag eq "th")) + { if ($startflag) + { if ($table_first_column) + { print TEXI "\n\@item "; + $table_first_column = 0; } + elsif ($table_columns > 2) + { print TEXI "\n\@tab "; } } + else + { print TEXI "\n"; } } + elsif ($tag eq "tr") + { if ($startflag) + { $table_first_column = 1; } } + elsif ($tag eq "ul") + { if ($startflag) + { print TEXI "\n\@itemize \@bullet\n"; } + else + { print TEXI "\n\@end itemize\n"; } } + else + { # I used to have a newline before "output_body" here. + print STDERR "output_body: ignoring <$tag> tag\n"; + $he->dump; + return 0; } + + return 1; +} + +sub print_pre ( $ ) +{ my ($he_pre) = check_args(1, @_); + if (!has_single_content_string($he_pre)) + { die "Multiple or non-string content for
    : ", @{$he_pre->content}; }
    +  my $pre_content = $ {$he_pre->content}[0];
    +  print TEXI "\n\@example";
    +  print TEXI &texi_quote($pre_content);
    +  print TEXI "\@end example\n";
    +}
    +
    +sub table_columns ( $ )
    +{ my ($table) = check_args(1, @_);
    +  my $result = 0;
    +  for my $row (@{$table->content})
    +    { if ($row->tag ne "tr")
    +	{ $table->dump;
    +	  $row->dump;
    +	  die "Expected  as table row."; }
    +      $result = max($result, scalar(@{$row->content})); }
    +  return $result;
    +}
    +
    +
    +###########################################################################
    +### Utilities
    +###
    +
    +sub min ( $$ )
    +{ my ($x, $y) = check_args(2, @_);
    +  return ($x < $y) ? $x : $y;
    +}
    +
    +sub max ( $$ )
    +{ my ($x, $y) = check_args(2, @_);
    +  return ($x > $y) ? $x : $y;
    +}
    +
    +sub file_to_tree ( $ )
    +{ my ($file) = check_args(1, @_);
    +
    +  my $tree = new HTML::TreeBuilder;
    +  $tree->ignore_unknown(1);
    +  # $tree->warn(1);
    +  $tree->parse_file($file);
    +  cleanup_parse_tree($tree);
    +  return $tree
    +}
    +
    +
    +sub has_single_content ( $ )
    +{ my ($he) = check_args(1, @_);
    +  if (!ref $he)
    +    { # return 0;
    +      die "Non-reference argument: $he"; }
    +  my $ref_content = $he->content;
    +  if (!defined $ref_content)
    +    { return 0; }
    +  my @content = @{$ref_content};
    +  if (scalar(@content) != 1)
    +    { return 0; }
    +  return 1;
    +}
    +
    +
    +# Return true if the content of the element contains only one element itself,
    +# and that inner element has the specified tag.
    +sub has_single_content_with_tag ( $$ )
    +{ my ($he, $tag) = check_args(2, @_);
    +  if (!has_single_content($he))
    +    { return 0; }
    +  my $content = $ {$he->content}[0];
    +  if (!ref $content)
    +    { return 0; }
    +  my $content_tag = $content->tag;
    +  if (!defined $content_tag)
    +    { return 0; }
    +  return $content_tag eq $tag;
    +}
    +
    +sub has_single_content_string ( $ )
    +{ my ($he) = check_args(1, @_);
    +  if (!has_single_content($he))
    +    { return 0; }
    +  my $content = $ {$he->content}[0];
    +  if (ref $content)
    +    { return 0; }
    +  return 1;
    +}
    +
    +
    +# Return name, href, content.  First two may be undefined; third is an array.
    +# I don't see how to determine if there are more attributes.
    +sub anchor_info ( $ )
    +{ my ($he) = check_args(1, @_);
    +  if ($he->tag ne "a")
    +    { $he->dump;
    +      die "passed non-anchor to anchor_info"; }
    +  my $name = $he->attr('name');
    +  my $href = $he->attr('href');
    +  my @content = ();
    +  { my $ref_content = $he->content;
    +    if (defined $ref_content)
    +      { @content = @{$ref_content}; } }
    +  return ($name, $href, @content);
    +}
    +
    +
    +sub texi_quote ( $ )
    +{ my ($text) = check_args(1, @_);
    +  $text =~ s/([\@\{\}])/\@$1/g;
    +  $text =~ s/ -- / --- /g;
    +  return $text;
    +}
    +
    +# Eliminate bad punctuation (that confuses Makeinfo or Info) for section titles.
    +sub texi_remove_punctuation ( $ )
    +{ my ($text) = check_args(1, @_);
    +
    +  $text =~ s/^ +//g;
    +  $text =~ s/[ :]+$//g;
    +  $text =~ s/^[1-9][0-9.]* +//g;
    +  $text =~ s/,//g;
    +  # Both embedded colons and " -- " confuse makeinfo.  (Perhaps " -- "
    +  # gets converted into " - ", just as "---" would be converted into " -- ",
    +  # so the names end up differing.)
    +  # $text =~ s/:/ -- /g;
    +  $text =~ s/://g;
    +  return $text;
    +}
    +
    +
    +## Do not use this inside `traverse':  it throws off the traversal.  Use
    +## html_replace_by_ignore or html_replace_by_meta instead.
    +# Returns 1 if success, 0 if failure.
    +sub html_remove ( $;$ )
    +{ my ($he, $parent) = check_args_range(1, 2, @_);
    +  if (!defined $parent)
    +    { $parent = $he->parent; }
    +  my $ref_pcontent = $parent->content;
    +  my @pcontent = @{$ref_pcontent};
    +  for (my $i=0; $iparent(undef);
    +	  return 1; } }
    +  die "Didn't find $he in $parent";
    +}
    +
    +
    +sub html_replace ( $$;$ )
    +{ my ($orig, $new, $parent) = check_args_range(2, 3, @_);
    +  if (!defined $parent)
    +    { $parent = $orig->parent; }
    +  my $ref_pcontent = $parent->content;
    +  my @pcontent = @{$ref_pcontent};
    +  for (my $i=0; $iparent($parent);
    +	  $orig->parent(undef);
    +	  return 1; } }
    +  die "Didn't find $orig in $parent";
    +}
    +
    +sub html_replace_by_meta ( $;$ )
    +{ my ($orig, $parent) = check_args_range(1, 2, @_);
    +  my $meta = new HTML::Element "meta";
    +  if (!defined $parent)
    +    { $parent = $orig->parent; }
    +  return html_replace($orig, $meta, $parent);
    +}
    +
    +sub html_replace_by_ignore ( $;$ )
    +{ my ($orig, $parent) = check_args_range(1, 2, @_);
    +  my $ignore = new HTML::Element "ignore";
    +  if (!defined $parent)
    +    { $parent = $orig->parent; }
    +  return html_replace($orig, $ignore, $parent);
    +}
    +
    +
    +
    +###
    +### Collect text elements
    +###
    +
    +my @collected_texts;
    +my $collect_texts_stoppoint;
    +my $done_collecting;
    +
    +sub collect_texts ( $;$ )
    +{ my ($root, $stop) = check_args_range(1, 2, @_);
    +  # print STDERR "collect_texts: $root $stop\n";
    +  $collect_texts_stoppoint = $stop;
    +  $done_collecting = 0;
    +  @collected_texts = ();
    +  $root->traverse(\&collect_if_text); # process texts
    +  # print STDERR "collect_texts => ", join(";;;", @collected_texts), "\n";
    +  return @collected_texts;
    +}
    +
    +sub collect_if_text ( $$$ )
    +{ my $he = (check_args(3, @_))[0]; #  ignore depth and startflag arguments
    +  if ($done_collecting)
    +    { return 0; }
    +  if (!defined $he)
    +    { return 0; }
    +  if (!ref $he)
    +    { push @collected_texts, $he;
    +      return 0; }
    +  if ((defined $collect_texts_stoppoint) && ($he eq $collect_texts_stoppoint))
    +    { $done_collecting = 1;
    +      return 0; }
    +  return 1;
    +}
    +
    +
    +###########################################################################
    +### Clean up parse tree
    +###
    +
    +sub cleanup_parse_tree ( $ )
    +{ my ($he) = check_args(1, @_);
    +  $he->traverse(\&delete_if_navigation, 'ignore text');
    +  $he->traverse(\&delete_extra_spaces, 'ignore text');
    +  $he->traverse(\&merge_dl, 'ignore text');
    +  $he->traverse(\&reorder_dt_and_dl, 'ignore text');
    +  return $he;
    +}
    +
    +
    +## Simpler version that deletes contents but not the element itself.
    +# sub delete_if_navigation ( $$$ )
    +# { my $he = (check_args(3, @_))[0]; # ignore startflag and depth
    +#   if (($he->tag() eq "div") && ($he->attr('class') eq 'navigation'))
    +#     { $he->delete();
    +#       return 0; }
    +#   else
    +#     { return 1; }
    +# }
    +
    +sub delete_if_navigation ( $$$ )
    +{ my ($he, $startflag) = (check_args(3, @_))[0,1]; #  ignore depth argument
    +  if (!$startflag)
    +    { return; }
    +
    +  if (($he->tag() eq "div") && (defined $he->attr('class')) && ($he->attr('class') eq 'navigation'))
    +    { my $ref_pcontent = $he->parent()->content();
    +      # Don't try to modify @pcontent, which appears to be a COPY.
    +      # my @pcontent = @{$ref_pcontent};
    +      for (my $i = 0; $idelete();
    +      return 0; }
    +  else
    +    { return 1; }
    +}
    +
    +sub delete_extra_spaces ( $$$ )
    +{ my ($he, $startflag) = (check_args(3, @_))[0,1]; #  ignore depth argument
    +  if (!$startflag)
    +    { return; }
    +
    +  my $tag = $he->tag;
    +  if ($tag =~ /^(head|html|table|tr|ul)$/)
    +    { delete_child_spaces($he); }
    +  delete_trailing_spaces($he);
    +  return 1;
    +}
    +
    +
    +sub delete_child_spaces ( $ )
    +{ my ($he) = check_args(1, @_);
    +  my $ref_content = $he->content();
    +  for (my $i = 0; $icontent();
    +  if (! defined $ref_content)
    +    { return; }
    +  # Could also check for previous element = /^h[1-6]$/.
    +  for (my $i = 0; $itag =~ /^(br|dd|dl|dt|hr|p|ul)$/))
    +	    { splice(@{$ref_content}, $i, 1);
    +	      $i--; } } }
    +  if ($he->tag =~ /^(dd|dt|^h[1-6]|li|p)$/)
    +    { my $last_elt = $ {$ref_content}[$#{$ref_content}];
    +      if ((defined $last_elt) && ($last_elt =~ /^ *$/))
    +	{ pop @{$ref_content}; } }
    +}
    +
    +
    +# LaTeX2HTML sometimes creates
    +#   
    text +#
    text +# which should actually be: +#
    +#
    text +#
    text +# Since a
    gets added, this ends up looking like +#

    +#

    +#
    +# text1... +#
    +#
    +# text2... +# dt_or_dd1... +# dt_or_dd2... +# which should become +#

    +#

    +#
    +# text1... +#
    +# text2... +# dt_or_dd1... +# dt_or_dd2... + +sub reorder_dt_and_dl ( $$$ ) +{ my ($he, $startflag) = (check_args(3, @_))[0,1]; # ignore depth argument + if (!$startflag) + { return; } + + if ($he->tag() eq "p") + { my $ref_pcontent = $he->content(); + if (defined $ref_pcontent) + { my @pcontent = @{$ref_pcontent}; + # print "reorder_dt_and_dl found a

    \n"; $he->dump(); + if ((scalar(@pcontent) >= 1) + && (ref $pcontent[0]) && ($pcontent[0]->tag() eq "dl") + && $pcontent[0]->implicit()) + { my $ref_dlcontent = $pcontent[0]->content(); + # print "reorder_dt_and_dl found a

    and implicit

    \n"; + if (defined $ref_dlcontent) + { my @dlcontent = @{$ref_dlcontent}; + if ((scalar(@dlcontent) >= 1) + && (ref $dlcontent[0]) && ($dlcontent[0]->tag() eq "dt")) + { my $ref_dtcontent = $dlcontent[0]->content(); + # print "reorder_dt_and_dl found a

    , implicit

    , and
    \n"; + if (defined $ref_dtcontent) + { my @dtcontent = @{$ref_dtcontent}; + if ((scalar(@dtcontent) > 0) + && (ref $dtcontent[$#dtcontent]) + && ($dtcontent[$#dtcontent]->tag() eq "dl")) + { my $ref_dl2content = $dtcontent[$#dtcontent]->content(); + # print "reorder_dt_and_dl found a

    , implicit

    ,
    , and
    \n"; + if (defined $ref_dl2content) + { my @dl2content = @{$ref_dl2content}; + if ((scalar(@dl2content) > 0) + && (ref ($dl2content[0])) + && ($dl2content[0]->tag() eq "dd")) + { + # print "reorder_dt_and_dl found a

    , implicit

    ,
    ,
    , and
    \n"; + # print STDERR "CHANGING\n"; $he->dump(); + html_replace_by_ignore($dtcontent[$#dtcontent]); + splice(@{$ref_dlcontent}, 1, 0, @dl2content); + # print STDERR "CHANGED TO:\n"; $he->dump(); + return 0; # don't traverse children + } } } } } } } } } + return 1; +} + + +# If we find a paragraph that looks like +#

    +#


    +#