From dd663e4ef315aa60ebf06a1ae1050d3cbc064e98 Mon Sep 17 00:00:00 2001 From: Kyungwook Tak Date: Fri, 30 Oct 2015 09:34:57 +0900 Subject: [PATCH] Revert "Upgrade upstream version to 0.14" This reverts commit 55a345f86ac3e75576d794146eabf4df764de4f8, reversing changes made to 86127911c0159224e4d58325c9dc25a59bb91f5e. Change-Id: I08adbde03bea34b7d83788d443c23ccfa57daaa3 --- .gitignore | 3 - .travis.yml | 15 - ChangeLog | 68 - INSTALL | 137 +- MANIFEST.in | 5 +- OpenSSL/SSL.py | 1423 ------------- OpenSSL/__init__.py | 35 +- OpenSSL/_util.py | 53 - OpenSSL/crypto.py | 2314 ---------------------- OpenSSL/crypto/crl.c | 290 +++ OpenSSL/crypto/crl.h | 19 + OpenSSL/crypto/crypto.c | 898 +++++++++ OpenSSL/crypto/crypto.h | 142 ++ OpenSSL/crypto/netscape_spki.c | 316 +++ OpenSSL/crypto/netscape_spki.h | 30 + OpenSSL/crypto/pkcs12.c | 580 ++++++ OpenSSL/crypto/pkcs12.h | 39 + OpenSSL/crypto/pkcs7.c | 216 ++ OpenSSL/crypto/pkcs7.h | 30 + OpenSSL/crypto/pkey.c | 303 +++ OpenSSL/crypto/pkey.h | 52 + OpenSSL/crypto/revoked.c | 444 +++++ OpenSSL/crypto/revoked.h | 18 + OpenSSL/crypto/x509.c | 929 +++++++++ OpenSSL/crypto/x509.h | 34 + OpenSSL/crypto/x509ext.c | 336 ++++ OpenSSL/crypto/x509ext.h | 33 + OpenSSL/crypto/x509name.c | 555 ++++++ OpenSSL/crypto/x509name.h | 33 + OpenSSL/crypto/x509req.c | 431 ++++ OpenSSL/crypto/x509req.h | 30 + OpenSSL/crypto/x509store.c | 149 ++ OpenSSL/crypto/x509store.h | 30 + OpenSSL/py3k.h | 55 + OpenSSL/pymemcompat.h | 86 + OpenSSL/rand.py | 180 -- OpenSSL/rand/rand.c | 306 +++ OpenSSL/ssl/connection.c | 1581 +++++++++++++++ OpenSSL/ssl/connection.h | 53 + OpenSSL/ssl/context.c | 1419 +++++++++++++ OpenSSL/ssl/context.h | 43 + OpenSSL/ssl/ssl.c | 293 +++ OpenSSL/ssl/ssl.h | 76 + OpenSSL/test/README | 8 - OpenSSL/test/__init__.py | 2 +- OpenSSL/test/test_crypto.py | 961 +++------ OpenSSL/test/test_rand.py | 69 +- OpenSSL/test/test_ssl.py | 1165 ++--------- OpenSSL/test/util.py | 224 +-- OpenSSL/util.c | 96 + OpenSSL/util.h | 144 ++ OpenSSL/version.py | 2 +- PKG-INFO | 15 + README | 5 +- dapper/README.Debian | 13 - dapper/changelog | 201 -- dapper/control | 31 - dapper/copyright | 22 - dapper/pyopenssl-doc.doc-base | 13 - dapper/pyopenssl-doc.docs | 4 - dapper/pyopenssl-doc.examples | 1 - dapper/python-pyopenssl.docs | 1 - dapper/rules | 103 - doc/Makefile | 136 +- doc/Quotes | 2 - doc/README | 4 - doc/api.rst | 18 - doc/api/crypto.rst | 753 ------- doc/api/rand.rst | 79 - doc/api/ssl.rst | 773 -------- doc/conf.py | 219 -- doc/images/pyopenssl-brand.png | Bin 3636 -> 0 bytes doc/images/pyopenssl-icon.png | Bin 428 -> 0 bytes doc/images/pyopenssl-logo.png | Bin 1483 -> 0 bytes doc/images/pyopenssl.svg | 152 -- doc/index.rst | 26 - doc/internals.rst | 69 - doc/introduction.rst | 17 - doc/make.bat | 170 -- doc/pyOpenSSL.tex | 1451 ++++++++++++++ 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 + etch/README.Debian | 13 - etch/changelog | 189 -- etch/control | 29 - etch/copyright | 22 - etch/pycompat | 1 - etch/pyopenssl-doc.doc-base | 13 - etch/pyopenssl-doc.docs | 4 - etch/pyopenssl-doc.examples | 1 - etch/python-pyopenssl.docs | 1 - etch/rules | 96 - gutsy/README.Debian | 13 - gutsy/changelog | 209 -- gutsy/compat | 1 - gutsy/control | 40 - gutsy/copyright | 22 - gutsy/pycompat | 1 - gutsy/pyopenssl-doc.doc-base | 13 - gutsy/pyopenssl-doc.docs | 4 - gutsy/pyopenssl-doc.examples | 1 - gutsy/python-pyopenssl.docs | 1 - gutsy/rules | 102 - leakcheck/context-info-callback.py | 97 - leakcheck/context-passphrase-callback.py | 34 - leakcheck/context-verify-callback.py | 99 - leakcheck/crypto.py | 123 -- leakcheck/dhparam.pem | 4 - leakcheck/thread-crash.py | 71 - leakcheck/thread-key-gen.py | 38 - memdbg.py | 82 - packaging/python-pyOpenSSL.changes | 3 + packaging/python-pyOpenSSL.spec | 3 +- runtests.py | 11 - setup.cfg | 4 +- setup.py | 226 ++- tox.ini | 10 - 195 files changed, 25552 insertions(+), 10086 deletions(-) delete mode 100644 .gitignore delete mode 100644 .travis.yml delete mode 100644 OpenSSL/SSL.py delete mode 100644 OpenSSL/_util.py delete mode 100644 OpenSSL/crypto.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 delete mode 100644 OpenSSL/rand.py 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 delete mode 100644 OpenSSL/test/README create mode 100644 OpenSSL/util.c create mode 100644 OpenSSL/util.h create mode 100644 PKG-INFO delete mode 100644 dapper/README.Debian delete mode 100644 dapper/changelog delete mode 100644 dapper/control delete mode 100644 dapper/copyright delete mode 100644 dapper/pyopenssl-doc.doc-base delete mode 100644 dapper/pyopenssl-doc.docs delete mode 100644 dapper/pyopenssl-doc.examples delete mode 100644 dapper/python-pyopenssl.docs delete mode 100755 dapper/rules delete mode 100644 doc/Quotes delete mode 100644 doc/README delete mode 100644 doc/api.rst delete mode 100644 doc/api/crypto.rst delete mode 100644 doc/api/rand.rst delete mode 100644 doc/api/ssl.rst delete mode 100644 doc/conf.py delete mode 100644 doc/images/pyopenssl-brand.png delete mode 100644 doc/images/pyopenssl-icon.png delete mode 100644 doc/images/pyopenssl-logo.png delete mode 100644 doc/images/pyopenssl.svg delete mode 100644 doc/index.rst delete mode 100644 doc/internals.rst delete mode 100644 doc/introduction.rst delete mode 100644 doc/make.bat 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 delete mode 100644 etch/README.Debian delete mode 100644 etch/changelog delete mode 100644 etch/control delete mode 100644 etch/copyright delete mode 100644 etch/pycompat delete mode 100644 etch/pyopenssl-doc.doc-base delete mode 100644 etch/pyopenssl-doc.docs delete mode 100644 etch/pyopenssl-doc.examples delete mode 100644 etch/python-pyopenssl.docs delete mode 100644 etch/rules delete mode 100644 gutsy/README.Debian delete mode 100644 gutsy/changelog delete mode 100644 gutsy/compat delete mode 100644 gutsy/control delete mode 100644 gutsy/copyright delete mode 100644 gutsy/pycompat delete mode 100644 gutsy/pyopenssl-doc.doc-base delete mode 100644 gutsy/pyopenssl-doc.docs delete mode 100644 gutsy/pyopenssl-doc.examples delete mode 100644 gutsy/python-pyopenssl.docs delete mode 100755 gutsy/rules delete mode 100644 leakcheck/context-info-callback.py delete mode 100644 leakcheck/context-passphrase-callback.py delete mode 100644 leakcheck/context-verify-callback.py delete mode 100644 leakcheck/crypto.py delete mode 100644 leakcheck/dhparam.pem delete mode 100644 leakcheck/thread-crash.py delete mode 100644 leakcheck/thread-key-gen.py delete mode 100644 memdbg.py create mode 100644 packaging/python-pyOpenSSL.changes delete mode 100644 runtests.py delete mode 100644 tox.ini diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 867434b..0000000 --- a/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -build -dist -*.egg-info \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 659677a..0000000 --- a/.travis.yml +++ /dev/null @@ -1,15 +0,0 @@ -language: python - -os: - - linux - - osx - -python: - - "pypy" - - "2.6" - - "2.7" - - "3.2" - - "3.3" - -script: - - python setup.py test diff --git a/ChangeLog b/ChangeLog index 95cf45b..4f92b03 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,71 +1,3 @@ -2014-01-09 Jean-Paul Calderone - - * OpenSSL: Port to the cffi-based OpenSSL bindings provided by - - -2013-10-06 Jean-Paul Calderone - - * OpenSSL/ssl/context.c: Add support for negotiating TLS v1.1 or - v1.2. - -2013-10-03 Christian Heimes - - * OpenSSL/crypto/x509.c: Fix an inconsistency in memory management - in X509.get_serial_number which leads to crashes on some runtimes - (certain Windows/Python 3.3 environments, at least). - -2013-08-11 Christian Heimes - - * OpenSSL/crypto/x509ext.c: Fix handling of NULL bytes inside - subjectAltName general names when formatting an X509 extension - as a string. - * OpenSSL/crypto/x509.c: Fix memory leak in get_extension(). - -2012-04-03 Jean-Paul Calderone - - * OpenSSL/crypto/pkey.c: Release the GIL around RSA and DSA key - generation, based on code from INADA Naoki. - -2012-02-13 Jean-Paul Calderone - - * OpenSSL/ssl/ssl.c: Add session cache related constants for use - with the new Context.set_session_cache_mode method. - - * OpenSSL/ssl/context.c: Add new Context methods - set_session_cache_mode and get_session_cache_mode. - -2011-11-01 Jean-Paul Calderone - - * OpenSSL/crypto/pkey.c: Raise TypeError when trying to check a - PKey instance which has no private component, instead of crashing. - Based on fix by . - -2011-09-14 Žiga Seilnacht - - * OpenSSL/crypto/crypto.c: Allow exceptions from passphrase - callbacks to propagate up out of load_privatekey - * OpenSSL/crypto/crypto.c: Raise an exception when a too-long - passphrase is returned from a passphrase callback, instead of - silently truncating it. - * OpenSSL/crypto/crypto.c: Fix a memory leak when a passphrase - callback returns the wrong type. - -2011-09-13 Jean-Paul Calderone - - * OpenSSL/crypto/crl.c: Add error handling for the use of - X509_CRL_sign. - -2011-09-11 Jonathan Ballet - - * doc/: Convert the LaTeX documentation to Sphinx-using ReST. - * OpenSSL/: Convert the epytext API documentation to Sphinx-using ReST. - -2011-09-08 Guillermo Gonzalez - - * OpenSSL/ssl/context.c: Add Context.set_mode method. - * OpenSSL/ssl/ssl.c: Add MODE_RELEASE_BUFFERS and OP_NO_COMPRESSION - constants. - 2011-09-02 Jean-Paul Calderone * Release 0.13 diff --git a/INSTALL b/INSTALL index 3af722f..f4635c5 100644 --- a/INSTALL +++ b/INSTALL @@ -1,27 +1,132 @@ -Installation ------------- -pyOpenSSL uses distutils. Use setup.py to install it in the usual way: - $ python setup.py install --user +INSTALLATION INSTRUCTIONS FOR pyOpenSSL +------------------------------------------------------------------------------ -Or use pip: +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. - $ pip install --user . + +-- 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 -or +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: - $ pip install --help + > python setup.py build_ext --with-openssl=C:\path\to\openssl build -to find out more about how to use these tools. +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: -Documentation -------------- +> pexports python23.dll > libs\python23.def +> dlltool --dllname python23.dll --def libs\python23.def \ + --output-lib libs\libpython23.a -The documentation is written in reStructuredText and build using Sphinx. +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: @@ -31,3 +136,11 @@ what you do: 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 index 8137258..0c2be95 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,7 @@ +recursive-include OpenSSL *.h include LICENSE ChangeLog INSTALL README TODO MANIFEST.in OpenSSL/RATIONALE -recursive-include doc * +include doc/pyOpenSSL.tex doc/Makefile +recursive-include doc/tools * recursive-include examples * recursive-include rpm * global-exclude *.pyc -prune doc/_build diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py deleted file mode 100644 index a257f16..0000000 --- a/OpenSSL/SSL.py +++ /dev/null @@ -1,1423 +0,0 @@ -from sys import platform -from functools import wraps, partial -from itertools import count -from weakref import WeakValueDictionary -from errno import errorcode - -from six import text_type as _text_type -from six import integer_types as integer_types - -from OpenSSL._util import ( - ffi as _ffi, - lib as _lib, - exception_from_error_queue as _exception_from_error_queue, - native as _native) - -from OpenSSL.crypto import ( - FILETYPE_PEM, _PassphraseHelper, PKey, X509Name, X509, X509Store) - -_unspecified = object() - -try: - _memoryview = memoryview -except NameError: - class _memoryview(object): - pass - -OPENSSL_VERSION_NUMBER = _lib.OPENSSL_VERSION_NUMBER -SSLEAY_VERSION = _lib.SSLEAY_VERSION -SSLEAY_CFLAGS = _lib.SSLEAY_CFLAGS -SSLEAY_PLATFORM = _lib.SSLEAY_PLATFORM -SSLEAY_DIR = _lib.SSLEAY_DIR -SSLEAY_BUILT_ON = _lib.SSLEAY_BUILT_ON - -SENT_SHUTDOWN = _lib.SSL_SENT_SHUTDOWN -RECEIVED_SHUTDOWN = _lib.SSL_RECEIVED_SHUTDOWN - -SSLv2_METHOD = 1 -SSLv3_METHOD = 2 -SSLv23_METHOD = 3 -TLSv1_METHOD = 4 -TLSv1_1_METHOD = 5 -TLSv1_2_METHOD = 6 - -OP_NO_SSLv2 = _lib.SSL_OP_NO_SSLv2 -OP_NO_SSLv3 = _lib.SSL_OP_NO_SSLv3 -OP_NO_TLSv1 = _lib.SSL_OP_NO_TLSv1 - -OP_NO_TLSv1_1 = getattr(_lib, "SSL_OP_NO_TLSv1_1", 0) -OP_NO_TLSv1_2 = getattr(_lib, "SSL_OP_NO_TLSv1_2", 0) - -try: - MODE_RELEASE_BUFFERS = _lib.SSL_MODE_RELEASE_BUFFERS -except AttributeError: - pass - -OP_SINGLE_DH_USE = _lib.SSL_OP_SINGLE_DH_USE -OP_EPHEMERAL_RSA = _lib.SSL_OP_EPHEMERAL_RSA -OP_MICROSOFT_SESS_ID_BUG = _lib.SSL_OP_MICROSOFT_SESS_ID_BUG -OP_NETSCAPE_CHALLENGE_BUG = _lib.SSL_OP_NETSCAPE_CHALLENGE_BUG -OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG = _lib.SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG -OP_SSLREF2_REUSE_CERT_TYPE_BUG = _lib.SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG -OP_MICROSOFT_BIG_SSLV3_BUFFER = _lib.SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER -try: - OP_MSIE_SSLV2_RSA_PADDING = _lib.SSL_OP_MSIE_SSLV2_RSA_PADDING -except AttributeError: - pass -OP_SSLEAY_080_CLIENT_DH_BUG = _lib.SSL_OP_SSLEAY_080_CLIENT_DH_BUG -OP_TLS_D5_BUG = _lib.SSL_OP_TLS_D5_BUG -OP_TLS_BLOCK_PADDING_BUG = _lib.SSL_OP_TLS_BLOCK_PADDING_BUG -OP_DONT_INSERT_EMPTY_FRAGMENTS = _lib.SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS -OP_CIPHER_SERVER_PREFERENCE = _lib.SSL_OP_CIPHER_SERVER_PREFERENCE -OP_TLS_ROLLBACK_BUG = _lib.SSL_OP_TLS_ROLLBACK_BUG -OP_PKCS1_CHECK_1 = _lib.SSL_OP_PKCS1_CHECK_1 -OP_PKCS1_CHECK_2 = _lib.SSL_OP_PKCS1_CHECK_2 -OP_NETSCAPE_CA_DN_BUG = _lib.SSL_OP_NETSCAPE_CA_DN_BUG -OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG= _lib.SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG -try: - OP_NO_COMPRESSION = _lib.SSL_OP_NO_COMPRESSION -except AttributeError: - pass - -OP_NO_QUERY_MTU = _lib.SSL_OP_NO_QUERY_MTU -OP_COOKIE_EXCHANGE = _lib.SSL_OP_COOKIE_EXCHANGE -OP_NO_TICKET = _lib.SSL_OP_NO_TICKET - -OP_ALL = _lib.SSL_OP_ALL - -VERIFY_PEER = _lib.SSL_VERIFY_PEER -VERIFY_FAIL_IF_NO_PEER_CERT = _lib.SSL_VERIFY_FAIL_IF_NO_PEER_CERT -VERIFY_CLIENT_ONCE = _lib.SSL_VERIFY_CLIENT_ONCE -VERIFY_NONE = _lib.SSL_VERIFY_NONE - -SESS_CACHE_OFF = _lib.SSL_SESS_CACHE_OFF -SESS_CACHE_CLIENT = _lib.SSL_SESS_CACHE_CLIENT -SESS_CACHE_SERVER = _lib.SSL_SESS_CACHE_SERVER -SESS_CACHE_BOTH = _lib.SSL_SESS_CACHE_BOTH -SESS_CACHE_NO_AUTO_CLEAR = _lib.SSL_SESS_CACHE_NO_AUTO_CLEAR -SESS_CACHE_NO_INTERNAL_LOOKUP = _lib.SSL_SESS_CACHE_NO_INTERNAL_LOOKUP -SESS_CACHE_NO_INTERNAL_STORE = _lib.SSL_SESS_CACHE_NO_INTERNAL_STORE -SESS_CACHE_NO_INTERNAL = _lib.SSL_SESS_CACHE_NO_INTERNAL - -SSL_ST_CONNECT = _lib.SSL_ST_CONNECT -SSL_ST_ACCEPT = _lib.SSL_ST_ACCEPT -SSL_ST_MASK = _lib.SSL_ST_MASK -SSL_ST_INIT = _lib.SSL_ST_INIT -SSL_ST_BEFORE = _lib.SSL_ST_BEFORE -SSL_ST_OK = _lib.SSL_ST_OK -SSL_ST_RENEGOTIATE = _lib.SSL_ST_RENEGOTIATE - -SSL_CB_LOOP = _lib.SSL_CB_LOOP -SSL_CB_EXIT = _lib.SSL_CB_EXIT -SSL_CB_READ = _lib.SSL_CB_READ -SSL_CB_WRITE = _lib.SSL_CB_WRITE -SSL_CB_ALERT = _lib.SSL_CB_ALERT -SSL_CB_READ_ALERT = _lib.SSL_CB_READ_ALERT -SSL_CB_WRITE_ALERT = _lib.SSL_CB_WRITE_ALERT -SSL_CB_ACCEPT_LOOP = _lib.SSL_CB_ACCEPT_LOOP -SSL_CB_ACCEPT_EXIT = _lib.SSL_CB_ACCEPT_EXIT -SSL_CB_CONNECT_LOOP = _lib.SSL_CB_CONNECT_LOOP -SSL_CB_CONNECT_EXIT = _lib.SSL_CB_CONNECT_EXIT -SSL_CB_HANDSHAKE_START = _lib.SSL_CB_HANDSHAKE_START -SSL_CB_HANDSHAKE_DONE = _lib.SSL_CB_HANDSHAKE_DONE - - -class Error(Exception): - """ - An error occurred in an `OpenSSL.SSL` API. - """ - - - -_raise_current_error = partial(_exception_from_error_queue, Error) - - -class WantReadError(Error): - pass - - - -class WantWriteError(Error): - pass - - - -class WantX509LookupError(Error): - pass - - - -class ZeroReturnError(Error): - pass - - - -class SysCallError(Error): - pass - - - -class _VerifyHelper(object): - def __init__(self, connection, callback): - self._problems = [] - - @wraps(callback) - def wrapper(ok, store_ctx): - cert = X509.__new__(X509) - cert._x509 = _lib.X509_STORE_CTX_get_current_cert(store_ctx) - error_number = _lib.X509_STORE_CTX_get_error(store_ctx) - error_depth = _lib.X509_STORE_CTX_get_error_depth(store_ctx) - - try: - result = callback(connection, cert, error_number, error_depth, ok) - except Exception as e: - self._problems.append(e) - return 0 - else: - if result: - _lib.X509_STORE_CTX_set_error(store_ctx, _lib.X509_V_OK) - return 1 - else: - return 0 - - self.callback = _ffi.callback( - "int (*)(int, X509_STORE_CTX *)", wrapper) - - - def raise_if_problem(self): - if self._problems: - try: - _raise_current_error() - except Error: - pass - raise self._problems.pop(0) - - - -def _asFileDescriptor(obj): - fd = None - if not isinstance(obj, integer_types): - meth = getattr(obj, "fileno", None) - if meth is not None: - obj = meth() - - if isinstance(obj, integer_types): - fd = obj - - if not isinstance(fd, integer_types): - raise TypeError("argument must be an int, or have a fileno() method.") - elif fd < 0: - raise ValueError( - "file descriptor cannot be a negative integer (%i)" % (fd,)) - - return fd - - - -def SSLeay_version(type): - """ - Return a string describing the version of OpenSSL in use. - - :param type: One of the SSLEAY_ constants defined in this module. - """ - return _ffi.string(_lib.SSLeay_version(type)) - - - -class Session(object): - pass - - - -class Context(object): - """ - :py:obj:`OpenSSL.SSL.Context` instances define the parameters for setting up - new SSL connections. - """ - _methods = { - SSLv3_METHOD: "SSLv3_method", - SSLv23_METHOD: "SSLv23_method", - TLSv1_METHOD: "TLSv1_method", - TLSv1_1_METHOD: "TLSv1_1_method", - TLSv1_2_METHOD: "TLSv1_2_method", - } - _methods = dict( - (identifier, getattr(_lib, name)) - for (identifier, name) in _methods.items() - if getattr(_lib, name, None) is not None) - - - def __init__(self, method): - """ - :param method: One of SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, or - TLSv1_METHOD. - """ - if not isinstance(method, integer_types): - raise TypeError("method must be an integer") - - try: - method_func = self._methods[method] - except KeyError: - raise ValueError("No such protocol") - - method_obj = method_func() - if method_obj == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - context = _lib.SSL_CTX_new(method_obj) - if context == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - context = _ffi.gc(context, _lib.SSL_CTX_free) - - self._context = context - self._passphrase_helper = None - self._passphrase_callback = None - self._passphrase_userdata = None - self._verify_helper = None - self._verify_callback = None - self._info_callback = None - self._tlsext_servername_callback = None - self._app_data = None - - # SSL_CTX_set_app_data(self->ctx, self); - # SSL_CTX_set_mode(self->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE | - # SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | - # SSL_MODE_AUTO_RETRY); - self.set_mode(_lib.SSL_MODE_ENABLE_PARTIAL_WRITE) - - - def load_verify_locations(self, cafile, capath=None): - """ - Let SSL know where we can find trusted certificates for the certificate - chain - - :param cafile: In which file we can find the certificates - :param capath: In which directory we can find the certificates - :return: None - """ - if cafile is None: - cafile = _ffi.NULL - elif not isinstance(cafile, bytes): - raise TypeError("cafile must be None or a byte string") - - if capath is None: - capath = _ffi.NULL - elif not isinstance(capath, bytes): - raise TypeError("capath must be None or a byte string") - - load_result = _lib.SSL_CTX_load_verify_locations(self._context, cafile, capath) - if not load_result: - _raise_current_error() - - - def _wrap_callback(self, callback): - @wraps(callback) - def wrapper(size, verify, userdata): - return callback(size, verify, self._passphrase_userdata) - return _PassphraseHelper( - FILETYPE_PEM, wrapper, more_args=True, truncate=True) - - - def set_passwd_cb(self, callback, userdata=None): - """ - Set the passphrase callback - - :param callback: The Python callback to use - :param userdata: (optional) A Python object which will be given as - argument to the callback - :return: None - """ - if not callable(callback): - raise TypeError("callback must be callable") - - self._passphrase_helper = self._wrap_callback(callback) - self._passphrase_callback = self._passphrase_helper.callback - _lib.SSL_CTX_set_default_passwd_cb( - self._context, self._passphrase_callback) - self._passphrase_userdata = userdata - - - def set_default_verify_paths(self): - """ - Use the platform-specific CA certificate locations - - :return: None - """ - set_result = _lib.SSL_CTX_set_default_verify_paths(self._context) - if not set_result: - # TODO: This is untested. - _raise_current_error() - - - def use_certificate_chain_file(self, certfile): - """ - Load a certificate chain from a file - - :param certfile: The name of the certificate chain file - :return: None - """ - if isinstance(certfile, _text_type): - # Perhaps sys.getfilesystemencoding() could be better? - certfile = certfile.encode("utf-8") - - if not isinstance(certfile, bytes): - raise TypeError("certfile must be bytes or unicode") - - result = _lib.SSL_CTX_use_certificate_chain_file(self._context, certfile) - if not result: - _raise_current_error() - - - def use_certificate_file(self, certfile, filetype=FILETYPE_PEM): - """ - Load a certificate from a file - - :param certfile: The name of the certificate file - :param filetype: (optional) The encoding of the file, default is PEM - :return: None - """ - if isinstance(certfile, _text_type): - # Perhaps sys.getfilesystemencoding() could be better? - certfile = certfile.encode("utf-8") - if not isinstance(certfile, bytes): - raise TypeError("certfile must be bytes or unicode") - if not isinstance(filetype, integer_types): - raise TypeError("filetype must be an integer") - - use_result = _lib.SSL_CTX_use_certificate_file(self._context, certfile, filetype) - if not use_result: - _raise_current_error() - - - def use_certificate(self, cert): - """ - Load a certificate from a X509 object - - :param cert: The X509 object - :return: None - """ - if not isinstance(cert, X509): - raise TypeError("cert must be an X509 instance") - - use_result = _lib.SSL_CTX_use_certificate(self._context, cert._x509) - if not use_result: - _raise_current_error() - - - def add_extra_chain_cert(self, certobj): - """ - Add certificate to chain - - :param certobj: The X509 certificate object to add to the chain - :return: None - """ - if not isinstance(certobj, X509): - raise TypeError("certobj must be an X509 instance") - - copy = _lib.X509_dup(certobj._x509) - add_result = _lib.SSL_CTX_add_extra_chain_cert(self._context, copy) - if not add_result: - # TODO: This is untested. - _lib.X509_free(copy) - _raise_current_error() - - - def _raise_passphrase_exception(self): - if self._passphrase_helper is None: - _raise_current_error() - exception = self._passphrase_helper.raise_if_problem(Error) - if exception is not None: - raise exception - - - def use_privatekey_file(self, keyfile, filetype=_unspecified): - """ - Load a private key from a file - - :param keyfile: The name of the key file - :param filetype: (optional) The encoding of the file, default is PEM - :return: None - """ - if isinstance(keyfile, _text_type): - # Perhaps sys.getfilesystemencoding() could be better? - keyfile = keyfile.encode("utf-8") - - if not isinstance(keyfile, bytes): - raise TypeError("keyfile must be a byte string") - - if filetype is _unspecified: - filetype = FILETYPE_PEM - elif not isinstance(filetype, integer_types): - raise TypeError("filetype must be an integer") - - use_result = _lib.SSL_CTX_use_PrivateKey_file( - self._context, keyfile, filetype) - if not use_result: - self._raise_passphrase_exception() - - - def use_privatekey(self, pkey): - """ - Load a private key from a PKey object - - :param pkey: The PKey object - :return: None - """ - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey instance") - - use_result = _lib.SSL_CTX_use_PrivateKey(self._context, pkey._pkey) - if not use_result: - self._raise_passphrase_exception() - - - def check_privatekey(self): - """ - Check that the private key and certificate match up - - :return: None (raises an exception if something's wrong) - """ - - def load_client_ca(self, cafile): - """ - Load the trusted certificates that will be sent to the client (basically - telling the client "These are the guys I trust"). Does not actually - imply any of the certificates are trusted; that must be configured - separately. - - :param cafile: The name of the certificates file - :return: None - """ - - def set_session_id(self, buf): - """ - Set the session identifier. This is needed if you want to do session - resumption. - - :param buf: A Python object that can be safely converted to a string - :returns: None - """ - - def set_session_cache_mode(self, mode): - """ - Enable/disable session caching and specify the mode used. - - :param mode: One or more of the SESS_CACHE_* flags (combine using - bitwise or) - :returns: The previously set caching mode. - """ - if not isinstance(mode, integer_types): - raise TypeError("mode must be an integer") - - return _lib.SSL_CTX_set_session_cache_mode(self._context, mode) - - - def get_session_cache_mode(self): - """ - :returns: The currently used cache mode. - """ - return _lib.SSL_CTX_get_session_cache_mode(self._context) - - - def set_verify(self, mode, callback): - """ - Set the verify mode and verify callback - - :param mode: The verify mode, this is either VERIFY_NONE or - VERIFY_PEER combined with possible other flags - :param callback: The Python callback to use - :return: None - - See SSL_CTX_set_verify(3SSL) for further details. - """ - if not isinstance(mode, integer_types): - raise TypeError("mode must be an integer") - - if not callable(callback): - raise TypeError("callback must be callable") - - self._verify_helper = _VerifyHelper(self, callback) - self._verify_callback = self._verify_helper.callback - _lib.SSL_CTX_set_verify(self._context, mode, self._verify_callback) - - - def set_verify_depth(self, depth): - """ - Set the verify depth - - :param depth: An integer specifying the verify depth - :return: None - """ - if not isinstance(depth, integer_types): - raise TypeError("depth must be an integer") - - _lib.SSL_CTX_set_verify_depth(self._context, depth) - - - def get_verify_mode(self): - """ - Get the verify mode - - :return: The verify mode - """ - return _lib.SSL_CTX_get_verify_mode(self._context) - - - def get_verify_depth(self): - """ - Get the verify depth - - :return: The verify depth - """ - return _lib.SSL_CTX_get_verify_depth(self._context) - - - def load_tmp_dh(self, dhfile): - """ - Load parameters for Ephemeral Diffie-Hellman - - :param dhfile: The file to load EDH parameters from - :return: None - """ - if not isinstance(dhfile, bytes): - raise TypeError("dhfile must be a byte string") - - bio = _lib.BIO_new_file(dhfile, b"r") - if bio == _ffi.NULL: - _raise_current_error() - bio = _ffi.gc(bio, _lib.BIO_free) - - dh = _lib.PEM_read_bio_DHparams(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - dh = _ffi.gc(dh, _lib.DH_free) - _lib.SSL_CTX_set_tmp_dh(self._context, dh) - - - def set_cipher_list(self, cipher_list): - """ - Change the cipher list - - :param cipher_list: A cipher list, see ciphers(1) - :return: None - """ - if isinstance(cipher_list, _text_type): - cipher_list = cipher_list.encode("ascii") - - if not isinstance(cipher_list, bytes): - raise TypeError("cipher_list must be bytes or unicode") - - result = _lib.SSL_CTX_set_cipher_list(self._context, cipher_list) - if not result: - _raise_current_error() - - - def set_client_ca_list(self, certificate_authorities): - """ - Set the list of preferred client certificate signers for this server context. - - This list of certificate authorities will be sent to the client when the - server requests a client certificate. - - :param certificate_authorities: a sequence of X509Names. - :return: None - """ - name_stack = _lib.sk_X509_NAME_new_null() - if name_stack == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - try: - for ca_name in certificate_authorities: - if not isinstance(ca_name, X509Name): - raise TypeError( - "client CAs must be X509Name objects, not %s objects" % ( - type(ca_name).__name__,)) - copy = _lib.X509_NAME_dup(ca_name._name) - if copy == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - push_result = _lib.sk_X509_NAME_push(name_stack, copy) - if not push_result: - _lib.X509_NAME_free(copy) - _raise_current_error() - except: - _lib.sk_X509_NAME_free(name_stack) - raise - - _lib.SSL_CTX_set_client_CA_list(self._context, name_stack) - - - def add_client_ca(self, certificate_authority): - """ - Add the CA certificate to the list of preferred signers for this context. - - The list of certificate authorities will be sent to the client when the - server requests a client certificate. - - :param certificate_authority: certificate authority's X509 certificate. - :return: None - """ - if not isinstance(certificate_authority, X509): - raise TypeError("certificate_authority must be an X509 instance") - - add_result = _lib.SSL_CTX_add_client_CA( - self._context, certificate_authority._x509) - if not add_result: - # TODO: This is untested. - _raise_current_error() - - - def set_timeout(self, timeout): - """ - Set session timeout - - :param timeout: The timeout in seconds - :return: The previous session timeout - """ - if not isinstance(timeout, integer_types): - raise TypeError("timeout must be an integer") - - return _lib.SSL_CTX_set_timeout(self._context, timeout) - - - def get_timeout(self): - """ - Get the session timeout - - :return: The session timeout - """ - return _lib.SSL_CTX_get_timeout(self._context) - - - def set_info_callback(self, callback): - """ - Set the info callback - - :param callback: The Python callback to use - :return: None - """ - @wraps(callback) - def wrapper(ssl, where, return_code): - callback(Connection._reverse_mapping[ssl], where, return_code) - self._info_callback = _ffi.callback( - "void (*)(const SSL *, int, int)", wrapper) - _lib.SSL_CTX_set_info_callback(self._context, self._info_callback) - - - def get_app_data(self): - """ - Get the application data (supplied via set_app_data()) - - :return: The application data - """ - return self._app_data - - - def set_app_data(self, data): - """ - Set the application data (will be returned from get_app_data()) - - :param data: Any Python object - :return: None - """ - self._app_data = data - - - def get_cert_store(self): - """ - Get the certificate store for the context. - - :return: A X509Store object or None if it does not have one. - """ - store = _lib.SSL_CTX_get_cert_store(self._context) - if store == _ffi.NULL: - # TODO: This is untested. - return None - - pystore = X509Store.__new__(X509Store) - pystore._store = store - return pystore - - - def set_options(self, options): - """ - Add options. Options set before are not cleared! - - :param options: The options to add. - :return: The new option bitmask. - """ - if not isinstance(options, integer_types): - raise TypeError("options must be an integer") - - return _lib.SSL_CTX_set_options(self._context, options) - - - def set_mode(self, mode): - """ - Add modes via bitmask. Modes set before are not cleared! - - :param mode: The mode to add. - :return: The new mode bitmask. - """ - if not isinstance(mode, integer_types): - raise TypeError("mode must be an integer") - - return _lib.SSL_CTX_set_mode(self._context, mode) - - - def set_tlsext_servername_callback(self, callback): - """ - Specify a callback function to be called when clients specify a server name. - - :param callback: The callback function. It will be invoked with one - argument, the Connection instance. - """ - @wraps(callback) - def wrapper(ssl, alert, arg): - callback(Connection._reverse_mapping[ssl]) - return 0 - - self._tlsext_servername_callback = _ffi.callback( - "int (*)(const SSL *, int *, void *)", wrapper) - _lib.SSL_CTX_set_tlsext_servername_callback( - self._context, self._tlsext_servername_callback) - -ContextType = Context - - - -class Connection(object): - """ - """ - _reverse_mapping = WeakValueDictionary() - - def __init__(self, context, socket=None): - """ - Create a new Connection object, using the given OpenSSL.SSL.Context - instance and socket. - - :param context: An SSL Context to use for this connection - :param socket: The socket to use for transport layer - """ - if not isinstance(context, Context): - raise TypeError("context must be a Context instance") - - ssl = _lib.SSL_new(context._context) - self._ssl = _ffi.gc(ssl, _lib.SSL_free) - self._context = context - - self._reverse_mapping[self._ssl] = self - - if socket is None: - self._socket = None - # Don't set up any gc for these, SSL_free will take care of them. - self._into_ssl = _lib.BIO_new(_lib.BIO_s_mem()) - self._from_ssl = _lib.BIO_new(_lib.BIO_s_mem()) - - if self._into_ssl == _ffi.NULL or self._from_ssl == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - _lib.SSL_set_bio(self._ssl, self._into_ssl, self._from_ssl) - else: - self._into_ssl = None - self._from_ssl = None - self._socket = socket - set_result = _lib.SSL_set_fd(self._ssl, _asFileDescriptor(self._socket)) - if not set_result: - # TODO: This is untested. - _raise_current_error() - - - def __getattr__(self, name): - """ - Look up attributes on the wrapped socket object if they are not found on - the Connection object. - """ - return getattr(self._socket, name) - - - def _raise_ssl_error(self, ssl, result): - if self._context._verify_helper is not None: - self._context._verify_helper.raise_if_problem() - - error = _lib.SSL_get_error(ssl, result) - if error == _lib.SSL_ERROR_WANT_READ: - raise WantReadError() - elif error == _lib.SSL_ERROR_WANT_WRITE: - raise WantWriteError() - elif error == _lib.SSL_ERROR_ZERO_RETURN: - raise ZeroReturnError() - elif error == _lib.SSL_ERROR_WANT_X509_LOOKUP: - # TODO: This is untested. - raise WantX509LookupError() - elif error == _lib.SSL_ERROR_SYSCALL: - if _lib.ERR_peek_error() == 0: - if result < 0: - if platform == "win32": - errno = _ffi.getwinerror()[0] - else: - errno = _ffi.errno - raise SysCallError(errno, errorcode[errno]) - else: - raise SysCallError(-1, "Unexpected EOF") - else: - # TODO: This is untested. - _raise_current_error() - elif error == _lib.SSL_ERROR_NONE: - pass - else: - _raise_current_error() - - - def get_context(self): - """ - Get session context - """ - return self._context - - - def set_context(self, context): - """ - Switch this connection to a new session context - - :param context: A :py:class:`Context` instance giving the new session - context to use. - """ - if not isinstance(context, Context): - raise TypeError("context must be a Context instance") - - _lib.SSL_set_SSL_CTX(self._ssl, context._context) - self._context = context - - - def get_servername(self): - """ - Retrieve the servername extension value if provided in the client hello - message, or None if there wasn't one. - - :return: A byte string giving the server name or :py:data:`None`. - """ - name = _lib.SSL_get_servername(self._ssl, _lib.TLSEXT_NAMETYPE_host_name) - if name == _ffi.NULL: - return None - - return _ffi.string(name) - - - def set_tlsext_host_name(self, name): - """ - Set the value of the servername extension to send in the client hello. - - :param name: A byte string giving the name. - """ - if not isinstance(name, bytes): - raise TypeError("name must be a byte string") - elif b"\0" in name: - raise TypeError("name must not contain NUL byte") - - # XXX I guess this can fail sometimes? - _lib.SSL_set_tlsext_host_name(self._ssl, name) - - - def pending(self): - """ - Get the number of bytes that can be safely read from the connection - - :return: The number of bytes available in the receive buffer. - """ - return _lib.SSL_pending(self._ssl) - - - def send(self, buf, flags=0): - """ - Send data on the connection. NOTE: If you get one of the WantRead, - WantWrite or WantX509Lookup exceptions on this, you have to call the - method again with the SAME buffer. - - :param buf: The string to send - :param flags: (optional) Included for compatibility with the socket - API, the value is ignored - :return: The number of bytes written - """ - if isinstance(buf, _memoryview): - buf = buf.tobytes() - if not isinstance(buf, bytes): - raise TypeError("data must be a byte string") - - result = _lib.SSL_write(self._ssl, buf, len(buf)) - self._raise_ssl_error(self._ssl, result) - return result - write = send - - - def sendall(self, buf, flags=0): - """ - Send "all" data on the connection. This calls send() repeatedly until - all data is sent. If an error occurs, it's impossible to tell how much - data has been sent. - - :param buf: The string to send - :param flags: (optional) Included for compatibility with the socket - API, the value is ignored - :return: The number of bytes written - """ - if isinstance(buf, _memoryview): - buf = buf.tobytes() - if not isinstance(buf, bytes): - raise TypeError("buf must be a byte string") - - left_to_send = len(buf) - total_sent = 0 - data = _ffi.new("char[]", buf) - - while left_to_send: - result = _lib.SSL_write(self._ssl, data + total_sent, left_to_send) - self._raise_ssl_error(self._ssl, result) - total_sent += result - left_to_send -= result - - - def recv(self, bufsiz, flags=None): - """ - Receive data on the connection. NOTE: If you get one of the WantRead, - WantWrite or WantX509Lookup exceptions on this, you have to call the - method again with the SAME buffer. - - :param bufsiz: The maximum number of bytes to read - :param flags: (optional) Included for compatibility with the socket - API, the value is ignored - :return: The string read from the Connection - """ - buf = _ffi.new("char[]", bufsiz) - result = _lib.SSL_read(self._ssl, buf, bufsiz) - self._raise_ssl_error(self._ssl, result) - return _ffi.buffer(buf, result)[:] - read = recv - - - def _handle_bio_errors(self, bio, result): - if _lib.BIO_should_retry(bio): - if _lib.BIO_should_read(bio): - raise WantReadError() - elif _lib.BIO_should_write(bio): - # TODO: This is untested. - raise WantWriteError() - elif _lib.BIO_should_io_special(bio): - # TODO: This is untested. I think io_special means the socket - # BIO has a not-yet connected socket. - raise ValueError("BIO_should_io_special") - else: - # TODO: This is untested. - raise ValueError("unknown bio failure") - else: - # TODO: This is untested. - _raise_current_error() - - - def bio_read(self, bufsiz): - """ - When using non-socket connections this function reads the "dirty" data - that would have traveled away on the network. - - :param bufsiz: The maximum number of bytes to read - :return: The string read. - """ - if self._from_ssl is None: - raise TypeError("Connection sock was not None") - - if not isinstance(bufsiz, integer_types): - raise TypeError("bufsiz must be an integer") - - buf = _ffi.new("char[]", bufsiz) - result = _lib.BIO_read(self._from_ssl, buf, bufsiz) - if result <= 0: - self._handle_bio_errors(self._from_ssl, result) - - return _ffi.buffer(buf, result)[:] - - - def bio_write(self, buf): - """ - When using non-socket connections this function sends "dirty" data that - would have traveled in on the network. - - :param buf: The string to put into the memory BIO. - :return: The number of bytes written - """ - if self._into_ssl is None: - raise TypeError("Connection sock was not None") - - if not isinstance(buf, bytes): - raise TypeError("buf must be a byte string") - - result = _lib.BIO_write(self._into_ssl, buf, len(buf)) - if result <= 0: - self._handle_bio_errors(self._into_ssl, result) - return result - - - def renegotiate(self): - """ - Renegotiate the session - - :return: True if the renegotiation can be started, false otherwise - """ - - def do_handshake(self): - """ - Perform an SSL handshake (usually called after renegotiate() or one of - set_*_state()). This can raise the same exceptions as send and recv. - - :return: None. - """ - result = _lib.SSL_do_handshake(self._ssl) - self._raise_ssl_error(self._ssl, result) - - - def renegotiate_pending(self): - """ - Check if there's a renegotiation in progress, it will return false once - a renegotiation is finished. - - :return: Whether there's a renegotiation in progress - """ - - def total_renegotiations(self): - """ - Find out the total number of renegotiations. - - :return: The number of renegotiations. - """ - return _lib.SSL_total_renegotiations(self._ssl) - - - def connect(self, addr): - """ - Connect to remote host and set up client-side SSL - - :param addr: A remote address - :return: What the socket's connect method returns - """ - _lib.SSL_set_connect_state(self._ssl) - return self._socket.connect(addr) - - - def connect_ex(self, addr): - """ - Connect to remote host and set up client-side SSL. Note that if the socket's - connect_ex method doesn't return 0, SSL won't be initialized. - - :param addr: A remove address - :return: What the socket's connect_ex method returns - """ - connect_ex = self._socket.connect_ex - self.set_connect_state() - return connect_ex(addr) - - - def accept(self): - """ - Accept incoming connection and set up SSL on it - - :return: A (conn,addr) pair where conn is a Connection and addr is an - address - """ - client, addr = self._socket.accept() - conn = Connection(self._context, client) - conn.set_accept_state() - return (conn, addr) - - - def bio_shutdown(self): - """ - When using non-socket connections this function signals end of - data on the input for this connection. - - :return: None - """ - if self._from_ssl is None: - raise TypeError("Connection sock was not None") - - _lib.BIO_set_mem_eof_return(self._into_ssl, 0) - - - def shutdown(self): - """ - Send closure alert - - :return: True if the shutdown completed successfully (i.e. both sides - have sent closure alerts), false otherwise (i.e. you have to - wait for a ZeroReturnError on a recv() method call - """ - result = _lib.SSL_shutdown(self._ssl) - if result < 0: - # TODO: This is untested. - _raise_current_error() - elif result > 0: - return True - else: - return False - - - def get_cipher_list(self): - """ - Get the session cipher list - - :return: A list of cipher strings - """ - ciphers = [] - for i in count(): - result = _lib.SSL_get_cipher_list(self._ssl, i) - if result == _ffi.NULL: - break - ciphers.append(_native(_ffi.string(result))) - return ciphers - - - def get_client_ca_list(self): - """ - Get CAs whose certificates are suggested for client authentication. - - :return: If this is a server connection, a list of X509Names representing - the acceptable CAs as set by :py:meth:`OpenSSL.SSL.Context.set_client_ca_list` or - :py:meth:`OpenSSL.SSL.Context.add_client_ca`. If this is a client connection, - the list of such X509Names sent by the server, or an empty list if that - has not yet happened. - """ - ca_names = _lib.SSL_get_client_CA_list(self._ssl) - if ca_names == _ffi.NULL: - # TODO: This is untested. - return [] - - result = [] - for i in range(_lib.sk_X509_NAME_num(ca_names)): - name = _lib.sk_X509_NAME_value(ca_names, i) - copy = _lib.X509_NAME_dup(name) - if copy == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - pyname = X509Name.__new__(X509Name) - pyname._name = _ffi.gc(copy, _lib.X509_NAME_free) - result.append(pyname) - return result - - - def makefile(self): - """ - The makefile() method is not implemented, since there is no dup semantics - for SSL connections - - :raise NotImplementedError - """ - raise NotImplementedError("Cannot make file object of OpenSSL.SSL.Connection") - - - def get_app_data(self): - """ - Get application data - - :return: The application data - """ - return self._app_data - - - def set_app_data(self, data): - """ - Set application data - - :param data - The application data - :return: None - """ - self._app_data = data - - - def get_shutdown(self): - """ - Get shutdown state - - :return: The shutdown state, a bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN. - """ - return _lib.SSL_get_shutdown(self._ssl) - - - def set_shutdown(self, state): - """ - Set shutdown state - - :param state - bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN. - :return: None - """ - if not isinstance(state, integer_types): - raise TypeError("state must be an integer") - - _lib.SSL_set_shutdown(self._ssl, state) - - - def state_string(self): - """ - Get a verbose state description - - :return: A string representing the state - """ - - def server_random(self): - """ - Get a copy of the server hello nonce. - - :return: A string representing the state - """ - if self._ssl.session == _ffi.NULL: - return None - return _ffi.buffer( - self._ssl.s3.server_random, - _lib.SSL3_RANDOM_SIZE)[:] - - - def client_random(self): - """ - Get a copy of the client hello nonce. - - :return: A string representing the state - """ - if self._ssl.session == _ffi.NULL: - return None - return _ffi.buffer( - self._ssl.s3.client_random, - _lib.SSL3_RANDOM_SIZE)[:] - - - def master_key(self): - """ - Get a copy of the master key. - - :return: A string representing the state - """ - if self._ssl.session == _ffi.NULL: - return None - return _ffi.buffer( - self._ssl.session.master_key, - self._ssl.session.master_key_length)[:] - - - def sock_shutdown(self, *args, **kwargs): - """ - See shutdown(2) - - :return: What the socket's shutdown() method returns - """ - return self._socket.shutdown(*args, **kwargs) - - - def get_peer_certificate(self): - """ - Retrieve the other side's certificate (if any) - - :return: The peer's certificate - """ - cert = _lib.SSL_get_peer_certificate(self._ssl) - if cert != _ffi.NULL: - pycert = X509.__new__(X509) - pycert._x509 = _ffi.gc(cert, _lib.X509_free) - return pycert - return None - - - def get_peer_cert_chain(self): - """ - Retrieve the other side's certificate (if any) - - :return: A list of X509 instances giving the peer's certificate chain, - or None if it does not have one. - """ - cert_stack = _lib.SSL_get_peer_cert_chain(self._ssl) - if cert_stack == _ffi.NULL: - return None - - result = [] - for i in range(_lib.sk_X509_num(cert_stack)): - # TODO could incref instead of dup here - cert = _lib.X509_dup(_lib.sk_X509_value(cert_stack, i)) - pycert = X509.__new__(X509) - pycert._x509 = _ffi.gc(cert, _lib.X509_free) - result.append(pycert) - return result - - - def want_read(self): - """ - Checks if more data has to be read from the transport layer to complete an - operation. - - :return: True iff more data has to be read - """ - return _lib.SSL_want_read(self._ssl) - - - def want_write(self): - """ - Checks if there is data to write to the transport layer to complete an - operation. - - :return: True iff there is data to write - """ - return _lib.SSL_want_write(self._ssl) - - - def set_accept_state(self): - """ - Set the connection to work in server mode. The handshake will be handled - automatically by read/write. - - :return: None - """ - _lib.SSL_set_accept_state(self._ssl) - - - def set_connect_state(self): - """ - Set the connection to work in client mode. The handshake will be handled - automatically by read/write. - - :return: None - """ - _lib.SSL_set_connect_state(self._ssl) - - - def get_session(self): - """ - Returns the Session currently used. - - @return: An instance of :py:class:`OpenSSL.SSL.Session` or :py:obj:`None` if - no session exists. - """ - session = _lib.SSL_get1_session(self._ssl) - if session == _ffi.NULL: - return None - - pysession = Session.__new__(Session) - pysession._session = _ffi.gc(session, _lib.SSL_SESSION_free) - return pysession - - - def set_session(self, session): - """ - Set the session to be used when the TLS/SSL connection is established. - - :param session: A Session instance representing the session to use. - :returns: None - """ - if not isinstance(session, Session): - raise TypeError("session must be a Session instance") - - result = _lib.SSL_set_session(self._ssl, session._session) - if not result: - _raise_current_error() - -ConnectionType = Connection - -# This is similar to the initialization calls at the end of OpenSSL/crypto.py -# but is exercised mostly by the Context initializer. -_lib.SSL_library_init() diff --git a/OpenSSL/__init__.py b/OpenSSL/__init__.py index db96e1f..c9ea33b 100644 --- a/OpenSSL/__init__.py +++ b/OpenSSL/__init__.py @@ -5,7 +5,40 @@ pyOpenSSL - A simple wrapper around the OpenSSL library """ -from OpenSSL import rand, crypto, SSL +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__ = [ diff --git a/OpenSSL/_util.py b/OpenSSL/_util.py deleted file mode 100644 index baeecc6..0000000 --- a/OpenSSL/_util.py +++ /dev/null @@ -1,53 +0,0 @@ -from six import PY3, binary_type, text_type - -from cryptography.hazmat.bindings.openssl.binding import Binding -binding = Binding() -ffi = binding.ffi -lib = binding.lib - -def exception_from_error_queue(exceptionType): - def text(charp): - return native(ffi.string(charp)) - - errors = [] - while True: - error = lib.ERR_get_error() - if error == 0: - break - errors.append(( - text(lib.ERR_lib_error_string(error)), - text(lib.ERR_func_error_string(error)), - text(lib.ERR_reason_error_string(error)))) - - raise exceptionType(errors) - - - -def native(s): - """ - Convert :py:class:`bytes` or :py:class:`unicode` to the native - :py:class:`str` type, using UTF-8 encoding if conversion is necessary. - - :raise UnicodeError: The input string is not UTF-8 decodeable. - - :raise TypeError: The input is neither :py:class:`bytes` nor - :py:class:`unicode`. - """ - if not isinstance(s, (binary_type, text_type)): - raise TypeError("%r is neither bytes nor unicode" % s) - if PY3: - if isinstance(s, binary_type): - return s.decode("utf-8") - else: - if isinstance(s, text_type): - return s.encode("utf-8") - return s - - - -if PY3: - def byte_string(s): - return s.encode("charmap") -else: - def byte_string(s): - return s diff --git a/OpenSSL/crypto.py b/OpenSSL/crypto.py deleted file mode 100644 index d0026bd..0000000 --- a/OpenSSL/crypto.py +++ /dev/null @@ -1,2314 +0,0 @@ -from time import time -from base64 import b16encode -from functools import partial -from operator import __eq__, __ne__, __lt__, __le__, __gt__, __ge__ - -from six import ( - integer_types as _integer_types, - text_type as _text_type) - -from OpenSSL._util import ( - ffi as _ffi, - lib as _lib, - exception_from_error_queue as _exception_from_error_queue, - byte_string as _byte_string, - native as _native) - -FILETYPE_PEM = _lib.SSL_FILETYPE_PEM -FILETYPE_ASN1 = _lib.SSL_FILETYPE_ASN1 - -# TODO This was an API mistake. OpenSSL has no such constant. -FILETYPE_TEXT = 2 ** 16 - 1 - -TYPE_RSA = _lib.EVP_PKEY_RSA -TYPE_DSA = _lib.EVP_PKEY_DSA - - -class Error(Exception): - """ - An error occurred in an `OpenSSL.crypto` API. - """ - - -_raise_current_error = partial(_exception_from_error_queue, Error) - -def _untested_error(where): - """ - An OpenSSL API failed somehow. Additionally, the failure which was - encountered isn't one that's exercised by the test suite so future behavior - of pyOpenSSL is now somewhat less predictable. - """ - raise RuntimeError("Unknown %s failure" % (where,)) - - - -def _new_mem_buf(buffer=None): - """ - Allocate a new OpenSSL memory BIO. - - Arrange for the garbage collector to clean it up automatically. - - :param buffer: None or some bytes to use to put into the BIO so that they - can be read out. - """ - if buffer is None: - bio = _lib.BIO_new(_lib.BIO_s_mem()) - free = _lib.BIO_free - else: - data = _ffi.new("char[]", buffer) - bio = _lib.BIO_new_mem_buf(data, len(buffer)) - # Keep the memory alive as long as the bio is alive! - def free(bio, ref=data): - return _lib.BIO_free(bio) - - if bio == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - bio = _ffi.gc(bio, free) - return bio - - - -def _bio_to_string(bio): - """ - Copy the contents of an OpenSSL BIO object into a Python byte string. - """ - result_buffer = _ffi.new('char**') - buffer_length = _lib.BIO_get_mem_data(bio, result_buffer) - return _ffi.buffer(result_buffer[0], buffer_length)[:] - - - -def _set_asn1_time(boundary, when): - """ - The the time value of an ASN1 time object. - - @param boundary: An ASN1_GENERALIZEDTIME pointer (or an object safely - castable to that type) which will have its value set. - @param when: A string representation of the desired time value. - - @raise TypeError: If C{when} is not a L{bytes} string. - @raise ValueError: If C{when} does not represent a time in the required - format. - @raise RuntimeError: If the time value cannot be set for some other - (unspecified) reason. - """ - if not isinstance(when, bytes): - raise TypeError("when must be a byte string") - - set_result = _lib.ASN1_GENERALIZEDTIME_set_string( - _ffi.cast('ASN1_GENERALIZEDTIME*', boundary), when) - if set_result == 0: - dummy = _ffi.gc(_lib.ASN1_STRING_new(), _lib.ASN1_STRING_free) - _lib.ASN1_STRING_set(dummy, when, len(when)) - check_result = _lib.ASN1_GENERALIZEDTIME_check( - _ffi.cast('ASN1_GENERALIZEDTIME*', dummy)) - if not check_result: - raise ValueError("Invalid string") - else: - _untested_error() - - - -def _get_asn1_time(timestamp): - """ - Retrieve the time value of an ASN1 time object. - - @param timestamp: An ASN1_GENERALIZEDTIME* (or an object safely castable to - that type) from which the time value will be retrieved. - - @return: The time value from C{timestamp} as a L{bytes} string in a certain - format. Or C{None} if the object contains no time value. - """ - string_timestamp = _ffi.cast('ASN1_STRING*', timestamp) - if _lib.ASN1_STRING_length(string_timestamp) == 0: - return None - elif _lib.ASN1_STRING_type(string_timestamp) == _lib.V_ASN1_GENERALIZEDTIME: - return _ffi.string(_lib.ASN1_STRING_data(string_timestamp)) - else: - generalized_timestamp = _ffi.new("ASN1_GENERALIZEDTIME**") - _lib.ASN1_TIME_to_generalizedtime(timestamp, generalized_timestamp) - if generalized_timestamp[0] == _ffi.NULL: - # This may happen: - # - if timestamp was not an ASN1_TIME - # - if allocating memory for the ASN1_GENERALIZEDTIME failed - # - if a copy of the time data from timestamp cannot be made for - # the newly allocated ASN1_GENERALIZEDTIME - # - # These are difficult to test. cffi enforces the ASN1_TIME type. - # Memory allocation failures are a pain to trigger - # deterministically. - _untested_error("ASN1_TIME_to_generalizedtime") - else: - string_timestamp = _ffi.cast( - "ASN1_STRING*", generalized_timestamp[0]) - string_data = _lib.ASN1_STRING_data(string_timestamp) - string_result = _ffi.string(string_data) - _lib.ASN1_GENERALIZEDTIME_free(generalized_timestamp[0]) - return string_result - - - -class PKey(object): - _only_public = False - _initialized = True - - def __init__(self): - pkey = _lib.EVP_PKEY_new() - self._pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free) - self._initialized = False - - - def generate_key(self, type, bits): - """ - Generate a key of a given type, with a given number of a bits - - :param type: The key type (TYPE_RSA or TYPE_DSA) - :param bits: The number of bits - - :return: None - """ - if not isinstance(type, int): - raise TypeError("type must be an integer") - - if not isinstance(bits, int): - raise TypeError("bits must be an integer") - - # TODO Check error return - exponent = _lib.BN_new() - exponent = _ffi.gc(exponent, _lib.BN_free) - _lib.BN_set_word(exponent, _lib.RSA_F4) - - if type == TYPE_RSA: - if bits <= 0: - raise ValueError("Invalid number of bits") - - rsa = _lib.RSA_new() - - result = _lib.RSA_generate_key_ex(rsa, bits, exponent, _ffi.NULL) - if result == 0: - # TODO: The test for this case is commented out. Different - # builds of OpenSSL appear to have different failure modes that - # make it hard to test. Visual inspection of the OpenSSL - # source reveals that a return value of 0 signals an error. - # Manual testing on a particular build of OpenSSL suggests that - # this is probably the appropriate way to handle those errors. - _raise_current_error() - - result = _lib.EVP_PKEY_assign_RSA(self._pkey, rsa) - if not result: - # TODO: It appears as though this can fail if an engine is in - # use which does not support RSA. - _raise_current_error() - - elif type == TYPE_DSA: - dsa = _lib.DSA_generate_parameters( - bits, _ffi.NULL, 0, _ffi.NULL, _ffi.NULL, _ffi.NULL, _ffi.NULL) - if dsa == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - if not _lib.DSA_generate_key(dsa): - # TODO: This is untested. - _raise_current_error() - if not _lib.EVP_PKEY_assign_DSA(self._pkey, dsa): - # TODO: This is untested. - _raise_current_error() - else: - raise Error("No such key type") - - self._initialized = True - - - def check(self): - """ - Check the consistency of an RSA private key. - - :return: True if key is consistent. - :raise Error: if the key is inconsistent. - :raise TypeError: if the key is of a type which cannot be checked. - Only RSA keys can currently be checked. - """ - if self._only_public: - raise TypeError("public key only") - - if _lib.EVP_PKEY_type(self._pkey.type) != _lib.EVP_PKEY_RSA: - raise TypeError("key type unsupported") - - rsa = _lib.EVP_PKEY_get1_RSA(self._pkey) - rsa = _ffi.gc(rsa, _lib.RSA_free) - result = _lib.RSA_check_key(rsa) - if result: - return True - _raise_current_error() - - - def type(self): - """ - Returns the type of the key - - :return: The type of the key. - """ - return self._pkey.type - - - def bits(self): - """ - Returns the number of bits of the key - - :return: The number of bits of the key. - """ - return _lib.EVP_PKEY_bits(self._pkey) -PKeyType = PKey - - - -class X509Name(object): - def __init__(self, name): - """ - Create a new X509Name, copying the given X509Name instance. - - :param name: An X509Name object to copy - """ - name = _lib.X509_NAME_dup(name._name) - self._name = _ffi.gc(name, _lib.X509_NAME_free) - - - def __setattr__(self, name, value): - if name.startswith('_'): - return super(X509Name, self).__setattr__(name, value) - - # Note: we really do not want str subclasses here, so we do not use - # isinstance. - if type(name) is not str: - raise TypeError("attribute name must be string, not '%.200s'" % ( - type(value).__name__,)) - - nid = _lib.OBJ_txt2nid(_byte_string(name)) - if nid == _lib.NID_undef: - try: - _raise_current_error() - except Error: - pass - raise AttributeError("No such attribute") - - # If there's an old entry for this NID, remove it - for i in range(_lib.X509_NAME_entry_count(self._name)): - ent = _lib.X509_NAME_get_entry(self._name, i) - ent_obj = _lib.X509_NAME_ENTRY_get_object(ent) - ent_nid = _lib.OBJ_obj2nid(ent_obj) - if nid == ent_nid: - ent = _lib.X509_NAME_delete_entry(self._name, i) - _lib.X509_NAME_ENTRY_free(ent) - break - - if isinstance(value, _text_type): - value = value.encode('utf-8') - - add_result = _lib.X509_NAME_add_entry_by_NID( - self._name, nid, _lib.MBSTRING_UTF8, value, -1, -1, 0) - if not add_result: - _raise_current_error() - - - def __getattr__(self, name): - """ - Find attribute. An X509Name object has the following attributes: - countryName (alias C), stateOrProvince (alias ST), locality (alias L), - organization (alias O), organizationalUnit (alias OU), commonName (alias - CN) and more... - """ - nid = _lib.OBJ_txt2nid(_byte_string(name)) - if nid == _lib.NID_undef: - # This is a bit weird. OBJ_txt2nid indicated failure, but it seems - # a lower level function, a2d_ASN1_OBJECT, also feels the need to - # push something onto the error queue. If we don't clean that up - # now, someone else will bump into it later and be quite confused. - # See lp#314814. - try: - _raise_current_error() - except Error: - pass - return super(X509Name, self).__getattr__(name) - - entry_index = _lib.X509_NAME_get_index_by_NID(self._name, nid, -1) - if entry_index == -1: - return None - - entry = _lib.X509_NAME_get_entry(self._name, entry_index) - data = _lib.X509_NAME_ENTRY_get_data(entry) - - result_buffer = _ffi.new("unsigned char**") - data_length = _lib.ASN1_STRING_to_UTF8(result_buffer, data) - if data_length < 0: - # TODO: This is untested. - _raise_current_error() - - try: - result = _ffi.buffer(result_buffer[0], data_length)[:].decode('utf-8') - finally: - # XXX untested - _lib.OPENSSL_free(result_buffer[0]) - return result - - - def _cmp(op): - def f(self, other): - if not isinstance(other, X509Name): - return NotImplemented - result = _lib.X509_NAME_cmp(self._name, other._name) - return op(result, 0) - return f - - __eq__ = _cmp(__eq__) - __ne__ = _cmp(__ne__) - - __lt__ = _cmp(__lt__) - __le__ = _cmp(__le__) - - __gt__ = _cmp(__gt__) - __ge__ = _cmp(__ge__) - - def __repr__(self): - """ - String representation of an X509Name - """ - result_buffer = _ffi.new("char[]", 512); - format_result = _lib.X509_NAME_oneline( - self._name, result_buffer, len(result_buffer)) - - if format_result == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - return "" % ( - _native(_ffi.string(result_buffer)),) - - - def hash(self): - """ - Return the hash value of this name - - :return: None - """ - return _lib.X509_NAME_hash(self._name) - - - def der(self): - """ - Return the DER encoding of this name - - :return: A :py:class:`bytes` instance giving the DER encoded form of - this name. - """ - result_buffer = _ffi.new('unsigned char**') - encode_result = _lib.i2d_X509_NAME(self._name, result_buffer) - if encode_result < 0: - # TODO: This is untested. - _raise_current_error() - - string_result = _ffi.buffer(result_buffer[0], encode_result)[:] - _lib.OPENSSL_free(result_buffer[0]) - return string_result - - - def get_components(self): - """ - Returns the split-up components of this name. - - :return: List of tuples (name, value). - """ - result = [] - for i in range(_lib.X509_NAME_entry_count(self._name)): - ent = _lib.X509_NAME_get_entry(self._name, i) - - fname = _lib.X509_NAME_ENTRY_get_object(ent) - fval = _lib.X509_NAME_ENTRY_get_data(ent) - - nid = _lib.OBJ_obj2nid(fname) - name = _lib.OBJ_nid2sn(nid) - - result.append(( - _ffi.string(name), - _ffi.string( - _lib.ASN1_STRING_data(fval), - _lib.ASN1_STRING_length(fval)))) - - return result -X509NameType = X509Name - - -class X509Extension(object): - def __init__(self, type_name, critical, value, subject=None, issuer=None): - """ - :param typename: The name of the extension to create. - :type typename: :py:data:`str` - - :param critical: A flag indicating whether this is a critical extension. - - :param value: The value of the extension. - :type value: :py:data:`str` - - :param subject: Optional X509 cert to use as subject. - :type subject: :py:class:`X509` - - :param issuer: Optional X509 cert to use as issuer. - :type issuer: :py:class:`X509` - - :return: The X509Extension object - """ - ctx = _ffi.new("X509V3_CTX*") - - # A context is necessary for any extension which uses the r2i conversion - # method. That is, X509V3_EXT_nconf may segfault if passed a NULL ctx. - # Start off by initializing most of the fields to NULL. - _lib.X509V3_set_ctx(ctx, _ffi.NULL, _ffi.NULL, _ffi.NULL, _ffi.NULL, 0) - - # We have no configuration database - but perhaps we should (some - # extensions may require it). - _lib.X509V3_set_ctx_nodb(ctx) - - # Initialize the subject and issuer, if appropriate. ctx is a local, - # and as far as I can tell none of the X509V3_* APIs invoked here steal - # any references, so no need to mess with reference counts or duplicates. - if issuer is not None: - if not isinstance(issuer, X509): - raise TypeError("issuer must be an X509 instance") - ctx.issuer_cert = issuer._x509 - if subject is not None: - if not isinstance(subject, X509): - raise TypeError("subject must be an X509 instance") - ctx.subject_cert = subject._x509 - - if critical: - # There are other OpenSSL APIs which would let us pass in critical - # separately, but they're harder to use, and since value is already - # a pile of crappy junk smuggling a ton of utterly important - # structured data, what's the point of trying to avoid nasty stuff - # with strings? (However, X509V3_EXT_i2d in particular seems like it - # would be a better API to invoke. I do not know where to get the - # ext_struc it desires for its last parameter, though.) - value = b"critical," + value - - extension = _lib.X509V3_EXT_nconf(_ffi.NULL, ctx, type_name, value) - if extension == _ffi.NULL: - _raise_current_error() - self._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) - - - @property - def _nid(self): - return _lib.OBJ_obj2nid(self._extension.object) - - _prefixes = { - _lib.GEN_EMAIL: "email", - _lib.GEN_DNS: "DNS", - _lib.GEN_URI: "URI", - } - - def _subjectAltNameString(self): - method = _lib.X509V3_EXT_get(self._extension) - if method == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - payload = self._extension.value.data - length = self._extension.value.length - - payloadptr = _ffi.new("unsigned char**") - payloadptr[0] = payload - - if method.it != _ffi.NULL: - ptr = _lib.ASN1_ITEM_ptr(method.it) - data = _lib.ASN1_item_d2i(_ffi.NULL, payloadptr, length, ptr) - names = _ffi.cast("GENERAL_NAMES*", data) - else: - names = _ffi.cast( - "GENERAL_NAMES*", - method.d2i(_ffi.NULL, payloadptr, length)) - - parts = [] - for i in range(_lib.sk_GENERAL_NAME_num(names)): - name = _lib.sk_GENERAL_NAME_value(names, i) - try: - label = self._prefixes[name.type] - except KeyError: - bio = _new_mem_buf() - _lib.GENERAL_NAME_print(bio, name) - parts.append(_native(_bio_to_string(bio))) - else: - value = _native( - _ffi.buffer(name.d.ia5.data, name.d.ia5.length)[:]) - parts.append(label + ":" + value) - return ", ".join(parts) - - - def __str__(self): - """ - :return: a nice text representation of the extension - """ - if _lib.NID_subject_alt_name == self._nid: - return self._subjectAltNameString() - - bio = _new_mem_buf() - print_result = _lib.X509V3_EXT_print(bio, self._extension, 0, 0) - if not print_result: - # TODO: This is untested. - _raise_current_error() - - return _native(_bio_to_string(bio)) - - - def get_critical(self): - """ - Returns the critical field of the X509Extension - - :return: The critical field. - """ - return _lib.X509_EXTENSION_get_critical(self._extension) - - - def get_short_name(self): - """ - Returns the short version of the type name of the X509Extension - - :return: The short type name. - """ - obj = _lib.X509_EXTENSION_get_object(self._extension) - nid = _lib.OBJ_obj2nid(obj) - return _ffi.string(_lib.OBJ_nid2sn(nid)) - - - def get_data(self): - """ - Returns the data of the X509Extension - - :return: A :py:data:`str` giving the X509Extension's ASN.1 encoded data. - """ - octet_result = _lib.X509_EXTENSION_get_data(self._extension) - string_result = _ffi.cast('ASN1_STRING*', octet_result) - char_result = _lib.ASN1_STRING_data(string_result) - result_length = _lib.ASN1_STRING_length(string_result) - return _ffi.buffer(char_result, result_length)[:] - -X509ExtensionType = X509Extension - - -class X509Req(object): - def __init__(self): - req = _lib.X509_REQ_new() - self._req = _ffi.gc(req, _lib.X509_REQ_free) - - - def set_pubkey(self, pkey): - """ - Set the public key of the certificate request - - :param pkey: The public key to use - :return: None - """ - set_result = _lib.X509_REQ_set_pubkey(self._req, pkey._pkey) - if not set_result: - # TODO: This is untested. - _raise_current_error() - - - def get_pubkey(self): - """ - Get the public key from the certificate request - - :return: The public key - """ - pkey = PKey.__new__(PKey) - pkey._pkey = _lib.X509_REQ_get_pubkey(self._req) - if pkey._pkey == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) - pkey._only_public = True - return pkey - - - def set_version(self, version): - """ - Set the version subfield (RFC 2459, section 4.1.2.1) of the certificate - request. - - :param version: The version number - :return: None - """ - set_result = _lib.X509_REQ_set_version(self._req, version) - if not set_result: - _raise_current_error() - - - def get_version(self): - """ - Get the version subfield (RFC 2459, section 4.1.2.1) of the certificate - request. - - :return: an integer giving the value of the version subfield - """ - return _lib.X509_REQ_get_version(self._req) - - - def get_subject(self): - """ - Create an X509Name object for the subject of the certificate request - - :return: An X509Name object - """ - name = X509Name.__new__(X509Name) - name._name = _lib.X509_REQ_get_subject_name(self._req) - if name._name == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - # The name is owned by the X509Req structure. As long as the X509Name - # Python object is alive, keep the X509Req Python object alive. - name._owner = self - - return name - - - def add_extensions(self, extensions): - """ - Add extensions to the request. - - :param extensions: a sequence of X509Extension objects - :return: None - """ - stack = _lib.sk_X509_EXTENSION_new_null() - if stack == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - stack = _ffi.gc(stack, _lib.sk_X509_EXTENSION_free) - - for ext in extensions: - if not isinstance(ext, X509Extension): - raise ValueError("One of the elements is not an X509Extension") - - # TODO push can fail (here and elsewhere) - _lib.sk_X509_EXTENSION_push(stack, ext._extension) - - add_result = _lib.X509_REQ_add_extensions(self._req, stack) - if not add_result: - # TODO: This is untested. - _raise_current_error() - - - def sign(self, pkey, digest): - """ - Sign the certificate request using the supplied key and digest - - :param pkey: The key to sign with - :param digest: The message digest to use - :return: None - """ - if pkey._only_public: - raise ValueError("Key has only public part") - - if not pkey._initialized: - raise ValueError("Key is uninitialized") - - digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) - if digest_obj == _ffi.NULL: - raise ValueError("No such digest method") - - sign_result = _lib.X509_REQ_sign(self._req, pkey._pkey, digest_obj) - if not sign_result: - # TODO: This is untested. - _raise_current_error() - - - def verify(self, pkey): - """ - Verifies a certificate request using the supplied public key - - :param key: a public key - :return: True if the signature is correct. - - :raise OpenSSL.crypto.Error: If the signature is invalid or there is a - problem verifying the signature. - """ - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey instance") - - result = _lib.X509_REQ_verify(self._req, pkey._pkey) - if result <= 0: - _raise_current_error() - - return result - - -X509ReqType = X509Req - - - -class X509(object): - def __init__(self): - # TODO Allocation failure? And why not __new__ instead of __init__? - x509 = _lib.X509_new() - self._x509 = _ffi.gc(x509, _lib.X509_free) - - - def set_version(self, version): - """ - Set version number of the certificate - - :param version: The version number - :type version: :py:class:`int` - - :return: None - """ - if not isinstance(version, int): - raise TypeError("version must be an integer") - - _lib.X509_set_version(self._x509, version) - - - def get_version(self): - """ - Return version number of the certificate - - :return: Version number as a Python integer - """ - return _lib.X509_get_version(self._x509) - - - def get_pubkey(self): - """ - Get the public key of the certificate - - :return: The public key - """ - pkey = PKey.__new__(PKey) - pkey._pkey = _lib.X509_get_pubkey(self._x509) - if pkey._pkey == _ffi.NULL: - _raise_current_error() - pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) - pkey._only_public = True - return pkey - - - def set_pubkey(self, pkey): - """ - Set the public key of the certificate - - :param pkey: The public key - - :return: None - """ - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey instance") - - set_result = _lib.X509_set_pubkey(self._x509, pkey._pkey) - if not set_result: - _raise_current_error() - - - def sign(self, pkey, digest): - """ - Sign the certificate using the supplied key and digest - - :param pkey: The key to sign with - :param digest: The message digest to use - :return: None - """ - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey instance") - - if pkey._only_public: - raise ValueError("Key only has public part") - - if not pkey._initialized: - raise ValueError("Key is uninitialized") - - evp_md = _lib.EVP_get_digestbyname(_byte_string(digest)) - if evp_md == _ffi.NULL: - raise ValueError("No such digest method") - - sign_result = _lib.X509_sign(self._x509, pkey._pkey, evp_md) - if not sign_result: - _raise_current_error() - - - def get_signature_algorithm(self): - """ - Retrieve the signature algorithm used in the certificate - - :return: A byte string giving the name of the signature algorithm used in - the certificate. - :raise ValueError: If the signature algorithm is undefined. - """ - alg = self._x509.cert_info.signature.algorithm - nid = _lib.OBJ_obj2nid(alg) - if nid == _lib.NID_undef: - raise ValueError("Undefined signature algorithm") - return _ffi.string(_lib.OBJ_nid2ln(nid)) - - - def digest(self, digest_name): - """ - Return the digest of the X509 object. - - :param digest_name: The name of the digest algorithm to use. - :type digest_name: :py:class:`bytes` - - :return: The digest of the object - """ - digest = _lib.EVP_get_digestbyname(_byte_string(digest_name)) - if digest == _ffi.NULL: - raise ValueError("No such digest method") - - result_buffer = _ffi.new("char[]", _lib.EVP_MAX_MD_SIZE) - result_length = _ffi.new("unsigned int[]", 1) - result_length[0] = len(result_buffer) - - digest_result = _lib.X509_digest( - self._x509, digest, result_buffer, result_length) - - if not digest_result: - # TODO: This is untested. - _raise_current_error() - - return b":".join([ - b16encode(ch).upper() for ch - in _ffi.buffer(result_buffer, result_length[0])]) - - - def subject_name_hash(self): - """ - Return the hash of the X509 subject. - - :return: The hash of the subject. - """ - return _lib.X509_subject_name_hash(self._x509) - - - def set_serial_number(self, serial): - """ - Set serial number of the certificate - - :param serial: The serial number - :type serial: :py:class:`int` - - :return: None - """ - if not isinstance(serial, _integer_types): - raise TypeError("serial must be an integer") - - hex_serial = hex(serial)[2:] - if not isinstance(hex_serial, bytes): - hex_serial = hex_serial.encode('ascii') - - bignum_serial = _ffi.new("BIGNUM**") - - # BN_hex2bn stores the result in &bignum. Unless it doesn't feel like - # it. If bignum is still NULL after this call, then the return value is - # actually the result. I hope. -exarkun - small_serial = _lib.BN_hex2bn(bignum_serial, hex_serial) - - if bignum_serial[0] == _ffi.NULL: - set_result = _lib.ASN1_INTEGER_set( - _lib.X509_get_serialNumber(self._x509), small_serial) - if set_result: - # TODO Not tested - _raise_current_error() - else: - asn1_serial = _lib.BN_to_ASN1_INTEGER(bignum_serial[0], _ffi.NULL) - _lib.BN_free(bignum_serial[0]) - if asn1_serial == _ffi.NULL: - # TODO Not tested - _raise_current_error() - asn1_serial = _ffi.gc(asn1_serial, _lib.ASN1_INTEGER_free) - set_result = _lib.X509_set_serialNumber(self._x509, asn1_serial) - if not set_result: - # TODO Not tested - _raise_current_error() - - - def get_serial_number(self): - """ - Return serial number of the certificate - - :return: Serial number as a Python integer - """ - asn1_serial = _lib.X509_get_serialNumber(self._x509) - bignum_serial = _lib.ASN1_INTEGER_to_BN(asn1_serial, _ffi.NULL) - try: - hex_serial = _lib.BN_bn2hex(bignum_serial) - try: - hexstring_serial = _ffi.string(hex_serial) - serial = int(hexstring_serial, 16) - return serial - finally: - _lib.OPENSSL_free(hex_serial) - finally: - _lib.BN_free(bignum_serial) - - - def gmtime_adj_notAfter(self, amount): - """ - Adjust the time stamp for when the certificate stops being valid - - :param amount: The number of seconds by which to adjust the ending - validity time. - :type amount: :py:class:`int` - - :return: None - """ - if not isinstance(amount, int): - raise TypeError("amount must be an integer") - - notAfter = _lib.X509_get_notAfter(self._x509) - _lib.X509_gmtime_adj(notAfter, amount) - - - def gmtime_adj_notBefore(self, amount): - """ - Change the timestamp for when the certificate starts being valid to the current - time plus an offset. - - :param amount: The number of seconds by which to adjust the starting validity - time. - :return: None - """ - if not isinstance(amount, int): - raise TypeError("amount must be an integer") - - notBefore = _lib.X509_get_notBefore(self._x509) - _lib.X509_gmtime_adj(notBefore, amount) - - - def has_expired(self): - """ - Check whether the certificate has expired. - - :return: True if the certificate has expired, false otherwise - """ - now = int(time()) - notAfter = _lib.X509_get_notAfter(self._x509) - return _lib.ASN1_UTCTIME_cmp_time_t( - _ffi.cast('ASN1_UTCTIME*', notAfter), now) < 0 - - - def _get_boundary_time(self, which): - return _get_asn1_time(which(self._x509)) - - - def get_notBefore(self): - """ - Retrieve the time stamp for when the certificate starts being valid - - :return: A string giving the timestamp, in the format:: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - - or None if there is no value set. - """ - return self._get_boundary_time(_lib.X509_get_notBefore) - - - def _set_boundary_time(self, which, when): - return _set_asn1_time(which(self._x509), when) - - - def set_notBefore(self, when): - """ - Set the time stamp for when the certificate starts being valid - - :param when: A string giving the timestamp, in the format: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - :type when: :py:class:`bytes` - - :return: None - """ - return self._set_boundary_time(_lib.X509_get_notBefore, when) - - - def get_notAfter(self): - """ - Retrieve the time stamp for when the certificate stops being valid - - :return: A string giving the timestamp, in the format:: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - - or None if there is no value set. - """ - return self._get_boundary_time(_lib.X509_get_notAfter) - - - def set_notAfter(self, when): - """ - Set the time stamp for when the certificate stops being valid - - :param when: A string giving the timestamp, in the format: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - :type when: :py:class:`bytes` - - :return: None - """ - return self._set_boundary_time(_lib.X509_get_notAfter, when) - - - def _get_name(self, which): - name = X509Name.__new__(X509Name) - name._name = which(self._x509) - if name._name == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - # The name is owned by the X509 structure. As long as the X509Name - # Python object is alive, keep the X509 Python object alive. - name._owner = self - - return name - - - def _set_name(self, which, name): - if not isinstance(name, X509Name): - raise TypeError("name must be an X509Name") - set_result = which(self._x509, name._name) - if not set_result: - # TODO: This is untested. - _raise_current_error() - - - def get_issuer(self): - """ - Create an X509Name object for the issuer of the certificate - - :return: An X509Name object - """ - return self._get_name(_lib.X509_get_issuer_name) - - - def set_issuer(self, issuer): - """ - Set the issuer of the certificate - - :param issuer: The issuer name - :type issuer: :py:class:`X509Name` - - :return: None - """ - return self._set_name(_lib.X509_set_issuer_name, issuer) - - - def get_subject(self): - """ - Create an X509Name object for the subject of the certificate - - :return: An X509Name object - """ - return self._get_name(_lib.X509_get_subject_name) - - - def set_subject(self, subject): - """ - Set the subject of the certificate - - :param subject: The subject name - :type subject: :py:class:`X509Name` - :return: None - """ - return self._set_name(_lib.X509_set_subject_name, subject) - - - def get_extension_count(self): - """ - Get the number of extensions on the certificate. - - :return: The number of extensions as an integer. - """ - return _lib.X509_get_ext_count(self._x509) - - - def add_extensions(self, extensions): - """ - Add extensions to the certificate. - - :param extensions: a sequence of X509Extension objects - :return: None - """ - for ext in extensions: - if not isinstance(ext, X509Extension): - raise ValueError("One of the elements is not an X509Extension") - - add_result = _lib.X509_add_ext(self._x509, ext._extension, -1) - if not add_result: - _raise_current_error() - - - def get_extension(self, index): - """ - Get a specific extension of the certificate by index. - - :param index: The index of the extension to retrieve. - :return: The X509Extension object at the specified index. - """ - ext = X509Extension.__new__(X509Extension) - ext._extension = _lib.X509_get_ext(self._x509, index) - if ext._extension == _ffi.NULL: - raise IndexError("extension index out of bounds") - - extension = _lib.X509_EXTENSION_dup(ext._extension) - ext._extension = _ffi.gc(extension, _lib.X509_EXTENSION_free) - return ext - -X509Type = X509 - - - -class X509Store(object): - def __init__(self): - store = _lib.X509_STORE_new() - self._store = _ffi.gc(store, _lib.X509_STORE_free) - - - def add_cert(self, cert): - if not isinstance(cert, X509): - raise TypeError() - - result = _lib.X509_STORE_add_cert(self._store, cert._x509) - if not result: - _raise_current_error() - - -X509StoreType = X509Store - - - -def load_certificate(type, buffer): - """ - Load a certificate from a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - - :param buffer: The buffer the certificate is stored in - :type buffer: :py:class:`bytes` - - :return: The X509 object - """ - if isinstance(buffer, _text_type): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - if type == FILETYPE_PEM: - x509 = _lib.PEM_read_bio_X509(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - elif type == FILETYPE_ASN1: - x509 = _lib.d2i_X509_bio(bio, _ffi.NULL); - else: - raise ValueError( - "type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if x509 == _ffi.NULL: - _raise_current_error() - - cert = X509.__new__(X509) - cert._x509 = _ffi.gc(x509, _lib.X509_free) - return cert - - -def dump_certificate(type, cert): - """ - Dump a certificate to a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1, or - FILETYPE_TEXT) - :param cert: The certificate to dump - :return: The buffer with the dumped certificate in - """ - bio = _new_mem_buf() - - if type == FILETYPE_PEM: - result_code = _lib.PEM_write_bio_X509(bio, cert._x509) - elif type == FILETYPE_ASN1: - result_code = _lib.i2d_X509_bio(bio, cert._x509) - elif type == FILETYPE_TEXT: - result_code = _lib.X509_print_ex(bio, cert._x509, 0, 0) - else: - raise ValueError( - "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " - "FILETYPE_TEXT") - - return _bio_to_string(bio) - - - -def dump_privatekey(type, pkey, cipher=None, passphrase=None): - """ - Dump a private key to a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1, or - FILETYPE_TEXT) - :param pkey: The PKey to dump - :param cipher: (optional) if encrypted PEM format, the cipher to - use - :param passphrase: (optional) if encrypted PEM format, this can be either - the passphrase to use, or a callback for providing the - passphrase. - :return: The buffer with the dumped key in - :rtype: :py:data:`str` - """ - bio = _new_mem_buf() - - if cipher is not None: - if passphrase is None: - raise TypeError( - "if a value is given for cipher " - "one must also be given for passphrase") - cipher_obj = _lib.EVP_get_cipherbyname(_byte_string(cipher)) - if cipher_obj == _ffi.NULL: - raise ValueError("Invalid cipher name") - else: - cipher_obj = _ffi.NULL - - helper = _PassphraseHelper(type, passphrase) - if type == FILETYPE_PEM: - result_code = _lib.PEM_write_bio_PrivateKey( - bio, pkey._pkey, cipher_obj, _ffi.NULL, 0, - helper.callback, helper.callback_args) - helper.raise_if_problem() - elif type == FILETYPE_ASN1: - result_code = _lib.i2d_PrivateKey_bio(bio, pkey._pkey) - elif type == FILETYPE_TEXT: - rsa = _lib.EVP_PKEY_get1_RSA(pkey._pkey) - result_code = _lib.RSA_print(bio, rsa, 0) - # TODO RSA_free(rsa)? - else: - raise ValueError( - "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or " - "FILETYPE_TEXT") - - if result_code == 0: - _raise_current_error() - - return _bio_to_string(bio) - - - -def _X509_REVOKED_dup(original): - copy = _lib.X509_REVOKED_new() - if copy == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - if original.serialNumber != _ffi.NULL: - copy.serialNumber = _lib.ASN1_INTEGER_dup(original.serialNumber) - - if original.revocationDate != _ffi.NULL: - copy.revocationDate = _lib.M_ASN1_TIME_dup(original.revocationDate) - - if original.extensions != _ffi.NULL: - extension_stack = _lib.sk_X509_EXTENSION_new_null() - for i in range(_lib.sk_X509_EXTENSION_num(original.extensions)): - original_ext = _lib.sk_X509_EXTENSION_value(original.extensions, i) - copy_ext = _lib.X509_EXTENSION_dup(original_ext) - _lib.sk_X509_EXTENSION_push(extension_stack, copy_ext) - copy.extensions = extension_stack - - copy.sequence = original.sequence - return copy - - - -class Revoked(object): - # http://www.openssl.org/docs/apps/x509v3_config.html#CRL_distribution_points_ - # which differs from crl_reasons of crypto/x509v3/v3_enum.c that matches - # OCSP_crl_reason_str. We use the latter, just like the command line - # program. - _crl_reasons = [ - b"unspecified", - b"keyCompromise", - b"CACompromise", - b"affiliationChanged", - b"superseded", - b"cessationOfOperation", - b"certificateHold", - # b"removeFromCRL", - ] - - def __init__(self): - revoked = _lib.X509_REVOKED_new() - self._revoked = _ffi.gc(revoked, _lib.X509_REVOKED_free) - - - def set_serial(self, hex_str): - """ - Set the serial number of a revoked Revoked structure - - :param hex_str: The new serial number. - :type hex_str: :py:data:`str` - :return: None - """ - bignum_serial = _ffi.gc(_lib.BN_new(), _lib.BN_free) - bignum_ptr = _ffi.new("BIGNUM**") - bignum_ptr[0] = bignum_serial - bn_result = _lib.BN_hex2bn(bignum_ptr, hex_str) - if not bn_result: - raise ValueError("bad hex string") - - asn1_serial = _ffi.gc( - _lib.BN_to_ASN1_INTEGER(bignum_serial, _ffi.NULL), - _lib.ASN1_INTEGER_free) - _lib.X509_REVOKED_set_serialNumber(self._revoked, asn1_serial) - - - def get_serial(self): - """ - Return the serial number of a Revoked structure - - :return: The serial number as a string - """ - bio = _new_mem_buf() - - result = _lib.i2a_ASN1_INTEGER(bio, self._revoked.serialNumber) - if result < 0: - # TODO: This is untested. - _raise_current_error() - - return _bio_to_string(bio) - - - def _delete_reason(self): - stack = self._revoked.extensions - for i in range(_lib.sk_X509_EXTENSION_num(stack)): - ext = _lib.sk_X509_EXTENSION_value(stack, i) - if _lib.OBJ_obj2nid(ext.object) == _lib.NID_crl_reason: - _lib.X509_EXTENSION_free(ext) - _lib.sk_X509_EXTENSION_delete(stack, i) - break - - - def set_reason(self, reason): - """ - Set the reason of a Revoked object. - - If :py:data:`reason` is :py:data:`None`, delete the reason instead. - - :param reason: The reason string. - :type reason: :py:class:`str` or :py:class:`NoneType` - :return: None - """ - if reason is None: - self._delete_reason() - elif not isinstance(reason, bytes): - raise TypeError("reason must be None or a byte string") - else: - reason = reason.lower().replace(b' ', b'') - reason_code = [r.lower() for r in self._crl_reasons].index(reason) - - new_reason_ext = _lib.ASN1_ENUMERATED_new() - if new_reason_ext == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - new_reason_ext = _ffi.gc(new_reason_ext, _lib.ASN1_ENUMERATED_free) - - set_result = _lib.ASN1_ENUMERATED_set(new_reason_ext, reason_code) - if set_result == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - self._delete_reason() - add_result = _lib.X509_REVOKED_add1_ext_i2d( - self._revoked, _lib.NID_crl_reason, new_reason_ext, 0, 0) - - if not add_result: - # TODO: This is untested. - _raise_current_error() - - - def get_reason(self): - """ - Return the reason of a Revoked object. - - :return: The reason as a string - """ - extensions = self._revoked.extensions - for i in range(_lib.sk_X509_EXTENSION_num(extensions)): - ext = _lib.sk_X509_EXTENSION_value(extensions, i) - if _lib.OBJ_obj2nid(ext.object) == _lib.NID_crl_reason: - bio = _new_mem_buf() - - print_result = _lib.X509V3_EXT_print(bio, ext, 0, 0) - if not print_result: - print_result = _lib.M_ASN1_OCTET_STRING_print(bio, ext.value) - if print_result == 0: - # TODO: This is untested. - _raise_current_error() - - return _bio_to_string(bio) - - - def all_reasons(self): - """ - Return a list of all the supported reason strings. - - :return: A list of reason strings. - """ - return self._crl_reasons[:] - - - def set_rev_date(self, when): - """ - Set the revocation timestamp - - :param when: A string giving the timestamp, in the format: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - - :return: None - """ - return _set_asn1_time(self._revoked.revocationDate, when) - - - def get_rev_date(self): - """ - Retrieve the revocation date - - :return: A string giving the timestamp, in the format: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - """ - return _get_asn1_time(self._revoked.revocationDate) - - - -class CRL(object): - def __init__(self): - """ - Create a new empty CRL object. - """ - crl = _lib.X509_CRL_new() - self._crl = _ffi.gc(crl, _lib.X509_CRL_free) - - - def get_revoked(self): - """ - Return revoked portion of the CRL structure (by value not reference). - - :return: A tuple of Revoked objects. - """ - results = [] - revoked_stack = self._crl.crl.revoked - for i in range(_lib.sk_X509_REVOKED_num(revoked_stack)): - revoked = _lib.sk_X509_REVOKED_value(revoked_stack, i) - revoked_copy = _X509_REVOKED_dup(revoked) - pyrev = Revoked.__new__(Revoked) - pyrev._revoked = _ffi.gc(revoked_copy, _lib.X509_REVOKED_free) - results.append(pyrev) - if results: - return tuple(results) - - - def add_revoked(self, revoked): - """ - Add a revoked (by value not reference) to the CRL structure - - :param revoked: The new revoked. - :type revoked: :class:`X509` - - :return: None - """ - copy = _X509_REVOKED_dup(revoked._revoked) - if copy == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - add_result = _lib.X509_CRL_add0_revoked(self._crl, copy) - if add_result == 0: - # TODO: This is untested. - _raise_current_error() - - - def export(self, cert, key, type=FILETYPE_PEM, days=100): - """ - export a CRL as a string - - :param cert: Used to sign CRL. - :type cert: :class:`X509` - - :param key: Used to sign CRL. - :type key: :class:`PKey` - - :param type: The export format, either :py:data:`FILETYPE_PEM`, :py:data:`FILETYPE_ASN1`, or :py:data:`FILETYPE_TEXT`. - - :param days: The number of days until the next update of this CRL. - :type days: :py:data:`int` - - :return: :py:data:`str` - """ - if not isinstance(cert, X509): - raise TypeError("cert must be an X509 instance") - if not isinstance(key, PKey): - raise TypeError("key must be a PKey instance") - if not isinstance(type, int): - raise TypeError("type must be an integer") - - bio = _lib.BIO_new(_lib.BIO_s_mem()) - if bio == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - # A scratch time object to give different values to different CRL fields - sometime = _lib.ASN1_TIME_new() - if sometime == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - _lib.X509_gmtime_adj(sometime, 0) - _lib.X509_CRL_set_lastUpdate(self._crl, sometime) - - _lib.X509_gmtime_adj(sometime, days * 24 * 60 * 60) - _lib.X509_CRL_set_nextUpdate(self._crl, sometime) - - _lib.X509_CRL_set_issuer_name(self._crl, _lib.X509_get_subject_name(cert._x509)) - - sign_result = _lib.X509_CRL_sign(self._crl, key._pkey, _lib.EVP_md5()) - if not sign_result: - _raise_current_error() - - if type == FILETYPE_PEM: - ret = _lib.PEM_write_bio_X509_CRL(bio, self._crl) - elif type == FILETYPE_ASN1: - ret = _lib.i2d_X509_CRL_bio(bio, self._crl) - elif type == FILETYPE_TEXT: - ret = _lib.X509_CRL_print(bio, self._crl) - else: - raise ValueError( - "type argument must be FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT") - - if not ret: - # TODO: This is untested. - _raise_current_error() - - return _bio_to_string(bio) -CRLType = CRL - - - -class PKCS7(object): - def type_is_signed(self): - """ - Check if this NID_pkcs7_signed object - - :return: True if the PKCS7 is of type signed - """ - if _lib.PKCS7_type_is_signed(self._pkcs7): - return True - return False - - - def type_is_enveloped(self): - """ - Check if this NID_pkcs7_enveloped object - - :returns: True if the PKCS7 is of type enveloped - """ - if _lib.PKCS7_type_is_enveloped(self._pkcs7): - return True - return False - - - def type_is_signedAndEnveloped(self): - """ - Check if this NID_pkcs7_signedAndEnveloped object - - :returns: True if the PKCS7 is of type signedAndEnveloped - """ - if _lib.PKCS7_type_is_signedAndEnveloped(self._pkcs7): - return True - return False - - - def type_is_data(self): - """ - Check if this NID_pkcs7_data object - - :return: True if the PKCS7 is of type data - """ - if _lib.PKCS7_type_is_data(self._pkcs7): - return True - return False - - - def get_type_name(self): - """ - Returns the type name of the PKCS7 structure - - :return: A string with the typename - """ - nid = _lib.OBJ_obj2nid(self._pkcs7.type) - string_type = _lib.OBJ_nid2sn(nid) - return _ffi.string(string_type) - -PKCS7Type = PKCS7 - - - -class PKCS12(object): - def __init__(self): - self._pkey = None - self._cert = None - self._cacerts = None - self._friendlyname = None - - - def get_certificate(self): - """ - Return certificate portion of the PKCS12 structure - - :return: X509 object containing the certificate - """ - return self._cert - - - def set_certificate(self, cert): - """ - Replace the certificate portion of the PKCS12 structure - - :param cert: The new certificate. - :type cert: :py:class:`X509` or :py:data:`None` - :return: None - """ - if not isinstance(cert, X509): - raise TypeError("cert must be an X509 instance") - self._cert = cert - - - def get_privatekey(self): - """ - Return private key portion of the PKCS12 structure - - :returns: PKey object containing the private key - """ - return self._pkey - - - def set_privatekey(self, pkey): - """ - Replace or set the certificate portion of the PKCS12 structure - - :param pkey: The new private key. - :type pkey: :py:class:`PKey` - :return: None - """ - if not isinstance(pkey, PKey): - raise TypeError("pkey must be a PKey instance") - self._pkey = pkey - - - def get_ca_certificates(self): - """ - Return CA certificates within of the PKCS12 object - - :return: A newly created tuple containing the CA certificates in the chain, - if any are present, or None if no CA certificates are present. - """ - if self._cacerts is not None: - return tuple(self._cacerts) - - - def set_ca_certificates(self, cacerts): - """ - Replace or set the CA certificates withing the PKCS12 object. - - :param cacerts: The new CA certificates. - :type cacerts: :py:data:`None` or an iterable of :py:class:`X509` - :return: None - """ - if cacerts is None: - self._cacerts = None - else: - cacerts = list(cacerts) - for cert in cacerts: - if not isinstance(cert, X509): - raise TypeError("iterable must only contain X509 instances") - self._cacerts = cacerts - - - def set_friendlyname(self, name): - """ - Replace or set the certificate portion of the PKCS12 structure - - :param name: The new friendly name. - :type name: :py:class:`bytes` - :return: None - """ - if name is None: - self._friendlyname = None - elif not isinstance(name, bytes): - raise TypeError("name must be a byte string or None (not %r)" % (name,)) - self._friendlyname = name - - - def get_friendlyname(self): - """ - Return friendly name portion of the PKCS12 structure - - :returns: String containing the friendlyname - """ - return self._friendlyname - - - def export(self, passphrase=None, iter=2048, maciter=1): - """ - Dump a PKCS12 object as a string. See also "man PKCS12_create". - - :param passphrase: used to encrypt the PKCS12 - :type passphrase: :py:data:`bytes` - - :param iter: How many times to repeat the encryption - :type iter: :py:data:`int` - - :param maciter: How many times to repeat the MAC - :type maciter: :py:data:`int` - - :return: The string containing the PKCS12 - """ - if self._cacerts is None: - cacerts = _ffi.NULL - else: - cacerts = _lib.sk_X509_new_null() - cacerts = _ffi.gc(cacerts, _lib.sk_X509_free) - for cert in self._cacerts: - _lib.sk_X509_push(cacerts, cert._x509) - - if passphrase is None: - passphrase = _ffi.NULL - - friendlyname = self._friendlyname - if friendlyname is None: - friendlyname = _ffi.NULL - - if self._pkey is None: - pkey = _ffi.NULL - else: - pkey = self._pkey._pkey - - if self._cert is None: - cert = _ffi.NULL - else: - cert = self._cert._x509 - - pkcs12 = _lib.PKCS12_create( - passphrase, friendlyname, pkey, cert, cacerts, - _lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC, - _lib.NID_pbe_WithSHA1And3_Key_TripleDES_CBC, - iter, maciter, 0) - if pkcs12 == _ffi.NULL: - _raise_current_error() - pkcs12 = _ffi.gc(pkcs12, _lib.PKCS12_free) - - bio = _new_mem_buf() - _lib.i2d_PKCS12_bio(bio, pkcs12) - return _bio_to_string(bio) - -PKCS12Type = PKCS12 - - - -class NetscapeSPKI(object): - def __init__(self): - spki = _lib.NETSCAPE_SPKI_new() - self._spki = _ffi.gc(spki, _lib.NETSCAPE_SPKI_free) - - - def sign(self, pkey, digest): - """ - Sign the certificate request using the supplied key and digest - - :param pkey: The key to sign with - :param digest: The message digest to use - :return: None - """ - if pkey._only_public: - raise ValueError("Key has only public part") - - if not pkey._initialized: - raise ValueError("Key is uninitialized") - - digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) - if digest_obj == _ffi.NULL: - raise ValueError("No such digest method") - - sign_result = _lib.NETSCAPE_SPKI_sign(self._spki, pkey._pkey, digest_obj) - if not sign_result: - # TODO: This is untested. - _raise_current_error() - - - def verify(self, key): - """ - Verifies a certificate request using the supplied public key - - :param key: a public key - :return: True if the signature is correct. - :raise OpenSSL.crypto.Error: If the signature is invalid or there is a - problem verifying the signature. - """ - answer = _lib.NETSCAPE_SPKI_verify(self._spki, key._pkey) - if answer <= 0: - _raise_current_error() - return True - - - def b64_encode(self): - """ - Generate a base64 encoded string from an SPKI - - :return: The base64 encoded string - """ - encoded = _lib.NETSCAPE_SPKI_b64_encode(self._spki) - result = _ffi.string(encoded) - _lib.CRYPTO_free(encoded) - return result - - - def get_pubkey(self): - """ - Get the public key of the certificate - - :return: The public key - """ - pkey = PKey.__new__(PKey) - pkey._pkey = _lib.NETSCAPE_SPKI_get_pubkey(self._spki) - if pkey._pkey == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - pkey._pkey = _ffi.gc(pkey._pkey, _lib.EVP_PKEY_free) - pkey._only_public = True - return pkey - - - def set_pubkey(self, pkey): - """ - Set the public key of the certificate - - :param pkey: The public key - :return: None - """ - set_result = _lib.NETSCAPE_SPKI_set_pubkey(self._spki, pkey._pkey) - if not set_result: - # TODO: This is untested. - _raise_current_error() -NetscapeSPKIType = NetscapeSPKI - - -class _PassphraseHelper(object): - def __init__(self, type, passphrase, more_args=False, truncate=False): - if type != FILETYPE_PEM and passphrase is not None: - raise ValueError("only FILETYPE_PEM key format supports encryption") - self._passphrase = passphrase - self._more_args = more_args - self._truncate = truncate - self._problems = [] - - - @property - def callback(self): - if self._passphrase is None: - return _ffi.NULL - elif isinstance(self._passphrase, bytes): - return _ffi.NULL - elif callable(self._passphrase): - return _ffi.callback("pem_password_cb", self._read_passphrase) - else: - raise TypeError("Last argument must be string or callable") - - - @property - def callback_args(self): - if self._passphrase is None: - return _ffi.NULL - elif isinstance(self._passphrase, bytes): - return self._passphrase - elif callable(self._passphrase): - return _ffi.NULL - else: - raise TypeError("Last argument must be string or callable") - - - def raise_if_problem(self, exceptionType=Error): - try: - _exception_from_error_queue(exceptionType) - except exceptionType as e: - from_queue = e - if self._problems: - raise self._problems[0] - return from_queue - - - def _read_passphrase(self, buf, size, rwflag, userdata): - try: - if self._more_args: - result = self._passphrase(size, rwflag, userdata) - else: - result = self._passphrase(rwflag) - if not isinstance(result, bytes): - raise ValueError("String expected") - if len(result) > size: - if self._truncate: - result = result[:size] - else: - raise ValueError("passphrase returned by callback is too long") - for i in range(len(result)): - buf[i] = result[i:i + 1] - return len(result) - except Exception as e: - self._problems.append(e) - return 0 - - - -def load_privatekey(type, buffer, passphrase=None): - """ - Load a private key from a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - :param buffer: The buffer the key is stored in - :param passphrase: (optional) if encrypted PEM format, this can be - either the passphrase to use, or a callback for - providing the passphrase. - - :return: The PKey object - """ - if isinstance(buffer, _text_type): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - helper = _PassphraseHelper(type, passphrase) - if type == FILETYPE_PEM: - evp_pkey = _lib.PEM_read_bio_PrivateKey( - bio, _ffi.NULL, helper.callback, helper.callback_args) - helper.raise_if_problem() - elif type == FILETYPE_ASN1: - evp_pkey = _lib.d2i_PrivateKey_bio(bio, _ffi.NULL) - else: - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if evp_pkey == _ffi.NULL: - _raise_current_error() - - pkey = PKey.__new__(PKey) - pkey._pkey = _ffi.gc(evp_pkey, _lib.EVP_PKEY_free) - return pkey - - - -def dump_certificate_request(type, req): - """ - Dump a certificate request to a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - :param req: The certificate request to dump - :return: The buffer with the dumped certificate request in - """ - bio = _new_mem_buf() - - if type == FILETYPE_PEM: - result_code = _lib.PEM_write_bio_X509_REQ(bio, req._req) - elif type == FILETYPE_ASN1: - result_code = _lib.i2d_X509_REQ_bio(bio, req._req) - elif type == FILETYPE_TEXT: - result_code = _lib.X509_REQ_print_ex(bio, req._req, 0, 0) - else: - raise ValueError("type argument must be FILETYPE_PEM, FILETYPE_ASN1, or FILETYPE_TEXT") - - if result_code == 0: - # TODO: This is untested. - _raise_current_error() - - return _bio_to_string(bio) - - - -def load_certificate_request(type, buffer): - """ - Load a certificate request from a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - :param buffer: The buffer the certificate request is stored in - :return: The X509Req object - """ - if isinstance(buffer, _text_type): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - if type == FILETYPE_PEM: - req = _lib.PEM_read_bio_X509_REQ(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - elif type == FILETYPE_ASN1: - req = _lib.d2i_X509_REQ_bio(bio, _ffi.NULL) - else: - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if req == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - - x509req = X509Req.__new__(X509Req) - x509req._req = _ffi.gc(req, _lib.X509_REQ_free) - return x509req - - - -def sign(pkey, data, digest): - """ - Sign data with a digest - - :param pkey: Pkey to sign with - :param data: data to be signed - :param digest: message digest to use - :return: signature - """ - digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) - if digest_obj == _ffi.NULL: - raise ValueError("No such digest method") - - md_ctx = _ffi.new("EVP_MD_CTX*") - md_ctx = _ffi.gc(md_ctx, _lib.EVP_MD_CTX_cleanup) - - _lib.EVP_SignInit(md_ctx, digest_obj) - _lib.EVP_SignUpdate(md_ctx, data, len(data)) - - signature_buffer = _ffi.new("unsigned char[]", 512) - signature_length = _ffi.new("unsigned int*") - signature_length[0] = len(signature_buffer) - final_result = _lib.EVP_SignFinal( - md_ctx, signature_buffer, signature_length, pkey._pkey) - - if final_result != 1: - # TODO: This is untested. - _raise_current_error() - - return _ffi.buffer(signature_buffer, signature_length[0])[:] - - - -def verify(cert, signature, data, digest): - """ - Verify a signature - - :param cert: signing certificate (X509 object) - :param signature: signature returned by sign function - :param data: data to be verified - :param digest: message digest to use - :return: None if the signature is correct, raise exception otherwise - """ - digest_obj = _lib.EVP_get_digestbyname(_byte_string(digest)) - if digest_obj == _ffi.NULL: - raise ValueError("No such digest method") - - pkey = _lib.X509_get_pubkey(cert._x509) - if pkey == _ffi.NULL: - # TODO: This is untested. - _raise_current_error() - pkey = _ffi.gc(pkey, _lib.EVP_PKEY_free) - - md_ctx = _ffi.new("EVP_MD_CTX*") - md_ctx = _ffi.gc(md_ctx, _lib.EVP_MD_CTX_cleanup) - - _lib.EVP_VerifyInit(md_ctx, digest_obj) - _lib.EVP_VerifyUpdate(md_ctx, data, len(data)) - verify_result = _lib.EVP_VerifyFinal(md_ctx, signature, len(signature), pkey) - - if verify_result != 1: - _raise_current_error() - - - -def load_crl(type, buffer): - """ - Load a certificate revocation list from a buffer - - :param type: The file type (one of FILETYPE_PEM, FILETYPE_ASN1) - :param buffer: The buffer the CRL is stored in - - :return: The PKey object - """ - if isinstance(buffer, _text_type): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - if type == FILETYPE_PEM: - crl = _lib.PEM_read_bio_X509_CRL(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - elif type == FILETYPE_ASN1: - crl = _lib.d2i_X509_CRL_bio(bio, _ffi.NULL) - else: - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if crl == _ffi.NULL: - _raise_current_error() - - result = CRL.__new__(CRL) - result._crl = crl - return result - - - -def load_pkcs7_data(type, buffer): - """ - Load pkcs7 data from a buffer - - :param type: The file type (one of FILETYPE_PEM or FILETYPE_ASN1) - :param buffer: The buffer with the pkcs7 data. - :return: The PKCS7 object - """ - if isinstance(buffer, _text_type): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - if type == FILETYPE_PEM: - pkcs7 = _lib.PEM_read_bio_PKCS7(bio, _ffi.NULL, _ffi.NULL, _ffi.NULL) - elif type == FILETYPE_ASN1: - pass - else: - # TODO: This is untested. - _raise_current_error() - raise ValueError("type argument must be FILETYPE_PEM or FILETYPE_ASN1") - - if pkcs7 == _ffi.NULL: - _raise_current_error() - - pypkcs7 = PKCS7.__new__(PKCS7) - pypkcs7._pkcs7 = _ffi.gc(pkcs7, _lib.PKCS7_free) - return pypkcs7 - - - -def load_pkcs12(buffer, passphrase): - """ - Load a PKCS12 object from a buffer - - :param buffer: The buffer the certificate is stored in - :param passphrase: (Optional) The password to decrypt the PKCS12 lump - :returns: The PKCS12 object - """ - if isinstance(buffer, _text_type): - buffer = buffer.encode("ascii") - - bio = _new_mem_buf(buffer) - - p12 = _lib.d2i_PKCS12_bio(bio, _ffi.NULL) - if p12 == _ffi.NULL: - _raise_current_error() - p12 = _ffi.gc(p12, _lib.PKCS12_free) - - pkey = _ffi.new("EVP_PKEY**") - cert = _ffi.new("X509**") - cacerts = _ffi.new("Cryptography_STACK_OF_X509**") - - parse_result = _lib.PKCS12_parse(p12, passphrase, pkey, cert, cacerts) - if not parse_result: - _raise_current_error() - - cacerts = _ffi.gc(cacerts[0], _lib.sk_X509_free) - - # openssl 1.0.0 sometimes leaves an X509_check_private_key error in the - # queue for no particular reason. This error isn't interesting to anyone - # outside this function. It's not even interesting to us. Get rid of it. - try: - _raise_current_error() - except Error: - pass - - if pkey[0] == _ffi.NULL: - pykey = None - else: - pykey = PKey.__new__(PKey) - pykey._pkey = _ffi.gc(pkey[0], _lib.EVP_PKEY_free) - - if cert[0] == _ffi.NULL: - pycert = None - friendlyname = None - else: - pycert = X509.__new__(X509) - pycert._x509 = _ffi.gc(cert[0], _lib.X509_free) - - friendlyname_length = _ffi.new("int*") - friendlyname_buffer = _lib.X509_alias_get0(cert[0], friendlyname_length) - friendlyname = _ffi.buffer(friendlyname_buffer, friendlyname_length[0])[:] - if friendlyname_buffer == _ffi.NULL: - friendlyname = None - - pycacerts = [] - for i in range(_lib.sk_X509_num(cacerts)): - pycacert = X509.__new__(X509) - pycacert._x509 = _lib.sk_X509_value(cacerts, i) - pycacerts.append(pycacert) - if not pycacerts: - pycacerts = None - - pkcs12 = PKCS12.__new__(PKCS12) - pkcs12._pkey = pykey - pkcs12._cert = pycert - pkcs12._cacerts = pycacerts - pkcs12._friendlyname = friendlyname - return pkcs12 - - -def _initialize_openssl_threads(get_ident, Lock): - import _ssl - return - - locks = list(Lock() for n in range(_lib.CRYPTO_num_locks())) - - def locking_function(mode, index, filename, line): - if mode & _lib.CRYPTO_LOCK: - locks[index].acquire() - else: - locks[index].release() - - _lib.CRYPTO_set_id_callback( - _ffi.callback("unsigned long (*)(void)", get_ident)) - - _lib.CRYPTO_set_locking_callback( - _ffi.callback( - "void (*)(int, int, const char*, int)", locking_function)) - - -try: - from thread import get_ident - from threading import Lock -except ImportError: - pass -else: - _initialize_openssl_threads(get_ident, Lock) - del get_ident, Lock - -# There are no direct unit tests for this initialization. It is tested -# indirectly since it is necessary for functions like dump_privatekey when -# using encryption. -# -# Thus OpenSSL.test.test_crypto.FunctionTests.test_dump_privatekey_passphrase -# and some other similar tests may fail without this (though they may not if -# the Python runtime has already done some initialization of the underlying -# OpenSSL library (and is linked against the same one that cryptography is -# using)). -_lib.OpenSSL_add_all_algorithms() - -# This is similar but exercised mainly by exception_from_error_queue. It calls -# both ERR_load_crypto_strings() and ERR_load_SSL_strings(). -_lib.SSL_load_error_strings() diff --git a/OpenSSL/crypto/crl.c b/OpenSSL/crypto/crl.c new file mode 100644 index 0000000..eec5bcb --- /dev/null +++ b/OpenSSL/crypto/crl.c @@ -0,0 +1,290 @@ +#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; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_CRL_Type); + 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..3573a12 --- /dev/null +++ b/OpenSSL/crypto/crypto.c @@ -0,0 +1,898 @@ +/* + * 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; + int data_len; + 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, &data_len, &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, data_len); + 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 data_len; + 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, &data_len, &digest_name)) { +#else + if (!PyArg_ParseTuple(args, "O!t#s#s:verify", &crypto_X509_Type, &cert, &signature, &sig_len, &data, &data_len, &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, data_len); + 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 steals a reference. + */ + Py_INCREF(c_api_object); + PyModule_AddObject(module, "_C_API", c_api_object); + } +#endif + + crypto_Error = PyErr_NewException("OpenSSL.crypto.Error", NULL, NULL); + if (crypto_Error == NULL) + goto error; + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF(crypto_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..4006e71 --- /dev/null +++ b/OpenSSL/crypto/crypto.h @@ -0,0 +1,142 @@ +/* + * 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 +/* Work around a bug in OpenSSL 1.0.0 which is caused by winsock.h being + included (from dtls1.h) too late by the OpenSSL header files, overriding + the fixes (in ossl_typ.h) for symbol clashes caused by this OS header + file. + + In order to have those fixes still take effect, we include winsock.h + here, prior to including any OpenSSL header files. + + */ +#ifdef _WIN32 +# include "winsock.h" +#endif + +#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 (crypto_api_object && 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..9369d50 --- /dev/null +++ b/OpenSSL/crypto/netscape_spki.c @@ -0,0 +1,316 @@ +/* + * 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; + } + + /* PyModule_AddObject steals a reference + */ + Py_INCREF((PyObject *)&crypto_NetscapeSPKI_Type); + if (PyModule_AddObject(module, "NetscapeSPKI", (PyObject *)&crypto_NetscapeSPKI_Type) != 0) { + return 0; + } + + /* PyModule_AddObject steals a reference + */ + Py_INCREF((PyObject *)&crypto_NetscapeSPKI_Type); + 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..a1a5a79 --- /dev/null +++ b/OpenSSL/crypto/pkcs12.c @@ -0,0 +1,580 @@ +/* + * 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) { + if (!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; + } else { + /* + * OpenSSL 1.0.0 sometimes leaves an X509_check_private_key error in + * the queue for no particular reason. This error isn't interesting + * to anyone outside this function. It's not even interesting to + * us. Get rid of it. + */ + flush_error_queue(); + } + } + + 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; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_PKCS12_Type); + if (PyModule_AddObject(module, "PKCS12", (PyObject *)&crypto_PKCS12_Type) != 0) { + return 0; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_PKCS12_Type); + 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..1770f7f --- /dev/null +++ b/OpenSSL/crypto/pkcs7.c @@ -0,0 +1,216 @@ +/* + * 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; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_PKCS7_Type); + 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..1f78682 --- /dev/null +++ b/OpenSSL/crypto/pkey.c @@ -0,0 +1,303 @@ +/* + * 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); +} + +static char crypto_PKey_check_doc[] = "\n\ +Check the consistency of an RSA private key.\n\ +\n\ +@return: True if key is consistent.\n\ +@raise Error: if the key is inconsistent.\n\ +@raise TypeError: if the key is of a type which cannot be checked.\n\ + Only RSA keys can currently be checked.\n\ +"; + +static PyObject * +crypto_PKey_check(crypto_PKeyObj *self, PyObject *args) { + int r; + + if (!PyArg_ParseTuple(args, ":check")) { + return NULL; + } + + if (self->pkey->type == EVP_PKEY_RSA) { + RSA *rsa; + rsa = EVP_PKEY_get1_RSA(self->pkey); + r = RSA_check_key(rsa); + if (r == 1) { + return PyBool_FromLong(1L); + } else { + FAIL(); + } + } else { + PyErr_SetString(PyExc_TypeError, "key type unsupported"); + return NULL; + } +} + +/* + * 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), + ADD_METHOD(check), + { 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; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_PKey_Type); + if (PyModule_AddObject(module, "PKey", (PyObject *)&crypto_PKey_Type) != 0) { + return 0; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_PKey_Type); + 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..93f9946 --- /dev/null +++ b/OpenSSL/crypto/revoked.c @@ -0,0 +1,444 @@ +#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; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_Revoked_Type); + 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..0754dec --- /dev/null +++ b/OpenSSL/crypto/x509.c @@ -0,0 +1,929 @@ +/* + * 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_get_signature_algorithm_doc[] = "\n\ +Retrieve the signature algorithm used in the certificate\n\ +\n\ +@return: A byte string giving the name of the signature algorithm used in\n\ + the certificate.\n\ +@raise ValueError: If the signature algorithm is undefined.\n\ +"; + +static PyObject * +crypto_X509_get_signature_algorithm(crypto_X509Obj *self, PyObject *args) { + ASN1_OBJECT *alg; + int nid; + + if (!PyArg_ParseTuple(args, ":get_signature_algorithm")) { + return NULL; + } + + alg = self->x509->cert_info->signature->algorithm; + nid = OBJ_obj2nid(alg); + if (nid == NID_undef) { + PyErr_SetString(PyExc_ValueError, "Undefined signature algorithm"); + return NULL; + } + return PyBytes_FromString(OBJ_nid2ln(nid)); +} + + +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(get_signature_algorithm), + 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; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_X509_Type); + if (PyModule_AddObject(module, "X509", (PyObject *)&crypto_X509_Type) != 0) { + return 0; + } + + Py_INCREF((PyObject *)&crypto_X509_Type); + 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..adbe084 --- /dev/null +++ b/OpenSSL/crypto/x509ext.c @@ -0,0 +1,336 @@ +/* + * 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; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_X509Extension_Type); + if (PyModule_AddObject(module, "X509Extension", + (PyObject *)&crypto_X509Extension_Type) != 0) { + return 0; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_X509Extension_Type); + 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..a62c957 --- /dev/null +++ b/OpenSSL/crypto/x509name.c @@ -0,0 +1,555 @@ +/* + * 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_setattro(crypto_X509NameObj *self, PyObject *nameobj, PyObject *value) +{ + int nid; + int result; + char *buffer; + char *name; + + if (!PyBytes_CheckExact(nameobj) && !PyUnicode_CheckExact(nameobj)) { + PyErr_Format(PyExc_TypeError, + "attribute name must be string, not '%.200s'", + Py_TYPE(nameobj)->tp_name); + return -1; + } + +#ifdef PY3 + name = PyBytes_AsString(PyUnicode_AsASCIIString(nameobj)); +#else + name = PyBytes_AsString(nameobj); +#endif + + if ((nid = OBJ_txt2nid(name)) == NID_undef) + { + /* Just like the case in the getattr function */ + flush_error_queue(); + 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 */ + NULL, /* 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 */ + (setattrofunc)crypto_X509Name_setattro, /* 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; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_X509Name_Type); + if (PyModule_AddObject(module, "X509Name", (PyObject *)&crypto_X509Name_Type) != 0) { + return 0; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_X509Name_Type); + 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..a2d1f11 --- /dev/null +++ b/OpenSSL/crypto/x509req.c @@ -0,0 +1,431 @@ +/* + * 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; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_X509Req_Type); + if (PyModule_AddObject(module, "X509Req", (PyObject *)&crypto_X509Req_Type) != 0) { + return 0; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_X509Req_Type); + 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..bf22756 --- /dev/null +++ b/OpenSSL/crypto/x509store.c @@ -0,0 +1,149 @@ +/* + * 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; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&crypto_X509Store_Type); + 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.py b/OpenSSL/rand.py deleted file mode 100644 index e754378..0000000 --- a/OpenSSL/rand.py +++ /dev/null @@ -1,180 +0,0 @@ -""" -PRNG management routines, thin wrappers. - -See the file RATIONALE for a short explanation of why this module was written. -""" - -from functools import partial - -from six import integer_types as _integer_types - -from OpenSSL._util import ( - ffi as _ffi, - lib as _lib, - exception_from_error_queue as _exception_from_error_queue) - - -class Error(Exception): - """ - An error occurred in an `OpenSSL.rand` API. - """ - -_raise_current_error = partial(_exception_from_error_queue, Error) - -_unspecified = object() - -_builtin_bytes = bytes - -def bytes(num_bytes): - """ - Get some random bytes as a string. - - :param num_bytes: The number of bytes to fetch - :return: A string of random bytes - """ - if not isinstance(num_bytes, _integer_types): - raise TypeError("num_bytes must be an integer") - - if num_bytes < 0: - raise ValueError("num_bytes must not be negative") - - result_buffer = _ffi.new("char[]", num_bytes) - result_code = _lib.RAND_bytes(result_buffer, num_bytes) - if result_code == -1: - # TODO: No tests for this code path. Triggering a RAND_bytes failure - # might involve supplying a custom ENGINE? That's hard. - _raise_current_error() - - return _ffi.buffer(result_buffer)[:] - - - -def add(buffer, entropy): - """ - Add data with a given entropy to the PRNG - - :param buffer: Buffer with random data - :param entropy: The entropy (in bytes) measurement of the buffer - :return: None - """ - if not isinstance(buffer, _builtin_bytes): - raise TypeError("buffer must be a byte string") - - if not isinstance(entropy, int): - raise TypeError("entropy must be an integer") - - # TODO Nothing tests this call actually being made, or made properly. - _lib.RAND_add(buffer, len(buffer), entropy) - - - -def seed(buffer): - """ - Alias for rand_add, with entropy equal to length - - :param buffer: Buffer with random data - :return: None - """ - if not isinstance(buffer, _builtin_bytes): - raise TypeError("buffer must be a byte string") - - # TODO Nothing tests this call actually being made, or made properly. - _lib.RAND_seed(buffer, len(buffer)) - - - -def status(): - """ - Retrieve the status of the PRNG - - :return: True if the PRNG is seeded enough, false otherwise - """ - return _lib.RAND_status() - - - -def egd(path, bytes=_unspecified): - """ - Query an entropy gathering daemon (EGD) for random data and add it to the - PRNG. I haven't found any problems when the socket is missing, the function - just returns 0. - - :param path: The path to the EGD socket - :param bytes: (optional) The number of bytes to read, default is 255 - :returns: The number of bytes read (NB: a value of 0 isn't necessarily an - error, check rand.status()) - """ - if not isinstance(path, _builtin_bytes): - raise TypeError("path must be a byte string") - - if bytes is _unspecified: - bytes = 255 - elif not isinstance(bytes, int): - raise TypeError("bytes must be an integer") - - return _lib.RAND_egd_bytes(path, bytes) - - - -def cleanup(): - """ - Erase the memory used by the PRNG. - - :return: None - """ - # TODO Nothing tests this call actually being made, or made properly. - _lib.RAND_cleanup() - - - -def load_file(filename, maxbytes=_unspecified): - """ - Seed the PRNG with data from a file - - :param filename: The file to read data from - :param maxbytes: (optional) The number of bytes to read, default is - to read the entire file - :return: The number of bytes read - """ - if not isinstance(filename, _builtin_bytes): - raise TypeError("filename must be a string") - - if maxbytes is _unspecified: - maxbytes = -1 - elif not isinstance(maxbytes, int): - raise TypeError("maxbytes must be an integer") - - return _lib.RAND_load_file(filename, maxbytes) - - - -def write_file(filename): - """ - Save PRNG state to a file - - :param filename: The file to write data to - :return: The number of bytes written - """ - if not isinstance(filename, _builtin_bytes): - raise TypeError("filename must be a string") - - return _lib.RAND_write_file(filename) - - -# TODO There are no tests for screen at all -def screen(): - """ - Add the current contents of the screen to the PRNG state. Availability: - Windows. - - :return: None - """ - _lib.RAND_screen() - -if getattr(_lib, 'RAND_screen', None) is None: - del screen - - -# TODO There are no tests for the RAND strings being loaded, whatever that -# means. -_lib.ERR_load_RAND_strings() diff --git a/OpenSSL/rand/rand.c b/OpenSSL/rand/rand.c new file mode 100644 index 0000000..8307ac6 --- /dev/null +++ b/OpenSSL/rand/rand.c @@ -0,0 +1,306 @@ +/* + * 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; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF(rand_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..9e9794b --- /dev/null +++ b/OpenSSL/ssl/connection.c @@ -0,0 +1,1581 @@ +/* + * 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_set_context_doc[] = "\n\ +Switch this connection to a new session context\n\ +\n\ +@param context: A L{Context} instance giving the new session context to use.\n\ +\n\ +"; +static PyObject * +ssl_Connection_set_context(ssl_ConnectionObj *self, PyObject *args) { + ssl_ContextObj *ctx; + ssl_ContextObj *old; + + if (!PyArg_ParseTuple(args, "O!:set_context", &ssl_Context_Type, &ctx)) { + return NULL; + } + + /* This Connection will hold on to this context now. Make sure it stays + * alive. + */ + Py_INCREF(ctx); + + /* XXX The unit tests don't actually verify that this call is made. + * They're satisfied if self->context gets updated. + */ + SSL_set_SSL_CTX(self->ssl, ctx->ctx); + + /* Swap the old out and the new in. + */ + old = self->context; + self->context = ctx; + + /* XXX The unit tests don't verify that this reference is dropped. + */ + Py_DECREF(old); + + Py_INCREF(Py_None); + return Py_None; +} + +static char ssl_Connection_get_servername_doc[] = "\n\ +Retrieve the servername extension value if provided in the client hello\n\ +message, or None if there wasn't one.\n\ +\n\ +@return: A byte string giving the server name or C{None}.\n\ +\n\ +"; +static PyObject * +ssl_Connection_get_servername(ssl_ConnectionObj *self, PyObject *args) { + int type = TLSEXT_NAMETYPE_host_name; + const char *name; + + if (!PyArg_ParseTuple(args, ":get_servername")) { + return NULL; + } + + name = SSL_get_servername(self->ssl, type); + + if (name == NULL) { + Py_INCREF(Py_None); + return Py_None; + } else { + return PyBytes_FromString(name); + } +} + + +static char ssl_Connection_set_tlsext_host_name_doc[] = "\n\ +Set the value of the servername extension to send in the client hello.\n\ +\n\ +@param name: A byte string giving the name.\n\ +\n\ +"; +static PyObject * +ssl_Connection_set_tlsext_host_name(ssl_ConnectionObj *self, PyObject *args) { + char *buf; + + if (!PyArg_ParseTuple(args, BYTESTRING_FMT ":set_tlsext_host_name", &buf)) { + return NULL; + } + + /* XXX I guess this can fail sometimes? */ + SSL_set_tlsext_host_name(self->ssl, buf); + + Py_INCREF(Py_None); + return Py_None; +} + + + +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_get_peer_cert_chain_doc[] = "\n\ +Retrieve the other side's certificate (if any)\n\ +\n\ +@return: A list of X509 instances giving the peer's certificate chain,\n\ + or None if it does not have one.\n\ +"; +static PyObject * +ssl_Connection_get_peer_cert_chain(ssl_ConnectionObj *self, PyObject *args) { + STACK_OF(X509) *sk; + PyObject *chain; + crypto_X509Obj *cert; + Py_ssize_t i; + + if (!PyArg_ParseTuple(args, ":get_peer_cert_chain")) { + return NULL; + } + + sk = SSL_get_peer_cert_chain(self->ssl); + if (sk != NULL) { + chain = PyList_New(sk_X509_num(sk)); + for (i = 0; i < sk_X509_num(sk); i++) { + cert = new_x509(sk_X509_value(sk, i), 1); + if (!cert) { + /* XXX Untested */ + Py_DECREF(chain); + return NULL; + } + CRYPTO_add(&cert->x509->references, 1, CRYPTO_LOCK_X509); + PyList_SET_ITEM(chain, i, (PyObject *)cert); + } + return chain; + } 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(set_context), + ADD_METHOD(get_servername), + ADD_METHOD(set_tlsext_host_name), + 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(get_peer_cert_chain), + 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; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&ssl_Connection_Type); + if (PyModule_AddObject(module, "Connection", (PyObject *)&ssl_Connection_Type) != 0) { + return 0; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&ssl_Connection_Type); + 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..c2bdcab --- /dev/null +++ b/OpenSSL/ssl/context.c @@ -0,0 +1,1419 @@ +/* + * 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; +} + +/* + * Globally defined TLS extension server name 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. + * + * ssl represents the connection this callback is for + * + * alert is a pointer to the alert value which maybe will be emitted to the + * client if there is an error handling the client hello (which contains the + * server name). This is an out parameter, maybe. + * + * arg is an arbitrary pointer specified by SSL_CTX_set_tlsext_servername_arg. + * It will be NULL for all pyOpenSSL uses. + */ +static int +global_tlsext_servername_callback(const SSL *ssl, int *alert, void *arg) { + int result = 0; + PyObject *argv, *ret; + ssl_ConnectionObj *conn = (ssl_ConnectionObj *)SSL_get_app_data(ssl); + + /* + * 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("(O)", (PyObject *)conn); + ret = PyEval_CallObject(conn->context->tlsext_servername_callback, argv); + Py_DECREF(argv); + Py_DECREF(ret); + + /* + * This function is returning into OpenSSL. Release the GIL again. + */ + MY_BEGIN_ALLOW_THREADS(conn->tstate); + return result; +} + +/* + * More recent builds of OpenSSL may have SSLv2 completely disabled. + */ +#ifdef OPENSSL_NO_SSL2 +#define SSLv2_METHOD_TEXT "" +#else +#define SSLv2_METHOD_TEXT "SSLv2_METHOD, " +#endif + + +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_TEXT "SSLv3_METHOD, SSLv23_METHOD, or\n\ + TLSv1_METHOD.\n\ +"; + +#undef SSLv2_METHOD_TEXT + +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)); +} + +static char ssl_Context_set_tlsext_servername_callback_doc[] = "\n\ +Specify a callback function to be called when clients specify a server name.\n\ +\n\ +@param callback: The callback function. It will be invoked with one\n\ + argument, the Connection instance.\n\ +\n\ +"; +static PyObject * +ssl_Context_set_tlsext_servername_callback(ssl_ContextObj *self, PyObject *args) { + PyObject *callback; + PyObject *old; + + if (!PyArg_ParseTuple(args, "O:set_tlsext_servername_callback", &callback)) { + return NULL; + } + + Py_INCREF(callback); + old = self->tlsext_servername_callback; + self->tlsext_servername_callback = callback; + Py_DECREF(old); + + SSL_CTX_set_tlsext_servername_callback(self->ctx, global_tlsext_servername_callback); + SSL_CTX_set_tlsext_servername_arg(self->ctx, NULL); + + Py_INCREF(Py_None); + return Py_None; +} + + +/* + * 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), + ADD_METHOD(set_tlsext_servername_callback), + { 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) { +#if (OPENSSL_VERSION_NUMBER >> 28) == 0x01 + const +#endif + SSL_METHOD *method; + + switch (i_method) { + case ssl_SSLv2_METHOD: +#ifdef OPENSSL_NO_SSL2 + PyErr_SetString(PyExc_ValueError, "SSLv2_METHOD not supported by this version of OpenSSL"); + return NULL; +#else + method = SSLv2_method(); +#endif + 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->tlsext_servername_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; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&ssl_Context_Type); + if (PyModule_AddObject(module, "Context", (PyObject *)&ssl_Context_Type) < 0) { + return 0; + } + + /* PyModule_AddObject steals a reference. + */ + Py_INCREF((PyObject *)&ssl_Context_Type); + 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..19b5e9e --- /dev/null +++ b/OpenSSL/ssl/context.h @@ -0,0 +1,43 @@ +/* + * 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, + *tlsext_servername_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..0dd9871 --- /dev/null +++ b/OpenSSL/ssl/ssl.c @@ -0,0 +1,293 @@ +/* + * 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) */ + +static char ssl_SSLeay_version_doc[] = "\n\ +Return a string describing the version of OpenSSL in use.\n\ +\n\ +@param type: One of the SSLEAY_ constants defined in this module.\n\ +"; + +static PyObject * +ssl_SSLeay_version(PyObject *spam, PyObject *args) { + int t; + const char *version; + + if (!PyArg_ParseTuple(args, "i:SSLeay_version", &t)) { + return NULL; + } + + version = SSLeay_version(t); + return PyBytes_FromStringAndSize(version, strlen(version)); +} + + + +/* Methods in the OpenSSL.SSL module */ +static PyMethodDef ssl_methods[] = { + { "SSLeay_version", ssl_SSLeay_version, METH_VARARGS, ssl_SSLeay_version_doc }, + { 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 steals a reference. + */ + Py_INCREF(ssl_api_object); + 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; \ + /* PyModule_AddObject steals a reference. */ \ + Py_INCREF(ssl_##_name); \ + 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; + } + + /* PyModule_AddObject steals a reference. */ + Py_INCREF(ssl_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); + + /* Version information indicators, used with SSLeay_version */ + PyModule_AddIntConstant(module, "SSLEAY_VERSION", SSLEAY_VERSION); + PyModule_AddIntConstant(module, "SSLEAY_CFLAGS", SSLEAY_CFLAGS); + PyModule_AddIntConstant(module, "SSLEAY_BUILT_ON", SSLEAY_BUILT_ON); + PyModule_AddIntConstant(module, "SSLEAY_PLATFORM", SSLEAY_PLATFORM); + PyModule_AddIntConstant(module, "SSLEAY_DIR", SSLEAY_DIR); + + /* Straight up version number */ + PyModule_AddIntConstant(module, "OPENSSL_VERSION_NUMBER", OPENSSL_VERSION_NUMBER); + + 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/README b/OpenSSL/test/README deleted file mode 100644 index 8b3b3ff..0000000 --- a/OpenSSL/test/README +++ /dev/null @@ -1,8 +0,0 @@ -These tests are meant to be run using twisted's "trial" command. - -See http://twistedmatrix.com/trac/wiki/TwistedTrial - -For example... - -$ sudo python ./setup install -$ trial OpenSSL diff --git a/OpenSSL/test/__init__.py b/OpenSSL/test/__init__.py index 9b08060..ccb4e9a 100644 --- a/OpenSSL/test/__init__.py +++ b/OpenSSL/test/__init__.py @@ -2,5 +2,5 @@ # See LICENSE for details. """ -Package containing unit tests for :py:mod:`OpenSSL`. +Package containing unit tests for L{OpenSSL}. """ diff --git a/OpenSSL/test/test_crypto.py b/OpenSSL/test/test_crypto.py index 4e42f70..5cdcefb 100644 --- a/OpenSSL/test/test_crypto.py +++ b/OpenSSL/test/test_crypto.py @@ -2,7 +2,7 @@ # See LICENSE file for details. """ -Unit tests for :py:mod:`OpenSSL.crypto`. +Unit tests for L{OpenSSL.crypto}. """ from unittest import main @@ -11,11 +11,9 @@ import os, re from subprocess import PIPE, Popen from datetime import datetime, timedelta -from six import binary_type - from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType -from OpenSSL.crypto import X509Store, X509StoreType, X509Req, X509ReqType +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 @@ -26,8 +24,7 @@ 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, b -from OpenSSL._util import native +from OpenSSL.test.util import TestCase, bytes, b def normalize_certificate_pem(pem): return dump_certificate(FILETYPE_PEM, load_certificate(FILETYPE_PEM, pem)) @@ -37,12 +34,6 @@ def normalize_privatekey_pem(pem): return dump_privatekey(FILETYPE_PEM, load_privatekey(FILETYPE_PEM, pem)) -GOOD_CIPHER = "blowfish" -BAD_CIPHER = "zippers" - -GOOD_DIGEST = "MD5" -BAD_DIGEST = "monkeys" - root_cert_pem = b("""-----BEGIN CERTIFICATE----- MIIC7TCCAlagAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU @@ -274,41 +265,10 @@ oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w= -----END RSA PRIVATE KEY----- """) -# certificate with NULL bytes in subjectAltName and common name - -nulbyteSubjectAltNamePEM = b("""-----BEGIN CERTIFICATE----- -MIIE2DCCA8CgAwIBAgIBADANBgkqhkiG9w0BAQUFADCBxTELMAkGA1UEBhMCVVMx -DzANBgNVBAgMBk9yZWdvbjESMBAGA1UEBwwJQmVhdmVydG9uMSMwIQYDVQQKDBpQ -eXRob24gU29mdHdhcmUgRm91bmRhdGlvbjEgMB4GA1UECwwXUHl0aG9uIENvcmUg -RGV2ZWxvcG1lbnQxJDAiBgNVBAMMG251bGwucHl0aG9uLm9yZwBleGFtcGxlLm9y -ZzEkMCIGCSqGSIb3DQEJARYVcHl0aG9uLWRldkBweXRob24ub3JnMB4XDTEzMDgw -NzEzMTE1MloXDTEzMDgwNzEzMTI1MlowgcUxCzAJBgNVBAYTAlVTMQ8wDQYDVQQI -DAZPcmVnb24xEjAQBgNVBAcMCUJlYXZlcnRvbjEjMCEGA1UECgwaUHl0aG9uIFNv -ZnR3YXJlIEZvdW5kYXRpb24xIDAeBgNVBAsMF1B5dGhvbiBDb3JlIERldmVsb3Bt -ZW50MSQwIgYDVQQDDBtudWxsLnB5dGhvbi5vcmcAZXhhbXBsZS5vcmcxJDAiBgkq -hkiG9w0BCQEWFXB5dGhvbi1kZXZAcHl0aG9uLm9yZzCCASIwDQYJKoZIhvcNAQEB -BQADggEPADCCAQoCggEBALXq7cn7Rn1vO3aA3TrzA5QLp6bb7B3f/yN0CJ2XFj+j -pHs+Gw6WWSUDpybiiKnPec33BFawq3kyblnBMjBU61ioy5HwQqVkJ8vUVjGIUq3P -vX/wBmQfzCe4o4uM89gpHyUL9UYGG8oCRa17dgqcv7u5rg0Wq2B1rgY+nHwx3JIv -KRrgSwyRkGzpN8WQ1yrXlxWjgI9de0mPVDDUlywcWze1q2kwaEPTM3hLAmD1PESA -oY/n8A/RXoeeRs9i/Pm/DGUS8ZPINXk/yOzsR/XvvkTVroIeLZqfmFpnZeF0cHzL -08LODkVJJ9zjLdT7SA4vnne4FEbAxDbKAq5qkYzaL4UCAwEAAaOB0DCBzTAMBgNV -HRMBAf8EAjAAMB0GA1UdDgQWBBSIWlXAUv9hzVKjNQ/qWpwkOCL3XDALBgNVHQ8E -BAMCBeAwgZAGA1UdEQSBiDCBhYIeYWx0bnVsbC5weXRob24ub3JnAGV4YW1wbGUu -Y29tgSBudWxsQHB5dGhvbi5vcmcAdXNlckBleGFtcGxlLm9yZ4YpaHR0cDovL251 -bGwucHl0aG9uLm9yZwBodHRwOi8vZXhhbXBsZS5vcmeHBMAAAgGHECABDbgAAAAA -AAAAAAAAAAEwDQYJKoZIhvcNAQEFBQADggEBAKxPRe99SaghcI6IWT7UNkJw9aO9 -i9eo0Fj2MUqxpKbdb9noRDy2CnHWf7EIYZ1gznXPdwzSN4YCjV5d+Q9xtBaowT0j -HPERs1ZuytCNNJTmhyqZ8q6uzMLoht4IqH/FBfpvgaeC5tBTnTT0rD5A/olXeimk -kX4LxlEx5RAvpGB2zZVRGr6LobD9rVK91xuHYNIxxxfEGE8tCCWjp0+3ksri9SXx -VHWBnbM9YaL32u3hxm8sYB/Yb8WSBavJCWJJqRStVRHM1koZlJmXNx2BX4vPo6iW -RFEIPQsFZRLrtnCAiEhyT8bC2s/Njlu6ly9gtJZWSV46Q3ZjBL4q9sHKqZQ= ------END CERTIFICATE-----""") - class X509ExtTests(TestCase): """ - Tests for :py:class:`OpenSSL.crypto.X509Extension`. + Tests for L{OpenSSL.crypto.X509Extension}. """ def setUp(self): @@ -316,7 +276,6 @@ class X509ExtTests(TestCase): Create a new private key and start a certificate request (for a test method to finish in one way or another). """ - super(X509ExtTests, self).setUp() # Basic setup stuff to generate a certificate self.pkey = PKey() self.pkey.generate_key(TYPE_RSA, 384) @@ -335,19 +294,10 @@ class X509ExtTests(TestCase): self.x509.set_notAfter(expire) - def tearDown(self): - """ - Forget all of the pyOpenSSL objects so they can be garbage collected, - their memory released, and not interfere with the leak detection code. - """ - self.pkey = self.req = self.x509 = self.subject = None - super(X509ExtTests, self).tearDown() - - def test_str(self): """ - The string representation of :py:class:`X509Extension` instances as returned by - :py:data:`str` includes stuff. + 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. @@ -358,7 +308,7 @@ class X509ExtTests(TestCase): def test_type(self): """ - :py:class:`X509Extension` and :py:class:`X509ExtensionType` refer to the same type object + 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) @@ -369,8 +319,8 @@ class X509ExtTests(TestCase): def test_construction(self): """ - :py:class:`X509Extension` accepts an extension type name, a critical flag, - and an extension value and returns an :py:class:`X509ExtensionType` instance. + 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( @@ -388,7 +338,7 @@ class X509ExtTests(TestCase): def test_invalid_extension(self): """ - :py:class:`X509Extension` raises something if it is passed a bad extension + L{X509Extension} raises something if it is passed a bad extension name or value. """ self.assertRaises( @@ -407,7 +357,7 @@ class X509ExtTests(TestCase): def test_get_critical(self): """ - :py:meth:`X509ExtensionType.get_critical` returns the value of the + L{X509ExtensionType.get_critical} returns the value of the extension's critical flag. """ ext = X509Extension(b('basicConstraints'), True, b('CA:true')) @@ -418,7 +368,7 @@ class X509ExtTests(TestCase): def test_get_short_name(self): """ - :py:meth:`X509ExtensionType.get_short_name` returns a string giving the short + L{X509ExtensionType.get_short_name} returns a string giving the short type name of the extension. """ ext = X509Extension(b('basicConstraints'), True, b('CA:true')) @@ -429,7 +379,7 @@ class X509ExtTests(TestCase): def test_get_data(self): """ - :py:meth:`X509Extension.get_data` returns a string giving the data of the + L{X509Extension.get_data} returns a string giving the data of the extension. """ ext = X509Extension(b('basicConstraints'), True, b('CA:true')) @@ -439,7 +389,7 @@ class X509ExtTests(TestCase): def test_get_data_wrong_args(self): """ - :py:meth:`X509Extension.get_data` raises :py:exc:`TypeError` if passed any arguments. + 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) @@ -449,7 +399,7 @@ class X509ExtTests(TestCase): def test_unused_subject(self): """ - The :py:data:`subject` parameter to :py:class:`X509Extension` may be provided for an + 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( @@ -464,8 +414,8 @@ class X509ExtTests(TestCase): def test_subject(self): """ - If an extension requires a subject, the :py:data:`subject` parameter to - :py:class:`X509Extension` provides its value. + 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) @@ -477,7 +427,7 @@ class X509ExtTests(TestCase): def test_missing_subject(self): """ - If an extension requires a subject and the :py:data:`subject` parameter is + If an extension requires a subject and the C{subject} parameter is given no value, something happens. """ self.assertRaises( @@ -486,8 +436,8 @@ class X509ExtTests(TestCase): def test_invalid_subject(self): """ - If the :py:data:`subject` parameter is given a value which is not an - :py:class:`X509` instance, :py:exc:`TypeError` is raised. + 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( @@ -498,7 +448,7 @@ class X509ExtTests(TestCase): def test_unused_issuer(self): """ - The :py:data:`issuer` parameter to :py:class:`X509Extension` may be provided for an + 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( @@ -512,8 +462,8 @@ class X509ExtTests(TestCase): def test_issuer(self): """ - If an extension requires a issuer, the :py:data:`issuer` parameter to - :py:class:`X509Extension` provides its value. + If an extension requires a issuer, the C{issuer} parameter to + L{X509Extension} provides its value. """ ext2 = X509Extension( b('authorityKeyIdentifier'), False, b('issuer:always'), @@ -527,7 +477,7 @@ class X509ExtTests(TestCase): def test_missing_issuer(self): """ - If an extension requires an issue and the :py:data:`issuer` parameter is given + If an extension requires an issue and the C{issuer} parameter is given no value, something happens. """ self.assertRaises( @@ -539,8 +489,8 @@ class X509ExtTests(TestCase): def test_invalid_issuer(self): """ - If the :py:data:`issuer` parameter is given a value which is not an - :py:class:`X509` instance, :py:exc:`TypeError` is raised. + 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( @@ -553,12 +503,12 @@ class X509ExtTests(TestCase): class PKeyTests(TestCase): """ - Unit tests for :py:class:`OpenSSL.crypto.PKey`. + Unit tests for L{OpenSSL.crypto.PKey}. """ def test_type(self): """ - :py:class:`PKey` and :py:class:`PKeyType` refer to the same type object - and can be used to create instances of that type. + 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') @@ -566,7 +516,7 @@ class PKeyTests(TestCase): def test_construction(self): """ - :py:class:`PKey` takes no arguments and returns a new :py:class:`PKey` instance. + L{PKey} takes no arguments and returns a new L{PKey} instance. """ self.assertRaises(TypeError, PKey, None) key = PKey() @@ -577,8 +527,8 @@ class PKeyTests(TestCase): def test_pregeneration(self): """ - :py:attr:`PKeyType.bits` and :py:attr:`PKeyType.type` return :py:data:`0` before the key is - generated. :py:attr:`PKeyType.check` raises :py:exc:`TypeError` before the key is + L{PKeyType.bits} and L{PKeyType.type} return C{0} before the key is + generated. L{PKeyType.check} raises L{TypeError} before the key is generated. """ key = PKey() @@ -589,11 +539,11 @@ class PKeyTests(TestCase): def test_failedGeneration(self): """ - :py:meth:`PKeyType.generate_key` takes two arguments, the first giving the key - type as one of :py:data:`TYPE_RSA` or :py:data:`TYPE_DSA` and the second giving the + 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, :py:exc:`Error` is raised. If an invalid number of bits is - specified, :py:exc:`ValueError` or :py:exc:`Error` is raised. + 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) @@ -624,8 +574,8 @@ class PKeyTests(TestCase): def test_rsaGeneration(self): """ - :py:meth:`PKeyType.generate_key` generates an RSA key when passed - :py:data:`TYPE_RSA` as a type and a reasonable number of bits. + 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() @@ -637,8 +587,8 @@ class PKeyTests(TestCase): def test_dsaGeneration(self): """ - :py:meth:`PKeyType.generate_key` generates a DSA key when passed - :py:data:`TYPE_DSA` as a type and a reasonable number of bits. + 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 @@ -646,14 +596,14 @@ class PKeyTests(TestCase): bits = 512 key = PKey() key.generate_key(TYPE_DSA, bits) - # self.assertEqual(key.type(), TYPE_DSA) - # self.assertEqual(key.bits(), bits) - # self.assertRaises(TypeError, key.check) + self.assertEqual(key.type(), TYPE_DSA) + self.assertEqual(key.bits(), bits) + self.assertRaises(TypeError, key.check) def test_regeneration(self): """ - :py:meth:`PKeyType.generate_key` can be called multiple times on the same + L{PKeyType.generate_key} can be called multiple times on the same key to generate new keys. """ key = PKey() @@ -665,7 +615,7 @@ class PKeyTests(TestCase): def test_inconsistentKey(self): """ - :py:`PKeyType.check` returns :py:exc:`Error` if the key is not consistent. + L{PKeyType.check} returns C{False} if the key is not consistent. """ key = load_privatekey(FILETYPE_PEM, inconsistentPrivateKeyPEM) self.assertRaises(Error, key.check) @@ -673,31 +623,17 @@ class PKeyTests(TestCase): def test_check_wrong_args(self): """ - :py:meth:`PKeyType.check` raises :py:exc:`TypeError` if called with any arguments. + L{PKeyType.check} raises L{TypeError} if called with any arguments. """ self.assertRaises(TypeError, PKey().check, None) self.assertRaises(TypeError, PKey().check, object()) self.assertRaises(TypeError, PKey().check, 1) - def test_check_public_key(self): - """ - :py:meth:`PKeyType.check` raises :py:exc:`TypeError` if only the public - part of the key is available. - """ - # A trick to get a public-only key - key = PKey() - key.generate_key(TYPE_RSA, 512) - cert = X509() - cert.set_pubkey(key) - pub = cert.get_pubkey() - self.assertRaises(TypeError, pub.check) - - class X509NameTests(TestCase): """ - Unit tests for :py:class:`OpenSSL.crypto.X509Name`. + Unit tests for L{OpenSSL.crypto.X509Name}. """ def _x509name(self, **attrs): # XXX There's no other way to get a new X509Name yet. @@ -714,7 +650,7 @@ class X509NameTests(TestCase): def test_type(self): """ - The type of X509Name objects is :py:class:`X509NameType`. + The type of X509Name objects is L{X509NameType}. """ self.assertIdentical(X509Name, X509NameType) self.assertEqual(X509NameType.__name__, 'X509Name') @@ -729,28 +665,25 @@ class X509NameTests(TestCase): def test_onlyStringAttributes(self): """ - Attempting to set a non-:py:data:`str` attribute name on an :py:class:`X509NameType` - instance causes :py:exc:`TypeError` to be raised. + Attempting to set a non-L{str} attribute name on an L{X509NameType} + instance causes L{TypeError} to be raised. """ name = self._x509name() # Beyond these cases, you may also think that unicode should be # rejected. Sorry, you're wrong. unicode is automatically converted to # str outside of the control of X509Name, so there's no way to reject # it. - - # Also, this used to test str subclasses, but that test is less relevant - # now that the implementation is in Python instead of C. Also PyPy - # automatically converts str subclasses to str when they are passed to - # setattr, so we can't test it on PyPy. Apparently CPython does this - # sometimes as well. self.assertRaises(TypeError, setattr, name, None, "hello") self.assertRaises(TypeError, setattr, name, 30, "hello") + class evil(str): + pass + self.assertRaises(TypeError, setattr, name, evil(), "hello") def test_setInvalidAttribute(self): """ - Attempting to set any attribute name on an :py:class:`X509NameType` instance for - which no corresponding NID is defined causes :py:exc:`AttributeError` to be + Attempting to set any attribute name on an L{X509NameType} instance for + which no corresponding NID is defined causes L{AttributeError} to be raised. """ name = self._x509name() @@ -759,7 +692,7 @@ class X509NameTests(TestCase): def test_attributes(self): """ - :py:class:`X509NameType` instances have attributes for each standard (?) + L{X509NameType} instances have attributes for each standard (?) X509Name field. """ name = self._x509name() @@ -779,8 +712,8 @@ class X509NameTests(TestCase): def test_copy(self): """ - :py:class:`X509Name` creates a new :py:class:`X509NameType` instance with all the same - attributes as an existing :py:class:`X509NameType` instance when called with + 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") @@ -800,7 +733,7 @@ class X509NameTests(TestCase): def test_repr(self): """ - :py:func:`repr` passed an :py:class:`X509NameType` instance should return a string + 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. """ @@ -812,7 +745,7 @@ class X509NameTests(TestCase): def test_comparison(self): """ - :py:class:`X509NameType` instances should compare based on their NIDs. + L{X509NameType} instances should compare based on their NIDs. """ def _equality(a, b, assertTrue, assertFalse): assertTrue(a == b, "(%r == %r) --> False" % (a, b)) @@ -887,7 +820,7 @@ class X509NameTests(TestCase): def test_hash(self): """ - :py:meth:`X509Name.hash` returns an integer hash based on the value of the + L{X509Name.hash} returns an integer hash based on the value of the name. """ a = self._x509name(CN="foo") @@ -899,7 +832,7 @@ class X509NameTests(TestCase): def test_der(self): """ - :py:meth:`X509Name.der` returns the DER encoded form of the name. + L{X509Name.der} returns the DER encoded form of the name. """ a = self._x509name(CN="foo", C="US") self.assertEqual( @@ -910,8 +843,7 @@ class X509NameTests(TestCase): def test_get_components(self): """ - :py:meth:`X509Name.get_components` returns a :py:data:`list` of - two-tuples of :py:data:`str` + 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() @@ -924,85 +856,60 @@ class X509NameTests(TestCase): [(b("CN"), b("foo")), (b("OU"), b("bar"))]) - def test_load_nul_byte_attribute(self): - """ - An :py:class:`OpenSSL.crypto.X509Name` from an - :py:class:`OpenSSL.crypto.X509` instance loaded from a file can have a - NUL byte in the value of one of its attributes. - """ - cert = load_certificate(FILETYPE_PEM, nulbyteSubjectAltNamePEM) - subject = cert.get_subject() - self.assertEqual( - "null.python.org\x00example.org", subject.commonName) - - - def test_setAttributeFailure(self): - """ - If the value of an attribute cannot be set for some reason then - :py:class:`OpenSSL.crypto.Error` is raised. - """ - name = self._x509name() - # This value is too long - self.assertRaises(Error, setattr, name, "O", b"x" * 512) - - - class _PKeyInteractionTestsMixin: """ Tests which involve another thing and a PKey. """ def signable(self): """ - Return something with a :py:meth:`set_pubkey`, :py:meth:`set_pubkey`, - and :py:meth:`sign` method. + Return something with a C{set_pubkey}, C{set_pubkey}, and C{sign} method. """ raise NotImplementedError() def test_signWithUngenerated(self): """ - :py:meth:`X509Req.sign` raises :py:exc:`ValueError` when pass a - :py:class:`PKey` with no parts. + 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, GOOD_DIGEST) + self.assertRaises(ValueError, request.sign, key, 'MD5') def test_signWithPublicKey(self): """ - :py:meth:`X509Req.sign` raises :py:exc:`ValueError` when pass a - :py:class:`PKey` with no private part as the signing key. + 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, GOOD_DIGEST) + self.assertRaises(ValueError, request.sign, pub, 'MD5') def test_signWithUnknownDigest(self): """ - :py:meth:`X509Req.sign` raises :py:exc:`ValueError` when passed a digest name which is + 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, BAD_DIGEST) + self.assertRaises(ValueError, request.sign, key, "monkeys") def test_sign(self): """ - :py:meth:`X509Req.sign` succeeds when passed a private key object and a valid - digest function. :py:meth:`X509Req.verify` can be used to check the signature. + 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, GOOD_DIGEST) + 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() @@ -1017,18 +924,18 @@ class _PKeyInteractionTestsMixin: class X509ReqTests(TestCase, _PKeyInteractionTestsMixin): """ - Tests for :py:class:`OpenSSL.crypto.X509Req`. + Tests for L{OpenSSL.crypto.X509Req}. """ def signable(self): """ - Create and return a new :py:class:`X509Req`. + Create and return a new L{X509Req}. """ return X509Req() def test_type(self): """ - :py:obj:`X509Req` and :py:obj:`X509ReqType` refer to the same type object and can be + 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) @@ -1037,7 +944,7 @@ class X509ReqTests(TestCase, _PKeyInteractionTestsMixin): def test_construction(self): """ - :py:obj:`X509Req` takes no arguments and returns an :py:obj:`X509ReqType` instance. + L{X509Req} takes no arguments and returns an L{X509ReqType} instance. """ request = X509Req() self.assertTrue( @@ -1047,8 +954,8 @@ class X509ReqTests(TestCase, _PKeyInteractionTestsMixin): def test_version(self): """ - :py:obj:`X509ReqType.set_version` sets the X.509 version of the certificate - request. :py:obj:`X509ReqType.get_version` returns the X.509 version of + 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() @@ -1061,9 +968,9 @@ class X509ReqTests(TestCase, _PKeyInteractionTestsMixin): def test_version_wrong_args(self): """ - :py:obj:`X509ReqType.set_version` raises :py:obj:`TypeError` if called with the wrong - number of arguments or with a non-:py:obj:`int` argument. - :py:obj:`X509ReqType.get_version` raises :py:obj:`TypeError` if called with any + 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() @@ -1075,7 +982,7 @@ class X509ReqTests(TestCase, _PKeyInteractionTestsMixin): def test_get_subject(self): """ - :py:obj:`X509ReqType.get_subject` returns an :py:obj:`X509Name` for the subject of + 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. """ @@ -1093,7 +1000,7 @@ class X509ReqTests(TestCase, _PKeyInteractionTestsMixin): def test_get_subject_wrong_args(self): """ - :py:obj:`X509ReqType.get_subject` raises :py:obj:`TypeError` if called with any + L{X509ReqType.get_subject} raises L{TypeError} if called with any arguments. """ request = X509Req() @@ -1102,7 +1009,7 @@ class X509ReqTests(TestCase, _PKeyInteractionTestsMixin): def test_add_extensions(self): """ - :py:obj:`X509Req.add_extensions` accepts a :py:obj:`list` of :py:obj:`X509Extension` + L{X509Req.add_extensions} accepts a C{list} of L{X509Extension} instances and adds them to the X509 request. """ request = X509Req() @@ -1113,9 +1020,9 @@ class X509ReqTests(TestCase, _PKeyInteractionTestsMixin): def test_add_extensions_wrong_args(self): """ - :py:obj:`X509Req.add_extensions` raises :py:obj:`TypeError` if called with the wrong - number of arguments or with a non-:py:obj:`list`. Or it raises :py:obj:`ValueError` - if called with a :py:obj:`list` containing objects other than :py:obj:`X509Extension` + 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() @@ -1125,57 +1032,10 @@ class X509ReqTests(TestCase, _PKeyInteractionTestsMixin): self.assertRaises(TypeError, request.add_extensions, [], None) - def test_verify_wrong_args(self): - """ - :py:obj:`X509Req.verify` raises :py:obj:`TypeError` if called with zero - arguments or more than one argument or if passed anything other than a - :py:obj:`PKey` instance as its single argument. - """ - request = X509Req() - self.assertRaises(TypeError, request.verify) - self.assertRaises(TypeError, request.verify, object()) - self.assertRaises(TypeError, request.verify, PKey(), object()) - - - def test_verify_uninitialized_key(self): - """ - :py:obj:`X509Req.verify` raises :py:obj:`OpenSSL.crypto.Error` if called - with a :py:obj:`OpenSSL.crypto.PKey` which contains no key data. - """ - request = X509Req() - pkey = PKey() - self.assertRaises(Error, request.verify, pkey) - - - def test_verify_wrong_key(self): - """ - :py:obj:`X509Req.verify` raises :py:obj:`OpenSSL.crypto.Error` if called - with a :py:obj:`OpenSSL.crypto.PKey` which does not represent the public - part of the key which signed the request. - """ - request = X509Req() - pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - request.sign(pkey, GOOD_DIGEST) - another_pkey = load_privatekey(FILETYPE_PEM, client_key_pem) - self.assertRaises(Error, request.verify, another_pkey) - - - def test_verify_success(self): - """ - :py:obj:`X509Req.verify` returns :py:obj:`True` if called with a - :py:obj:`OpenSSL.crypto.PKey` which represents the public part ofthe key - which signed the request. - """ - request = X509Req() - pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - request.sign(pkey, GOOD_DIGEST) - self.assertEqual(True, request.verify(pkey)) - - class X509Tests(TestCase, _PKeyInteractionTestsMixin): """ - Tests for :py:obj:`OpenSSL.crypto.X509`. + Tests for L{OpenSSL.crypto.X509}. """ pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM @@ -1201,14 +1061,14 @@ WpOdIpB8KksUTCzV591Nr1wd """ def signable(self): """ - Create and return a new :py:obj:`X509`. + Create and return a new L{X509}. """ return X509() def test_type(self): """ - :py:obj:`X509` and :py:obj:`X509Type` refer to the same type object and can be used + 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) @@ -1217,7 +1077,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_construction(self): """ - :py:obj:`X509` takes no arguments and returns an instance of :py:obj:`X509Type`. + L{X509} takes no arguments and returns an instance of L{X509Type}. """ certificate = X509() self.assertTrue( @@ -1233,7 +1093,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_get_version_wrong_args(self): """ - :py:obj:`X509.get_version` raises :py:obj:`TypeError` if invoked with any arguments. + L{X509.get_version} raises L{TypeError} if invoked with any arguments. """ cert = X509() self.assertRaises(TypeError, cert.get_version, None) @@ -1241,8 +1101,8 @@ WpOdIpB8KksUTCzV591Nr1wd def test_set_version_wrong_args(self): """ - :py:obj:`X509.set_version` raises :py:obj:`TypeError` if invoked with the wrong number - of arguments or an argument not of type :py:obj:`int`. + 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) @@ -1252,8 +1112,8 @@ WpOdIpB8KksUTCzV591Nr1wd def test_version(self): """ - :py:obj:`X509.set_version` sets the certificate version number. - :py:obj:`X509.get_version` retrieves it. + L{X509.set_version} sets the certificate version number. + L{X509.get_version} retrieves it. """ cert = X509() cert.set_version(1234) @@ -1262,7 +1122,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_get_serial_number_wrong_args(self): """ - :py:obj:`X509.get_serial_number` raises :py:obj:`TypeError` if invoked with any + L{X509.get_serial_number} raises L{TypeError} if invoked with any arguments. """ cert = X509() @@ -1271,8 +1131,8 @@ WpOdIpB8KksUTCzV591Nr1wd def test_serial_number(self): """ - The serial number of an :py:obj:`X509Type` can be retrieved and modified with - :py:obj:`X509Type.get_serial_number` and :py:obj:`X509Type.set_serial_number`. + 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) @@ -1292,7 +1152,7 @@ WpOdIpB8KksUTCzV591Nr1wd def _setBoundTest(self, which): """ - :py:obj:`X509Type.set_notBefore` takes a string in the format of an ASN1 + 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. """ @@ -1331,7 +1191,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_set_notBefore(self): """ - :py:obj:`X509Type.set_notBefore` takes a string in the format of an ASN1 + 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. """ @@ -1340,7 +1200,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_set_notAfter(self): """ - :py:obj:`X509Type.set_notAfter` takes a string in the format of an ASN1 + 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. """ @@ -1349,7 +1209,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_get_notBefore(self): """ - :py:obj:`X509Type.get_notBefore` returns a string in the format of an ASN1 + L{X509Type.get_notBefore} returns a string in the format of an ASN1 GENERALIZEDTIME even for certificates which store it as UTCTIME internally. """ @@ -1359,7 +1219,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_get_notAfter(self): """ - :py:obj:`X509Type.get_notAfter` returns a string in the format of an ASN1 + L{X509Type.get_notAfter} returns a string in the format of an ASN1 GENERALIZEDTIME even for certificates which store it as UTCTIME internally. """ @@ -1369,8 +1229,8 @@ WpOdIpB8KksUTCzV591Nr1wd def test_gmtime_adj_notBefore_wrong_args(self): """ - :py:obj:`X509Type.gmtime_adj_notBefore` raises :py:obj:`TypeError` if called with the - wrong number of arguments or a non-:py:obj:`int` argument. + 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) @@ -1380,7 +1240,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_gmtime_adj_notBefore(self): """ - :py:obj:`X509Type.gmtime_adj_notBefore` changes the not-before timestamp to be + 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) @@ -1391,8 +1251,8 @@ WpOdIpB8KksUTCzV591Nr1wd def test_gmtime_adj_notAfter_wrong_args(self): """ - :py:obj:`X509Type.gmtime_adj_notAfter` raises :py:obj:`TypeError` if called with the - wrong number of arguments or a non-:py:obj:`int` argument. + 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) @@ -1402,7 +1262,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_gmtime_adj_notAfter(self): """ - :py:obj:`X509Type.gmtime_adj_notAfter` changes the not-after timestamp to be + 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) @@ -1413,7 +1273,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_has_expired_wrong_args(self): """ - :py:obj:`X509Type.has_expired` raises :py:obj:`TypeError` if called with any + L{X509Type.has_expired} raises L{TypeError} if called with any arguments. """ cert = X509() @@ -1422,7 +1282,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_has_expired(self): """ - :py:obj:`X509Type.has_expired` returns :py:obj:`True` if the certificate's not-after + L{X509Type.has_expired} returns C{True} if the certificate's not-after time is in the past. """ cert = X509() @@ -1432,7 +1292,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_has_not_expired(self): """ - :py:obj:`X509Type.has_expired` returns :py:obj:`False` if the certificate's not-after + L{X509Type.has_expired} returns C{False} if the certificate's not-after time is in the future. """ cert = X509() @@ -1442,15 +1302,12 @@ WpOdIpB8KksUTCzV591Nr1wd def test_digest(self): """ - :py:obj:`X509.digest` returns a string giving ":"-separated hex-encoded words + L{X509.digest} returns a string giving ":"-separated hex-encoded words of the digest of the certificate. """ cert = X509() self.assertEqual( - # This is MD5 instead of GOOD_DIGEST because the digest algorithm - # actually matters to the assertion (ie, another arbitrary, good - # digest will not product the same digest). - cert.digest("MD5"), + cert.digest("md5"), b("A8:EB:07:F8:53:25:0A:F2:56:05:C5:A5:C4:C4:C7:15")) @@ -1470,7 +1327,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_extension_count(self): """ - :py:obj:`X509.get_extension_count` returns the number of extensions that are + L{X509.get_extension_count} returns the number of extensions that are present in the certificate. """ pkey = load_privatekey(FILETYPE_PEM, client_key_pem) @@ -1494,7 +1351,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_get_extension(self): """ - :py:obj:`X509.get_extension` takes an integer and returns an :py:obj:`X509Extension` + 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) @@ -1525,36 +1382,18 @@ WpOdIpB8KksUTCzV591Nr1wd self.assertRaises(TypeError, cert.get_extension, "hello") - def test_nullbyte_subjectAltName(self): - """ - The fields of a `subjectAltName` extension on an X509 may contain NUL - bytes and this value is reflected in the string representation of the - extension object. - """ - cert = load_certificate(FILETYPE_PEM, nulbyteSubjectAltNamePEM) - - ext = cert.get_extension(3) - self.assertEqual(ext.get_short_name(), b('subjectAltName')) - self.assertEqual( - b("DNS:altnull.python.org\x00example.com, " - "email:null@python.org\x00user@example.org, " - "URI:http://null.python.org\x00http://example.org, " - "IP Address:192.0.2.1, IP Address:2001:DB8:0:0:0:0:0:1\n"), - b(str(ext))) - - def test_invalid_digest_algorithm(self): """ - :py:obj:`X509.digest` raises :py:obj:`ValueError` if called with an unrecognized hash + L{X509.digest} raises L{ValueError} if called with an unrecognized hash algorithm. """ cert = X509() - self.assertRaises(ValueError, cert.digest, BAD_DIGEST) + self.assertRaises(ValueError, cert.digest, "monkeys") def test_get_subject_wrong_args(self): """ - :py:obj:`X509.get_subject` raises :py:obj:`TypeError` if called with any arguments. + L{X509.get_subject} raises L{TypeError} if called with any arguments. """ cert = X509() self.assertRaises(TypeError, cert.get_subject, None) @@ -1562,7 +1401,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_get_subject(self): """ - :py:obj:`X509.get_subject` returns an :py:obj:`X509Name` instance. + L{X509.get_subject} returns an L{X509Name} instance. """ cert = load_certificate(FILETYPE_PEM, self.pemData) subj = cert.get_subject() @@ -1575,8 +1414,8 @@ WpOdIpB8KksUTCzV591Nr1wd def test_set_subject_wrong_args(self): """ - :py:obj:`X509.set_subject` raises a :py:obj:`TypeError` if called with the wrong - number of arguments or an argument not of type :py:obj:`X509Name`. + 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) @@ -1586,7 +1425,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_set_subject(self): """ - :py:obj:`X509.set_subject` changes the subject of the certificate to the one + L{X509.set_subject} changes the subject of the certificate to the one passed in. """ cert = X509() @@ -1601,7 +1440,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_get_issuer_wrong_args(self): """ - :py:obj:`X509.get_issuer` raises :py:obj:`TypeError` if called with any arguments. + L{X509.get_issuer} raises L{TypeError} if called with any arguments. """ cert = X509() self.assertRaises(TypeError, cert.get_issuer, None) @@ -1609,7 +1448,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_get_issuer(self): """ - :py:obj:`X509.get_issuer` returns an :py:obj:`X509Name` instance. + L{X509.get_issuer} returns an L{X509Name} instance. """ cert = load_certificate(FILETYPE_PEM, self.pemData) subj = cert.get_issuer() @@ -1623,8 +1462,8 @@ WpOdIpB8KksUTCzV591Nr1wd def test_set_issuer_wrong_args(self): """ - :py:obj:`X509.set_issuer` raises a :py:obj:`TypeError` if called with the wrong - number of arguments or an argument not of type :py:obj:`X509Name`. + 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) @@ -1634,7 +1473,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_set_issuer(self): """ - :py:obj:`X509.set_issuer` changes the issuer of the certificate to the one + L{X509.set_issuer} changes the issuer of the certificate to the one passed in. """ cert = X509() @@ -1649,8 +1488,8 @@ WpOdIpB8KksUTCzV591Nr1wd def test_get_pubkey_uninitialized(self): """ - When called on a certificate with no public key, :py:obj:`X509.get_pubkey` - raises :py:obj:`OpenSSL.crypto.Error`. + 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) @@ -1658,7 +1497,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_subject_name_hash_wrong_args(self): """ - :py:obj:`X509.subject_name_hash` raises :py:obj:`TypeError` if called with any + L{X509.subject_name_hash} raises L{TypeError} if called with any arguments. """ cert = X509() @@ -1667,7 +1506,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_subject_name_hash(self): """ - :py:obj:`X509.subject_name_hash` returns the hash of the certificate's subject + L{X509.subject_name_hash} returns the hash of the certificate's subject name. """ cert = load_certificate(FILETYPE_PEM, self.pemData) @@ -1680,7 +1519,7 @@ WpOdIpB8KksUTCzV591Nr1wd def test_get_signature_algorithm(self): """ - :py:obj:`X509Type.get_signature_algorithm` returns a string which means + L{X509Type.get_signature_algorithm} returns a string which means the algorithm used to sign the certificate. """ cert = load_certificate(FILETYPE_PEM, self.pemData) @@ -1690,12 +1529,12 @@ WpOdIpB8KksUTCzV591Nr1wd def test_get_undefined_signature_algorithm(self): """ - :py:obj:`X509Type.get_signature_algorithm` raises :py:obj:`ValueError` if the + L{X509Type.get_signature_algorithm} raises L{ValueError} if the signature algorithm is undefined or unknown. """ # This certificate has been modified to indicate a bogus OID in the # signature algorithm field so that OpenSSL does not recognize it. - certPEM = b("""\ + certPEM = """\ -----BEGIN CERTIFICATE----- MIIC/zCCAmigAwIBAgIBATAGBgJ8BQUAMHsxCzAJBgNVBAYTAlNHMREwDwYDVQQK EwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5 @@ -1715,62 +1554,21 @@ jEY7xKfpQngV599k1xhl11IMqizDwu0855agrckg2MCTmOI9DZzDD77tAYb+Dk0O PEVk0Mk/V0aIsDE9bolfCi/i/QWZ3N8s5nTWMNyBBBmoSliWCm4jkkRZRD0ejgTN tgI5 -----END CERTIFICATE----- -""") +""" cert = load_certificate(FILETYPE_PEM, certPEM) self.assertRaises(ValueError, cert.get_signature_algorithm) -class X509StoreTests(TestCase): - """ - Test for :py:obj:`OpenSSL.crypto.X509Store`. - """ - def test_type(self): - """ - :py:obj:`X509StoreType` is a type object. - """ - self.assertIdentical(X509Store, X509StoreType) - self.assertConsistentType(X509Store, 'X509Store') - - - def test_add_cert_wrong_args(self): - store = X509Store() - self.assertRaises(TypeError, store.add_cert) - self.assertRaises(TypeError, store.add_cert, object()) - self.assertRaises(TypeError, store.add_cert, X509(), object()) - - - def test_add_cert(self): - """ - :py:obj:`X509Store.add_cert` adds a :py:obj:`X509` instance to the - certificate store. - """ - cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) - store = X509Store() - store.add_cert(cert) - - - def test_add_cert_rejects_duplicate(self): - """ - :py:obj:`X509Store.add_cert` raises :py:obj:`OpenSSL.crypto.Error` if an - attempt is made to add the same certificate to the store more than once. - """ - cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) - store = X509Store() - store.add_cert(cert) - self.assertRaises(Error, store.add_cert, cert) - - - class PKCS12Tests(TestCase): """ - Test for :py:obj:`OpenSSL.crypto.PKCS12` and :py:obj:`OpenSSL.crypto.load_pkcs12`. + Test for L{OpenSSL.crypto.PKCS12} and L{OpenSSL.crypto.load_pkcs12}. """ pemData = cleartextCertificatePEM + cleartextPrivateKeyPEM def test_type(self): """ - :py:obj:`PKCS12Type` is a type object. + L{PKCS12Type} is a type object. """ self.assertIdentical(PKCS12, PKCS12Type) self.assertConsistentType(PKCS12, 'PKCS12') @@ -1778,7 +1576,7 @@ class PKCS12Tests(TestCase): def test_empty_construction(self): """ - :py:obj:`PKCS12` returns a new instance of :py:obj:`PKCS12` with no certificate, + L{PKCS12} returns a new instance of L{PKCS12} with no certificate, private key, CA certificates, or friendly name. """ p12 = PKCS12() @@ -1790,8 +1588,8 @@ class PKCS12Tests(TestCase): def test_type_errors(self): """ - The :py:obj:`PKCS12` setter functions (:py:obj:`set_certificate`, :py:obj:`set_privatekey`, - :py:obj:`set_ca_certificates`, and :py:obj:`set_friendlyname`) raise :py:obj:`TypeError` + 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() @@ -1811,10 +1609,10 @@ class PKCS12Tests(TestCase): def test_key_only(self): """ - A :py:obj:`PKCS12` with only a private key can be exported using - :py:obj:`PKCS12.export` and loaded again using :py:obj:`load_pkcs12`. + A L{PKCS12} with only a private key can be exported using + L{PKCS12.export} and loaded again using L{load_pkcs12}. """ - passwd = b"blah" + passwd = 'blah' p12 = PKCS12() pkey = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) p12.set_privatekey(pkey) @@ -1838,10 +1636,10 @@ class PKCS12Tests(TestCase): def test_cert_only(self): """ - A :py:obj:`PKCS12` with only a certificate can be exported using - :py:obj:`PKCS12.export` and loaded again using :py:obj:`load_pkcs12`. + A L{PKCS12} with only a certificate can be exported using + L{PKCS12.export} and loaded again using L{load_pkcs12}. """ - passwd = b"blah" + passwd = 'blah' p12 = PKCS12() cert = load_certificate(FILETYPE_PEM, cleartextCertificatePEM) p12.set_certificate(cert) @@ -1892,7 +1690,7 @@ class PKCS12Tests(TestCase): return p12 - def check_recovery(self, p12_str, key=None, cert=None, ca=None, passwd=b"", + 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 @@ -1900,30 +1698,30 @@ class PKCS12Tests(TestCase): """ if key: recovered_key = _runopenssl( - p12_str, b"pkcs12", b"-nocerts", b"-nodes", b"-passin", - b"pass:" + passwd, *extra) + p12_str, "pkcs12", '-nocerts', '-nodes', '-passin', + 'pass:' + passwd, *extra) self.assertEqual(recovered_key[-len(key):], key) if cert: recovered_cert = _runopenssl( - p12_str, b"pkcs12", b"-clcerts", b"-nodes", b"-passin", - b"pass:" + passwd, b"-nokeys", *extra) + p12_str, "pkcs12", '-clcerts', '-nodes', '-passin', + 'pass:' + passwd, '-nokeys', *extra) self.assertEqual(recovered_cert[-len(cert):], cert) if ca: recovered_cert = _runopenssl( - p12_str, b"pkcs12", b"-cacerts", b"-nodes", b"-passin", - b"pass:" + passwd, b"-nokeys", *extra) + 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 :py:obj:`load_pkcs12` and its components extracted and examined. + with L{load_pkcs12} and its components extracted and examined. """ - passwd = b"whatever" + passwd = 'whatever' pem = client_key_pem + client_cert_pem p12_str = _runopenssl( - pem, b"pkcs12", b"-export", b"-clcerts", b"-passout", b"pass:" + passwd) + pem, "pkcs12", '-export', '-clcerts', '-passout', 'pass:' + passwd) p12 = load_pkcs12(p12_str, passwd) # verify self.assertTrue(isinstance(p12, PKCS12)) @@ -1936,20 +1734,20 @@ class PKCS12Tests(TestCase): def test_load_pkcs12_garbage(self): """ - :py:obj:`load_pkcs12` raises :py:obj:`OpenSSL.crypto.Error` when passed a string + 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, b'fruit loops', passwd) + 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): """ - :py:obj:`PKCS12.set_certificate` replaces the certificate in a PKCS12 cluster. - :py:obj:`PKCS12.set_privatekey` replaces the private key. - :py:obj:`PKCS12.set_ca_certificates` replaces the CA certificates. + 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)) @@ -1967,11 +1765,11 @@ class PKCS12Tests(TestCase): def test_friendly_name(self): """ - The *friendlyName* of a PKCS12 can be set and retrieved via - :py:obj:`PKCS12.get_friendlyname` and :py:obj:`PKCS12_set_friendlyname`, and a - :py:obj:`PKCS12` with a friendly name set can be dumped with :py:obj:`PKCS12.export`. + 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 = b'Dogmeat[]{}!@#$%^&*()~`?/.,<>-_+=";:' + 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) @@ -1995,7 +1793,7 @@ class PKCS12Tests(TestCase): export. """ p12 = self.gen_pkcs12(client_cert_pem, client_key_pem, root_cert_pem) - passwd = b"" + 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) @@ -2007,7 +1805,7 @@ class PKCS12Tests(TestCase): def test_removing_ca_cert(self): """ - Passing :py:obj:`None` to :py:obj:`PKCS12.set_ca_certificates` removes all CA + 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) @@ -2017,22 +1815,22 @@ class PKCS12Tests(TestCase): def test_export_without_mac(self): """ - Exporting a PKCS12 with a :py:obj:`maciter` of ``-1`` excludes the MAC + Exporting a PKCS12 with a C{maciter} of C{-1} excludes the MAC entirely. """ - passwd = b"Lake Michigan" + 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=(b"-nomacver",)) + passwd=passwd, extra=('-nomacver',)) def test_load_without_mac(self): """ Loading a PKCS12 without a MAC does something other than crash. """ - passwd = b"Lake Michigan" + 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: @@ -2055,27 +1853,27 @@ class PKCS12Tests(TestCase): """ 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) + p12.set_ca_certificates([]) + self.assertEqual((), p12.get_ca_certificates()) + dumped_p12 = p12.export(passphrase=passwd, iter=3) + self.check_recovery( + dumped_p12, key=server_key_pem, cert=server_cert_pem, + passwd=passwd) def test_export_without_args(self): """ - All the arguments to :py:obj:`PKCS12.export` are optional. + 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=b"") + dumped_p12, key=server_key_pem, cert=server_cert_pem, passwd='') def test_key_cert_mismatch(self): """ - :py:obj:`PKCS12.export` raises an exception when a key and certificate + 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) @@ -2084,39 +1882,39 @@ class PKCS12Tests(TestCase): # These quoting functions taken directly from Twisted's twisted.python.win32. -_cmdLineQuoteRe = re.compile(br'(\\*)"') -_cmdLineQuoteRe2 = re.compile(br'(\\+)\Z') +_cmdLineQuoteRe = re.compile(r'(\\*)"') +_cmdLineQuoteRe2 = re.compile(r'(\\+)\Z') def cmdLineQuote(s): """ Internal method for quoting a single command-line argument. - See http://www.perlmonks.org/?node_id=764004 - - :type: :py:obj:`str` - :param s: A single unquoted string to quote for something that is expecting + @type: C{str} + @param s: A single unquoted string to quote for something that is expecting cmd.exe-style quoting - :rtype: :py:obj:`str` - :return: A cmd.exe-style quoted string + @rtype: C{str} + @return: A cmd.exe-style quoted string + + @see: U{http://www.perlmonks.org/?node_id=764004} """ - s = _cmdLineQuoteRe2.sub(br"\1\1", _cmdLineQuoteRe.sub(br'\1\1\\"', s)) - return b'"' + s + b'"' + 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 :py:obj:`reactor.spawnProcess` to - match the child process's :py:obj:`sys.argv` properly. + a similar API. This allows the list passed to C{reactor.spawnProcess} to + match the child process's C{sys.argv} properly. - :type arguments: :py:obj:`iterable` of :py:obj:`str` - :param arguments: An iterable of unquoted arguments to quote + @type arguments: C{iterable} of C{str} + @param arguments: An iterable of unquoted arguments to quote - :rtype: :py:obj:`str` - :return: A space-delimited string containing quoted versions of :py:obj:`arguments` + @rtype: C{str} + @return: A space-delimited string containing quoted versions of L{arguments} """ - return b' '.join(map(cmdLineQuote, arguments)) + return ' '.join(map(cmdLineQuote, arguments)) @@ -2126,37 +1924,33 @@ def _runopenssl(pem, *args): the given PEM to its stdin. Not safe for quotes. """ if os.name == 'posix': - command = b"openssl " + b" ".join([ - (b"'" + arg.replace(b"'", b"'\\''") + b"'") - for arg in args]) + command = "openssl " + " ".join([ + "'%s'" % (arg.replace("'", "'\\''"),) for arg in args]) else: - command = b"openssl " + quoteArguments(args) - proc = Popen(native(command), shell=True, stdin=PIPE, stdout=PIPE) + command = "openssl " + quoteArguments(args) + proc = Popen(command, shell=True, stdin=PIPE, stdout=PIPE) proc.stdin.write(pem) proc.stdin.close() - output = proc.stdout.read() - proc.stdout.close() - proc.wait() - return output + return proc.stdout.read() class FunctionTests(TestCase): """ - Tests for free-functions in the :py:obj:`OpenSSL.crypto` module. + Tests for free-functions in the L{OpenSSL.crypto} module. """ def test_load_privatekey_invalid_format(self): """ - :py:obj:`load_privatekey` raises :py:obj:`ValueError` if passed an unknown filetype. + 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): """ - :py:obj:`load_privatekey` raises :py:obj:`TypeError` if passed a passphrase that is - neither a :py:obj:`str` nor a callable. + L{load_privatekey} raises L{TypeError} if passed a passphrase that is + neither a c{str} nor a callable. """ self.assertRaises( TypeError, @@ -2166,7 +1960,7 @@ class FunctionTests(TestCase): def test_load_privatekey_wrong_args(self): """ - :py:obj:`load_privatekey` raises :py:obj:`TypeError` if called with the wrong number + L{load_privatekey} raises L{TypeError} if called with the wrong number of arguments. """ self.assertRaises(TypeError, load_privatekey) @@ -2174,7 +1968,7 @@ class FunctionTests(TestCase): def test_load_privatekey_wrongPassphrase(self): """ - :py:obj:`load_privatekey` raises :py:obj:`OpenSSL.crypto.Error` when it is passed an + L{load_privatekey} raises L{OpenSSL.crypto.Error} when it is passed an encrypted PEM and an incorrect passphrase. """ self.assertRaises( @@ -2182,21 +1976,9 @@ class FunctionTests(TestCase): load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, b("quack")) - def test_load_privatekey_passphraseWrongType(self): - """ - :py:obj:`load_privatekey` raises :py:obj:`ValueError` when it is passed a passphrase - with a private key encoded in a format, that doesn't support - encryption. - """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - blob = dump_privatekey(FILETYPE_ASN1, key) - self.assertRaises(ValueError, - load_privatekey, FILETYPE_ASN1, blob, "secret") - - def test_load_privatekey_passphrase(self): """ - :py:obj:`load_privatekey` can create a :py:obj:`PKey` object from an encrypted PEM + L{load_privatekey} can create a L{PKey} object from an encrypted PEM string if given the passphrase. """ key = load_privatekey( @@ -2205,28 +1987,16 @@ class FunctionTests(TestCase): self.assertTrue(isinstance(key, PKeyType)) - def test_load_privatekey_passphrase_exception(self): - """ - If the passphrase callback raises an exception, that exception is raised - by :py:obj:`load_privatekey`. - """ - def cb(ignored): - raise ArithmeticError - - self.assertRaises(ArithmeticError, - load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb) - - def test_load_privatekey_wrongPassphraseCallback(self): """ - :py:obj:`load_privatekey` raises :py:obj:`OpenSSL.crypto.Error` when it - is passed an encrypted PEM and a passphrase callback which returns an - incorrect passphrase. + 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 b("quack") + return "quack" self.assertRaises( Error, load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb) @@ -2235,7 +2005,7 @@ class FunctionTests(TestCase): def test_load_privatekey_passphraseCallback(self): """ - :py:obj:`load_privatekey` can create a :py:obj:`PKey` object from an encrypted PEM + L{load_privatekey} can create a L{PKey} object from an encrypted PEM string if given a passphrase callback which returns the correct password. """ @@ -2248,55 +2018,57 @@ class FunctionTests(TestCase): self.assertEqual(called, [False]) - def test_load_privatekey_passphrase_wrong_return_type(self): + def test_load_privatekey_passphrase_exception(self): """ - :py:obj:`load_privatekey` raises :py:obj:`ValueError` if the passphrase - callback returns something other than a byte string. + 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( - ValueError, + Error, load_privatekey, - FILETYPE_PEM, encryptedPrivateKeyPEM, lambda *args: 3) + FILETYPE_PEM, encryptedPrivateKeyPEM, broken) def test_dump_privatekey_wrong_args(self): """ - :py:obj:`dump_privatekey` raises :py:obj:`TypeError` if called with the wrong number + L{dump_privatekey} raises L{TypeError} if called with the wrong number of arguments. """ self.assertRaises(TypeError, dump_privatekey) - # If cipher name is given, password is required. - self.assertRaises( - TypeError, dump_privatekey, FILETYPE_PEM, PKey(), GOOD_CIPHER) def test_dump_privatekey_unknown_cipher(self): """ - :py:obj:`dump_privatekey` raises :py:obj:`ValueError` if called with an unrecognized + 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, BAD_CIPHER, "passphrase") + FILETYPE_PEM, key, "zippers", "passphrase") def test_dump_privatekey_invalid_passphrase_type(self): """ - :py:obj:`dump_privatekey` raises :py:obj:`TypeError` if called with a passphrase which - is neither a :py:obj:`str` nor a callable. + 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, GOOD_CIPHER, object()) + dump_privatekey, FILETYPE_PEM, key, "blowfish", object()) def test_dump_privatekey_invalid_filetype(self): """ - :py:obj:`dump_privatekey` raises :py:obj:`ValueError` if called with an unrecognized + L{dump_privatekey} raises L{ValueError} if called with an unrecognized filetype. """ key = PKey() @@ -2304,122 +2076,81 @@ class FunctionTests(TestCase): self.assertRaises(ValueError, dump_privatekey, 100, key) - def test_load_privatekey_passphraseCallbackLength(self): - """ - :py:obj:`crypto.load_privatekey` should raise an error when the passphrase - provided by the callback is too long, not silently truncate it. - """ - def cb(ignored): - return "a" * 1025 - - self.assertRaises(ValueError, - load_privatekey, FILETYPE_PEM, encryptedPrivateKeyPEM, cb) - - def test_dump_privatekey_passphrase(self): """ - :py:obj:`dump_privatekey` writes an encrypted PEM when given a passphrase. + 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, GOOD_CIPHER, passphrase) - self.assertTrue(isinstance(pem, binary_type)) + 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_privatekey_passphraseWrongType(self): - """ - :py:obj:`dump_privatekey` raises :py:obj:`ValueError` when it is passed a passphrase - with a private key encoded in a format, that doesn't support - encryption. - """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - self.assertRaises(ValueError, - dump_privatekey, FILETYPE_ASN1, key, GOOD_CIPHER, "secret") - - def test_dump_certificate(self): """ - :py:obj:`dump_certificate` writes PEM, DER, and text. + 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, b"x509", b"-outform", b"DER") + 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, b"x509", b"-noout", b"-text") + good_text = _runopenssl(dumped_pem, "x509", "-noout", "-text") self.assertEqual(dumped_text, good_text) - def test_dump_privatekey_pem(self): + def test_dump_privatekey(self): """ - :py:obj:`dump_privatekey` writes a PEM + L{dump_privatekey} writes a PEM, DER, and text. """ key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) self.assertTrue(key.check()) dumped_pem = dump_privatekey(FILETYPE_PEM, key) self.assertEqual(dumped_pem, cleartextPrivateKeyPEM) - - - def test_dump_privatekey_asn1(self): - """ - :py:obj:`dump_privatekey` writes a DER - """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - dumped_pem = dump_privatekey(FILETYPE_PEM, key) - dumped_der = dump_privatekey(FILETYPE_ASN1, key) # XXX This OpenSSL call writes "writing RSA key" to standard out. Sad. - good_der = _runopenssl(dumped_pem, b"rsa", b"-outform", b"DER") + 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) - - - def test_dump_privatekey_text(self): - """ - :py:obj:`dump_privatekey` writes a text - """ - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - dumped_pem = dump_privatekey(FILETYPE_PEM, key) - dumped_text = dump_privatekey(FILETYPE_TEXT, key) - good_text = _runopenssl(dumped_pem, b"rsa", b"-noout", b"-text") + good_text = _runopenssl(dumped_pem, "rsa", "-noout", "-text") self.assertEqual(dumped_text, good_text) def test_dump_certificate_request(self): """ - :py:obj:`dump_certificate_request` writes a PEM, DER, and text. + 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, b"req", b"-outform", b"DER") + 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, b"req", b"-noout", b"-text") + 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): """ - :py:obj:`dump_privatekey` writes an encrypted PEM when given a callback which + L{dump_privatekey} writes an encrypted PEM when given a callback which returns the correct passphrase. """ passphrase = b("foo") @@ -2428,8 +2159,8 @@ class FunctionTests(TestCase): called.append(writing) return passphrase key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - pem = dump_privatekey(FILETYPE_PEM, key, GOOD_CIPHER, cb) - self.assertTrue(isinstance(pem, binary_type)) + 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)) @@ -2437,71 +2168,23 @@ class FunctionTests(TestCase): self.assertEqual(loadedKey.bits(), key.bits()) - def test_dump_privatekey_passphrase_exception(self): - """ - :py:obj:`dump_privatekey` should not overwrite the exception raised - by the passphrase callback. - """ - def cb(ignored): - raise ArithmeticError - - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - self.assertRaises(ArithmeticError, - dump_privatekey, FILETYPE_PEM, key, GOOD_CIPHER, cb) - - - def test_dump_privatekey_passphraseCallbackLength(self): - """ - :py:obj:`crypto.dump_privatekey` should raise an error when the passphrase - provided by the callback is too long, not silently truncate it. - """ - def cb(ignored): - return "a" * 1025 - - key = load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM) - self.assertRaises(ValueError, - dump_privatekey, FILETYPE_PEM, key, GOOD_CIPHER, cb) - - def test_load_pkcs7_data(self): """ - :py:obj:`load_pkcs7_data` accepts a PKCS#7 string and returns an instance of - :py:obj:`PKCS7Type`. + 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)) - def test_load_pkcs7_data_invalid(self): - """ - If the data passed to :py:obj:`load_pkcs7_data` is invalid, - :py:obj:`Error` is raised. - """ - self.assertRaises(Error, load_pkcs7_data, FILETYPE_PEM, b"foo") - - - -class LoadCertificateTests(TestCase): - """ - Tests for :py:obj:`load_certificate_request`. - """ - def test_badFileType(self): - """ - If the file type passed to :py:obj:`load_certificate_request` is - neither :py:obj:`FILETYPE_PEM` nor :py:obj:`FILETYPE_ASN1` then - :py:class:`ValueError` is raised. - """ - self.assertRaises(ValueError, load_certificate_request, object(), b"") - - class PKCS7Tests(TestCase): """ - Tests for :py:obj:`PKCS7Type`. + Tests for L{PKCS7Type}. """ def test_type(self): """ - :py:obj:`PKCS7Type` is a type object. + L{PKCS7Type} is a type object. """ self.assertTrue(isinstance(PKCS7Type, type)) self.assertEqual(PKCS7Type.__name__, 'PKCS7') @@ -2514,7 +2197,7 @@ class PKCS7Tests(TestCase): def test_type_is_signed_wrong_args(self): """ - :py:obj:`PKCS7Type.type_is_signed` raises :py:obj:`TypeError` if called with any + L{PKCS7Type.type_is_signed} raises L{TypeError} if called with any arguments. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) @@ -2523,8 +2206,8 @@ class PKCS7Tests(TestCase): def test_type_is_signed(self): """ - :py:obj:`PKCS7Type.type_is_signed` returns :py:obj:`True` if the PKCS7 object is of - the type *signed*. + 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()) @@ -2532,7 +2215,7 @@ class PKCS7Tests(TestCase): def test_type_is_enveloped_wrong_args(self): """ - :py:obj:`PKCS7Type.type_is_enveloped` raises :py:obj:`TypeError` if called with any + L{PKCS7Type.type_is_enveloped} raises L{TypeError} if called with any arguments. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) @@ -2541,8 +2224,8 @@ class PKCS7Tests(TestCase): def test_type_is_enveloped(self): """ - :py:obj:`PKCS7Type.type_is_enveloped` returns :py:obj:`False` if the PKCS7 object is - not of the type *enveloped*. + 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()) @@ -2550,7 +2233,7 @@ class PKCS7Tests(TestCase): def test_type_is_signedAndEnveloped_wrong_args(self): """ - :py:obj:`PKCS7Type.type_is_signedAndEnveloped` raises :py:obj:`TypeError` if called + L{PKCS7Type.type_is_signedAndEnveloped} raises L{TypeError} if called with any arguments. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) @@ -2559,8 +2242,8 @@ class PKCS7Tests(TestCase): def test_type_is_signedAndEnveloped(self): """ - :py:obj:`PKCS7Type.type_is_signedAndEnveloped` returns :py:obj:`False` if the PKCS7 - object is not of the type *signed and enveloped*. + 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()) @@ -2568,7 +2251,7 @@ class PKCS7Tests(TestCase): def test_type_is_data(self): """ - :py:obj:`PKCS7Type.type_is_data` returns :py:obj:`False` if the PKCS7 object is not of + 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) @@ -2577,7 +2260,7 @@ class PKCS7Tests(TestCase): def test_type_is_data_wrong_args(self): """ - :py:obj:`PKCS7Type.type_is_data` raises :py:obj:`TypeError` if called with any + L{PKCS7Type.type_is_data} raises L{TypeError} if called with any arguments. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) @@ -2586,7 +2269,7 @@ class PKCS7Tests(TestCase): def test_get_type_name_wrong_args(self): """ - :py:obj:`PKCS7Type.get_type_name` raises :py:obj:`TypeError` if called with any + L{PKCS7Type.get_type_name} raises L{TypeError} if called with any arguments. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) @@ -2595,7 +2278,7 @@ class PKCS7Tests(TestCase): def test_get_type_name(self): """ - :py:obj:`PKCS7Type.get_type_name` returns a :py:obj:`str` giving the type name. + 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')) @@ -2604,7 +2287,7 @@ class PKCS7Tests(TestCase): def test_attribute(self): """ If an attribute other than one of the methods tested here is accessed on - an instance of :py:obj:`PKCS7Type`, :py:obj:`AttributeError` is raised. + an instance of L{PKCS7Type}, L{AttributeError} is raised. """ pkcs7 = load_pkcs7_data(FILETYPE_PEM, pkcs7Data) self.assertRaises(AttributeError, getattr, pkcs7, "foo") @@ -2613,18 +2296,18 @@ class PKCS7Tests(TestCase): class NetscapeSPKITests(TestCase, _PKeyInteractionTestsMixin): """ - Tests for :py:obj:`OpenSSL.crypto.NetscapeSPKI`. + Tests for L{OpenSSL.crypto.NetscapeSPKI}. """ def signable(self): """ - Return a new :py:obj:`NetscapeSPKI` for use with signing tests. + Return a new L{NetscapeSPKI} for use with signing tests. """ return NetscapeSPKI() def test_type(self): """ - :py:obj:`NetscapeSPKI` and :py:obj:`NetscapeSPKIType` refer to the same type object + 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) @@ -2633,7 +2316,7 @@ class NetscapeSPKITests(TestCase, _PKeyInteractionTestsMixin): def test_construction(self): """ - :py:obj:`NetscapeSPKI` returns an instance of :py:obj:`NetscapeSPKIType`. + L{NetscapeSPKI} returns an instance of L{NetscapeSPKIType}. """ nspki = NetscapeSPKI() self.assertTrue(isinstance(nspki, NetscapeSPKIType)) @@ -2641,8 +2324,8 @@ class NetscapeSPKITests(TestCase, _PKeyInteractionTestsMixin): def test_invalid_attribute(self): """ - Accessing a non-existent attribute of a :py:obj:`NetscapeSPKI` instance causes - an :py:obj:`AttributeError` to be raised. + 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) @@ -2650,21 +2333,21 @@ class NetscapeSPKITests(TestCase, _PKeyInteractionTestsMixin): def test_b64_encode(self): """ - :py:obj:`NetscapeSPKI.b64_encode` encodes the certificate to a base64 blob. + L{NetscapeSPKI.b64_encode} encodes the certificate to a base64 blob. """ nspki = NetscapeSPKI() blob = nspki.b64_encode() - self.assertTrue(isinstance(blob, binary_type)) + self.assertTrue(isinstance(blob, bytes)) class RevokedTests(TestCase): """ - Tests for :py:obj:`OpenSSL.crypto.Revoked` + Tests for L{OpenSSL.crypto.Revoked} """ def test_construction(self): """ - Confirm we can create :py:obj:`OpenSSL.crypto.Revoked`. Check + Confirm we can create L{OpenSSL.crypto.Revoked}. Check that it is empty. """ revoked = Revoked() @@ -2677,8 +2360,8 @@ class RevokedTests(TestCase): def test_construction_wrong_args(self): """ - Calling :py:obj:`OpenSSL.crypto.Revoked` with any arguments results - in a :py:obj:`TypeError` being raised. + 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) @@ -2688,7 +2371,7 @@ class RevokedTests(TestCase): def test_serial(self): """ Confirm we can set and get serial numbers from - :py:obj:`OpenSSL.crypto.Revoked`. Confirm errors are handled + L{OpenSSL.crypto.Revoked}. Confirm errors are handled with grace. """ revoked = Revoked() @@ -2711,7 +2394,7 @@ class RevokedTests(TestCase): def test_date(self): """ Confirm we can set and get revocation dates from - :py:obj:`OpenSSL.crypto.Revoked`. Confirm errors are handled + L{OpenSSL.crypto.Revoked}. Confirm errors are handled with grace. """ revoked = Revoked() @@ -2728,7 +2411,7 @@ class RevokedTests(TestCase): def test_reason(self): """ Confirm we can set and get revocation reasons from - :py:obj:`OpenSSL.crypto.Revoked`. The "get" need to work + L{OpenSSL.crypto.Revoked}. The "get" need to work as "set". Likewise, each reason of all_reasons() must work. """ revoked = Revoked() @@ -2748,9 +2431,9 @@ class RevokedTests(TestCase): def test_set_reason_wrong_arguments(self): """ - Calling :py:obj:`OpenSSL.crypto.Revoked.set_reason` with other than + Calling L{OpenSSL.crypto.Revoked.set_reason} with other than one argument, or an argument which isn't a valid reason, - results in :py:obj:`TypeError` or :py:obj:`ValueError` being raised. + results in L{TypeError} or L{ValueError} being raised. """ revoked = Revoked() self.assertRaises(TypeError, revoked.set_reason, 100) @@ -2759,8 +2442,8 @@ class RevokedTests(TestCase): def test_get_reason_wrong_arguments(self): """ - Calling :py:obj:`OpenSSL.crypto.Revoked.get_reason` with any - arguments results in :py:obj:`TypeError` being raised. + 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) @@ -2771,14 +2454,14 @@ class RevokedTests(TestCase): class CRLTests(TestCase): """ - Tests for :py:obj:`OpenSSL.crypto.CRL` + 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 :py:obj:`OpenSSL.crypto.CRL`. Check + Confirm we can create L{OpenSSL.crypto.CRL}. Check that it is empty """ crl = CRL() @@ -2788,8 +2471,8 @@ class CRLTests(TestCase): def test_construction_wrong_args(self): """ - Calling :py:obj:`OpenSSL.crypto.CRL` with any number of arguments - results in a :py:obj:`TypeError` being raised. + 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, "") @@ -2812,14 +2495,14 @@ class CRLTests(TestCase): # PEM format dumped_crl = crl.export(self.cert, self.pkey, days=20) - text = _runopenssl(dumped_crl, b"crl", b"-noout", b"-text") + 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, b"crl", b"-noout", b"-text", b"-inform", b"DER") + 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')) @@ -2829,19 +2512,10 @@ class CRLTests(TestCase): self.assertEqual(text, dumped_text) - def test_export_invalid(self): - """ - If :py:obj:`CRL.export` is used with an uninitialized :py:obj:`X509` - instance, :py:obj:`OpenSSL.crypto.Error` is raised. - """ - crl = CRL() - self.assertRaises(Error, crl.export, X509(), PKey()) - - def test_add_revoked_keyword(self): """ - :py:obj:`OpenSSL.CRL.add_revoked` accepts its single argument as the - ``revoked`` keyword argument. + L{OpenSSL.CRL.add_revoked} accepts its single argument as the + I{revoked} keyword argument. """ crl = CRL() revoked = Revoked() @@ -2851,10 +2525,10 @@ class CRLTests(TestCase): def test_export_wrong_args(self): """ - Calling :py:obj:`OpenSSL.CRL.export` with fewer than two or more than + 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 :py:obj:`TypeError` being raised. + expects, results in a L{TypeError} being raised. """ crl = CRL() self.assertRaises(TypeError, crl.export) @@ -2869,9 +2543,9 @@ class CRLTests(TestCase): def test_export_unknown_filetype(self): """ - Calling :py:obj:`OpenSSL.CRL.export` with a file type other than - :py:obj:`FILETYPE_PEM`, :py:obj:`FILETYPE_ASN1`, or :py:obj:`FILETYPE_TEXT` results - in a :py:obj:`ValueError` being raised. + 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) @@ -2880,7 +2554,7 @@ class CRLTests(TestCase): def test_get_revoked(self): """ Use python to create a simple CRL with two revocations. - Get back the :py:obj:`Revoked` using :py:obj:`OpenSSL.CRL.get_revoked` and + Get back the L{Revoked} using L{OpenSSL.CRL.get_revoked} and verify them. """ crl = CRL() @@ -2906,8 +2580,8 @@ class CRLTests(TestCase): def test_get_revoked_wrong_args(self): """ - Calling :py:obj:`OpenSSL.CRL.get_revoked` with any arguments results - in a :py:obj:`TypeError` being raised. + 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) @@ -2918,8 +2592,8 @@ class CRLTests(TestCase): def test_add_revoked_wrong_args(self): """ - Calling :py:obj:`OpenSSL.CRL.add_revoked` with other than one - argument results in a :py:obj:`TypeError` being raised. + 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) @@ -2940,7 +2614,7 @@ class CRLTests(TestCase): self.assertEqual(revs[1].get_serial(), b('0100')) self.assertEqual(revs[1].get_reason(), b('Superseded')) - der = _runopenssl(crlData, b"crl", b"-outform", b"DER") + der = _runopenssl(crlData, "crl", "-outform", "DER") crl = load_crl(FILETYPE_ASN1, der) revs = crl.get_revoked() self.assertEqual(len(revs), 2) @@ -2952,8 +2626,8 @@ class CRLTests(TestCase): def test_load_crl_wrong_args(self): """ - Calling :py:obj:`OpenSSL.crypto.load_crl` with other than two - arguments results in a :py:obj:`TypeError` being raised. + 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) @@ -2962,28 +2636,27 @@ class CRLTests(TestCase): def test_load_crl_bad_filetype(self): """ - Calling :py:obj:`OpenSSL.crypto.load_crl` with an unknown file type - raises a :py:obj:`ValueError`. + 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 :py:obj:`OpenSSL.crypto.load_crl` with file data which can't - be loaded raises a :py:obj:`OpenSSL.crypto.Error`. + 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, b"hello, world") - + self.assertRaises(Error, load_crl, FILETYPE_PEM, "hello, world") class SignVerifyTests(TestCase): """ - Tests for :py:obj:`OpenSSL.crypto.sign` and :py:obj:`OpenSSL.crypto.verify`. + Tests for L{OpenSSL.crypto.sign} and L{OpenSSL.crypto.verify}. """ def test_sign_verify(self): """ - :py:obj:`sign` generates a cryptographic signature which :py:obj:`verify` can check. + 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 " @@ -3024,7 +2697,7 @@ class SignVerifyTests(TestCase): def test_sign_nulls(self): """ - :py:obj:`sign` produces a signature for a string with embedded nulls. + L{sign} produces a signature for a string with embedded nulls. """ content = b("Watch out! \0 Did you see it?") priv_key = load_privatekey(FILETYPE_PEM, root_key_pem) diff --git a/OpenSSL/test/test_rand.py b/OpenSSL/test/test_rand.py index c52cb6b..00fc6d1 100644 --- a/OpenSSL/test/test_rand.py +++ b/OpenSSL/test/test_rand.py @@ -2,13 +2,12 @@ # See LICENSE for details. """ -Unit tests for :py:obj:`OpenSSL.rand`. +Unit tests for L{OpenSSL.rand}. """ from unittest import main import os import stat -import sys from OpenSSL.test.util import TestCase, b from OpenSSL import rand @@ -17,21 +16,14 @@ from OpenSSL import rand class RandTests(TestCase): def test_bytes_wrong_args(self): """ - :py:obj:`OpenSSL.rand.bytes` raises :py:obj:`TypeError` if called with the wrong - number of arguments or with a non-:py:obj:`int` argument. + 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) - - def test_insufficientMemory(self): - """ - :py:obj:`OpenSSL.rand.bytes` raises :py:obj:`MemoryError` if more bytes - are requested than will fit in memory. - """ - self.assertRaises(MemoryError, rand.bytes, sys.maxsize) - + # XXX Test failure of the malloc() in rand_bytes. def test_bytes(self): """ @@ -52,7 +44,7 @@ class RandTests(TestCase): def test_add_wrong_args(self): """ When called with the wrong number of arguments, or with arguments not of - type :py:obj:`str` and :py:obj:`int`, :py:obj:`OpenSSL.rand.add` raises :py:obj:`TypeError`. + 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) @@ -62,15 +54,15 @@ class RandTests(TestCase): def test_add(self): """ - :py:obj:`OpenSSL.rand.add` adds entropy to the PRNG. + 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-:py:obj:`str` - argument, :py:obj:`OpenSSL.rand.seed` raises :py:obj:`TypeError`. + 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) @@ -79,14 +71,14 @@ class RandTests(TestCase): def test_seed(self): """ - :py:obj:`OpenSSL.rand.seed` adds entropy to the PRNG. + L{OpenSSL.rand.seed} adds entropy to the PRNG. """ rand.seed(b('milk shake')) def test_status_wrong_args(self): """ - :py:obj:`OpenSSL.rand.status` raises :py:obj:`TypeError` when called with any + L{OpenSSL.rand.status} raises L{TypeError} when called with any arguments. """ self.assertRaises(TypeError, rand.status, None) @@ -94,8 +86,8 @@ class RandTests(TestCase): def test_status(self): """ - :py:obj:`OpenSSL.rand.status` returns :py:obj:`True` if the PRNG has sufficient - entropy, :py:obj:`False` otherwise. + 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 @@ -105,8 +97,8 @@ class RandTests(TestCase): def test_egd_wrong_args(self): """ - :py:obj:`OpenSSL.rand.egd` raises :py:obj:`TypeError` when called with the wrong - number of arguments or with arguments not of type :py:obj:`str` and :py:obj:`int`. + 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) @@ -117,8 +109,8 @@ class RandTests(TestCase): def test_egd_missing(self): """ - :py:obj:`OpenSSL.rand.egd` returns :py:obj:`0` or :py:obj:`-1` if the - EGD socket passed to it does not exist. + 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) @@ -127,22 +119,9 @@ class RandTests(TestCase): "%r not in %r" % (result, expected)) - def test_egd_missing_and_bytes(self): - """ - :py:obj:`OpenSSL.rand.egd` returns :py:obj:`0` or :py:obj:`-1` if the - EGD socket passed to it does not exist even if a size argument is - explicitly passed. - """ - result = rand.egd(self.mktemp(), 1024) - expected = (-1, 0) - self.assertTrue( - result in expected, - "%r not in %r" % (result, expected)) - - def test_cleanup_wrong_args(self): """ - :py:obj:`OpenSSL.rand.cleanup` raises :py:obj:`TypeError` when called with any + L{OpenSSL.rand.cleanup} raises L{TypeError} when called with any arguments. """ self.assertRaises(TypeError, rand.cleanup, None) @@ -150,16 +129,16 @@ class RandTests(TestCase): def test_cleanup(self): """ - :py:obj:`OpenSSL.rand.cleanup` releases the memory used by the PRNG and returns - :py:obj:`None`. + 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): """ - :py:obj:`OpenSSL.rand.load_file` raises :py:obj:`TypeError` when called the wrong - number of arguments or arguments not of type :py:obj:`str` and :py:obj:`int`. + 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) @@ -169,8 +148,8 @@ class RandTests(TestCase): def test_write_file_wrong_args(self): """ - :py:obj:`OpenSSL.rand.write_file` raises :py:obj:`TypeError` when called with the - wrong number of arguments or a non-:py:obj:`str` argument. + 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) @@ -190,7 +169,7 @@ class RandTests(TestCase): rand.write_file(tmpfile) # Verify length of written file size = os.stat(tmpfile)[stat.ST_SIZE] - self.assertEqual(1024, size) + self.assertEquals(size, 1024) # Read random bytes from file rand.load_file(tmpfile) rand.load_file(tmpfile, 4) # specify a length diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py index a6f0127..2ab67fd 100644 --- a/OpenSSL/test/test_ssl.py +++ b/OpenSSL/test/test_ssl.py @@ -2,46 +2,35 @@ # See LICENSE for details. """ -Unit tests for :py:obj:`OpenSSL.SSL`. +Unit tests for L{OpenSSL.SSL}. """ -from gc import collect, get_referrers -from errno import ECONNREFUSED, EINPROGRESS, EWOULDBLOCK, EPIPE, ESHUTDOWN +from gc import collect +from errno import ECONNREFUSED, EINPROGRESS, EWOULDBLOCK from sys import platform, version_info -from socket import SHUT_RDWR, error, socket +from socket import error, socket from os import makedirs from os.path import join from unittest import main from weakref import ref -from six import PY3, u - from OpenSSL.crypto import TYPE_RSA, FILETYPE_PEM -from OpenSSL.crypto import PKey, X509, X509Extension, X509Store +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 OPENSSL_VERSION_NUMBER, SSLEAY_VERSION, SSLEAY_CFLAGS from OpenSSL.SSL import SSLEAY_PLATFORM, SSLEAY_DIR, SSLEAY_BUILT_ON from OpenSSL.SSL import SENT_SHUTDOWN, RECEIVED_SHUTDOWN -from OpenSSL.SSL import ( - SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD, - TLSv1_1_METHOD, TLSv1_2_METHOD) -from OpenSSL.SSL import OP_SINGLE_DH_USE, OP_NO_SSLv2, OP_NO_SSLv3 +from OpenSSL.SSL import 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, VERIFY_NONE) - -from OpenSSL.SSL import ( - SESS_CACHE_OFF, SESS_CACHE_CLIENT, SESS_CACHE_SERVER, SESS_CACHE_BOTH, - SESS_CACHE_NO_AUTO_CLEAR, SESS_CACHE_NO_INTERNAL_LOOKUP, - SESS_CACHE_NO_INTERNAL_STORE, SESS_CACHE_NO_INTERNAL) - -from OpenSSL.SSL import ( - Error, SysCallError, WantReadError, WantWriteError, ZeroReturnError) from OpenSSL.SSL import ( - Context, ContextType, Session, Connection, ConnectionType, SSLeay_version) + Error, SysCallError, WantReadError, ZeroReturnError, SSLeay_version) +from OpenSSL.SSL import Context, ContextType, Connection, ConnectionType -from OpenSSL.test.util import TestCase, b +from OpenSSL.test.util import TestCase, bytes, b from OpenSSL.test.test_crypto import ( cleartextCertificatePEM, cleartextPrivateKeyPEM) from OpenSSL.test.test_crypto import ( @@ -61,21 +50,6 @@ try: except ImportError: OP_NO_TICKET = None -try: - from OpenSSL.SSL import OP_NO_COMPRESSION -except ImportError: - OP_NO_COMPRESSION = None - -try: - from OpenSSL.SSL import MODE_RELEASE_BUFFERS -except ImportError: - MODE_RELEASE_BUFFERS = None - -try: - from OpenSSL.SSL import OP_NO_TLSv1, OP_NO_TLSv1_1, OP_NO_TLSv1_2 -except ImportError: - OP_NO_TLSv1 = OP_NO_TLSv1_1 = OP_NO_TLSv1_2 = None - from OpenSSL.SSL import ( SSL_ST_CONNECT, SSL_ST_ACCEPT, SSL_ST_MASK, SSL_ST_INIT, SSL_ST_BEFORE, SSL_ST_OK, SSL_ST_RENEGOTIATE, @@ -198,30 +172,16 @@ class _LoopbackMixin: Helper mixin which defines methods for creating a connected socket pair and for forcing two connected SSL sockets to talk to each other via memory BIOs. """ - def _loopbackClientFactory(self, socket): - client = Connection(Context(TLSv1_METHOD), socket) - client.set_connect_state() - return client - + def _loopback(self): + (server, client) = socket_pair() - def _loopbackServerFactory(self, socket): ctx = Context(TLSv1_METHOD) ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem)) ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem)) - server = Connection(ctx, socket) + server = Connection(ctx, server) server.set_accept_state() - return server - - - def _loopback(self, serverFactory=None, clientFactory=None): - if serverFactory is None: - serverFactory = self._loopbackServerFactory - if clientFactory is None: - clientFactory = self._loopbackClientFactory - - (server, client) = socket_pair() - server = serverFactory(server) - client = clientFactory(client) + client = Connection(Context(TLSv1_METHOD), client) + client.set_connect_state() handshake(client, server) @@ -232,10 +192,10 @@ class _LoopbackMixin: def _interactInMemory(self, client_conn, server_conn): """ - Try to read application bytes from each of the two :py:obj:`Connection` + 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 :py:obj:`None`. If one of them actually manages to deliver + 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. """ @@ -281,12 +241,12 @@ class _LoopbackMixin: class VersionTests(TestCase): """ Tests for version information exposed by - :py:obj:`OpenSSL.SSL.SSLeay_version` and - :py:obj:`OpenSSL.SSL.OPENSSL_VERSION_NUMBER`. + L{OpenSSL.SSL.SSLeay_version} and + L{OpenSSL.SSL.OPENSSL_VERSION_NUMBER}. """ def test_OPENSSL_VERSION_NUMBER(self): """ - :py:obj:`OPENSSL_VERSION_NUMBER` is an integer with status in the low + L{OPENSSL_VERSION_NUMBER} is an integer with status in the low byte and the patch, fix, minor, and major versions in the nibbles above that. """ @@ -295,7 +255,7 @@ class VersionTests(TestCase): def test_SSLeay_version(self): """ - :py:obj:`SSLeay_version` takes a version type indicator and returns + L{SSLeay_version} takes a version type indicator and returns one of a number of version strings based on that indicator. """ versions = {} @@ -310,46 +270,30 @@ class VersionTests(TestCase): class ContextTests(TestCase, _LoopbackMixin): """ - Unit tests for :py:obj:`OpenSSL.SSL.Context`. + Unit tests for L{OpenSSL.SSL.Context}. """ def test_method(self): """ - :py:obj:`Context` can be instantiated with one of :py:obj:`SSLv2_METHOD`, - :py:obj:`SSLv3_METHOD`, :py:obj:`SSLv23_METHOD`, :py:obj:`TLSv1_METHOD`, - :py:obj:`TLSv1_1_METHOD`, or :py:obj:`TLSv1_2_METHOD`. + L{Context} can be instantiated with one of L{SSLv2_METHOD}, + L{SSLv3_METHOD}, L{SSLv23_METHOD}, or L{TLSv1_METHOD}. """ - methods = [ - SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD] - for meth in methods: + for meth in [SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD]: Context(meth) - - maybe = [SSLv2_METHOD, TLSv1_1_METHOD, TLSv1_2_METHOD] - for meth in maybe: - try: - Context(meth) - except (Error, ValueError): - # Some versions of OpenSSL have SSLv2 / TLSv1.1 / TLSv1.2, some - # don't. Difficult to say in advance. - pass + try: + Context(SSLv2_METHOD) + except ValueError: + # Some versions of OpenSSL have SSLv2, some don't. + # Difficult to say in advance. + pass self.assertRaises(TypeError, Context, "") self.assertRaises(ValueError, Context, 10) - if not PY3: - def test_method_long(self): - """ - On Python 2 :py:class:`Context` accepts values of type - :py:obj:`long` as well as :py:obj:`int`. - """ - Context(long(TLSv1_METHOD)) - - - def test_type(self): """ - :py:obj:`Context` and :py:obj:`ContextType` refer to the same type object and can be + 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) @@ -358,7 +302,7 @@ class ContextTests(TestCase, _LoopbackMixin): def test_use_privatekey(self): """ - :py:obj:`Context.use_privatekey` takes an :py:obj:`OpenSSL.crypto.PKey` instance. + L{Context.use_privatekey} takes an L{OpenSSL.crypto.PKey} instance. """ key = PKey() key.generate_key(TYPE_RSA, 128) @@ -367,130 +311,9 @@ class ContextTests(TestCase, _LoopbackMixin): self.assertRaises(TypeError, ctx.use_privatekey, "") - def test_use_privatekey_file_missing(self): - """ - :py:obj:`Context.use_privatekey_file` raises :py:obj:`OpenSSL.SSL.Error` - when passed the name of a file which does not exist. - """ - ctx = Context(TLSv1_METHOD) - self.assertRaises(Error, ctx.use_privatekey_file, self.mktemp()) - - - if not PY3: - def test_use_privatekey_file_long(self): - """ - On Python 2 :py:obj:`Context.use_privatekey_file` accepts a - filetype of type :py:obj:`long` as well as :py:obj:`int`. - """ - pemfile = self.mktemp() - - key = PKey() - key.generate_key(TYPE_RSA, 128) - - with open(pemfile, "wt") as pem: - pem.write( - dump_privatekey(FILETYPE_PEM, key).decode("ascii")) - - ctx = Context(TLSv1_METHOD) - ctx.use_privatekey_file(pemfile, long(FILETYPE_PEM)) - - - def test_use_certificate_wrong_args(self): - """ - :py:obj:`Context.use_certificate_wrong_args` raises :py:obj:`TypeError` - when not passed exactly one :py:obj:`OpenSSL.crypto.X509` instance as an - argument. - """ - ctx = Context(TLSv1_METHOD) - self.assertRaises(TypeError, ctx.use_certificate) - self.assertRaises(TypeError, ctx.use_certificate, "hello, world") - self.assertRaises(TypeError, ctx.use_certificate, X509(), "hello, world") - - - def test_use_certificate_uninitialized(self): - """ - :py:obj:`Context.use_certificate` raises :py:obj:`OpenSSL.SSL.Error` - when passed a :py:obj:`OpenSSL.crypto.X509` instance which has not been - initialized (ie, which does not actually have any certificate data). - """ - ctx = Context(TLSv1_METHOD) - self.assertRaises(Error, ctx.use_certificate, X509()) - - - def test_use_certificate(self): - """ - :py:obj:`Context.use_certificate` sets the certificate which will be - used to identify connections created using the context. - """ - # TODO - # Hard to assert anything. But we could set a privatekey then ask - # OpenSSL if the cert and key agree using check_privatekey. Then as - # long as check_privatekey works right we're good... - ctx = Context(TLSv1_METHOD) - ctx.use_certificate(load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) - - - def test_use_certificate_file_wrong_args(self): - """ - :py:obj:`Context.use_certificate_file` raises :py:obj:`TypeError` if - called with zero arguments or more than two arguments, or if the first - argument is not a byte string or the second argumnent is not an integer. - """ - ctx = Context(TLSv1_METHOD) - self.assertRaises(TypeError, ctx.use_certificate_file) - self.assertRaises(TypeError, ctx.use_certificate_file, b"somefile", object()) - self.assertRaises( - TypeError, ctx.use_certificate_file, b"somefile", FILETYPE_PEM, object()) - self.assertRaises( - TypeError, ctx.use_certificate_file, object(), FILETYPE_PEM) - self.assertRaises( - TypeError, ctx.use_certificate_file, b"somefile", object()) - - - def test_use_certificate_file_missing(self): - """ - :py:obj:`Context.use_certificate_file` raises - `:py:obj:`OpenSSL.SSL.Error` if passed the name of a file which does not - exist. - """ - ctx = Context(TLSv1_METHOD) - self.assertRaises(Error, ctx.use_certificate_file, self.mktemp()) - - - def test_use_certificate_file(self): - """ - :py:obj:`Context.use_certificate` sets the certificate which will be - used to identify connections created using the context. - """ - # TODO - # Hard to assert anything. But we could set a privatekey then ask - # OpenSSL if the cert and key agree using check_privatekey. Then as - # long as check_privatekey works right we're good... - pem_filename = self.mktemp() - with open(pem_filename, "wb") as pem_file: - pem_file.write(cleartextCertificatePEM) - - ctx = Context(TLSv1_METHOD) - ctx.use_certificate_file(pem_filename) - - - if not PY3: - def test_use_certificate_file_long(self): - """ - On Python 2 :py:obj:`Context.use_certificate_file` accepts a - filetype of type :py:obj:`long` as well as :py:obj:`int`. - """ - pem_filename = self.mktemp() - with open(pem_filename, "wb") as pem_file: - pem_file.write(cleartextCertificatePEM) - - ctx = Context(TLSv1_METHOD) - ctx.use_certificate_file(pem_filename, long(FILETYPE_PEM)) - - def test_set_app_data_wrong_args(self): """ - :py:obj:`Context.set_app_data` raises :py:obj:`TypeError` if called with other than + L{Context.set_app_data} raises L{TypeError} if called with other than one argument. """ context = Context(TLSv1_METHOD) @@ -500,7 +323,7 @@ class ContextTests(TestCase, _LoopbackMixin): def test_get_app_data_wrong_args(self): """ - :py:obj:`Context.get_app_data` raises :py:obj:`TypeError` if called with any + L{Context.get_app_data} raises L{TypeError} if called with any arguments. """ context = Context(TLSv1_METHOD) @@ -509,8 +332,8 @@ class ContextTests(TestCase, _LoopbackMixin): def test_app_data(self): """ - :py:obj:`Context.set_app_data` stores an object for later retrieval using - :py:obj:`Context.get_app_data`. + L{Context.set_app_data} stores an object for later retrieval using + L{Context.get_app_data}. """ app_data = object() context = Context(TLSv1_METHOD) @@ -520,8 +343,8 @@ class ContextTests(TestCase, _LoopbackMixin): def test_set_options_wrong_args(self): """ - :py:obj:`Context.set_options` raises :py:obj:`TypeError` if called with the wrong - number of arguments or a non-:py:obj:`int` argument. + 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) @@ -529,64 +352,10 @@ class ContextTests(TestCase, _LoopbackMixin): self.assertRaises(TypeError, context.set_options, 1, None) - def test_set_options(self): - """ - :py:obj:`Context.set_options` returns the new options value. - """ - context = Context(TLSv1_METHOD) - options = context.set_options(OP_NO_SSLv2) - self.assertTrue(OP_NO_SSLv2 & options) - - - if not PY3: - def test_set_options_long(self): - """ - On Python 2 :py:obj:`Context.set_options` accepts values of type - :py:obj:`long` as well as :py:obj:`int`. - """ - context = Context(TLSv1_METHOD) - options = context.set_options(long(OP_NO_SSLv2)) - self.assertTrue(OP_NO_SSLv2 & options) - - - def test_set_mode_wrong_args(self): - """ - :py:obj:`Context.set`mode} raises :py:obj:`TypeError` if called with the wrong - number of arguments or a non-:py:obj:`int` argument. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.set_mode) - self.assertRaises(TypeError, context.set_mode, None) - self.assertRaises(TypeError, context.set_mode, 1, None) - - - if MODE_RELEASE_BUFFERS is not None: - def test_set_mode(self): - """ - :py:obj:`Context.set_mode` accepts a mode bitvector and returns the newly - set mode. - """ - context = Context(TLSv1_METHOD) - self.assertTrue( - MODE_RELEASE_BUFFERS & context.set_mode(MODE_RELEASE_BUFFERS)) - - if not PY3: - def test_set_mode_long(self): - """ - On Python 2 :py:obj:`Context.set_mode` accepts values of type - :py:obj:`long` as well as :py:obj:`int`. - """ - context = Context(TLSv1_METHOD) - mode = context.set_mode(long(MODE_RELEASE_BUFFERS)) - self.assertTrue(MODE_RELEASE_BUFFERS & mode) - else: - "MODE_RELEASE_BUFFERS unavailable - OpenSSL version may be too old" - - def test_set_timeout_wrong_args(self): """ - :py:obj:`Context.set_timeout` raises :py:obj:`TypeError` if called with the wrong - number of arguments or a non-:py:obj:`int` argument. + 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) @@ -596,7 +365,7 @@ class ContextTests(TestCase, _LoopbackMixin): def test_get_timeout_wrong_args(self): """ - :py:obj:`Context.get_timeout` raises :py:obj:`TypeError` if called with any arguments. + L{Context.get_timeout} raises L{TypeError} if called with any arguments. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.get_timeout, None) @@ -604,8 +373,8 @@ class ContextTests(TestCase, _LoopbackMixin): def test_timeout(self): """ - :py:obj:`Context.set_timeout` sets the session timeout for all connections - created using the context object. :py:obj:`Context.get_timeout` retrieves this + 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) @@ -613,21 +382,10 @@ class ContextTests(TestCase, _LoopbackMixin): self.assertEquals(context.get_timeout(), 1234) - if not PY3: - def test_timeout_long(self): - """ - On Python 2 :py:obj:`Context.set_timeout` accepts values of type - `long` as well as int. - """ - context = Context(TLSv1_METHOD) - context.set_timeout(long(1234)) - self.assertEquals(context.get_timeout(), 1234) - - def test_set_verify_depth_wrong_args(self): """ - :py:obj:`Context.set_verify_depth` raises :py:obj:`TypeError` if called with the wrong - number of arguments or a non-:py:obj:`int` argument. + 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) @@ -637,7 +395,7 @@ class ContextTests(TestCase, _LoopbackMixin): def test_get_verify_depth_wrong_args(self): """ - :py:obj:`Context.get_verify_depth` raises :py:obj:`TypeError` if called with any arguments. + 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) @@ -645,26 +403,15 @@ class ContextTests(TestCase, _LoopbackMixin): def test_verify_depth(self): """ - :py:obj:`Context.set_verify_depth` sets the number of certificates in a chain + L{Context.set_verify_depth} sets the number of certificates in a chain to follow before giving up. The value can be retrieved with - :py:obj:`Context.get_verify_depth`. + L{Context.get_verify_depth}. """ context = Context(TLSv1_METHOD) context.set_verify_depth(11) self.assertEquals(context.get_verify_depth(), 11) - if not PY3: - def test_verify_depth_long(self): - """ - On Python 2 :py:obj:`Context.set_verify_depth` accepts values of - type `long` as well as int. - """ - context = Context(TLSv1_METHOD) - context.set_verify_depth(long(11)) - self.assertEquals(context.get_verify_depth(), 11) - - def _write_encrypted_pem(self, passphrase): """ Write a new private key out to a new file, encrypted using the given @@ -682,7 +429,7 @@ class ContextTests(TestCase, _LoopbackMixin): def test_set_passwd_cb_wrong_args(self): """ - :py:obj:`Context.set_passwd_cb` raises :py:obj:`TypeError` if called with the + 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) @@ -693,7 +440,7 @@ class ContextTests(TestCase, _LoopbackMixin): def test_set_passwd_cb(self): """ - :py:obj:`Context.set_passwd_cb` accepts a callable which will be invoked when + 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") @@ -713,7 +460,7 @@ class ContextTests(TestCase, _LoopbackMixin): def test_passwd_callback_exception(self): """ - :py:obj:`Context.use_privatekey_file` propagates any exception raised by the + L{Context.use_privatekey_file} propagates any exception raised by the passphrase callback. """ pemFile = self._write_encrypted_pem(b("monkeys are nice")) @@ -727,12 +474,12 @@ class ContextTests(TestCase, _LoopbackMixin): def test_passwd_callback_false(self): """ - :py:obj:`Context.use_privatekey_file` raises :py:obj:`OpenSSL.SSL.Error` if the + 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 b"" + return None context = Context(TLSv1_METHOD) context.set_passwd_cb(passphraseCallback) @@ -741,7 +488,7 @@ class ContextTests(TestCase, _LoopbackMixin): def test_passwd_callback_non_string(self): """ - :py:obj:`Context.use_privatekey_file` raises :py:obj:`OpenSSL.SSL.Error` if the + 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")) @@ -750,7 +497,7 @@ class ContextTests(TestCase, _LoopbackMixin): context = Context(TLSv1_METHOD) context.set_passwd_cb(passphraseCallback) - self.assertRaises(ValueError, context.use_privatekey_file, pemFile) + self.assertRaises(Error, context.use_privatekey_file, pemFile) def test_passwd_callback_too_long(self): @@ -774,7 +521,7 @@ class ContextTests(TestCase, _LoopbackMixin): def test_set_info_callback(self): """ - :py:obj:`Context.set_info_callback` accepts a callable which will be invoked + 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() @@ -795,26 +542,22 @@ class ContextTests(TestCase, _LoopbackMixin): serverSSL = Connection(context, server) serverSSL.set_accept_state() - handshake(clientSSL, serverSSL) + while not called: + for ssl in clientSSL, serverSSL: + try: + ssl.do_handshake() + except WantReadError: + pass - # The callback must always be called with a Connection instance as the - # first argument. It would probably be better to split this into - # separate tests for client and server side info callbacks so we could - # assert it is called with the right Connection instance. It would - # also be good to assert *something* about `where` and `ret`. - notConnections = [ - conn for (conn, where, ret) in called - if not isinstance(conn, Connection)] - self.assertEqual( - [], notConnections, - "Some info callback arguments were not Connection instaces.") + # 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 :py:obj:`load_verify_locations` method with the given arguments. - Then connect it to a server and ensure that the handshake succeeds. + 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() @@ -850,7 +593,7 @@ class ContextTests(TestCase, _LoopbackMixin): def test_load_verify_file(self): """ - :py:obj:`Context.load_verify_locations` accepts a file name and uses the + L{Context.load_verify_locations} accepts a file name and uses the certificates within for verification purposes. """ cafile = self.mktemp() @@ -863,7 +606,7 @@ class ContextTests(TestCase, _LoopbackMixin): def test_load_verify_invalid_file(self): """ - :py:obj:`Context.load_verify_locations` raises :py:obj:`Error` when passed a + L{Context.load_verify_locations} raises L{Error} when passed a non-existent cafile. """ clientContext = Context(TLSv1_METHOD) @@ -873,7 +616,7 @@ class ContextTests(TestCase, _LoopbackMixin): def test_load_verify_directory(self): """ - :py:obj:`Context.load_verify_locations` accepts a directory name and uses + L{Context.load_verify_locations} accepts a directory name and uses the certificates within for verification purposes. """ capath = self.mktemp() @@ -881,7 +624,7 @@ class ContextTests(TestCase, _LoopbackMixin): # Hash values computed manually with c_rehash to avoid depending on # c_rehash in the test suite. One is from OpenSSL 0.9.8, the other # from OpenSSL 1.0.0. - for name in [b'c7adac82.0', b'c3705638.0']: + for name in ['c7adac82.0', 'c3705638.0']: cafile = join(capath, name) fObj = open(cafile, 'w') fObj.write(cleartextCertificatePEM.decode('ascii')) @@ -892,8 +635,8 @@ class ContextTests(TestCase, _LoopbackMixin): def test_load_verify_locations_wrong_args(self): """ - :py:obj:`Context.load_verify_locations` raises :py:obj:`TypeError` if called with - the wrong number of arguments or with non-:py:obj:`str` arguments. + 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) @@ -908,7 +651,7 @@ class ContextTests(TestCase, _LoopbackMixin): else: def test_set_default_verify_paths(self): """ - :py:obj:`Context.set_default_verify_paths` causes the platform-specific CA + 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 @@ -931,14 +674,14 @@ class ContextTests(TestCase, _LoopbackMixin): clientSSL = Connection(context, client) clientSSL.set_connect_state() clientSSL.do_handshake() - clientSSL.send(b"GET / HTTP/1.0\r\n\r\n") + clientSSL.send('GET / HTTP/1.0\r\n\r\n') self.assertTrue(clientSSL.recv(1024)) def test_set_default_verify_paths_signature(self): """ - :py:obj:`Context.set_default_verify_paths` takes no arguments and raises - :py:obj:`TypeError` if given any. + 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) @@ -948,9 +691,9 @@ class ContextTests(TestCase, _LoopbackMixin): def test_add_extra_chain_cert_invalid_cert(self): """ - :py:obj:`Context.add_extra_chain_cert` raises :py:obj:`TypeError` if called with + 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 :py:obj:`X509`. + instance of L{X509}. """ context = Context(TLSv1_METHOD) self.assertRaises(TypeError, context.add_extra_chain_cert) @@ -981,34 +724,12 @@ class ContextTests(TestCase, _LoopbackMixin): pass - def test_set_verify_callback_exception(self): - """ - If the verify callback passed to :py:obj:`Context.set_verify` raises an - exception, verification fails and the exception is propagated to the - caller of :py:obj:`Connection.do_handshake`. - """ - serverContext = Context(TLSv1_METHOD) - serverContext.use_privatekey( - load_privatekey(FILETYPE_PEM, cleartextPrivateKeyPEM)) - serverContext.use_certificate( - load_certificate(FILETYPE_PEM, cleartextCertificatePEM)) - - clientContext = Context(TLSv1_METHOD) - def verify_callback(*args): - raise Exception("silly verify failure") - clientContext.set_verify(VERIFY_PEER, verify_callback) - - exc = self.assertRaises( - Exception, self._handshake_test, serverContext, clientContext) - self.assertEqual("silly verify failure", str(exc)) - - def test_add_extra_chain_cert(self): """ - :py:obj:`Context.add_extra_chain_cert` accepts an :py:obj:`X509` instance to add to + L{Context.add_extra_chain_cert} accepts an L{X509} instance to add to the certificate chain. - See :py:obj:`_create_certificate_chain` for the details of the certificate + 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 @@ -1041,7 +762,7 @@ class ContextTests(TestCase, _LoopbackMixin): clientContext = Context(TLSv1_METHOD) clientContext.set_verify( VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) - clientContext.load_verify_locations(b"ca.pem") + clientContext.load_verify_locations('ca.pem') # Try it out. self._handshake_test(serverContext, clientContext) @@ -1049,7 +770,7 @@ class ContextTests(TestCase, _LoopbackMixin): def test_use_certificate_chain_file(self): """ - :py:obj:`Context.use_certificate_chain_file` reads a certificate chain from + 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 @@ -1061,11 +782,11 @@ class ContextTests(TestCase, _LoopbackMixin): # Write out the chain file. chainFile = self.mktemp() - fObj = open(chainFile, 'wb') + fObj = open(chainFile, 'w') # Most specific to least general. - fObj.write(dump_certificate(FILETYPE_PEM, scert)) - fObj.write(dump_certificate(FILETYPE_PEM, icert)) - fObj.write(dump_certificate(FILETYPE_PEM, cacert)) + 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) @@ -1079,42 +800,26 @@ class ContextTests(TestCase, _LoopbackMixin): clientContext = Context(TLSv1_METHOD) clientContext.set_verify( VERIFY_PEER | VERIFY_FAIL_IF_NO_PEER_CERT, verify_cb) - clientContext.load_verify_locations(b"ca.pem") + clientContext.load_verify_locations('ca.pem') self._handshake_test(serverContext, clientContext) - - def test_use_certificate_chain_file_wrong_args(self): - """ - :py:obj:`Context.use_certificate_chain_file` raises :py:obj:`TypeError` - if passed zero or more than one argument or when passed a non-byte - string single argument. It also raises :py:obj:`OpenSSL.SSL.Error` when - passed a bad chain file name (for example, the name of a file which does - not exist). - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.use_certificate_chain_file) - self.assertRaises(TypeError, context.use_certificate_chain_file, object()) - self.assertRaises(TypeError, context.use_certificate_chain_file, b"foo", object()) - - self.assertRaises(Error, context.use_certificate_chain_file, self.mktemp()) - # XXX load_client_ca # XXX set_session_id def test_get_verify_mode_wrong_args(self): """ - :py:obj:`Context.get_verify_mode` raises :py:obj:`TypeError` if called with any + 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_set_verify_mode(self): + def test_get_verify_mode(self): """ - :py:obj:`Context.get_verify_mode` returns the verify mode flags previously - passed to :py:obj:`Context.set_verify`. + 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) @@ -1124,24 +829,10 @@ class ContextTests(TestCase, _LoopbackMixin): context.get_verify_mode(), VERIFY_PEER | VERIFY_CLIENT_ONCE) - if not PY3: - def test_set_verify_mode_long(self): - """ - On Python 2 :py:obj:`Context.set_verify_mode` accepts values of - type :py:obj:`long` as well as :py:obj:`int`. - """ - context = Context(TLSv1_METHOD) - self.assertEquals(context.get_verify_mode(), 0) - context.set_verify( - long(VERIFY_PEER | VERIFY_CLIENT_ONCE), lambda *args: None) - self.assertEquals( - context.get_verify_mode(), VERIFY_PEER | VERIFY_CLIENT_ONCE) - - def test_load_tmp_dh_wrong_args(self): """ - :py:obj:`Context.load_tmp_dh` raises :py:obj:`TypeError` if called with the wrong - number of arguments or with a non-:py:obj:`str` argument. + 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) @@ -1151,16 +842,16 @@ class ContextTests(TestCase, _LoopbackMixin): def test_load_tmp_dh_missing_file(self): """ - :py:obj:`Context.load_tmp_dh` raises :py:obj:`OpenSSL.SSL.Error` if the specified file + 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, b"hello") + self.assertRaises(Error, context.load_tmp_dh, "hello") def test_load_tmp_dh(self): """ - :py:obj:`Context.load_tmp_dh` loads Diffie-Hellman parameters from the + L{Context.load_tmp_dh} loads Diffie-Hellman parameters from the specified file. """ context = Context(TLSv1_METHOD) @@ -1172,106 +863,26 @@ class ContextTests(TestCase, _LoopbackMixin): # XXX What should I assert here? -exarkun - def test_set_cipher_list_bytes(self): + def test_set_cipher_list(self): """ - :py:obj:`Context.set_cipher_list` accepts a :py:obj:`bytes` naming the - ciphers which connections created with the context object will be able - to choose from. + 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(b"hello world:EXP-RC4-MD5") + context.set_cipher_list("hello world:EXP-RC4-MD5") conn = Connection(context, None) self.assertEquals(conn.get_cipher_list(), ["EXP-RC4-MD5"]) - def test_set_cipher_list_text(self): - """ - :py:obj:`Context.set_cipher_list` accepts a :py:obj:`unicode` naming - the ciphers which connections created with the context object will be - able to choose from. - """ - context = Context(TLSv1_METHOD) - context.set_cipher_list(u("hello world:EXP-RC4-MD5")) - conn = Connection(context, None) - self.assertEquals(conn.get_cipher_list(), ["EXP-RC4-MD5"]) - - - def test_set_cipher_list_wrong_args(self): - """ - :py:obj:`Context.set_cipher_list` raises :py:obj:`TypeError` when - passed zero arguments or more than one argument or when passed a - non-string single argument and raises :py:obj:`OpenSSL.SSL.Error` when - passed an incorrect cipher list string. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.set_cipher_list) - self.assertRaises(TypeError, context.set_cipher_list, object()) - self.assertRaises(TypeError, context.set_cipher_list, b"EXP-RC4-MD5", object()) - - self.assertRaises(Error, context.set_cipher_list, "imaginary-cipher") - - - def test_set_session_cache_mode_wrong_args(self): - """ - :py:obj:`Context.set_session_cache_mode` raises :py:obj:`TypeError` if - called with other than one integer argument. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.set_session_cache_mode) - self.assertRaises(TypeError, context.set_session_cache_mode, object()) - - - def test_get_session_cache_mode_wrong_args(self): - """ - :py:obj:`Context.get_session_cache_mode` raises :py:obj:`TypeError` if - called with any arguments. - """ - context = Context(TLSv1_METHOD) - self.assertRaises(TypeError, context.get_session_cache_mode, 1) - - - def test_session_cache_mode(self): - """ - :py:obj:`Context.set_session_cache_mode` specifies how sessions are - cached. The setting can be retrieved via - :py:obj:`Context.get_session_cache_mode`. - """ - context = Context(TLSv1_METHOD) - context.set_session_cache_mode(SESS_CACHE_OFF) - off = context.set_session_cache_mode(SESS_CACHE_BOTH) - self.assertEqual(SESS_CACHE_OFF, off) - self.assertEqual(SESS_CACHE_BOTH, context.get_session_cache_mode()) - - if not PY3: - def test_session_cache_mode_long(self): - """ - On Python 2 :py:obj:`Context.set_session_cache_mode` accepts values - of type :py:obj:`long` as well as :py:obj:`int`. - """ - context = Context(TLSv1_METHOD) - context.set_session_cache_mode(long(SESS_CACHE_BOTH)) - self.assertEqual( - SESS_CACHE_BOTH, context.get_session_cache_mode()) - - - def test_get_cert_store(self): - """ - :py:obj:`Context.get_cert_store` returns a :py:obj:`X509Store` instance. - """ - context = Context(TLSv1_METHOD) - store = context.get_cert_store() - self.assertIsInstance(store, X509Store) - - class ServerNameCallbackTests(TestCase, _LoopbackMixin): """ - Tests for :py:obj:`Context.set_tlsext_servername_callback` and its interaction with - :py:obj:`Connection`. + Tests for L{Context.set_tlsext_servername_callback} and its interaction with + L{Connection}. """ def test_wrong_args(self): """ - :py:obj:`Context.set_tlsext_servername_callback` raises :py:obj:`TypeError` if called + L{Context.set_tlsext_servername_callback} raises L{TypeError} if called with other than one argument. """ context = Context(TLSv1_METHOD) @@ -1279,10 +890,9 @@ class ServerNameCallbackTests(TestCase, _LoopbackMixin): self.assertRaises( TypeError, context.set_tlsext_servername_callback, 1, 2) - def test_old_callback_forgotten(self): """ - If :py:obj:`Context.set_tlsext_servername_callback` is used to specify a new + If L{Context.set_tlsext_servername_callback} is used to specify a new callback, the one it replaces is dereferenced. """ def callback(connection): @@ -1298,26 +908,15 @@ class ServerNameCallbackTests(TestCase, _LoopbackMixin): del callback context.set_tlsext_servername_callback(replacement) - - # One run of the garbage collector happens to work on CPython. PyPy - # doesn't collect the underlying object until a second run for whatever - # reason. That's fine, it still demonstrates our code has properly - # dropped the reference. - collect() collect() - - callback = tracker() - if callback is not None: - referrers = get_referrers(callback) - if len(referrers) > 1: - self.fail("Some references remain: %r" % (referrers,)) + self.assertIdentical(None, tracker()) def test_no_servername(self): """ When a client specifies no server name, the callback passed to - :py:obj:`Context.set_tlsext_servername_callback` is invoked and the result of - :py:obj:`Connection.get_servername` is :py:obj:`None`. + L{Context.set_tlsext_servername_callback} is invoked and the result of + L{Connection.get_servername} is C{None}. """ args = [] def servername(conn): @@ -1349,8 +948,8 @@ class ServerNameCallbackTests(TestCase, _LoopbackMixin): def test_servername(self): """ When a client specifies a server name in its hello message, the callback - passed to :py:obj:`Contexts.set_tlsext_servername_callback` is invoked and the - result of :py:obj:`Connection.get_servername` is that server name. + passed to L{Contexts.set_tlsext_servername_callback} is invoked and the + result of L{Connection.get_servername} is that server name. """ args = [] def servername(conn): @@ -1376,34 +975,12 @@ class ServerNameCallbackTests(TestCase, _LoopbackMixin): -class SessionTests(TestCase): - """ - Unit tests for :py:obj:`OpenSSL.SSL.Session`. - """ - def test_construction(self): - """ - :py:class:`Session` can be constructed with no arguments, creating a new - instance of that type. - """ - new_session = Session() - self.assertTrue(isinstance(new_session, Session)) - - - def test_construction_wrong_args(self): - """ - If any arguments are passed to :py:class:`Session`, :py:obj:`TypeError` - is raised. - """ - self.assertRaises(TypeError, Session, 123) - self.assertRaises(TypeError, Session, "hello") - self.assertRaises(TypeError, Session, object()) - - - class ConnectionTests(TestCase, _LoopbackMixin): """ - Unit tests for :py:obj:`OpenSSL.SSL.Connection`. + 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 @@ -1422,7 +999,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_type(self): """ - :py:obj:`Connection` and :py:obj:`ConnectionType` refer to the same type object and + 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) @@ -1432,8 +1009,8 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_get_context(self): """ - :py:obj:`Connection.get_context` returns the :py:obj:`Context` instance used to - construct the :py:obj:`Connection` instance. + L{Connection.get_context} returns the L{Context} instance used to + construct the L{Connection} instance. """ context = Context(TLSv1_METHOD) connection = Connection(context, None) @@ -1442,7 +1019,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_get_context_wrong_args(self): """ - :py:obj:`Connection.get_context` raises :py:obj:`TypeError` if called with any + L{Connection.get_context} raises L{TypeError} if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), None) @@ -1451,8 +1028,8 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_set_context_wrong_args(self): """ - :py:obj:`Connection.set_context` raises :py:obj:`TypeError` if called with a - non-:py:obj:`Context` instance argument or with any number of arguments other + L{Connection.set_context} raises L{TypeError} if called with a + non-L{Context} instance argument or with any number of arguments other than 1. """ ctx = Context(TLSv1_METHOD) @@ -1469,7 +1046,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_set_context(self): """ - :py:obj:`Connection.set_context` specifies a new :py:obj:`Context` instance to be used + L{Connection.set_context} specifies a new L{Context} instance to be used for the connection. """ original = Context(SSLv23_METHOD) @@ -1485,9 +1062,9 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_set_tlsext_host_name_wrong_args(self): """ - If :py:obj:`Connection.set_tlsext_host_name` is called with a non-byte string + If L{Connection.set_tlsext_host_name} is called with a non-byte string argument or a byte string with an embedded NUL or other than one - argument, :py:obj:`TypeError` is raised. + argument, L{TypeError} is raised. """ conn = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, conn.set_tlsext_host_name) @@ -1505,7 +1082,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_get_servername_wrong_args(self): """ - :py:obj:`Connection.get_servername` raises :py:obj:`TypeError` if called with any + L{Connection.get_servername} raises L{TypeError} if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), None) @@ -1516,7 +1093,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_pending(self): """ - :py:obj:`Connection.pending` returns the number of bytes available for + L{Connection.pending} returns the number of bytes available for immediate read. """ connection = Connection(Context(TLSv1_METHOD), None) @@ -1525,7 +1102,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_pending_wrong_args(self): """ - :py:obj:`Connection.pending` raises :py:obj:`TypeError` if called with any arguments. + L{Connection.pending} raises L{TypeError} if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), None) self.assertRaises(TypeError, connection.pending, None) @@ -1533,7 +1110,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_connect_wrong_args(self): """ - :py:obj:`Connection.connect` raises :py:obj:`TypeError` if called with a non-address + 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()) @@ -1544,7 +1121,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_connect_refused(self): """ - :py:obj:`Connection.connect` raises :py:obj:`socket.error` if the underlying socket + L{Connection.connect} raises L{socket.error} if the underlying socket connect method raises it. """ client = socket() @@ -1556,7 +1133,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_connect(self): """ - :py:obj:`Connection.connect` establishes a connection to the specified address. + L{Connection.connect} establishes a connection to the specified address. """ port = socket() port.bind(('', 0)) @@ -1572,7 +1149,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): else: def test_connect_ex(self): """ - If there is a connection error, :py:obj:`Connection.connect_ex` returns the + If there is a connection error, L{Connection.connect_ex} returns the errno instead of raising an exception. """ port = socket() @@ -1589,7 +1166,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_accept_wrong_args(self): """ - :py:obj:`Connection.accept` raises :py:obj:`TypeError` if called with any arguments. + L{Connection.accept} raises L{TypeError} if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), socket()) self.assertRaises(TypeError, connection.accept, None) @@ -1597,8 +1174,8 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_accept(self): """ - :py:obj:`Connection.accept` accepts a pending connection attempt and returns a - tuple of a new :py:obj:`Connection` (the accepted client) and the address the + 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) @@ -1624,7 +1201,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_shutdown_wrong_args(self): """ - :py:obj:`Connection.shutdown` raises :py:obj:`TypeError` if called with the wrong + 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) @@ -1637,7 +1214,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_shutdown(self): """ - :py:obj:`Connection.shutdown` performs an SSL-level connection shutdown. + L{Connection.shutdown} performs an SSL-level connection shutdown. """ server, client = self._loopback() self.assertFalse(server.shutdown()) @@ -1652,7 +1229,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_set_shutdown(self): """ - :py:obj:`Connection.set_shutdown` sets the state of the SSL connection shutdown + L{Connection.set_shutdown} sets the state of the SSL connection shutdown process. """ connection = Connection(Context(TLSv1_METHOD), socket()) @@ -1660,21 +1237,10 @@ class ConnectionTests(TestCase, _LoopbackMixin): self.assertEquals(connection.get_shutdown(), RECEIVED_SHUTDOWN) - if not PY3: - def test_set_shutdown_long(self): - """ - On Python 2 :py:obj:`Connection.set_shutdown` accepts an argument - of type :py:obj:`long` as well as :py:obj:`int`. - """ - connection = Connection(Context(TLSv1_METHOD), socket()) - connection.set_shutdown(long(RECEIVED_SHUTDOWN)) - self.assertEquals(connection.get_shutdown(), RECEIVED_SHUTDOWN) - - def test_app_data_wrong_args(self): """ - :py:obj:`Connection.set_app_data` raises :py:obj:`TypeError` if called with other than - one argument. :py:obj:`Connection.get_app_data` raises :py:obj:`TypeError` if called + 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) @@ -1686,8 +1252,8 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_app_data(self): """ Any object can be set as app data by passing it to - :py:obj:`Connection.set_app_data` and later retrieved with - :py:obj:`Connection.get_app_data`. + L{Connection.set_app_data} and later retrieved with + L{Connection.get_app_data}. """ conn = Connection(Context(TLSv1_METHOD), None) app_data = object() @@ -1697,8 +1263,8 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_makefile(self): """ - :py:obj:`Connection.makefile` is not implemented and calling that method raises - :py:obj:`NotImplementedError`. + L{Connection.makefile} is not implemented and calling that method raises + L{NotImplementedError}. """ conn = Connection(Context(TLSv1_METHOD), None) self.assertRaises(NotImplementedError, conn.makefile) @@ -1706,7 +1272,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_get_peer_cert_chain_wrong_args(self): """ - :py:obj:`Connection.get_peer_cert_chain` raises :py:obj:`TypeError` if called with any + L{Connection.get_peer_cert_chain} raises L{TypeError} if called with any arguments. """ conn = Connection(Context(TLSv1_METHOD), None) @@ -1718,7 +1284,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_get_peer_cert_chain(self): """ - :py:obj:`Connection.get_peer_cert_chain` returns a list of certificates which + L{Connection.get_peer_cert_chain} returns a list of certificates which the connected server returned for the certification verification. """ chain = _create_certificate_chain() @@ -1752,7 +1318,7 @@ class ConnectionTests(TestCase, _LoopbackMixin): def test_get_peer_cert_chain_none(self): """ - :py:obj:`Connection.get_peer_cert_chain` returns :py:obj:`None` if the peer sends no + L{Connection.get_peer_cert_chain} returns C{None} if the peer sends no certificate chain. """ ctx = Context(TLSv1_METHOD) @@ -1766,181 +1332,14 @@ class ConnectionTests(TestCase, _LoopbackMixin): self.assertIdentical(None, server.get_peer_cert_chain()) - def test_get_session_wrong_args(self): - """ - :py:obj:`Connection.get_session` raises :py:obj:`TypeError` if called - with any arguments. - """ - ctx = Context(TLSv1_METHOD) - server = Connection(ctx, None) - self.assertRaises(TypeError, server.get_session, 123) - self.assertRaises(TypeError, server.get_session, "hello") - self.assertRaises(TypeError, server.get_session, object()) - - - def test_get_session_unconnected(self): - """ - :py:obj:`Connection.get_session` returns :py:obj:`None` when used with - an object which has not been connected. - """ - ctx = Context(TLSv1_METHOD) - server = Connection(ctx, None) - session = server.get_session() - self.assertIdentical(None, session) - - - def test_server_get_session(self): - """ - On the server side of a connection, :py:obj:`Connection.get_session` - returns a :py:class:`Session` instance representing the SSL session for - that connection. - """ - server, client = self._loopback() - session = server.get_session() - self.assertIsInstance(session, Session) - - - def test_client_get_session(self): - """ - On the client side of a connection, :py:obj:`Connection.get_session` - returns a :py:class:`Session` instance representing the SSL session for - that connection. - """ - server, client = self._loopback() - session = client.get_session() - self.assertIsInstance(session, Session) - - - def test_set_session_wrong_args(self): - """ - If called with an object that is not an instance of :py:class:`Session`, - or with other than one argument, :py:obj:`Connection.set_session` raises - :py:obj:`TypeError`. - """ - ctx = Context(TLSv1_METHOD) - connection = Connection(ctx, None) - self.assertRaises(TypeError, connection.set_session) - self.assertRaises(TypeError, connection.set_session, 123) - self.assertRaises(TypeError, connection.set_session, "hello") - self.assertRaises(TypeError, connection.set_session, object()) - self.assertRaises( - TypeError, connection.set_session, Session(), Session()) - - - def test_client_set_session(self): - """ - :py:obj:`Connection.set_session`, when used prior to a connection being - established, accepts a :py:class:`Session` instance and causes an - attempt to re-use the session it represents when the SSL handshake is - performed. - """ - key = load_privatekey(FILETYPE_PEM, server_key_pem) - cert = load_certificate(FILETYPE_PEM, server_cert_pem) - ctx = Context(TLSv1_METHOD) - ctx.use_privatekey(key) - ctx.use_certificate(cert) - ctx.set_session_id("unity-test") - - def makeServer(socket): - server = Connection(ctx, socket) - server.set_accept_state() - return server - - originalServer, originalClient = self._loopback( - serverFactory=makeServer) - originalSession = originalClient.get_session() - - def makeClient(socket): - client = self._loopbackClientFactory(socket) - client.set_session(originalSession) - return client - resumedServer, resumedClient = self._loopback( - serverFactory=makeServer, - clientFactory=makeClient) - - # This is a proxy: in general, we have no access to any unique - # identifier for the session (new enough versions of OpenSSL expose a - # hash which could be usable, but "new enough" is very, very new). - # Instead, exploit the fact that the master key is re-used if the - # session is re-used. As long as the master key for the two connections - # is the same, the session was re-used! - self.assertEqual( - originalServer.master_key(), resumedServer.master_key()) - - - def test_set_session_wrong_method(self): - """ - If :py:obj:`Connection.set_session` is passed a :py:class:`Session` - instance associated with a context using a different SSL method than the - :py:obj:`Connection` is using, a :py:class:`OpenSSL.SSL.Error` is - raised. - """ - key = load_privatekey(FILETYPE_PEM, server_key_pem) - cert = load_certificate(FILETYPE_PEM, server_cert_pem) - ctx = Context(TLSv1_METHOD) - ctx.use_privatekey(key) - ctx.use_certificate(cert) - ctx.set_session_id("unity-test") - - def makeServer(socket): - server = Connection(ctx, socket) - server.set_accept_state() - return server - - originalServer, originalClient = self._loopback( - serverFactory=makeServer) - originalSession = originalClient.get_session() - - def makeClient(socket): - # Intentionally use a different, incompatible method here. - client = Connection(Context(SSLv3_METHOD), socket) - client.set_connect_state() - client.set_session(originalSession) - return client - - self.assertRaises( - Error, - self._loopback, clientFactory=makeClient, serverFactory=makeServer) - - - def test_wantWriteError(self): - """ - :py:obj:`Connection` methods which generate output raise - :py:obj:`OpenSSL.SSL.WantWriteError` if writing to the connection's BIO - fail indicating a should-write state. - """ - client_socket, server_socket = socket_pair() - # Fill up the client's send buffer so Connection won't be able to write - # anything. - msg = b"x" * 512 - for i in range(2048): - try: - client_socket.send(msg) - except error as e: - if e.errno == EWOULDBLOCK: - break - raise - else: - self.fail( - "Failed to fill socket buffer, cannot test BIO want write") - - ctx = Context(TLSv1_METHOD) - conn = Connection(ctx, client_socket) - # Client's speak first, so make it an SSL client - conn.set_connect_state() - self.assertRaises(WantWriteError, conn.do_handshake) - - # XXX want_read - - class ConnectionGetCipherListTests(TestCase): """ - Tests for :py:obj:`Connection.get_cipher_list`. + Tests for L{Connection.get_cipher_list}. """ def test_wrong_args(self): """ - :py:obj:`Connection.get_cipher_list` raises :py:obj:`TypeError` if called with any + L{Connection.get_cipher_list} raises L{TypeError} if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), None) @@ -1949,8 +1348,8 @@ class ConnectionGetCipherListTests(TestCase): def test_result(self): """ - :py:obj:`Connection.get_cipher_list` returns a :py:obj:`list` of - :py:obj:`bytes` giving the names of the ciphers which might be used. + 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() @@ -1962,23 +1361,22 @@ class ConnectionGetCipherListTests(TestCase): class ConnectionSendTests(TestCase, _LoopbackMixin): """ - Tests for :py:obj:`Connection.send` + Tests for L{Connection.send} """ def test_wrong_args(self): """ - When called with arguments other than string argument for its first - parameter or more than two arguments, :py:obj:`Connection.send` raises - :py:obj:`TypeError`. + 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", object(), "bar") + self.assertRaises(TypeError, connection.send, "foo", "bar") def test_short_bytes(self): """ - When passed a short byte string, :py:obj:`Connection.send` transmits all of it + When passed a short byte string, L{Connection.send} transmits all of it and returns the number of bytes sent. """ server, client = self._loopback() @@ -1994,7 +1392,7 @@ class ConnectionSendTests(TestCase, _LoopbackMixin): def test_short_memoryview(self): """ When passed a memoryview onto a small number of bytes, - :py:obj:`Connection.send` transmits all of them and returns the number of + L{Connection.send} transmits all of them and returns the number of bytes sent. """ server, client = self._loopback() @@ -2006,24 +1404,22 @@ class ConnectionSendTests(TestCase, _LoopbackMixin): class ConnectionSendallTests(TestCase, _LoopbackMixin): """ - Tests for :py:obj:`Connection.sendall`. + Tests for L{Connection.sendall}. """ def test_wrong_args(self): """ - When called with arguments other than a string argument for its first - parameter or with more than two arguments, :py:obj:`Connection.sendall` - raises :py:obj:`TypeError`. + 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", object(), "bar") + self.assertRaises(TypeError, connection.sendall, "foo", "bar") def test_short(self): """ - :py:obj:`Connection.sendall` transmits all of the bytes in the string passed to + L{Connection.sendall} transmits all of the bytes in the string passed to it. """ server, client = self._loopback() @@ -2039,7 +1435,7 @@ class ConnectionSendallTests(TestCase, _LoopbackMixin): def test_short_memoryview(self): """ When passed a memoryview onto a small number of bytes, - :py:obj:`Connection.sendall` transmits all of them. + L{Connection.sendall} transmits all of them. """ server, client = self._loopback() server.sendall(memoryview(b('x'))) @@ -2048,7 +1444,7 @@ class ConnectionSendallTests(TestCase, _LoopbackMixin): def test_long(self): """ - :py:obj:`Connection.sendall` transmits all of the bytes in the string passed to + 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() @@ -2068,16 +1464,12 @@ class ConnectionSendallTests(TestCase, _LoopbackMixin): def test_closed(self): """ - If the underlying socket is closed, :py:obj:`Connection.sendall` propagates the + 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) - exc = self.assertRaises(SysCallError, server.sendall, b"hello, world") - if platform == "win32": - self.assertEqual(exc.args[0], ESHUTDOWN) - else: - self.assertEqual(exc.args[0], EPIPE) + self.assertRaises(SysCallError, server.sendall, "hello, world") @@ -2087,7 +1479,7 @@ class ConnectionRenegotiateTests(TestCase, _LoopbackMixin): """ def test_renegotiate_wrong_args(self): """ - :py:obj:`Connection.renegotiate` raises :py:obj:`TypeError` if called with any + L{Connection.renegotiate} raises L{TypeError} if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), None) @@ -2096,7 +1488,7 @@ class ConnectionRenegotiateTests(TestCase, _LoopbackMixin): def test_total_renegotiations_wrong_args(self): """ - :py:obj:`Connection.total_renegotiations` raises :py:obj:`TypeError` if called with + L{Connection.total_renegotiations} raises L{TypeError} if called with any arguments. """ connection = Connection(Context(TLSv1_METHOD), None) @@ -2105,7 +1497,7 @@ class ConnectionRenegotiateTests(TestCase, _LoopbackMixin): def test_total_renegotiations(self): """ - :py:obj:`Connection.total_renegotiations` returns :py:obj:`0` before any + L{Connection.total_renegotiations} returns C{0} before any renegotiations have happened. """ connection = Connection(Context(TLSv1_METHOD), None) @@ -2136,11 +1528,11 @@ class ConnectionRenegotiateTests(TestCase, _LoopbackMixin): class ErrorTests(TestCase): """ - Unit tests for :py:obj:`OpenSSL.SSL.Error`. + Unit tests for L{OpenSSL.SSL.Error}. """ def test_type(self): """ - :py:obj:`Error` is an exception type. + L{Error} is an exception type. """ self.assertTrue(issubclass(Error, Exception)) self.assertEqual(Error.__name__, 'Error') @@ -2149,7 +1541,7 @@ class ErrorTests(TestCase): class ConstantsTests(TestCase): """ - Tests for the values of constants exposed in :py:obj:`OpenSSL.SSL`. + 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 @@ -2159,8 +1551,8 @@ class ConstantsTests(TestCase): if OP_NO_QUERY_MTU is not None: def test_op_no_query_mtu(self): """ - The value of :py:obj:`OpenSSL.SSL.OP_NO_QUERY_MTU` is 0x1000, the value of - :py:const:`SSL_OP_NO_QUERY_MTU` defined by :file:`openssl/ssl.h`. + 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: @@ -2170,8 +1562,8 @@ class ConstantsTests(TestCase): if OP_COOKIE_EXCHANGE is not None: def test_op_cookie_exchange(self): """ - The value of :py:obj:`OpenSSL.SSL.OP_COOKIE_EXCHANGE` is 0x2000, the value - of :py:const:`SSL_OP_COOKIE_EXCHANGE` defined by :file:`openssl/ssl.h`. + 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: @@ -2181,102 +1573,23 @@ class ConstantsTests(TestCase): if OP_NO_TICKET is not None: def test_op_no_ticket(self): """ - The value of :py:obj:`OpenSSL.SSL.OP_NO_TICKET` is 0x4000, the value of - :py:const:`SSL_OP_NO_TICKET` defined by :file:`openssl/ssl.h`. + 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" - if OP_NO_COMPRESSION is not None: - def test_op_no_compression(self): - """ - The value of :py:obj:`OpenSSL.SSL.OP_NO_COMPRESSION` is 0x20000, the value - of :py:const:`SSL_OP_NO_COMPRESSION` defined by :file:`openssl/ssl.h`. - """ - self.assertEqual(OP_NO_COMPRESSION, 0x20000) - else: - "OP_NO_COMPRESSION unavailable - OpenSSL version may be too old" - - - def test_sess_cache_off(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_OFF` 0x0, the value of - :py:obj:`SSL_SESS_CACHE_OFF` defined by ``openssl/ssl.h``. - """ - self.assertEqual(0x0, SESS_CACHE_OFF) - - - def test_sess_cache_client(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_CLIENT` 0x1, the value of - :py:obj:`SSL_SESS_CACHE_CLIENT` defined by ``openssl/ssl.h``. - """ - self.assertEqual(0x1, SESS_CACHE_CLIENT) - - - def test_sess_cache_server(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_SERVER` 0x2, the value of - :py:obj:`SSL_SESS_CACHE_SERVER` defined by ``openssl/ssl.h``. - """ - self.assertEqual(0x2, SESS_CACHE_SERVER) - - - def test_sess_cache_both(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_BOTH` 0x3, the value of - :py:obj:`SSL_SESS_CACHE_BOTH` defined by ``openssl/ssl.h``. - """ - self.assertEqual(0x3, SESS_CACHE_BOTH) - - - def test_sess_cache_no_auto_clear(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_AUTO_CLEAR` 0x80, the - value of :py:obj:`SSL_SESS_CACHE_NO_AUTO_CLEAR` defined by - ``openssl/ssl.h``. - """ - self.assertEqual(0x80, SESS_CACHE_NO_AUTO_CLEAR) - - - def test_sess_cache_no_internal_lookup(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_INTERNAL_LOOKUP` 0x100, - the value of :py:obj:`SSL_SESS_CACHE_NO_INTERNAL_LOOKUP` defined by - ``openssl/ssl.h``. - """ - self.assertEqual(0x100, SESS_CACHE_NO_INTERNAL_LOOKUP) - - - def test_sess_cache_no_internal_store(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_INTERNAL_STORE` 0x200, - the value of :py:obj:`SSL_SESS_CACHE_NO_INTERNAL_STORE` defined by - ``openssl/ssl.h``. - """ - self.assertEqual(0x200, SESS_CACHE_NO_INTERNAL_STORE) - - - def test_sess_cache_no_internal(self): - """ - The value of :py:obj:`OpenSSL.SSL.SESS_CACHE_NO_INTERNAL` 0x300, the - value of :py:obj:`SSL_SESS_CACHE_NO_INTERNAL` defined by - ``openssl/ssl.h``. - """ - self.assertEqual(0x300, SESS_CACHE_NO_INTERNAL) - - class MemoryBIOTests(TestCase, _LoopbackMixin): """ - Tests for :py:obj:`OpenSSL.SSL.Connection` using a memory BIO. + Tests for L{OpenSSL.SSL.Connection} using a memory BIO. """ def _server(self, sock): """ - Create a new server-side SSL :py:obj:`Connection` object wrapped around - :py:obj:`sock`. + Create 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. @@ -2297,8 +1610,8 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def _client(self, sock): """ - Create a new client-side SSL :py:obj:`Connection` object wrapped around - :py:obj:`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. @@ -2317,7 +1630,7 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_memoryConnect(self): """ - Two :py:obj:`Connection`s which use memory BIOs can be manually connected by + 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. @@ -2361,10 +1674,10 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_socketConnect(self): """ - Just like :py:obj:`test_memoryConnect` but with an actual socket. + 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 :py:obj:`Connection` (if + 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. @@ -2385,8 +1698,8 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_socketOverridesMemory(self): """ - Test that :py:obj:`OpenSSL.SSL.bio_read` and :py:obj:`OpenSSL.SSL.bio_write` don't - work on :py:obj:`OpenSSL.SSL.Connection`() that use sockets. + 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() @@ -2399,7 +1712,7 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_outgoingOverflow(self): """ If more bytes than can be written to the memory BIO are passed to - :py:obj:`Connection.send` at once, the number of bytes which were written is + 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. """ @@ -2409,7 +1722,7 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): self._interactInMemory(client, server) size = 2 ** 15 - sent = client.send(b"x" * size) + 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. @@ -2425,8 +1738,8 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_shutdown(self): """ - :py:obj:`Connection.bio_shutdown` signals the end of the data stream from - which the :py:obj:`Connection` reads. + L{Connection.bio_shutdown} signals the end of the data stream from + which the L{Connection} reads. """ server = self._server(None) server.bio_shutdown() @@ -2436,27 +1749,15 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): self.assertEquals(e.__class__, Error) - def test_unexpectedEndOfFile(self): - """ - If the connection is lost before an orderly SSL shutdown occurs, - :py:obj:`OpenSSL.SSL.SysCallError` is raised with a message of - "Unexpected EOF". - """ - server_conn, client_conn = self._loopback() - client_conn.sock_shutdown(SHUT_RDWR) - exc = self.assertRaises(SysCallError, server_conn.recv, 1024) - self.assertEqual(exc.args, (-1, "Unexpected EOF")) - - def _check_client_ca_list(self, func): """ - Verify the return value of the :py:obj:`get_client_ca_list` method for server and client connections. + 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 + @param func: A function which will be called with the server context before the client and server are connected to each other. This function should specify a list of CAs for the server to send to the client and return that same list. The list will be used to verify - that :py:obj:`get_client_ca_list` returns the proper value at various + that C{get_client_ca_list} returns the proper value at various times. """ server = self._server(None) @@ -2474,7 +1775,7 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_set_client_ca_list_errors(self): """ - :py:obj:`Context.set_client_ca_list` raises a :py:obj:`TypeError` if called with a + 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) @@ -2485,9 +1786,9 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_set_empty_ca_list(self): """ - If passed an empty list, :py:obj:`Context.set_client_ca_list` configures the + 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, :py:obj:`Connection.get_client_ca_list` returns an empty list + client sides, L{Connection.get_client_ca_list} returns an empty list after the connection is set up. """ def no_ca(ctx): @@ -2499,9 +1800,9 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_set_one_ca_list(self): """ If passed a list containing a single X509Name, - :py:obj:`Context.set_client_ca_list` configures the context to send that CA + 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, - :py:obj:`Connection.get_client_ca_list` returns a list containing that + 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) @@ -2515,9 +1816,9 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_set_multiple_ca_list(self): """ If passed a list containing multiple X509Name objects, - :py:obj:`Context.set_client_ca_list` configures the context to send those CA + 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, - :py:obj:`Connection.get_client_ca_list` returns a list containing those + 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) @@ -2536,7 +1837,7 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_reset_ca_list(self): """ If called multiple times, only the X509Names passed to the final call - of :py:obj:`Context.set_client_ca_list` are used to configure the CA names + 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) @@ -2556,7 +1857,7 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_mutated_ca_list(self): """ - If the list passed to :py:obj:`Context.set_client_ca_list` is mutated + 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. """ @@ -2576,7 +1877,7 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_add_client_ca_errors(self): """ - :py:obj:`Context.add_client_ca` raises :py:obj:`TypeError` if called with a non-X509 + 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) @@ -2589,7 +1890,7 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_one_add_client_ca(self): """ A certificate's subject can be added as a CA to be sent to the client - with :py:obj:`Context.add_client_ca`. + with L{Context.add_client_ca}. """ cacert = load_certificate(FILETYPE_PEM, root_cert_pem) cadesc = cacert.get_subject() @@ -2602,7 +1903,7 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_multiple_add_client_ca(self): """ Multiple CA names can be sent to the client by calling - :py:obj:`Context.add_client_ca` with multiple X509 objects. + 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) @@ -2619,8 +1920,8 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_set_and_add_client_ca(self): """ - A call to :py:obj:`Context.set_client_ca_list` followed by a call to - :py:obj:`Context.add_client_ca` results in using the CA names from the first + 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) @@ -2640,8 +1941,8 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): def test_set_after_add_client_ca(self): """ - A call to :py:obj:`Context.set_client_ca_list` after a call to - :py:obj:`Context.add_client_ca` replaces the CA name specified by the former + 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) @@ -2659,56 +1960,6 @@ class MemoryBIOTests(TestCase, _LoopbackMixin): self._check_client_ca_list(set_replaces_add_ca) - -class ConnectionBIOTests(TestCase): - """ - Tests for :py:obj:`Connection.bio_read` and :py:obj:`Connection.bio_write`. - """ - def test_wantReadError(self): - """ - :py:obj:`Connection.bio_read` raises :py:obj:`OpenSSL.SSL.WantReadError` - if there are no bytes available to be read from the BIO. - """ - ctx = Context(TLSv1_METHOD) - conn = Connection(ctx, None) - self.assertRaises(WantReadError, conn.bio_read, 1024) - - - def test_buffer_size(self): - """ - :py:obj:`Connection.bio_read` accepts an integer giving the maximum - number of bytes to read and return. - """ - ctx = Context(TLSv1_METHOD) - conn = Connection(ctx, None) - conn.set_connect_state() - try: - conn.do_handshake() - except WantReadError: - pass - data = conn.bio_read(2) - self.assertEqual(2, len(data)) - - - if not PY3: - def test_buffer_size_long(self): - """ - On Python 2 :py:obj:`Connection.bio_read` accepts values of type - :py:obj:`long` as well as :py:obj:`int`. - """ - ctx = Context(TLSv1_METHOD) - conn = Connection(ctx, None) - conn.set_connect_state() - try: - conn.do_handshake() - except WantReadError: - pass - data = conn.bio_read(long(2)) - self.assertEqual(2, len(data)) - - - - class InfoConstantTests(TestCase): """ Tests for assorted constants exposed for use in info callbacks. diff --git a/OpenSSL/test/util.py b/OpenSSL/test/util.py index 4e4d812..643fb91 100644 --- a/OpenSSL/test/util.py +++ b/OpenSSL/test/util.py @@ -8,151 +8,31 @@ U{Twisted}. """ import shutil -import traceback import os, os.path from tempfile import mktemp from unittest import TestCase import sys -from OpenSSL._util import exception_from_error_queue -from OpenSSL.crypto import Error +from OpenSSL.crypto import Error, _exception_from_error_queue -try: - import memdbg -except Exception: - class _memdbg(object): heap = None - memdbg = _memdbg() +if sys.version_info < (3, 0): + def b(s): + return s + bytes = str +else: + def b(s): + return s.encode("charmap") + bytes = bytes -from OpenSSL._util import ffi, lib, byte_string as b class TestCase(TestCase): """ - :py:class:`TestCase` adds useful testing functionality beyond what is available - from the standard library :py:class:`unittest.TestCase`. + L{TestCase} adds useful testing functionality beyond what is available + from the standard library L{unittest.TestCase}. """ - def run(self, result): - run = super(TestCase, self).run - if memdbg.heap is None: - return run(result) - - # Run the test as usual - before = set(memdbg.heap) - run(result) - - # Clean up some long-lived allocations so they won't be reported as - # memory leaks. - lib.CRYPTO_cleanup_all_ex_data() - lib.ERR_remove_thread_state(ffi.NULL) - after = set(memdbg.heap) - - if not after - before: - # No leaks, fast succeed - return - - if result.wasSuccessful(): - # If it passed, run it again with memory debugging - before = set(memdbg.heap) - run(result) - - # Clean up some long-lived allocations so they won't be reported as - # memory leaks. - lib.CRYPTO_cleanup_all_ex_data() - lib.ERR_remove_thread_state(ffi.NULL) - - after = set(memdbg.heap) - - self._reportLeaks(after - before, result) - - - def _reportLeaks(self, leaks, result): - def format_leak(p): - stacks = memdbg.heap[p] - # Eventually look at multiple stacks for the realloc() case. For - # now just look at the original allocation location. - (size, python_stack, c_stack) = stacks[0] - - stack = traceback.format_list(python_stack)[:-1] - - # c_stack looks something like this (interesting parts indicated - # with inserted arrows not part of the data): - # - # /home/exarkun/Projects/pyOpenSSL/branches/use-opentls/__pycache__/_cffi__x89095113xb9185b9b.so(+0x12cf) [0x7fe2e20582cf] - # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a] - # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52] - # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e] - # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419] - # /home/exarkun/Projects/cpython/2.7/python() [0x4d6129] - # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e] - # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalCodeEx+0x1043) [0x4d3726] - # /home/exarkun/Projects/cpython/2.7/python() [0x55fd51] - # /home/exarkun/Projects/cpython/2.7/python(PyObject_Call+0x7e) [0x420ee6] - # /home/exarkun/Projects/cpython/2.7/python(PyEval_CallObjectWithKeywords+0x158) [0x4d56ec] - # /home/exarkun/.local/lib/python2.7/site-packages/cffi-0.5-py2.7-linux-x86_64.egg/_cffi_backend.so(+0xe96e) [0x7fe2e38be96e] - # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64_inner+0x1b9) [0x7fe2e36ad819] - # /usr/lib/x86_64-linux-gnu/libffi.so.6(ffi_closure_unix64+0x46) [0x7fe2e36adb7c] - # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(CRYPTO_malloc+0x64) [0x7fe2e1cef784] <------ end interesting - # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(lh_insert+0x16b) [0x7fe2e1d6a24b] . - # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x61c18) [0x7fe2e1cf0c18] . - # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(+0x625ec) [0x7fe2e1cf15ec] . - # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_new_method+0xe6) [0x7fe2e1d524d6] . - # /lib/x86_64-linux-gnu/libcrypto.so.1.0.0(DSA_generate_parameters+0x3a) [0x7fe2e1d5364a] <------ begin interesting - # /home/exarkun/Projects/opentls/trunk/tls/c/__pycache__/_cffi__x305d4698xb539baaa.so(+0x1f397) [0x7fe2df84d397] - # /home/exarkun/Projects/cpython/2.7/python(PyCFunction_Call+0x8b) [0x56265a] - # /home/exarkun/Projects/cpython/2.7/python() [0x4d5f52] - # /home/exarkun/Projects/cpython/2.7/python(PyEval_EvalFrameEx+0x753b) [0x4d0e1e] - # /home/exarkun/Projects/cpython/2.7/python() [0x4d6419] - # ... - # - # Notice the stack is upside down compared to a Python traceback. - # Identify the start and end of interesting bits and stuff it into the stack we report. - - saved = list(c_stack) - - # Figure the first interesting frame will be after a the cffi-compiled module - while c_stack and '/__pycache__/_cffi__' not in c_stack[-1]: - c_stack.pop() - - # Figure the last interesting frame will always be CRYPTO_malloc, - # since that's where we hooked in to things. - while c_stack and 'CRYPTO_malloc' not in c_stack[0] and 'CRYPTO_realloc' not in c_stack[0]: - c_stack.pop(0) - - if c_stack: - c_stack.reverse() - else: - c_stack = saved[::-1] - stack.extend([frame + "\n" for frame in c_stack]) - - stack.insert(0, "Leaked (%s) at:\n") - return "".join(stack) - - if leaks: - unique_leaks = {} - for p in leaks: - size = memdbg.heap[p][-1][0] - new_leak = format_leak(p) - if new_leak not in unique_leaks: - unique_leaks[new_leak] = [(size, p)] - else: - unique_leaks[new_leak].append((size, p)) - memdbg.free(p) - - for (stack, allocs) in unique_leaks.iteritems(): - allocs_accum = [] - for (size, pointer) in allocs: - - addr = int(ffi.cast('uintptr_t', pointer)) - allocs_accum.append("%d@0x%x" % (size, addr)) - allocs_report = ", ".join(sorted(allocs_accum)) - - result.addError( - self, - (None, Exception(stack % (allocs_report,)), None)) - - def tearDown(self): """ - Clean up any files or directories created using :py:meth:`TestCase.mktemp`. + 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. """ @@ -163,45 +43,21 @@ class TestCase(TestCase): elif os.path.exists(temp): os.unlink(temp) try: - exception_from_error_queue(Error) + _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 assertIsInstance(self, instance, classOrTuple, message=None): - """ - Fail if C{instance} is not an instance of the given class or of - one of the given classes. - - @param instance: the object to test the type (first argument of the - C{isinstance} call). - @type instance: any. - @param classOrTuple: the class or classes to test against (second - argument of the C{isinstance} call). - @type classOrTuple: class, type, or tuple. - - @param message: Custom text to include in the exception text if the - assertion fails. - """ - if not isinstance(instance, classOrTuple): - if message is None: - suffix = "" - else: - suffix = ": " + message - self.fail("%r is not an instance of %s%s" % ( - instance, classOrTuple, suffix)) - - def failUnlessIn(self, containee, container, msg=None): """ - Fail the test if :py:data:`containee` is not found in :py:data:`container`. + Fail the test if C{containee} is not found in C{container}. - :param containee: the value that should be in :py:class:`container` - :param container: a sequence type, or in the case of a mapping type, + @param containee: the value that should be in C{container} + @param container: a sequence type, or in the case of a mapping type, will follow semantics of 'if key in dict.keys()' - :param msg: if msg is None, then the failure message will be + @param msg: if msg is None, then the failure message will be '%r not in %r' % (first, second) """ if containee not in container: @@ -212,11 +68,11 @@ class TestCase(TestCase): def failUnlessIdentical(self, first, second, msg=None): """ - Fail the test if :py:data:`first` is not :py:data:`second`. This is an + Fail the test if C{first} is not C{second}. This is an obect-identity-equality test, not an object equality - (i.e. :py:func:`__eq__`) test. + (i.e. C{__eq__}) test. - :param msg: if msg is None, then the failure message will be + @param msg: if msg is None, then the failure message will be '%r is not %r' % (first, second) """ if first is not second: @@ -227,11 +83,11 @@ class TestCase(TestCase): def failIfIdentical(self, first, second, msg=None): """ - Fail the test if :py:data:`first` is :py:data:`second`. This is an + Fail the test if C{first} is C{second}. This is an obect-identity-equality test, not an object equality - (i.e. :py:func:`__eq__`) test. + (i.e. C{__eq__}) test. - :param msg: if msg is None, then the failure message will be + @param msg: if msg is None, then the failure message will be '%r is %r' % (first, second) """ if first is second: @@ -242,16 +98,15 @@ class TestCase(TestCase): def failUnlessRaises(self, exception, f, *args, **kwargs): """ - Fail the test unless calling the function :py:data:`f` with the given - :py:data:`args` and :py:data:`kwargs` raises :py:data:`exception`. The - failure will report the traceback and call stack of the unexpected - exception. + 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 + @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 + @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. """ @@ -278,22 +133,31 @@ class TestCase(TestCase): """ if self._temporaryFiles is None: self._temporaryFiles = [] - temp = b(mktemp(dir=".")) + 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 :py:data:`theType` to ensure that it is a + 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 :py:data:`theType` to + @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) 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..e634b01 --- /dev/null +++ b/OpenSSL/util.h @@ -0,0 +1,144 @@ +/* + * 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 + +#ifndef Py_TYPE +#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type) +#endif + +#endif diff --git a/OpenSSL/version.py b/OpenSSL/version.py index 307dba0..be51903 100644 --- a/OpenSSL/version.py +++ b/OpenSSL/version.py @@ -6,4 +6,4 @@ pyOpenSSL - A simple wrapper around the OpenSSL library """ -__version__ = '0.14' +__version__ = '0.13' diff --git a/PKG-INFO b/PKG-INFO new file mode 100644 index 0000000..2c881b4 --- /dev/null +++ b/PKG-INFO @@ -0,0 +1,15 @@ +Metadata-Version: 1.0 +Name: pyOpenSSL +Version: 0.13 +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 index 1b2a093..191fa5b 100644 --- a/README +++ b/README @@ -4,6 +4,5 @@ pyOpenSSL - A Python wrapper around the OpenSSL library See the file INSTALL for installation instructions. -See http://github.com/pyca/pyopenssl for development. - -See https://mail.python.org/mailman/listinfo/pyopenssl-users for the discussion mailing list. +I appreciate bug reports and patches. Please visit +. diff --git a/dapper/README.Debian b/dapper/README.Debian deleted file mode 100644 index 884deb8..0000000 --- a/dapper/README.Debian +++ /dev/null @@ -1,13 +0,0 @@ -Debian notes -- pyOpenSSL -------------------------- - -The source package is split up over four binary packages: - -* python2.2-pyopenssl -- compiled for Python 2.2 -* python2.3-pyopenssl -- compiled for Python 2.3 -* python-pyopenssl -- dummy package that depends on the default - Python version (currently 2.3) -* pyopenssl-doc -- documentation for pyOpenSSL - - -Martin Sjögren diff --git a/dapper/changelog b/dapper/changelog deleted file mode 100644 index a9db879..0000000 --- a/dapper/changelog +++ /dev/null @@ -1,201 +0,0 @@ -pyopenssl (0.6-2ubuntu3) dapper; urgency=low - - * Drop python2.3 package. - - -- Matthias Klose Tue, 14 Feb 2006 15:32:47 +0000 - -pyopenssl (0.6-2ubuntu2) dapper; urgency=low - - * Rebuild against openssl 0.9.8. - - -- Martin Pitt Mon, 30 Jan 2006 12:54:52 +0000 - -pyopenssl (0.6-2ubuntu1) breezy; urgency=low - - * Synchronize with Debian. - - -- Matthias Klose Wed, 27 Jul 2005 08:52:44 +0000 - -pyopenssl (0.6-2) unstable; urgency=low - - * Add support for python 2.4. (Closes: #297870) - * Build-depend on v0.9.7 of openssl. - - -- Martin Sjogren Mon, 14 Mar 2005 08:56:27 +0100 - -pyopenssl (0.6-1ubuntu3) hoary; urgency=low - - * Drop python2.2 package. - - -- Matthias Klose Mon, 24 Jan 2005 08:17:19 +0100 - -pyopenssl (0.6-1ubuntu2) hoary; urgency=low - - * Tighten build dependency on python. - - -- Matthias Klose Thu, 16 Dec 2004 22:56:53 +0100 - -pyopenssl (0.6-1ubuntu1) hoary; urgency=low - - * Support python2.4 as default python version. - - -- Matthias Klose Wed, 15 Dec 2004 20:59:11 +0100 - -pyopenssl (0.6-1) unstable; urgency=low - - * New upstream release, including: - - Add Netscape SPKI extensions. (Closes: #205132) - - Add X509.subject_name_hash, X509.digest. (Closes: #205136) - - Fix full names of exceptions. (Closes: #250342) - - Add SSL.Context.use_certificate_chain_file. (Closes: #260134) - * Docs are built upstream, so the build-deps have been trimmed. - This also means that HTML and text documentation are back. - * Bumped standards-version. - * Use dh_python. - - -- Martin Sjogren Fri, 13 Aug 2004 20:53:27 +0200 - -pyopenssl (0.5.1-4) unstable; urgency=low - - * Drop HTML and text documentation since latex2html moved to non-free. - This is a temporary solution, until I can hack mkhowto to use something - else. (Closes: #221344) - * Fix the copyright file to mention the copyright holder. - - -- Martin Sjogren Mon, 15 Dec 2003 20:16:25 +0100 - -pyopenssl (0.5.1-3) unstable; urgency=low - - * MANIFEST.in: Include the src/RATIONALE file. (Closes: #197401) - * doc/pyOpenSSL.tex: Fix typo. (Closes: #197435) - * Drop Python 1.5 and 2.1 support. - * Make python-pyopenssl depend on python2.3-pyopenssl, which is no longer - "experimental". - - -- Martin Sjogren Mon, 11 Aug 2003 18:37:07 +0200 - -pyopenssl (0.5.1-2) unstable; urgency=low - - * Make sure names in control and changelog match. Stupid changelogs, bleh. - * Change section to 'python'. - * Rebuild with openssl 0.9.7. (Closes: #189826) - * __init__.py: Import tsafe module. - * tsafe.py: Add some missing methods. - * debian/copyright: Fix Author(s) boilerplate thingy to shut lintian up. - - -- Martin Sjogren Sun, 20 Apr 2003 17:50:24 +0200 - -pyopenssl (0.5.1-1) unstable; urgency=low - - * New upstream version. (Closes: #159530) - * Added a python-pyopenssl dummy package. - * Added an experimental python2.3-pyopenssl package. - - -- Martin Sjögren Sun, 25 Aug 2002 12:08:31 +0200 - -pyopenssl (0.5-1) unstable; urgency=low - - * New upstream version - * Support for python1.5. - * Fix stupid mistakes for python 1.5 and python 2.1. - - -- Martin Sjögren Wed, 24 Jul 2002 09:05:28 +0200 - -pyopenssl (0.4.1-8) unstable; urgency=low - - * Added examples to pyopenssl-doc - - -- Martin Sjögren Wed, 5 Jun 2002 14:58:04 +0200 - -pyopenssl (0.4.1-7) unstable; urgency=low - - * The cute arrow icons in the HTML documentation should be there now too. - - -- Martin Sjögren Thu, 30 May 2002 00:53:44 +0200 - -pyopenssl (0.4.1-6) unstable; urgency=low - - * Commented out some unused things in debian/rules - - -- Martin Sjögren Wed, 29 May 2002 11:20:33 +0200 - -pyopenssl (0.4.1-5) unstable; urgency=low - - * Adding to the build-depends. - * Initial upload (Closes: #140687) - - -- Martin Sjögren Sat, 6 Apr 2002 14:15:49 +0200 - -pyopenssl (0.4.1-4) unstable; urgency=low - - * Fixes in packaging, it shouldn't be regarded a native package now. - - -- Martin Sjögren Sat, 6 Apr 2002 11:26:39 +0200 - -pyopenssl (0.4.1-3) unstable; urgency=low - - * Moved from non-US to main/devel - - -- Martin Sjögren Fri, 5 Apr 2002 22:44:10 +0200 - -pyopenssl (0.4.1-2) unstable; urgency=low - - * Fixes in the packaging, dependencies and build dependencies should be - all right now. - - -- Martin Sjögren Thu, 10 Jan 2002 10:00:06 +0100 - -pyopenssl (0.4.1-1) unstable; urgency=low - - * New "upstream" release - * New packaging, python2.1-pyopenssl, python2.2-pyopenssl, pyopenssl-doc - - -- Martin Sjögren Mon, 7 Jan 2002 15:38:51 +0100 - -pyopenssl (0.4-4) unstable; urgency=low - - * Grrr, this time then... - - -- Martin Sjögren Fri, 17 Aug 2001 14:53:19 +0200 - -pyopenssl (0.4-3) unstable; urgency=low - - * Fixed a big nasty bug - - -- Martin Sjögren Fri, 17 Aug 2001 14:33:06 +0200 - -pyopenssl (0.4-2) unstable; urgency=low - - * Fixes - - -- Martin Sjögren Fri, 17 Aug 2001 13:53:11 +0200 - -pyopenssl (0.4-1) unstable; urgency=low - - * New "upstream" version - - -- Martin Sjögren Thu, 9 Aug 2001 12:32:47 +0200 - -pyopenssl (0.3-3) unstable; urgency=low - - * X509 objects now has a has_expired method - - -- Martin Sjögren Tue, 7 Aug 2001 14:16:13 +0200 - -pyopenssl (0.3-2) unstable; urgency=low - - * X509Name objects now has a compare method - - -- Martin Sjögren Tue, 7 Aug 2001 10:53:58 +0200 - -pyopenssl (0.3-1) unstable; urgency=low - - * New "upstream" version - - -- Martin Sjögren Fri, 3 Aug 2001 16:36:26 +0200 - -pyopenssl (0.1-1) unstable; urgency=low - - * Initial version. - - -- Anders Hammarquist Mon, 23 Jul 2001 15:17:38 +0200 diff --git a/dapper/control b/dapper/control deleted file mode 100644 index 5d33a7f..0000000 --- a/dapper/control +++ /dev/null @@ -1,31 +0,0 @@ -Source: pyopenssl -Section: python -Priority: optional -Maintainer: Martin Sjogren -Build-Depends: debhelper (>> 4.2.28), python (>= 2.4), python2.4-dev, libssl-dev (>= 0.9.7) -Standards-Version: 3.6.1 - -Package: python2.4-pyopenssl -Architecture: any -Depends: ${shlibs:Depends}, ${python:Depends} -Suggests: pyopenssl-doc -Description: Python wrapper around the OpenSSL library, experimental! - Includes: SSL Context objects, SSL Connection objects, using - Python sockets as transport layer. The Connection object - wraps all the socket methods and can therefore be used - interchangeably. - -Package: python-pyopenssl -Architecture: all -Depends: ${python:Depends} -Description: Python wrapper around the OpenSSL library (dummy package) - This is a dummy package that depends on the version of pyOpenSSL - that is compiled for the default version of Python. - -Package: pyopenssl-doc -Section: doc -Architecture: all -Suggests: python-pyopenssl -Description: Documentation for pyOpenSSL - This package provides documentation for the pyOpenSSL package, - in HTML, postscript and text formats. diff --git a/dapper/copyright b/dapper/copyright deleted file mode 100644 index 2705399..0000000 --- a/dapper/copyright +++ /dev/null @@ -1,22 +0,0 @@ -This package was debianized by Martin Sjögren on -Mon, 7 Jan 2002 16:25:58 +0100. - -It was downloaded from pyopenssl.sourceforge.net - -Upstream Author: Martin Sjögren - -Copyright (C) 2001-2004 AB Strakt - -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -A copy of the GNU Lesser General Public License (version 2.1) -can be found in /usr/share/common-licenses/LGPL on Debian systems, -or in the file COPYING in the source package. diff --git a/dapper/pyopenssl-doc.doc-base b/dapper/pyopenssl-doc.doc-base deleted file mode 100644 index e57eb3f..0000000 --- a/dapper/pyopenssl-doc.doc-base +++ /dev/null @@ -1,13 +0,0 @@ -Document: pyopenssl-manual -Title: Python OpenSSL Manual -Author: Martin Sjögren -Abstract: Manual for the pyOpenSSL package. - 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. -Section: Apps/programming - -Format: HTML -Index: /usr/share/doc/pyopenssl-doc/html/index.html -Files: /usr/share/doc/pyopenssl-doc/html/* diff --git a/dapper/pyopenssl-doc.docs b/dapper/pyopenssl-doc.docs deleted file mode 100644 index 0149db1..0000000 --- a/dapper/pyopenssl-doc.docs +++ /dev/null @@ -1,4 +0,0 @@ -doc/pyOpenSSL.ps -doc/pyOpenSSL.txt -doc/html -debian/README.Debian diff --git a/dapper/pyopenssl-doc.examples b/dapper/pyopenssl-doc.examples deleted file mode 100644 index e39721e..0000000 --- a/dapper/pyopenssl-doc.examples +++ /dev/null @@ -1 +0,0 @@ -examples/* diff --git a/dapper/python-pyopenssl.docs b/dapper/python-pyopenssl.docs deleted file mode 100644 index e174728..0000000 --- a/dapper/python-pyopenssl.docs +++ /dev/null @@ -1 +0,0 @@ -debian/README.Debian diff --git a/dapper/rules b/dapper/rules deleted file mode 100755 index 2007485..0000000 --- a/dapper/rules +++ /dev/null @@ -1,103 +0,0 @@ -#!/usr/bin/make -f -# Sample debian/rules that uses debhelper. -# GNU copyright 1997 by Joey Hess. -# -# This version is for a hypothetical package that builds an -# architecture-dependant package, as well as an architecture-independent -# package. - -# Uncomment this to turn on verbose mode. -#export DH_VERBOSE=1 - -# This is the debhelper compatability version to use. -export DH_COMPAT=3 - -VERSIONS = 2.3 2.4 2.5 - -configure: configure-stamp -configure-stamp: - dh_testdir - - for ver in $(VERSIONS); do \ - cp debian/python-pyopenssl.docs debian/python$$ver-pyopenssl.docs; \ - done - - touch configure-stamp - -build: configure-stamp build-stamp -build-stamp: - dh_testdir - - for ver in $(VERSIONS); do \ - /usr/bin/python$$ver setup.py build; \ - done - - touch build-stamp - -clean: - dh_testdir - dh_testroot - rm -f build-stamp configure-stamp - - for ver in $(VERSIONS); do \ - rm -f debian/python$$ver-pyopenssl.docs; \ - done - rm -rf build - - dh_clean - -install: DH_OPTIONS= -install: build - dh_testdir - dh_testroot - dh_clean -k - dh_installdirs - - for ver in $(VERSIONS); do \ - /usr/bin/python$$ver setup.py install --prefix=$(CURDIR)/debian/python$$ver-pyopenssl/usr; \ - done - -# Build architecture-independent files here. -# Pass -i to all debhelper commands in this target to reduce clutter. -binary-indep: build install - dh_testdir -i - dh_testroot -i - dh_installdocs -i - dh_installexamples -i -# dh_installmenu -i -# dh_installcron -i -# dh_installinfo -i - dh_installchangelogs ChangeLog -i -# dh_link -i - dh_compress -i - dh_fixperms -i - dh_python -i - dh_installdeb -i - dh_gencontrol -i - dh_md5sums -i - dh_builddeb -i - -# Build architecture-dependent files here. -binary-arch: build install - dh_testdir -a - dh_testroot -a - dh_installdocs -a - dh_installexamples -a -# dh_installmenu -a -# dh_installcron -a -# dh_installinfo -a - dh_installchangelogs ChangeLog -a - dh_strip -a -# dh_link -a - dh_compress -a - dh_fixperms -a - dh_python -a - dh_makeshlibs -a - dh_installdeb -a - dh_shlibdeps -a - dh_gencontrol -a - dh_md5sums -a - dh_builddeb -a - -binary: binary-indep binary-arch -.PHONY: build clean binary-indep binary-arch binary install configure diff --git a/doc/Makefile b/doc/Makefile index 4202bee..07aabdc 100644 --- a/doc/Makefile +++ b/doc/Makefile @@ -1,130 +1,20 @@ -# Makefile for Sphinx documentation -# +PAPER = a4 +MKHOWTO = python tools/mkhowto --$(PAPER) -# You can set these variables from the command line. -SPHINXOPTS = -SPHINXBUILD = sphinx-build -PAPER = -BUILDDIR = _build +default: html -# Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +all: ps html text dvi -.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest +dvi ps text: pyOpenSSL.tex + $(MKHOWTO) --$@ $^ -help: - @echo "Please use \`make ' where is one of" - @echo " html to make standalone HTML files" - @echo " dirhtml to make HTML files named index.html in directories" - @echo " singlehtml to make a single large HTML file" - @echo " pickle to make pickle files" - @echo " json to make JSON files" - @echo " htmlhelp to make HTML files and a HTML help project" - @echo " qthelp to make HTML files and a qthelp project" - @echo " devhelp to make HTML files and a Devhelp project" - @echo " epub to make an epub" - @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" - @echo " latexpdf to make LaTeX files and run them through pdflatex" - @echo " text to make text files" - @echo " man to make manual pages" - @echo " changes to make an overview of all changed/added/deprecated items" - @echo " linkcheck to check all external links for integrity" - @echo " doctest to run all doctests embedded in the documentation (if enabled)" +html: pyOpenSSL.tex + $(MKHOWTO) --html --iconserver . $^ + -rm -rf html + mv pyOpenSSL html clean: - -rm -rf $(BUILDDIR)/* + rm -rf html pyOpenSSL.dvi pyOpenSSL.ps pyOpenSSL.txt \ + pyOpenSSL.l2h pyOpenSSL.how -html: - $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." - -dirhtml: - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml - @echo - @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." - -singlehtml: - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml - @echo - @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." - -pickle: - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle - @echo - @echo "Build finished; now you can process the pickle files." - -json: - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json - @echo - @echo "Build finished; now you can process the JSON files." - -htmlhelp: - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp - @echo - @echo "Build finished; now you can run HTML Help Workshop with the" \ - ".hhp project file in $(BUILDDIR)/htmlhelp." - -qthelp: - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp - @echo - @echo "Build finished; now you can run "qcollectiongenerator" with the" \ - ".qhcp project file in $(BUILDDIR)/qthelp, like this:" - @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyOpenSSL.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyOpenSSL.qhc" - -devhelp: - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp - @echo - @echo "Build finished." - @echo "To view the help file:" - @echo "# mkdir -p $$HOME/.local/share/devhelp/pyOpenSSL" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyOpenSSL" - @echo "# devhelp" - -epub: - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub - @echo - @echo "Build finished. The epub file is in $(BUILDDIR)/epub." - -latex: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo - @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." - @echo "Run \`make' in that directory to run these through (pdf)latex" \ - "(use \`make latexpdf' here to do that automatically)." - -latexpdf: - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex - @echo "Running LaTeX files through pdflatex..." - make -C $(BUILDDIR)/latex all-pdf - @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." - -text: - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text - @echo - @echo "Build finished. The text files are in $(BUILDDIR)/text." - -man: - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man - @echo - @echo "Build finished. The manual pages are in $(BUILDDIR)/man." - -changes: - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes - @echo - @echo "The overview file is in $(BUILDDIR)/changes." - -linkcheck: - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck - @echo - @echo "Link check complete; look for any errors in the above output " \ - "or in $(BUILDDIR)/linkcheck/output.txt." - -doctest: - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest - @echo "Testing of doctests in the sources finished, look at the " \ - "results in $(BUILDDIR)/doctest/output.txt." +.PHONY: default all html dvi ps text clean diff --git a/doc/Quotes b/doc/Quotes deleted file mode 100644 index b2cdb2d..0000000 --- a/doc/Quotes +++ /dev/null @@ -1,2 +0,0 @@ -< Screwtape> I like how developing against OpenSSL is like a text adventure game with a maze of twisty passages, all alike. -% diff --git a/doc/README b/doc/README deleted file mode 100644 index 2a525bb..0000000 --- a/doc/README +++ /dev/null @@ -1,4 +0,0 @@ -This is the pyOpenSSL documentation source. It uses Sphinx. To build the -documentation, install Sphinx 1.0 and run: - - $ make html diff --git a/doc/api.rst b/doc/api.rst deleted file mode 100644 index 826ec4d..0000000 --- a/doc/api.rst +++ /dev/null @@ -1,18 +0,0 @@ -.. _openssl: - -:py:mod:`OpenSSL` --- Python interface to OpenSSL -================================================= - -.. py:module:: OpenSSL - :synopsis: Python interface to OpenSSL - - -This package provides a high-level interface to the functions in the -OpenSSL library. The following modules are defined: - -.. toctree:: - :maxdepth: 2 - - api/crypto - api/rand - api/ssl diff --git a/doc/api/crypto.rst b/doc/api/crypto.rst deleted file mode 100644 index 7c77b03..0000000 --- a/doc/api/crypto.rst +++ /dev/null @@ -1,753 +0,0 @@ -.. _openssl-crypto: - -:py:mod:`crypto` --- Generic cryptographic module -================================================= - -.. py:module:: OpenSSL.crypto - :synopsis: Generic cryptographic module - - -.. py:data:: X509Type - - See :py:class:`X509`. - - -.. py:class:: X509() - - A class representing X.509 certificates. - - -.. py:data:: X509NameType - - See :py:class:`X509Name`. - - -.. py:class:: X509Name(x509name) - - A class representing X.509 Distinguished Names. - - This constructor creates a copy of *x509name* which should be an - instance of :py:class:`X509Name`. - - -.. py:data:: X509ReqType - - See :py:class:`X509Req`. - - -.. py:class:: X509Req() - - A class representing X.509 certificate requests. - - -.. py:data:: X509StoreType - - A Python type object representing the X509Store object type. - - -.. py:data:: PKeyType - - See :py:class:`PKey`. - - -.. py:class:: PKey() - - A class representing DSA or RSA keys. - - -.. py:data:: PKCS7Type - - A Python type object representing the PKCS7 object type. - - -.. py:data:: PKCS12Type - - A Python type object representing the PKCS12 object type. - - -.. py:data:: X509ExtensionType - - See :py:class:`X509Extension`. - - -.. py:class:: X509Extension(typename, critical, value[, subject][, issuer]) - - A class representing an X.509 v3 certificate extensions. See - http://openssl.org/docs/apps/x509v3_config.html#STANDARD_EXTENSIONS for - *typename* strings and their options. Optional parameters *subject* and - *issuer* must be X509 objects. - - -.. py:data:: NetscapeSPKIType - - See :py:class:`NetscapeSPKI`. - - -.. py:class:: NetscapeSPKI([enc]) - - A class representing Netscape SPKI objects. - - If the *enc* argument is present, it should be a base64-encoded string - representing a NetscapeSPKI object, as returned by the :py:meth:`b64_encode` - method. - - -.. py:class:: CRL() - - A class representing Certifcate Revocation List objects. - - -.. py:class:: Revoked() - - A class representing Revocation objects of CRL. - - -.. py:data:: FILETYPE_PEM - FILETYPE_ASN1 - - File type constants. - - -.. py:data:: TYPE_RSA - TYPE_DSA - - Key type constants. - - -.. py:exception:: Error - - Generic exception used in the :py:mod:`.crypto` module. - - -.. py:function:: dump_certificate(type, cert) - - Dump the certificate *cert* into a buffer string encoded with the type - *type*. - - -.. py:function:: dump_certificate_request(type, req) - - Dump the certificate request *req* into a buffer string encoded with the - type *type*. - - -.. py:function:: dump_privatekey(type, pkey[, cipher, passphrase]) - - Dump the private key *pkey* into a buffer string encoded with the type - *type*, optionally (if *type* is :py:const:`FILETYPE_PEM`) encrypting it - using *cipher* and *passphrase*. - - *passphrase* must be either a string or a callback for providing the - pass phrase. - - -.. py:function:: load_certificate(type, buffer) - - Load a certificate (X509) from the string *buffer* encoded with the - type *type*. - - -.. py:function:: load_certificate_request(type, buffer) - - Load a certificate request (X509Req) from the string *buffer* encoded with - the type *type*. - - -.. py:function:: load_privatekey(type, buffer[, passphrase]) - - Load a private key (PKey) from the string *buffer* encoded with the type - *type* (must be one of :py:const:`FILETYPE_PEM` and - :py:const:`FILETYPE_ASN1`). - - *passphrase* must be either a string or a callback for providing the pass - phrase. - - -.. py:function:: load_crl(type, buffer) - - Load Certificate Revocation List (CRL) data from a string *buffer*. - *buffer* encoded with the type *type*. The type *type* must either - :py:const:`FILETYPE_PEM` or :py:const:`FILETYPE_ASN1`). - - -.. py:function:: load_pkcs7_data(type, buffer) - - Load pkcs7 data from the string *buffer* encoded with the type *type*. - - -.. py:function:: load_pkcs12(buffer[, passphrase]) - - Load pkcs12 data from the string *buffer*. If the pkcs12 structure is - encrypted, a *passphrase* must be included. The MAC is always - checked and thus required. - - See also the man page for the C function :py:func:`PKCS12_parse`. - - -.. py:function:: sign(key, data, digest) - - Sign a data string using the given key and message digest. - - *key* is a :py:class:`PKey` instance. *data* is a ``str`` instance. - *digest* is a ``str`` naming a supported message digest type, for example - :py:const:`sha1`. - - .. versionadded:: 0.11 - - -.. py:function:: verify(certificate, signature, data, digest) - - Verify the signature for a data string. - - *certificate* is a :py:class:`X509` instance corresponding to the private - key which generated the signature. *signature* is a *str* instance giving - the signature itself. *data* is a *str* instance giving the data to which - the signature applies. *digest* is a *str* instance naming the message - digest type of the signature, for example :py:const:`sha1`. - - .. versionadded:: 0.11 - - -.. _openssl-x509: - -X509 objects ------------- - -X509 objects have the following methods: - -.. py:method:: X509.get_issuer() - - Return an X509Name object representing the issuer of the certificate. - - -.. py:method:: X509.get_pubkey() - - Return a :py:class:`PKey` object representing the public key of the certificate. - - -.. py:method:: X509.get_serial_number() - - Return the certificate serial number. - - -.. py:method:: X509.get_signature_algorithm() - - Return the signature algorithm used in the certificate. If the algorithm is - undefined, raise :py:data:`ValueError`. - - ..versionadded:: 0.13 - - -.. py:method:: X509.get_subject() - - Return an :py:class:`X509Name` object representing the subject of the certificate. - - -.. py:method:: X509.get_version() - - Return the certificate version. - - -.. py:method:: X509.get_notBefore() - - Return a string giving the time before which the certificate is not valid. The - string is formatted as an ASN1 GENERALIZEDTIME:: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - - If no value exists for this field, :py:data:`None` is returned. - - -.. py:method:: X509.get_notAfter() - - Return a string giving the time after which the certificate is not valid. The - string is formatted as an ASN1 GENERALIZEDTIME:: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - - If no value exists for this field, :py:data:`None` is returned. - - -.. py:method:: X509.set_notBefore(when) - - Change the time before which the certificate is not valid. *when* is a - string formatted as an ASN1 GENERALIZEDTIME:: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - - -.. py:method:: X509.set_notAfter(when) - - Change the time after which the certificate is not valid. *when* is a - string formatted as an ASN1 GENERALIZEDTIME:: - - YYYYMMDDhhmmssZ - YYYYMMDDhhmmss+hhmm - YYYYMMDDhhmmss-hhmm - - - -.. py:method:: X509.gmtime_adj_notBefore(time) - - Adjust the timestamp (in GMT) when the certificate starts being valid. - - -.. py:method:: X509.gmtime_adj_notAfter(time) - - Adjust the timestamp (in GMT) when the certificate stops being valid. - - -.. py:method:: X509.has_expired() - - Checks the certificate's time stamp against current time. Returns true if the - certificate has expired and false otherwise. - - -.. py:method:: X509.set_issuer(issuer) - - Set the issuer of the certificate to *issuer*. - - -.. py:method:: X509.set_pubkey(pkey) - - Set the public key of the certificate to *pkey*. - - -.. py:method:: X509.set_serial_number(serialno) - - Set the serial number of the certificate to *serialno*. - - -.. py:method:: X509.set_subject(subject) - - Set the subject of the certificate to *subject*. - - -.. py:method:: X509.set_version(version) - - Set the certificate version to *version*. - - -.. py:method:: X509.sign(pkey, digest) - - Sign the certificate, using the key *pkey* and the message digest algorithm - identified by the string *digest*. - - -.. py:method:: X509.subject_name_hash() - - Return the hash of the certificate subject. - -.. py:method:: X509.digest(digest_name) - - Return a digest of the certificate, using the *digest_name* method. - *digest_name* must be a string describing a digest algorithm supported - by OpenSSL (by EVP_get_digestbyname, specifically). For example, - :py:const:`"md5"` or :py:const:`"sha1"`. - - -.. py:method:: X509.add_extensions(extensions) - - Add the extensions in the sequence *extensions* to the certificate. - - -.. py:method:: X509.get_extension_count() - - Return the number of extensions on this certificate. - - .. versionadded:: 0.12 - - -.. py:method:: X509.get_extension(index) - - Retrieve the extension on this certificate at the given index. - - Extensions on a certificate are kept in order. The index parameter selects - which extension will be returned. The returned object will be an - :py:class:`X509Extension` instance. - - .. versionadded:: 0.12 - - -.. _openssl-x509name: - -X509Name objects ----------------- - -X509Name objects have the following methods: - -.. py:method:: X509Name.hash() - - Return an integer giving the first four bytes of the MD5 digest of the DER - representation of the name. - - -.. py:method:: X509Name.der() - - Return a string giving the DER representation of the name. - - -.. py:method:: X509Name.get_components() - - Return a list of two-tuples of strings giving the components of the name. - - -X509Name objects have the following members: - -.. py:attribute:: X509Name.countryName - - The country of the entity. :py:attr:`C` may be used as an alias for - :py:attr:`countryName`. - - -.. py:attribute:: X509Name.stateOrProvinceName - - The state or province of the entity. :py:attr:`ST` may be used as an alias for - :py:attr:`stateOrProvinceName`. - - -.. py:attribute:: X509Name.localityName - - The locality of the entity. :py:attr:`L` may be used as an alias for - :py:attr:`localityName`. - - -.. py:attribute:: X509Name.organizationName - - The organization name of the entity. :py:attr:`O` may be used as an alias for - :py:attr:`organizationName`. - - -.. py:attribute:: X509Name.organizationalUnitName - - The organizational unit of the entity. :py:attr:`OU` may be used as an alias for - :py:attr:`organizationalUnitName`. - - -.. py:attribute:: X509Name.commonName - - The common name of the entity. :py:attr:`CN` may be used as an alias for - :py:attr:`commonName`. - - -.. py:attribute:: X509Name.emailAddress - - The e-mail address of the entity. - - -.. _openssl-x509req: - -X509Req objects ---------------- - -X509Req objects have the following methods: - -.. py:method:: X509Req.get_pubkey() - - Return a :py:class:`PKey` object representing the public key of the certificate request. - - -.. py:method:: X509Req.get_subject() - - Return an :py:class:`X509Name` object representing the subject of the certificate. - - -.. py:method:: X509Req.set_pubkey(pkey) - - Set the public key of the certificate request to *pkey*. - - -.. py:method:: X509Req.sign(pkey, digest) - - Sign the certificate request, using the key *pkey* and the message digest - algorithm identified by the string *digest*. - - -.. py:method:: X509Req.verify(pkey) - - Verify a certificate request using the public key *pkey*. - - -.. py:method:: X509Req.set_version(version) - - Set the version (RFC 2459, 4.1.2.1) of the certificate request to - *version*. - - -.. py:method:: X509Req.get_version() - - Get the version (RFC 2459, 4.1.2.1) of the certificate request. - - -.. _openssl-x509store: - -X509Store objects ------------------ - -The X509Store object has currently just one method: - -.. py:method:: X509Store.add_cert(cert) - - Add the certificate *cert* to the certificate store. - - -.. _openssl-pkey: - -PKey objects ------------- - -The PKey object has the following methods: - -.. py:method:: PKey.bits() - - Return the number of bits of the key. - - -.. py:method:: PKey.generate_key(type, bits) - - Generate a public/private key pair of the type *type* (one of - :py:const:`TYPE_RSA` and :py:const:`TYPE_DSA`) with the size *bits*. - - -.. py:method:: PKey.type() - - Return the type of the key. - - -.. py:method:: PKey.check() - - Check the consistency of this key, returning True if it is consistent and - raising an exception otherwise. This is only valid for RSA keys. See the - OpenSSL RSA_check_key man page for further limitations. - - -.. _openssl-pkcs7: - -PKCS7 objects -------------- - -PKCS7 objects have the following methods: - -.. py:method:: PKCS7.type_is_signed() - - FIXME - - -.. py:method:: PKCS7.type_is_enveloped() - - FIXME - - -.. py:method:: PKCS7.type_is_signedAndEnveloped() - - FIXME - - -.. py:method:: PKCS7.type_is_data() - - FIXME - - -.. py:method:: PKCS7.get_type_name() - - Get the type name of the PKCS7. - - -.. _openssl-pkcs12: - -PKCS12 objects --------------- - -PKCS12 objects have the following methods: - -.. py:method:: PKCS12.export([passphrase=None][, iter=2048][, maciter=1]) - - Returns a PKCS12 object as a string. - - The optional *passphrase* must be a string not a callback. - - See also the man page for the C function :py:func:`PKCS12_create`. - - -.. py:method:: PKCS12.get_ca_certificates() - - Return CA certificates within the PKCS12 object as a tuple. Returns - :py:const:`None` if no CA certificates are present. - - -.. py:method:: PKCS12.get_certificate() - - Return certificate portion of the PKCS12 structure. - - -.. py:method:: PKCS12.get_friendlyname() - - Return friendlyName portion of the PKCS12 structure. - - -.. py:method:: PKCS12.get_privatekey() - - Return private key portion of the PKCS12 structure - - -.. py:method:: PKCS12.set_ca_certificates(cacerts) - - Replace or set the CA certificates within the PKCS12 object with the sequence *cacerts*. - - Set *cacerts* to :py:const:`None` to remove all CA certificates. - - -.. py:method:: PKCS12.set_certificate(cert) - - Replace or set the certificate portion of the PKCS12 structure. - - -.. py:method:: PKCS12.set_friendlyname(name) - - Replace or set the friendlyName portion of the PKCS12 structure. - - -.. py:method:: PKCS12.set_privatekey(pkey) - - Replace or set private key portion of the PKCS12 structure - - -.. _openssl-509ext: - -X509Extension objects ---------------------- - -X509Extension objects have several methods: - -.. py:method:: X509Extension.get_critical() - - Return the critical field of the extension object. - - -.. py:method:: X509Extension.get_short_name() - - Retrieve the short descriptive name for this extension. - - The result is a byte string like :py:const:`basicConstraints`. - - .. versionadded:: 0.12 - - -.. py:method:: X509Extension.get_data() - - Retrieve the data for this extension. - - The result is the ASN.1 encoded form of the extension data as a byte string. - - .. versionadded:: 0.12 - - -.. _openssl-netscape-spki: - -NetscapeSPKI objects --------------------- - -NetscapeSPKI objects have the following methods: - -.. py:method:: NetscapeSPKI.b64_encode() - - Return a base64-encoded string representation of the object. - - -.. py:method:: NetscapeSPKI.get_pubkey() - - Return the public key of object. - - -.. py:method:: NetscapeSPKI.set_pubkey(key) - - Set the public key of the object to *key*. - - -.. py:method:: NetscapeSPKI.sign(key, digest_name) - - Sign the NetscapeSPKI object using the given *key* and *digest_name*. - *digest_name* must be a string describing a digest algorithm supported by - OpenSSL (by EVP_get_digestbyname, specifically). For example, - :py:const:`"md5"` or :py:const:`"sha1"`. - - -.. py:method:: NetscapeSPKI.verify(key) - - Verify the NetscapeSPKI object using the given *key*. - - -.. _crl: - -CRL objects ------------ - -CRL objects have the following methods: - -.. py:method:: CRL.add_revoked(revoked) - - Add a Revoked object to the CRL, by value not reference. - - -.. py:method:: CRL.export(cert, key[, type=FILETYPE_PEM][, days=100]) - - Use *cert* and *key* to sign the CRL and return the CRL as a string. - *days* is the number of days before the next CRL is due. - - -.. py:method:: CRL.get_revoked() - - Return a tuple of Revoked objects, by value not reference. - - -.. _revoked: - -Revoked objects ---------------- - -Revoked objects have the following methods: - -.. py:method:: Revoked.all_reasons() - - Return a list of all supported reasons. - - -.. py:method:: Revoked.get_reason() - - Return the revocation reason as a str. Can be - None, which differs from "Unspecified". - - -.. py:method:: Revoked.get_rev_date() - - Return the revocation date as a str. - The string is formatted as an ASN1 GENERALIZEDTIME. - - -.. py:method:: Revoked.get_serial() - - Return a str containing a hex number of the serial of the revoked certificate. - - -.. py:method:: Revoked.set_reason(reason) - - Set the revocation reason. *reason* must be None or a string, but the - values are limited. Spaces and case are ignored. See - :py:meth:`all_reasons`. - - -.. py:method:: Revoked.set_rev_date(date) - - Set the revocation date. - The string is formatted as an ASN1 GENERALIZEDTIME. - - -.. py:method:: Revoked.set_serial(serial) - - *serial* is a string containing a hex number of the serial of the revoked certificate. diff --git a/doc/api/rand.rst b/doc/api/rand.rst deleted file mode 100644 index 18789b8..0000000 --- a/doc/api/rand.rst +++ /dev/null @@ -1,79 +0,0 @@ -.. _openssl-rand: - -:py:mod:`rand` --- An interface to the OpenSSL pseudo random number generator -============================================================================= - -.. py:module:: OpenSSL.rand - :synopsis: An interface to the OpenSSL pseudo random number generator - - -This module handles the OpenSSL pseudo random number generator (PRNG) and -declares the following: - -.. py:function:: add(string, entropy) - - Mix bytes from *string* into the PRNG state. The *entropy* argument is - (the lower bound of) an estimate of how much randomness is contained in - *string*, measured in bytes. For more information, see e.g. :rfc:`1750`. - - -.. py:function:: bytes(num_bytes) - - Get some random bytes from the PRNG as a string. - - This is a wrapper for the C function :py:func:`RAND_bytes`. - - -.. py:function:: cleanup() - - Erase the memory used by the PRNG. - - This is a wrapper for the C function :py:func:`RAND_cleanup`. - - -.. py:function:: egd(path[, bytes]) - - Query the `Entropy Gathering Daemon `_ on - socket *path* for *bytes* bytes of random data and uses :py:func:`add` to - seed the PRNG. The default value of *bytes* is 255. - - -.. py:function:: load_file(path[, bytes]) - - Read *bytes* bytes (or all of it, if *bytes* is negative) of data from the - file *path* to seed the PRNG. The default value of *bytes* is -1. - - -.. py:function:: screen() - - Add the current contents of the screen to the PRNG state. - - Availability: Windows. - - -.. py:function:: seed(string) - - This is equivalent to calling :py:func:`add` with *entropy* as the length - of the string. - - -.. py:function:: status() - - Returns true if the PRNG has been seeded with enough data, and false otherwise. - - -.. py:function:: write_file(path) - - Write a number of random bytes (currently 1024) to the file *path*. This - file can then be used with :py:func:`load_file` to seed the PRNG again. - - -.. py:exception:: Error - - If the current RAND method supports any errors, this is raised when needed. - The default method does not raise this when the entropy pool is depleted. - - Whenever this exception is raised directly, it has a list of error messages - from the OpenSSL error queue, where each item is a tuple *(lib, function, - reason)*. Here *lib*, *function* and *reason* are all strings, describing - where and what the problem is. See :manpage:`err(3)` for more information. diff --git a/doc/api/ssl.rst b/doc/api/ssl.rst deleted file mode 100644 index b506757..0000000 --- a/doc/api/ssl.rst +++ /dev/null @@ -1,773 +0,0 @@ -.. _openssl-ssl: - -:py:mod:`SSL` --- An interface to the SSL-specific parts of OpenSSL -=================================================================== - -.. py:module:: OpenSSL.SSL - :synopsis: An interface to the SSL-specific parts of OpenSSL - - -This module handles things specific to SSL. There are two objects defined: -Context, Connection. - -.. py:data:: SSLv2_METHOD - SSLv3_METHOD - SSLv23_METHOD - TLSv1_METHOD - TLSv1_1_METHOD - TLSv1_2_METHOD - - These constants represent the different SSL methods to use when creating a - context object. If the underlying OpenSSL build is missing support for any - of these protocols, constructing a :py:class:`Context` using the - corresponding :py:const:`*_METHOD` will raise an exception. - - -.. py:data:: VERIFY_NONE - VERIFY_PEER - VERIFY_FAIL_IF_NO_PEER_CERT - - These constants represent the verification mode used by the Context - object's :py:meth:`set_verify` method. - - -.. py:data:: FILETYPE_PEM - FILETYPE_ASN1 - - File type constants used with the :py:meth:`use_certificate_file` and - :py:meth:`use_privatekey_file` methods of Context objects. - - -.. py:data:: OP_SINGLE_DH_USE - - Constant used with :py:meth:`set_options` of Context objects. - - When this option is used, a new key will always be created when using - ephemeral Diffie-Hellman. - - -.. py:data:: OP_EPHEMERAL_RSA - - Constant used with :py:meth:`set_options` of Context objects. - - When this option is used, ephemeral RSA keys will always be used when doing - RSA operations. - - -.. py:data:: OP_NO_TICKET - - Constant used with :py:meth:`set_options` of Context objects. - - When this option is used, the session ticket extension will not be used. - - -.. py:data:: OP_NO_COMPRESSION - - Constant used with :py:meth:`set_options` of Context objects. - - When this option is used, compression will not be used. - - -.. py:data:: OP_NO_SSLv2 - OP_NO_SSLv3 - OP_NO_TLSv1 - OP_NO_TLSv1_1 - OP_NO_TLSv1_2 - - Constants used with :py:meth:`set_options` of Context objects. - - Each of these options disables one version of the SSL/TLS protocol. This - is interesting if you're using e.g. :py:const:`SSLv23_METHOD` to get an - SSLv2-compatible handshake, but don't want to use SSLv2. If the underlying - OpenSSL build is missing support for any of these protocols, the - :py:const:`OP_NO_*` constant may be undefined. - - -.. py:data:: MODE_NO_COMPRESSION - - Constant used with :py:meth:`set_mode` of Context objects to disable - automatic compression of application traffic. - - -.. py:data:: SSLEAY_VERSION - SSLEAY_CFLAGS - SSLEAY_BUILT_ON - SSLEAY_PLATFORM - SSLEAY_DIR - - Constants used with :py:meth:`SSLeay_version` to specify what OpenSSL version - information to retrieve. See the man page for the :py:func:`SSLeay_version` C - API for details. - - -.. py:data:: SESS_CACHE_OFF - SESS_CACHE_CLIENT - SESS_CACHE_SERVER - SESS_CACHE_BOTH - SESS_CACHE_NO_AUTO_CLEAR - SESS_CACHE_NO_INTERNAL_LOOKUP - SESS_CACHE_NO_INTERNAL_STORE - SESS_CACHE_NO_INTERNAL - - Constants used with :py:meth:`Context.set_session_cache_mode` to specify - the behavior of the session cache and potential session reuse. See the man - page for the :py:func:`SSL_CTX_set_session_cache_mode` C API for details. - - .. versionadded:: 0.14 - - -.. py:data:: OPENSSL_VERSION_NUMBER - - An integer giving the version number of the OpenSSL library used to build this - version of pyOpenSSL. See the man page for the :py:func:`SSLeay_version` C API - for details. - - -.. py:function:: SSLeay_version(type) - - Retrieve a string describing some aspect of the underlying OpenSSL version. The - type passed in should be one of the :py:const:`SSLEAY_*` constants defined in - this module. - - -.. py:data:: ContextType - - See :py:class:`Context`. - - -.. py:class:: Context(method) - - A class representing SSL contexts. Contexts define the parameters of one or - more SSL connections. - - *method* should be :py:const:`SSLv2_METHOD`, :py:const:`SSLv3_METHOD`, - :py:const:`SSLv23_METHOD`, :py:const:`TLSv1_METHOD`, :py:const:`TLSv1_1_METHOD`, - or :py:const:`TLSv1_2_METHOD`. - - -.. py:class:: Session() - - A class representing an SSL session. A session defines certain connection - parameters which may be re-used to speed up the setup of subsequent - connections. - - .. versionadded:: 0.14 - - -.. py:data:: ConnectionType - - See :py:class:`Connection`. - - -.. py:class:: Connection(context, socket) - - A class representing SSL connections. - - *context* should be an instance of :py:class:`Context` and *socket* - should be a socket [#connection-context-socket]_ object. *socket* may be - *None*; in this case, the Connection is created with a memory BIO: see - the :py:meth:`bio_read`, :py:meth:`bio_write`, and :py:meth:`bio_shutdown` - methods. - -.. py:exception:: Error - - This exception is used as a base class for the other SSL-related - exceptions, but may also be raised directly. - - Whenever this exception is raised directly, it has a list of error messages - from the OpenSSL error queue, where each item is a tuple *(lib, function, - reason)*. Here *lib*, *function* and *reason* are all strings, describing - where and what the problem is. See :manpage:`err(3)` for more information. - - -.. py:exception:: ZeroReturnError - - This exception matches the error return code - :py:data:`SSL_ERROR_ZERO_RETURN`, and is raised when the SSL Connection has - been closed. In SSL 3.0 and TLS 1.0, this only occurs if a closure alert has - occurred in the protocol, i.e. the connection has been closed cleanly. Note - that this does not necessarily mean that the transport layer (e.g. a socket) - has been closed. - - It may seem a little strange that this is an exception, but it does match an - :py:data:`SSL_ERROR` code, and is very convenient. - - -.. py:exception:: WantReadError - - The operation did not complete; the same I/O method should be called again - later, with the same arguments. Any I/O method can lead to this since new - handshakes can occur at any time. - - The wanted read is for **dirty** data sent over the network, not the - **clean** data inside the tunnel. For a socket based SSL connection, - **read** means data coming at us over the network. Until that read - succeeds, the attempted :py:meth:`OpenSSL.SSL.Connection.recv`, - :py:meth:`OpenSSL.SSL.Connection.send`, or - :py:meth:`OpenSSL.SSL.Connection.do_handshake` is prevented or incomplete. You - probably want to :py:meth:`select()` on the socket before trying again. - - -.. py:exception:: WantWriteError - - See :py:exc:`WantReadError`. The socket send buffer may be too full to - write more data. - - -.. py:exception:: WantX509LookupError - - The operation did not complete because an application callback has asked to be - called again. The I/O method should be called again later, with the same - arguments. - - .. note:: This won't occur in this version, as there are no such - callbacks in this version. - - -.. py:exception:: SysCallError - - The :py:exc:`SysCallError` occurs when there's an I/O error and OpenSSL's - error queue does not contain any information. This can mean two things: An - error in the transport protocol, or an end of file that violates the protocol. - The parameter to the exception is always a pair *(errnum, - errstr)*. - - - -.. _openssl-context: - -Context objects ---------------- - -Context objects have the following methods: - -.. :py:class:: OpenSSL.SSL.Context - -.. py:method:: Context.check_privatekey() - - Check if the private key (loaded with :py:meth:`use_privatekey`) matches the - certificate (loaded with :py:meth:`use_certificate`). Returns - :py:data:`None` if they match, raises :py:exc:`Error` otherwise. - - -.. py:method:: Context.get_app_data() - - Retrieve application data as set by :py:meth:`set_app_data`. - - -.. py:method:: Context.get_cert_store() - - Retrieve the certificate store (a X509Store object) that the context uses. - This can be used to add "trusted" certificates without using the. - :py:meth:`load_verify_locations` method. - - -.. py:method:: Context.get_timeout() - - Retrieve session timeout, as set by :py:meth:`set_timeout`. The default is 300 - seconds. - - -.. py:method:: Context.get_verify_depth() - - Retrieve the Context object's verify depth, as set by - :py:meth:`set_verify_depth`. - - -.. py:method:: Context.get_verify_mode() - - Retrieve the Context object's verify mode, as set by :py:meth:`set_verify`. - - -.. py:method:: Context.load_client_ca(pemfile) - - Read a file with PEM-formatted certificates that will be sent to the client - when requesting a client certificate. - - -.. py:method:: Context.set_client_ca_list(certificate_authorities) - - Replace the current list of preferred certificate signers that would be - sent to the client when requesting a client certificate with the - *certificate_authorities* sequence of :py:class:`OpenSSL.crypto.X509Name`'s. - - .. versionadded:: 0.10 - - -.. py:method:: Context.add_client_ca(certificate_authority) - - Extract a :py:class:`OpenSSL.crypto.X509Name` from the *certificate_authority* - :py:class:`OpenSSL.crypto.X509` certificate and add it to the list of preferred - certificate signers sent to the client when requesting a client certificate. - - .. versionadded:: 0.10 - - -.. py:method:: Context.load_verify_locations(pemfile, capath) - - Specify where CA certificates for verification purposes are located. These - are trusted certificates. Note that the certificates have to be in PEM - format. If capath is passed, it must be a directory prepared using the - ``c_rehash`` tool included with OpenSSL. Either, but not both, of - *pemfile* or *capath* may be :py:data:`None`. - - -.. py:method:: Context.set_default_verify_paths() - - Specify that the platform provided CA certificates are to be used for - verification purposes. This method may not work properly on OS X. - - -.. py:method:: Context.load_tmp_dh(dhfile) - - Load parameters for Ephemeral Diffie-Hellman from *dhfile*. - - -.. py:method:: Context.set_app_data(data) - - Associate *data* with this Context object. *data* can be retrieved - later using the :py:meth:`get_app_data` method. - - -.. py:method:: Context.set_cipher_list(ciphers) - - Set the list of ciphers to be used in this context. See the OpenSSL manual for - more information (e.g. :manpage:`ciphers(1)`) - - -.. py:method:: Context.set_info_callback(callback) - - Set the information callback to *callback*. This function will be called - from time to time during SSL handshakes. - - *callback* should take three arguments: a Connection object and two integers. - The first integer specifies where in the SSL handshake the function was - called, and the other the return code from a (possibly failed) internal - function call. - - -.. py:method:: Context.set_options(options) - - Add SSL options. Options you have set before are not cleared! - This method should be used with the :py:const:`OP_*` constants. - - -.. py:method:: Context.set_mode(mode) - - Add SSL mode. Modes you have set before are not cleared! This method should - be used with the :py:const:`MODE_*` constants. - - -.. py:method:: Context.set_passwd_cb(callback[, userdata]) - - Set the passphrase callback to *callback*. This function will be called - when a private key with a passphrase is loaded. *callback* must accept - three positional arguments. First, an integer giving the maximum length of - the passphrase it may return. If the returned passphrase is longer than - this, it will be truncated. Second, a boolean value which will be true if - the user should be prompted for the passphrase twice and the callback should - verify that the two values supplied are equal. Third, the value given as the - *userdata* parameter to :py:meth:`set_passwd_cb`. If an error occurs, - *callback* should return a false value (e.g. an empty string). - - -.. py:method:: Context.set_session_cache_mode(mode) - - Set the behavior of the session cache used by all connections using this - Context. The previously set mode is returned. See :py:const:`SESS_CACHE_*` - for details about particular modes. - - .. versionadded:: 0.14 - - -.. py:method:: Context.get_session_cache_mode() - - Get the current session cache mode. - - .. versionadded:: 0.14 - - -.. py:method:: Context.set_session_id(name) - - Set the context *name* within which a session can be reused for this - Context object. This is needed when doing session resumption, because there is - no way for a stored session to know which Context object it is associated with. - *name* may be any binary data. - - -.. py:method:: Context.set_timeout(timeout) - - Set the timeout for newly created sessions for this Context object to - *timeout*. *timeout* must be given in (whole) seconds. The default - value is 300 seconds. See the OpenSSL manual for more information (e.g. - :manpage:`SSL_CTX_set_timeout(3)`). - - -.. py:method:: Context.set_verify(mode, callback) - - Set the verification flags for this Context object to *mode* and specify - that *callback* should be used for verification callbacks. *mode* should be - one of :py:const:`VERIFY_NONE` and :py:const:`VERIFY_PEER`. If - :py:const:`VERIFY_PEER` is used, *mode* can be OR:ed with - :py:const:`VERIFY_FAIL_IF_NO_PEER_CERT` and :py:const:`VERIFY_CLIENT_ONCE` - to further control the behaviour. - - *callback* should take five arguments: A Connection object, an X509 object, - and three integer variables, which are in turn potential error number, error - depth and return code. *callback* should return true if verification passes - and false otherwise. - - -.. py:method:: Context.set_verify_depth(depth) - - Set the maximum depth for the certificate chain verification that shall be - allowed for this Context object. - - -.. py:method:: Context.use_certificate(cert) - - Use the certificate *cert* which has to be a X509 object. - - -.. py:method:: Context.add_extra_chain_cert(cert) - - Adds the certificate *cert*, which has to be a X509 object, to the - certificate chain presented together with the certificate. - - -.. py:method:: Context.use_certificate_chain_file(file) - - Load a certificate chain from *file* which must be PEM encoded. - - -.. py:method:: Context.use_privatekey(pkey) - - Use the private key *pkey* which has to be a PKey object. - - -.. py:method:: Context.use_certificate_file(file[, format]) - - Load the first certificate found in *file*. The certificate must be in the - format specified by *format*, which is either :py:const:`FILETYPE_PEM` or - :py:const:`FILETYPE_ASN1`. The default is :py:const:`FILETYPE_PEM`. - - -.. py:method:: Context.use_privatekey_file(file[, format]) - - Load the first private key found in *file*. The private key must be in the - format specified by *format*, which is either :py:const:`FILETYPE_PEM` or - :py:const:`FILETYPE_ASN1`. The default is :py:const:`FILETYPE_PEM`. - - -.. py:method:: Context.set_tlsext_servername_callback(callback) - - Specify a one-argument callable to use as the TLS extension server name - callback. When a connection using the server name extension is made using - this context, the callback will be invoked with the :py:class:`Connection` - instance. - - .. versionadded:: 0.13 - - -.. _openssl-session: - -Session objects ---------------- - -Session objects have no methods. - - -.. _openssl-connection: - -Connection objects ------------------- - -Connection objects have the following methods: - -.. py:method:: Connection.accept() - - Call the :py:meth:`accept` method of the underlying socket and set up SSL on the - returned socket, using the Context object supplied to this Connection object at - creation. Returns a pair *(conn, address)*. where *conn* is the new - Connection object created, and *address* is as returned by the socket's - :py:meth:`accept`. - - -.. py:method:: Connection.bind(address) - - Call the :py:meth:`bind` method of the underlying socket. - - -.. py:method:: Connection.close() - - Call the :py:meth:`close` method of the underlying socket. Note: If you want - correct SSL closure, you need to call the :py:meth:`shutdown` method first. - - -.. py:method:: Connection.connect(address) - - Call the :py:meth:`connect` method of the underlying socket and set up SSL on the - socket, using the Context object supplied to this Connection object at - creation. - - -.. py:method:: Connection.connect_ex(address) - - Call the :py:meth:`connect_ex` method of the underlying socket and set up SSL on - the socket, using the Context object supplied to this Connection object at - creation. Note that if the :py:meth:`connect_ex` method of the socket doesn't - return 0, SSL won't be initialized. - - -.. py:method:: Connection.do_handshake() - - Perform an SSL handshake (usually called after :py:meth:`renegotiate` or one of - :py:meth:`set_accept_state` or :py:meth:`set_accept_state`). This can raise the - same exceptions as :py:meth:`send` and :py:meth:`recv`. - - -.. py:method:: Connection.fileno() - - Retrieve the file descriptor number for the underlying socket. - - -.. py:method:: Connection.listen(backlog) - - Call the :py:meth:`listen` method of the underlying socket. - - -.. py:method:: Connection.get_app_data() - - Retrieve application data as set by :py:meth:`set_app_data`. - - -.. py:method:: Connection.get_cipher_list() - - Retrieve the list of ciphers used by the Connection object. WARNING: This API - has changed. It used to take an optional parameter and just return a string, - but not it returns the entire list in one go. - - -.. py:method:: Connection.get_client_ca_list() - - Retrieve the list of preferred client certificate issuers sent by the server - as :py:class:`OpenSSL.crypto.X509Name` objects. - - If this is a client :py:class:`Connection`, the list will be empty until the - connection with the server is established. - - If this is a server :py:class:`Connection`, return the list of certificate - authorities that will be sent or has been sent to the client, as controlled - by this :py:class:`Connection`'s :py:class:`Context`. - - .. versionadded:: 0.10 - - -.. py:method:: Connection.get_context() - - Retrieve the Context object associated with this Connection. - - -.. py:method:: Connection.set_context(context) - - Specify a replacement Context object for this Connection. - - -.. py:method:: Connection.get_peer_certificate() - - Retrieve the other side's certificate (if any) - - -.. py:method:: Connection.get_peer_cert_chain() - - Retrieve the tuple of the other side's certificate chain (if any) - - -.. py:method:: Connection.getpeername() - - Call the :py:meth:`getpeername` method of the underlying socket. - - -.. py:method:: Connection.getsockname() - - Call the :py:meth:`getsockname` method of the underlying socket. - - -.. py:method:: Connection.getsockopt(level, optname[, buflen]) - - Call the :py:meth:`getsockopt` method of the underlying socket. - - -.. py:method:: Connection.pending() - - Retrieve the number of bytes that can be safely read from the SSL buffer - (**not** the underlying transport buffer). - - -.. py:method:: Connection.recv(bufsize) - - Receive data from the Connection. The return value is a string representing the - data received. The maximum amount of data to be received at once, is specified - by *bufsize*. - - -.. py:method:: Connection.bio_write(bytes) - - If the Connection was created with a memory BIO, this method can be used to add - bytes to the read end of that memory BIO. The Connection can then read the - bytes (for example, in response to a call to :py:meth:`recv`). - - -.. py:method:: Connection.renegotiate() - - Renegotiate the SSL session. Call this if you wish to change cipher suites or - anything like that. - - -.. py:method:: Connection.send(string) - - Send the *string* data to the Connection. - - -.. py:method:: Connection.bio_read(bufsize) - - If the Connection was created with a memory BIO, this method can be used to - read bytes from the write end of that memory BIO. Many Connection methods will - add bytes which must be read in this manner or the buffer will eventually fill - up and the Connection will be able to take no further actions. - - -.. py:method:: Connection.sendall(string) - - Send all of the *string* data to the Connection. This calls :py:meth:`send` - repeatedly until all data is sent. If an error occurs, it's impossible to tell - how much data has been sent. - - -.. py:method:: Connection.set_accept_state() - - Set the connection to work in server mode. The handshake will be handled - automatically by read/write. - - -.. py:method:: Connection.set_app_data(data) - - Associate *data* with this Connection object. *data* can be retrieved - later using the :py:meth:`get_app_data` method. - - -.. py:method:: Connection.set_connect_state() - - Set the connection to work in client mode. The handshake will be handled - automatically by read/write. - - -.. py:method:: Connection.setblocking(flag) - - Call the :py:meth:`setblocking` method of the underlying socket. - - -.. py:method:: Connection.setsockopt(level, optname, value) - - Call the :py:meth:`setsockopt` method of the underlying socket. - - -.. py:method:: Connection.shutdown() - - Send the shutdown message to the Connection. Returns true if the shutdown - message exchange is completed and false otherwise (in which case you call - :py:meth:`recv` or :py:meth:`send` when the connection becomes - readable/writeable. - - -.. py:method:: Connection.get_shutdown() - - Get the shutdown state of the Connection. Returns a bitvector of either or - both of *SENT_SHUTDOWN* and *RECEIVED_SHUTDOWN*. - - -.. py:method:: Connection.set_shutdown(state) - - Set the shutdown state of the Connection. *state* is a bitvector of - either or both of *SENT_SHUTDOWN* and *RECEIVED_SHUTDOWN*. - - -.. py:method:: Connection.sock_shutdown(how) - - Call the :py:meth:`shutdown` method of the underlying socket. - - -.. py:method:: Connection.bio_shutdown() - - If the Connection was created with a memory BIO, this method can be used to - indicate that *end of file* has been reached on the read end of that memory - BIO. - - -.. py:method:: Connection.state_string() - - Retrieve a verbose string detailing the state of the Connection. - - -.. py:method:: Connection.client_random() - - Retrieve the random value used with the client hello message. - - -.. py:method:: Connection.server_random() - - Retrieve the random value used with the server hello message. - - -.. py:method:: Connection.master_key() - - Retrieve the value of the master key for this session. - - -.. py:method:: Connection.want_read() - - Checks if more data has to be read from the transport layer to complete an - operation. - - -.. py:method:: Connection.want_write() - - Checks if there is data to write to the transport layer to complete an - operation. - - -.. py:method:: Connection.set_tlsext_host_name(name) - - Specify the byte string to send as the server name in the client hello message. - - .. versionadded:: 0.13 - - -.. py:method:: Connection.get_servername() - - Get the value of the server name received in the client hello message. - - .. versionadded:: 0.13 - - -.. py:method:: Connection.get_session() - - Get a :py:class:`Session` instance representing the SSL session in use by - the connection, or :py:obj:`None` if there is no session. - - .. versionadded:: 0.14 - - -.. py:method:: Connection.set_session(session) - - Set a new SSL session (using a :py:class:`Session` instance) to be used by - the connection. - - .. versionadded:: 0.14 - - -.. Rubric:: Footnotes - -.. [#connection-context-socket] Actually, all that is required is an object that - **behaves** like a socket, you could even use files, even though it'd be - tricky to get the handshakes right! diff --git a/doc/conf.py b/doc/conf.py deleted file mode 100644 index d9c9a67..0000000 --- a/doc/conf.py +++ /dev/null @@ -1,219 +0,0 @@ -# -*- coding: utf-8 -*- -# -# pyOpenSSL documentation build configuration file, created by -# sphinx-quickstart on Sat Jul 16 07:12:22 2011. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# Note that not all possible configuration values are present in this -# autogenerated file. -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os - -DOC_DIR = os.path.abspath(os.path.dirname(__file__)) -sys.path.insert(0, os.path.abspath(os.path.join(DOC_DIR, ".."))) - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -#sys.path.insert(0, os.path.abspath('.')) - -# -- General configuration ----------------------------------------------------- - -# If your documentation needs a minimal Sphinx version, state it here. -needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be extensions -# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. -extensions = [] - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# The suffix of source filenames. -source_suffix = '.rst' - -# The encoding of source files. -#source_encoding = 'utf-8-sig' - -# The master toctree document. -master_doc = 'index' - -# General information about the project. -project = u'pyOpenSSL' -copyright = u'2011, Jean-Paul Calderone' - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. -version = '0.14' -# The full version, including alpha/beta/rc tags. -release = '0.14' - -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -#language = None - -# There are two options for replacing |today|: either, you set today to some -# non-false value, then it is used: -#today = '' -# Else, today_fmt is used as the format for a strftime call. -#today_fmt = '%B %d, %Y' - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -exclude_patterns = ['_build'] - -# The reST default role (used for this markup: `text`) to use for all documents. -#default_role = None - -# If true, '()' will be appended to :func: etc. cross-reference text. -#add_function_parentheses = True - -# If true, the current module name will be prepended to all description -# unit titles (such as .. function::). -#add_module_names = True - -# If true, sectionauthor and moduleauthor directives will be shown in the -# output. They are ignored by default. -#show_authors = False - -# The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' - -# A list of ignored prefixes for module index sorting. -#modindex_common_prefix = [] - - -# -- Options for HTML output --------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -html_theme = 'default' - -# Theme options are theme-specific and customize the look and feel of a theme -# further. For a list of options available for each theme, see the -# documentation. -#html_theme_options = {} - -# Add any paths that contain custom themes here, relative to this directory. -#html_theme_path = [] - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -#html_title = None - -# A shorter title for the navigation bar. Default is the same as html_title. -#html_short_title = None - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. -#html_logo = None - -# The name of an image file (within the static path) to use as favicon of the -# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 -# pixels large. -#html_favicon = None - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -#html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -#html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -#html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -#html_additional_pages = {} - -# If false, no module index is generated. -#html_domain_indices = True - -# If false, no index is generated. -#html_use_index = True - -# If true, the index is split into individual pages for each letter. -#html_split_index = False - -# If true, links to the reST sources are added to the pages. -#html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. -#html_show_sphinx = True - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. -#html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -#html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -#html_file_suffix = None - -# Output file base name for HTML help builder. -htmlhelp_basename = 'pyOpenSSLdoc' - - -# -- Options for LaTeX output -------------------------------------------------- - -# The paper size ('letter' or 'a4'). -#latex_paper_size = 'letter' - -# The font size ('10pt', '11pt' or '12pt'). -#latex_font_size = '10pt' - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, documentclass [howto/manual]). -latex_documents = [ - ('index', 'pyOpenSSL.tex', u'pyOpenSSL Documentation', - u'Jean-Paul Calderone', 'manual'), -] - -# The name of an image file (relative to this directory) to place at the top of -# the title page. -#latex_logo = None - -# For "manual" documents, if this is true, then toplevel headings are parts, -# not chapters. -#latex_use_parts = False - -# If true, show page references after internal links. -#latex_show_pagerefs = False - -# If true, show URL addresses after external links. -#latex_show_urls = False - -# Additional stuff for the LaTeX preamble. -#latex_preamble = '' - -# Documents to append as an appendix to all manuals. -#latex_appendices = [] - -# If false, no module index is generated. -#latex_domain_indices = True - - -# -- Options for manual page output -------------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'pyopenssl', u'pyOpenSSL Documentation', - [u'Jean-Paul Calderone'], 1) -] diff --git a/doc/images/pyopenssl-brand.png b/doc/images/pyopenssl-brand.png deleted file mode 100644 index 9c461857a26de0f1bed561c45f321df27d190fa2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3636 zcmb_fcQhN`+fRsDJGH6_s%l54Sz@+ksno8mN-1iqB34VaMtu=mDXMDk5`wB3<7cG^ zQ9|w5LGb4L_j`VSyyv{nbMC$8Jm-$*x#xL4_jBWnA3bDczQPOu09bW(v`nZY=HJFh zPwh=+Js7A1y`%m^Ex^UUnvX6?p<0-{bu4`WfJ@+i8xWA0%||sd_~{yIGvGj%F5eP5 zysmga-LXSgOZ`dE^d>&s+mx8ovvbtkhn4N6Ghk#Ax~tB@aa)L!D>~sP}Oe_aNW9&WwQ^F!(Y?+hFU`a1h*M*pc$P2{8P6+&YrBw$g^YV z7tO+V!ARjzV2PeB(oF0Dhac2cVD4g-xzqFj?cHe(d~m)3gsJeat-sFZDW&@0ey=*b@-*lF3j~9 za(T>Jjkp}47&tOF_b4PJB(R~}u4xN3kS-gz;?C4a9{fz2lt}tthZlg1d3CPGoALS? zU7Vls-+OVnudnaAsOV^kk>F6S%0MZi#MRAh0TV%sM+b}QAxQ*p1(a&2QgCpvvx|!( zpM0L853!&7aO1b;%a=0S=p!fu0$Ib{QL+FrC( zS0|TSH=0;m3k<5>2YoP!l>)YYN>1+RPnFWu)!k|Sj&BJ>n3{HX$MdEFu7KXPQGM8@ z>Z`b)QrODL8p35Dt{Hq2^T zOi-4tbf2oQ`EFG&SqH`#-ZrKi1pG(Z(@D9C{cC66x)L38sHmv1HIL&bA(6fTAff)E!)Nvl z)qS;?n3%NOqrhim(!$d6@>|EiK|o|^(6=Wvppm7%6p?RpGfncK>6=#q?ei8BaHSj~ z4?frvFcIpdA%A|x;XLH-Lyg4!`$)MISC?G_9u_Oim#`F_ctfY7V?1SHRuG4)N=|tx zhL#y>dJS9xLx9q0IXRHJA7C(8*T{&y9=7ws{!gqAY43a9-cWR)IMihDZ(HH2*YUH^owRyP1^gag1cqe z;VmThq-RqX=d6Dr7Z@xvr?y2fJRi-f?c566Vdx4QIjc77WOQN8?yk%X=XPNtxCoJk zF@>PlG4;;tWeTAue#Xk@ldGCpnywrm7zRq>V zfyE-N!Vw6B#o6U2|cCNk7 zJ4kk=ZyDO$wJZ9F`56Dw**jN4Mdmg$QoB`m#oAJ%i;f^4p~!2buP;Is`>exh_sX5) z6=2Y050H{m?-a3?GC|oI+-@3w#gw@Exog9IB_oGYu){0;yFEIJm;FQ*Dv2~lLd-?{ zd++55TwQ_(wTo#BH1X@Mtovy^1RGyPeY?uWAlpicoQFoYq>q6YD9R=Xz)9E#~gTg?n-V*f|PlMg_V($lgRp1 zpcG=Q1`+MrFbZ{n}qhFm~I<#pkT> zcz*Se!j8j3X8IzZ4Z6oDbcRkv?5#7_C%!Z;!IQSBs$)lnN!zyKwu#f50aRi@(=6_(MpW=YW!+A{`jD-(y7bL%y3tp-`S5TXCF8;+LKQ@j z9RchY?Fr-EH&BD$_$=n5vhOK-U2Dl;rl>l`F;V{jPm(M3C$K z)2uf7;4XZKd<))B=6w8MoHbAIM^Nma(C+gb))R>w@IqTyXeXvmbA10x@6hVVqdA7T z*zt?AcP#v!UF>iVHwckP#Cf2fb7x`;39YdVsqp-b;in+u~PyC9owS_jE9X?x6 zuM2JsNjRz1w>6Y*)les8nU2(E@Fi*Oyx1 z9|YtRT3hc^!R>q55=}`&-s_!sud$D%JL%Ygjh3p!kgG?2ErgT3&n+wlT5Y1YRaA05 z3dqwR)Hov$;6(Vj!sh0tndt)F`WoKjb5@`oRN#b9HX*p$x*O`H@@SI5GJ9KL9-lTF zm4d|IjO!(irps9K?7Q!lmX==T;d%S)0_{f;eeoz@yA5%C?&B2+CtQO8iw!b5an+x9 zIA$rwdtHYrK<#kJKk_p2b}h?KXB5*mtUD7B${MeguSo(o0+gu_y6{*onH;32r>9Y* zXlwXf`FFr4#gOGH?`@5_7h&;lE0uV$<$)}D479xH z5jZ|R_Ly(WF@m4n#Tl-SB2H&A8>-9QW+s3|5vw7Tu+Jh$I_iUqlJ~OxYZG&mB~8{2 z%4~Std!!jV+!20RtRxk{BQ7B!41>Y0&!lB!=!S)bRb^Gv;sloDDDl4lJbY9($9|ur zrqfv6)P!4ENecb5#B59hTE{>30%lQHlUAG(%yJ8`d2o2sNAKNRId=eaUwn0^)mU2D zX$QP_A=wcSz{8(HLDO|^AFe9imY%k{UHIFpS?ro<}pi;r#1+ zF7V4<_{ybu40~YY;^JcPF)_Vs%_@Y-3*9HlL}J1mo+-f?eRA2gBa+S`uyD2QbW228 z**dutL^6EwLbCSxFxx=GQA3YdDnQ-X%$zJ zt{;|^X)fk=l*^fVbysBV^!A6pPQCNayT^0k^MVJS@ALh9Ki|*i?c?|Je7^@05)u*; z5)u*;5(^TUFN5Rb<72Y3v#WsBfLw%lOifKqd+O@ye)5+9J_W7U>yH4b!0W&w5w6(e zI(W+d%H#y*qwyu~^c90l);@oNtk)k^|WN zbXCrEG7J7+V8II@+Lxd+G#U*V85zXJ#-dOtFquqTx^#(_mX`U>d+r4=7!2g+=MxYx zv9Z%=G^D1cQeR(Bd3ia*!^88P|J(?Wl$1naVIgA`Vk+y`ug7dQQ&m+pJI8OJpokK5 ze85Z_rgpkGL@9mlH)eApKu%7M%jCz%%*>>wriOum0nZ&5k&BK4Ztda$BXaigsXJ5A z7r}^#2s9dvTRTAzNK8zet^0Qz8)+eF+*^B(o^?Iszm-2#gyo(%oOlDau#z}30VVL# z<%pYTx!~I2k|j$#&L%iGcxHnHv~1>!@pc@^j@{J>)kFqH0&pzu80Y)W^YF#Pv2Ksf z6h;Iv@;N#V0`8BZ24N-v`uqDm7Qn4-6p83?`kDwbfe=N=L|ZNpDug-;uo5HRkKRAx zGUYV#`&2)2wvdF5LmnmX0lmGwm`oJ$T-EIfqbnj{IJiFs) z@+La6(UHv>KBk19iD%I?A=yF+BCiG2)zuh{#u*pFbLY-sHk*B#r}U3fPX2QeRe*}- zC(YQgPd7RP`~ZAQREfZ;Xt0j96D%;W*CT)9F;MFlpS&1ru_L&NM#pbKt4 zy-m}TCg;fmdBBoRMEGM)@1sYT&yTxDO+*e*0X^tJP}JYPHjzt5hnsZrv*0M0Pj*rvQfv83l=7 zLgP)SD}od`jZ}-Bd_X_|1qB5z`?IpL2oLxD=zneST5hemMOSJUJ0f>DH(Cem1;ADs z&fA5cECl5N*wh18O3{lJmj2MY03kU>WxSVwuH4y2lTcuL5efxH|!Kb98 zuwumur|X2nT#tbshbLj9DIv%N!j%(mky5TCOc_Sc%W?BMz9Jof+YIC4i`$!DU~+&e zSDm|e?BeWw=p_mb!_avx1Xfnl1RIrhT(=`aw&W?+14)t6ciLp z+W&jP&Ye363JUVfxRENL)A7+Y%Qfl;>KS5ES?%RVj(FlywC!+max$w{t(x^XwOY-# zZQH1<^x*vSGWYq2cZp*$uK##;I - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/doc/index.rst b/doc/index.rst deleted file mode 100644 index e4a5a23..0000000 --- a/doc/index.rst +++ /dev/null @@ -1,26 +0,0 @@ -Welcome to pyOpenSSL's documentation! -===================================== - -.. topic:: Abstract - - This module is a rather thin wrapper around (a subset of) the OpenSSL library. - With thin wrapper I mean that a lot of the object methods do nothing more than - calling a corresponding function in the OpenSSL library. - - -Contents: - -.. toctree:: - :maxdepth: 3 - - introduction - api - internals - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/doc/internals.rst b/doc/internals.rst deleted file mode 100644 index a2a4cdc..0000000 --- a/doc/internals.rst +++ /dev/null @@ -1,69 +0,0 @@ -.. _internals: - -Internals -========= - -We ran into three main problems developing this: Exceptions, callbacks and -accessing socket methods. This is what this chapter is about. - - -.. _exceptions: - -Exceptions ----------- - -We realized early that most of the exceptions would be raised by the I/O -functions of OpenSSL, so it felt natural to mimic OpenSSL's error code system, -translating them into Python exceptions. This naturally gives us the exceptions -:py:exc:`.SSL.ZeroReturnError`, :py:exc:`.SSL.WantReadError`, -:py:exc:`.SSL.WantWriteError`, :py:exc:`.SSL.WantX509LookupError` and -:py:exc:`.SSL.SysCallError`. - -For more information about this, see section :ref:`openssl-ssl`. - - -.. _callbacks: - -Callbacks ---------- - -Callbacks were more of a problem when pyOpenSSL was written in C. -Having switched to being written in Python using cffi, callbacks are now straightforward. -The problems that originally existed no longer do -(if you are interested in the details you can find descriptions of those problems in the version control history for this document). - -.. _socket-methods: - -Accessing Socket Methods ------------------------- - -We quickly saw the benefit of wrapping socket methods in the -:py:class:`.SSL.Connection` class, for an easy transition into using SSL. The -problem here is that the :py:mod:`socket` module lacks a C API, and all the -methods are declared static. One approach would be to have :py:mod:`.OpenSSL` as -a submodule to the :py:mod:`socket` module, placing all the code in -``socketmodule.c``, but this is obviously not a good solution, since you -might not want to import tonnes of extra stuff you're not going to use when -importing the :py:mod:`socket` module. The other approach is to somehow get a -pointer to the method to be called, either the C function, or a callable Python -object. This is not really a good solution either, since there's a lot of -lookups involved. - -The way it works is that you have to supply a :py:class:`socket`- **like** transport -object to the :py:class:`.SSL.Connection`. The only requirement of this object is -that it has a :py:meth:`fileno()` method that returns a file descriptor that's -valid at the C level (i.e. you can use the system calls read and write). If you -want to use the :py:meth:`connect()` or :py:meth:`accept()` methods of the -:py:class:`.SSL.Connection` object, the transport object has to supply such -methods too. Apart from them, any method lookups in the :py:class:`.SSL.Connection` -object that fail are passed on to the underlying transport object. - -Future changes might be to allow Python-level transport objects, that instead -of having :py:meth:`fileno()` methods, have :py:meth:`read()` and :py:meth:`write()` -methods, so more advanced features of Python can be used. This would probably -entail some sort of OpenSSL **BIOs**, but converting Python strings back and -forth is expensive, so this shouldn't be used unless necessary. Other nice -things would be to be able to pass in different transport objects for reading -and writing, but then the :py:meth:`fileno()` method of :py:class:`.SSL.Connection` -becomes virtually useless. Also, should the method resolution be used on the -read-transport or the write-transport? diff --git a/doc/introduction.rst b/doc/introduction.rst deleted file mode 100644 index c29f80c..0000000 --- a/doc/introduction.rst +++ /dev/null @@ -1,17 +0,0 @@ -.. _intro: - -Introduction -============ - -The reason pyOpenSSL was created is that the SSL support in the socket module in -Python 2.1 (the contemporary version of Python when the pyOpenSSL project was -begun) was severely limited. Other OpenSSL wrappers for Python at the time were -also limited, though in different ways. Unfortunately, Python's standard -library SSL support has remained weak, although other packages (such as -`M2Crypto `_) -have made great advances and now equal or exceed pyOpenSSL's functionality. - -The reason pyOpenSSL continues to be maintained is that there is a significant -user community around it, as well as a large amount of software which depends on -it. It is a great benefit to many people for pyOpenSSL to continue to exist and -advance. diff --git a/doc/make.bat b/doc/make.bat deleted file mode 100644 index 5ef04de..0000000 --- a/doc/make.bat +++ /dev/null @@ -1,170 +0,0 @@ -@ECHO OFF - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set BUILDDIR=_build -set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . -if NOT "%PAPER%" == "" ( - set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% -) - -if "%1" == "" goto help - -if "%1" == "help" ( - :help - echo.Please use `make ^` where ^ is one of - echo. html to make standalone HTML files - echo. dirhtml to make HTML files named index.html in directories - echo. singlehtml to make a single large HTML file - echo. pickle to make pickle files - echo. json to make JSON files - echo. htmlhelp to make HTML files and a HTML help project - echo. qthelp to make HTML files and a qthelp project - echo. devhelp to make HTML files and a Devhelp project - echo. epub to make an epub - echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter - echo. text to make text files - echo. man to make manual pages - echo. changes to make an overview over all changed/added/deprecated items - echo. linkcheck to check all external links for integrity - echo. doctest to run all doctests embedded in the documentation if enabled - goto end -) - -if "%1" == "clean" ( - for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i - del /q /s %BUILDDIR%\* - goto end -) - -if "%1" == "html" ( - %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/html. - goto end -) - -if "%1" == "dirhtml" ( - %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. - goto end -) - -if "%1" == "singlehtml" ( - %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. - goto end -) - -if "%1" == "pickle" ( - %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the pickle files. - goto end -) - -if "%1" == "json" ( - %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can process the JSON files. - goto end -) - -if "%1" == "htmlhelp" ( - %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run HTML Help Workshop with the ^ -.hhp project file in %BUILDDIR%/htmlhelp. - goto end -) - -if "%1" == "qthelp" ( - %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; now you can run "qcollectiongenerator" with the ^ -.qhcp project file in %BUILDDIR%/qthelp, like this: - echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyOpenSSL.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyOpenSSL.ghc - goto end -) - -if "%1" == "devhelp" ( - %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. - goto end -) - -if "%1" == "epub" ( - %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The epub file is in %BUILDDIR%/epub. - goto end -) - -if "%1" == "latex" ( - %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex - if errorlevel 1 exit /b 1 - echo. - echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. - goto end -) - -if "%1" == "text" ( - %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The text files are in %BUILDDIR%/text. - goto end -) - -if "%1" == "man" ( - %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man - if errorlevel 1 exit /b 1 - echo. - echo.Build finished. The manual pages are in %BUILDDIR%/man. - goto end -) - -if "%1" == "changes" ( - %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes - if errorlevel 1 exit /b 1 - echo. - echo.The overview file is in %BUILDDIR%/changes. - goto end -) - -if "%1" == "linkcheck" ( - %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck - if errorlevel 1 exit /b 1 - echo. - echo.Link check complete; look for any errors in the above output ^ -or in %BUILDDIR%/linkcheck/output.txt. - goto end -) - -if "%1" == "doctest" ( - %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest - if errorlevel 1 exit /b 1 - echo. - echo.Testing of doctests in the sources finished, look at the ^ -results in %BUILDDIR%/doctest/output.txt. - goto end -) - -:end diff --git a/doc/pyOpenSSL.tex b/doc/pyOpenSSL.tex new file mode 100644 index 0000000..99e3479 --- /dev/null +++ b/doc/pyOpenSSL.tex @@ -0,0 +1,1451 @@ +\documentclass{howto} + +\title{Python OpenSSL Manual} + +\release{0.13} + +\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{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_signature_algorithm}{} +Return the signature algorithm used in the certificate. If the algorithm is +undefined, raise \code{ValueError}. +\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} + +\begin{methoddesc}[PKey]{check}{} +Check the consistency of this key, returning True if it is consistent and +raising an exception otherwise. This is only valid for RSA keys. See the +OpenSSL RSA_check_key man page for further limitations. +\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}{} +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{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}{SSLEAY_VERSION} +\dataline{SSLEAY_CFLAGS} +\dataline{SSLEAY_BUILT_ON} +\dataline{SSLEAY_PLATFORM} +\dataline{SSLEAY_DIR} +Constants used with \method{SSLeay_version} to specify what OpenSSL version +information to retrieve. See the man page for the \function{SSLeay_version} C +API for details. +\end{datadesc} + +\begin{datadesc}{OPENSSL_VERSION_NUMBER} +An integer giving the version number of the OpenSSL library used to build this +version of pyOpenSSL. See the man page for the \function{SSLeay_version} C API +for details. +\end{datadesc} + +\begin{funcdesc}{SSLeay_version}{type} +Retrieve a string describing some aspect of the underlying OpenSSL version. The +type passed in should be one of the \constant{SSLEAY_*} constants defined in +this module. +\end{funcdesc} + +\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} + +\begin{methoddesc}[Context]{set_tlsext_servername_callback}{callback} +Specify a one-argument callable to use as the TLS extension server name +callback. When a connection using the server name extension is made using this +context, the callback will be invoked with the \code{Connection} instance. +\versionadded{0.13} +\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]{set_context}{context} +Specify a replacement Context object for this Connection. +\end{methoddesc} + +\begin{methoddesc}[Connection]{get_peer_certificate}{} +Retrieve the other side's certificate (if any) +\end{methoddesc} + +\begin{methoddesc}[Connection]{get_peer_cert_chain}{} +Retrieve the tuple of the other side's certificate chain (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} + +\begin{methoddesc}[Connection]{set_tlsext_host_name}{name} +Specify the byte string to send as the server name in the client hello message. +\versionadded{0.13} +\end{methoddesc} + +\begin{methoddesc}[Connection]{get_servername}{} +Get the value of the server name received in the client hello message. +\versionadded{0.13} +\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 +#

    +#


    +#