summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnas Nashif <anas.nashif@intel.com>2013-02-11 21:10:32 -0800
committerAnas Nashif <anas.nashif@intel.com>2013-02-11 21:10:32 -0800
commit1112fa2720ec58bb98a2ae14d9353d7a43d9d757 (patch)
tree961b43fa3f434a386be81e26a4191c8f91a30c1b
downloadpython-M2Crypto-1112fa2720ec58bb98a2ae14d9353d7a43d9d757.tar.gz
python-M2Crypto-1112fa2720ec58bb98a2ae14d9353d7a43d9d757.tar.bz2
python-M2Crypto-1112fa2720ec58bb98a2ae14d9353d7a43d9d757.zip
Imported Upstream version 0.21.1
-rw-r--r--CHANGES488
-rw-r--r--INSTALL226
-rw-r--r--LICENCE26
-rw-r--r--M2Crypto.egg-info/PKG-INFO23
-rw-r--r--M2Crypto.egg-info/SOURCES.txt354
-rw-r--r--M2Crypto.egg-info/dependency_links.txt1
-rw-r--r--M2Crypto.egg-info/top_level.txt1
-rw-r--r--M2Crypto/ASN1.py192
-rw-r--r--M2Crypto/AuthCookie.py114
-rw-r--r--M2Crypto/BIO.py286
-rwxr-xr-xM2Crypto/BN.py47
-rw-r--r--M2Crypto/DH.py96
-rw-r--r--M2Crypto/DSA.py439
-rw-r--r--M2Crypto/EC.py335
-rw-r--r--M2Crypto/EVP.py408
-rw-r--r--M2Crypto/Engine.py119
-rw-r--r--M2Crypto/Err.py49
-rw-r--r--M2Crypto/PGP/PublicKey.py58
-rw-r--r--M2Crypto/PGP/PublicKeyRing.py81
-rw-r--r--M2Crypto/PGP/RSA.py36
-rw-r--r--M2Crypto/PGP/__init__.py14
-rw-r--r--M2Crypto/PGP/constants.py19
-rw-r--r--M2Crypto/PGP/packet.py379
-rw-r--r--M2Crypto/RC4.py31
-rw-r--r--M2Crypto/RSA.py448
-rw-r--r--M2Crypto/Rand.py17
-rw-r--r--M2Crypto/SMIME.py245
-rw-r--r--M2Crypto/SSL/Checker.py224
-rw-r--r--M2Crypto/SSL/Cipher.py44
-rw-r--r--M2Crypto/SSL/Connection.py361
-rw-r--r--M2Crypto/SSL/Context.py254
-rw-r--r--M2Crypto/SSL/SSLServer.py53
-rw-r--r--M2Crypto/SSL/Session.py58
-rw-r--r--M2Crypto/SSL/TwistedProtocolWrapper.py410
-rw-r--r--M2Crypto/SSL/__init__.py28
-rw-r--r--M2Crypto/SSL/cb.py83
-rw-r--r--M2Crypto/SSL/ssl_dispatcher.py36
-rw-r--r--M2Crypto/SSL/timeout.py30
-rw-r--r--M2Crypto/X509.py1104
-rw-r--r--M2Crypto/__init__.py60
-rw-r--r--M2Crypto/callback.py9
-rw-r--r--M2Crypto/ftpslib.py94
-rw-r--r--M2Crypto/httpslib.py212
-rw-r--r--M2Crypto/m2.py31
-rw-r--r--M2Crypto/m2urllib.py70
-rw-r--r--M2Crypto/m2urllib2.py152
-rw-r--r--M2Crypto/m2xmlrpclib.py65
-rw-r--r--M2Crypto/threading.py20
-rw-r--r--M2Crypto/util.py71
-rw-r--r--PKG-INFO23
-rw-r--r--README61
-rw-r--r--SWIG/Makefile25
-rw-r--r--SWIG/Makefile.mw34
-rw-r--r--SWIG/Makefile.osx32
-rw-r--r--SWIG/_aes.i85
-rw-r--r--SWIG/_asn1.i201
-rw-r--r--SWIG/_bio.i227
-rwxr-xr-xSWIG/_bn.i114
-rw-r--r--SWIG/_dh.i186
-rw-r--r--SWIG/_dsa.i349
-rw-r--r--SWIG/_ec.i419
-rw-r--r--SWIG/_engine.i207
-rw-r--r--SWIG/_evp.i596
-rw-r--r--SWIG/_lib.h27
-rw-r--r--SWIG/_lib.i511
-rw-r--r--SWIG/_m2crypto.def2
-rw-r--r--SWIG/_m2crypto.i82
-rw-r--r--SWIG/_objects.i95
-rw-r--r--SWIG/_pkcs7.i241
-rw-r--r--SWIG/_rand.i122
-rw-r--r--SWIG/_rc4.i58
-rw-r--r--SWIG/_rsa.i452
-rw-r--r--SWIG/_ssl.i739
-rw-r--r--SWIG/_threads.i66
-rw-r--r--SWIG/_util.i57
-rw-r--r--SWIG/_x509.i674
-rw-r--r--contrib/README6
-rw-r--r--contrib/SimpleX509create.README3
-rw-r--r--contrib/SimpleX509create.py162
-rw-r--r--contrib/dave.README64
-rw-r--r--contrib/dave.patch180
-rw-r--r--contrib/dispatcher.README30
-rw-r--r--contrib/dispatcher.py191
-rw-r--r--contrib/isaac.README18
-rw-r--r--contrib/isaac.httpslib.py269
-rw-r--r--contrib/m2crypto.spec46
-rw-r--r--contrib/smimeplus.README3
-rw-r--r--contrib/smimeplus.py187
-rw-r--r--demo/CipherSaber/CipherSaber.py62
-rw-r--r--demo/CipherSaber/cstest1.cs11
-rw-r--r--demo/Zope/ZServer/HTTPS_Server.py187
-rw-r--r--demo/Zope/ZServer/__init__.py95
-rw-r--r--demo/Zope/ZServer/medusa/ftps_server.py438
-rw-r--r--demo/Zope/ZServer/medusa/https_server.py83
-rw-r--r--demo/Zope/ca.pem20
-rw-r--r--demo/Zope/dh1024.pem5
-rw-r--r--demo/Zope/lib/python/Products/GuardedFile/GuardedFile.py53
-rw-r--r--demo/Zope/lib/python/Products/GuardedFile/README.txt16
-rw-r--r--demo/Zope/lib/python/Products/GuardedFile/TODO.txt1
-rw-r--r--demo/Zope/lib/python/Products/GuardedFile/__init__.py25
-rw-r--r--demo/Zope/lib/python/Products/GuardedFile/add.dtml78
-rw-r--r--demo/Zope/lib/python/Products/GuardedFile/refresh.txt0
-rw-r--r--demo/Zope/lib/python/Products/GuardedFile/version.txt1
-rw-r--r--demo/Zope/lib/python/Products/ZSmime/README.txt18
-rw-r--r--demo/Zope/lib/python/Products/ZSmime/SmimeTag.py104
-rw-r--r--demo/Zope/lib/python/Products/ZSmime/__init__.py9
-rw-r--r--demo/Zope/lib/python/Products/ZSmime/version.txt1
-rw-r--r--demo/Zope/server.pem36
-rw-r--r--demo/Zope/starts17
-rwxr-xr-xdemo/Zope/starts.bat1
-rw-r--r--demo/Zope/utilities/x509_user.py79
-rw-r--r--demo/Zope/z2s.py1078
-rw-r--r--demo/Zope/z2s.py.diff266
-rw-r--r--demo/Zope27/INSTALL.txt200
-rw-r--r--demo/Zope27/install_dir/lib/python/ZServer/HTTPS_Server.py187
-rw-r--r--demo/Zope27/install_dir/lib/python/ZServer/__init__.py.patch10
-rw-r--r--demo/Zope27/install_dir/lib/python/ZServer/component.xml.patch28
-rw-r--r--demo/Zope27/install_dir/lib/python/ZServer/datatypes.py.patch59
-rw-r--r--demo/Zope27/install_dir/lib/python/ZServer/medusa/https_server.py83
-rw-r--r--demo/Zope27/instance_home/README.txt.patch7
-rw-r--r--demo/Zope27/instance_home/etc/zope.conf.patch16
-rw-r--r--demo/Zope27/instance_home/ssl/ca.pem20
-rw-r--r--demo/Zope27/instance_home/ssl/dh1024.pem5
-rw-r--r--demo/Zope27/instance_home/ssl/server.pem36
-rw-r--r--demo/ZopeX3/INSTALL.txt73
-rw-r--r--demo/ZopeX3/install_dir/lib/python/zope/app/server/configure.zcml.patch28
-rw-r--r--demo/ZopeX3/install_dir/lib/python/zope/app/server/https.py28
-rw-r--r--demo/ZopeX3/install_dir/lib/python/zope/server/http/https_server.py104
-rw-r--r--demo/ZopeX3/install_dir/lib/python/zope/server/http/https_serverchannel.py55
-rw-r--r--demo/ZopeX3/install_dir/lib/python/zope/server/http/publisherhttps_server.py83
-rw-r--r--demo/ZopeX3/instance_home/etc/zope.conf.patch26
-rw-r--r--demo/ZopeX3/instance_home/ssl/ca.pem20
-rw-r--r--demo/ZopeX3/instance_home/ssl/dh1024.pem5
-rw-r--r--demo/ZopeX3/instance_home/ssl/server.pem36
-rw-r--r--demo/bio_mem_rw.py44
-rw-r--r--demo/dhtest.py27
-rw-r--r--demo/dsa1024pvtkey.pem12
-rw-r--r--demo/dsa_bench.py176
-rw-r--r--demo/dsatest.pem8
-rw-r--r--demo/dsatest.py57
-rw-r--r--demo/ec/ecdhtest.py31
-rw-r--r--demo/ec/ecdsa_bench.py368
-rw-r--r--demo/ec/ecdsatest.pem5
-rw-r--r--demo/ec/ecdsatest.py68
-rw-r--r--demo/ec/secp160r1pvtkey.pem4
-rw-r--r--demo/https.howto/ca.pem59
-rw-r--r--demo/https.howto/dh1024.pem5
-rwxr-xr-xdemo/https.howto/get_https.py32
-rw-r--r--demo/https.howto/https_cli.py49
-rw-r--r--demo/https.howto/orig_https_srv.py153
-rw-r--r--demo/https.howto/server.pem74
-rw-r--r--demo/medusa/00_README32
-rw-r--r--demo/medusa/START.py71
-rw-r--r--demo/medusa/START_xmlrpc.py72
-rw-r--r--demo/medusa/asynchat.py292
-rw-r--r--demo/medusa/asyncore.py552
-rw-r--r--demo/medusa/auth_handler.py135
-rw-r--r--demo/medusa/ca.pem20
-rw-r--r--demo/medusa/counter.py48
-rw-r--r--demo/medusa/default_handler.py215
-rw-r--r--demo/medusa/dh1024.pem5
-rw-r--r--demo/medusa/filesys.py466
-rw-r--r--demo/medusa/ftp_server.py1127
-rw-r--r--demo/medusa/ftps_server.py438
-rw-r--r--demo/medusa/http_date.py126
-rw-r--r--demo/medusa/http_server.py784
-rw-r--r--demo/medusa/https_server.py83
-rw-r--r--demo/medusa/index.html6
-rw-r--r--demo/medusa/logger.py262
-rw-r--r--demo/medusa/m_syslog.py177
-rw-r--r--demo/medusa/medusa_gif.py8
-rw-r--r--demo/medusa/mime_type_table.py113
-rw-r--r--demo/medusa/poison_handler.py69
-rw-r--r--demo/medusa/producers.py329
-rw-r--r--demo/medusa/put_handler.py113
-rw-r--r--demo/medusa/redirecting_handler.py44
-rw-r--r--demo/medusa/server.pem36
-rw-r--r--demo/medusa/status_handler.py282
-rw-r--r--demo/medusa/virtual_handler.py60
-rw-r--r--demo/medusa/xmlrpc_handler.py104
-rw-r--r--demo/medusa054/00_README30
-rw-r--r--demo/medusa054/START.py71
-rw-r--r--demo/medusa054/START_xmlrpc.py72
-rw-r--r--demo/medusa054/ca.pem20
-rw-r--r--demo/medusa054/counter.py51
-rw-r--r--demo/medusa054/default_handler.py213
-rw-r--r--demo/medusa054/dh1024.pem5
-rw-r--r--demo/medusa054/filesys.py394
-rw-r--r--demo/medusa054/ftp_server.py1111
-rw-r--r--demo/medusa054/ftps_server.py438
-rw-r--r--demo/medusa054/http_date.py126
-rw-r--r--demo/medusa054/http_server.py749
-rw-r--r--demo/medusa054/https_server.py85
-rw-r--r--demo/medusa054/index.html6
-rw-r--r--demo/medusa054/logger.py261
-rw-r--r--demo/medusa054/m_syslog.py182
-rw-r--r--demo/medusa054/medusa_gif.py8
-rw-r--r--demo/medusa054/poison_handler.py69
-rw-r--r--demo/medusa054/producers.py323
-rw-r--r--demo/medusa054/server.pem36
-rw-r--r--demo/medusa054/status_handler.py272
-rw-r--r--demo/medusa054/xmlrpc_handler.py103
-rw-r--r--demo/perf/memio.py49
-rw-r--r--demo/perf/sha1.py46
-rw-r--r--demo/pgp/pgpstep.py113
-rw-r--r--demo/pgp/pubring.pgpbin0 -> 1088 bytes
-rw-r--r--demo/pgp/secring.pgpbin0 -> 1969 bytes
-rw-r--r--demo/pkcs7/pkcs7-thawte.pem45
-rw-r--r--demo/pkcs7/test.py6
-rw-r--r--demo/rsa.priv.pem9
-rw-r--r--demo/rsa.priv0.pem15
-rw-r--r--demo/rsa.pub.pem4
-rw-r--r--demo/rsa1024pvtkey.pem15
-rw-r--r--demo/rsa_bench.py183
-rw-r--r--demo/rsatest.py45
-rw-r--r--demo/smime.howto/README5
-rw-r--r--demo/smime.howto/decrypt.py22
-rw-r--r--demo/smime.howto/dv.py41
-rw-r--r--demo/smime.howto/encrypt.p717
-rw-r--r--demo/smime.howto/encrypt.py44
-rw-r--r--demo/smime.howto/recipient.pem18
-rw-r--r--demo/smime.howto/recipient_key.pem15
-rw-r--r--demo/smime.howto/se.p756
-rw-r--r--demo/smime.howto/se.py54
-rw-r--r--demo/smime.howto/sendsmime.py82
-rw-r--r--demo/smime.howto/sign.p746
-rw-r--r--demo/smime.howto/sign.py37
-rw-r--r--demo/smime.howto/signer.pem18
-rw-r--r--demo/smime.howto/signer_key.pem15
-rw-r--r--demo/smime.howto/verify.py30
-rw-r--r--demo/smime/README54
-rw-r--r--demo/smime/ca.pem20
-rw-r--r--demo/smime/clear.p767
-rw-r--r--demo/smime/client.p12bin0 -> 1604 bytes
-rw-r--r--demo/smime/client.pem36
-rw-r--r--demo/smime/client2.pem36
-rw-r--r--demo/smime/m2.se.p784
-rw-r--r--demo/smime/ns.p753
-rw-r--r--demo/smime/ns.se.p775
-rw-r--r--demo/smime/opaque.p747
-rw-r--r--demo/smime/sendsmime.py82
-rw-r--r--demo/smime/test.py192
-rw-r--r--demo/smime/unsmime.py50
-rw-r--r--demo/ssl/README25
-rw-r--r--demo/ssl/c.py70
-rw-r--r--demo/ssl/c_bio.py70
-rw-r--r--demo/ssl/ca.derbin0 -> 841 bytes
-rw-r--r--demo/ssl/ca.pem20
-rw-r--r--demo/ssl/client.p12bin0 -> 1604 bytes
-rw-r--r--demo/ssl/client.pem35
-rw-r--r--demo/ssl/dh1024.pem5
-rw-r--r--demo/ssl/echo-eg.py51
-rw-r--r--demo/ssl/echo.py59
-rw-r--r--demo/ssl/echod-async.py112
-rw-r--r--demo/ssl/echod-eg1.py49
-rw-r--r--demo/ssl/echod-forking.py22
-rw-r--r--demo/ssl/echod-iterative.py23
-rw-r--r--demo/ssl/echod-thread.py55
-rw-r--r--demo/ssl/echod-threading.py32
-rw-r--r--demo/ssl/echod_lib.py45
-rw-r--r--demo/ssl/ftp_tls.py41
-rw-r--r--demo/ssl/http_cli_20.py22
-rw-r--r--demo/ssl/https_cli.py45
-rw-r--r--demo/ssl/https_cli_async.py106
-rw-r--r--demo/ssl/https_srv.py150
-rw-r--r--demo/ssl/myapp.py30
-rw-r--r--demo/ssl/s_client.py108
-rw-r--r--demo/ssl/s_server.py203
-rw-r--r--demo/ssl/server.pem36
-rw-r--r--demo/ssl/server3.py121
-rw-r--r--demo/ssl/sess.py89
-rw-r--r--demo/ssl/sess2.py78
-rw-r--r--demo/ssl/sess2.ssldump.out112
-rw-r--r--demo/ssl/socklib.py46
-rw-r--r--demo/ssl/somelib.py21
-rw-r--r--demo/ssl/ss.py25
-rwxr-xr-xdemo/ssl/twistedsslclient.py43
-rwxr-xr-xdemo/ssl/twistedsslserver.py37
-rw-r--r--demo/ssl/xmlrpc_cli.py26
-rw-r--r--demo/ssl/xmlrpc_srv.py26
-rw-r--r--demo/tinderbox/build_lib.py162
-rw-r--r--demo/tinderbox/killableprocess.py212
-rwxr-xr-xdemo/tinderbox/slave.py172
-rw-r--r--demo/tinderbox/winprocess.py262
-rw-r--r--demo/x509/ca.py104
-rwxr-xr-xdemo/x509/certdata2pem.py64
-rw-r--r--demo/x509/client2.pem36
-rw-r--r--demo/x509/demo1.py54
-rw-r--r--demo/x509/proxy_destroy.py43
-rw-r--r--demo/x509/proxy_info.py61
-rw-r--r--demo/x509/proxy_init.py49
-rw-r--r--demo/x509/proxylib.py327
-rw-r--r--demo/x509/server-expired.pem36
-rw-r--r--demo/x509/server.pem36
-rw-r--r--demo/x509/x509auth.py675
-rw-r--r--doc/ZServerSSL-HOWTO.html271
-rw-r--r--doc/howto.ca.html891
-rw-r--r--doc/howto.smime.html1570
-rw-r--r--doc/howto.ssl.html206
-rw-r--r--epydoc.conf29
-rwxr-xr-xfedora_setup.sh15
-rw-r--r--pack.py37
-rw-r--r--setup.cfg5
-rw-r--r--setup.py168
-rw-r--r--tests/README62
-rw-r--r--tests/__init__.py0
-rw-r--r--tests/alltests.py100
-rw-r--r--tests/ca.pem62
-rw-r--r--tests/der_encoded_seq.b6417
-rw-r--r--tests/dhparams.pem5
-rw-r--r--tests/dsa.param.pem9
-rw-r--r--tests/dsa.priv.pem9
-rw-r--r--tests/dsa.pub.pem12
-rw-r--r--tests/ec.priv.pem5
-rw-r--r--tests/ec.pub.pem4
-rw-r--r--tests/fips.py8
-rw-r--r--tests/long_serial_cert.pem17
-rw-r--r--tests/pubring.pgpbin0 -> 1088 bytes
-rw-r--r--tests/recipient.pem61
-rw-r--r--tests/recipient_key.pem15
-rw-r--r--tests/rsa.priv.pem16
-rw-r--r--tests/rsa.priv2.pem17
-rw-r--r--tests/rsa.pub.pem6
-rw-r--r--tests/server.pem75
-rw-r--r--tests/signer.pem61
-rw-r--r--tests/signer_key.pem15
-rw-r--r--tests/test_asn1.py90
-rw-r--r--tests/test_authcookie.py147
-rw-r--r--tests/test_bio.py76
-rw-r--r--tests/test_bio_file.py78
-rw-r--r--tests/test_bio_iobuf.py90
-rw-r--r--tests/test_bio_membuf.py64
-rw-r--r--tests/test_bio_ssl.py150
-rwxr-xr-xtests/test_bn.py78
-rw-r--r--tests/test_dh.py65
-rw-r--r--tests/test_dsa.py98
-rw-r--r--tests/test_ec_curves.py159
-rw-r--r--tests/test_ecdh.py49
-rw-r--r--tests/test_ecdsa.py75
-rw-r--r--tests/test_engine.py48
-rw-r--r--tests/test_evp.py471
-rw-r--r--tests/test_obj.py92
-rw-r--r--tests/test_pgp.py40
-rw-r--r--tests/test_rand.py51
-rw-r--r--tests/test_rc4.py41
-rw-r--r--tests/test_rsa.py285
-rw-r--r--tests/test_smime.py262
-rw-r--r--tests/test_ssl.py1108
-rw-r--r--tests/test_ssl_offline.py60
-rw-r--r--tests/test_ssl_win.py71
-rw-r--r--tests/test_threading.py42
-rw-r--r--tests/test_x509.py559
-rw-r--r--tests/thawte.pem25
-rw-r--r--tests/x509.derbin0 -> 656 bytes
-rw-r--r--tests/x509.pem75
355 files changed, 44868 insertions, 0 deletions
diff --git a/CHANGES b/CHANGES
new file mode 100644
index 0000000..8355a94
--- /dev/null
+++ b/CHANGES
@@ -0,0 +1,488 @@
+0.21.1 - 2011-01-15
+-------------------
+- Distribution fix
+
+0.21 - 2011-01-12
+-----------------
+- Support OpenSSL 1.0. Thanks to Miloslav Trmac for figuring out how to fix
+ test_smime.py
+- Rename m2.engine_init to engine_init_error so that
+ ENGINE_init and ENGINE_finish can be exposed, thanks to Erlo
+- 0.20 started releasing Python locks even around some operations that
+ interacted with the Python runtime, potentially causing crashes and other
+ weirdness, fix by Miloslav Trmac
+- Make httpslib.ProxyHTTPSConnection work with Python 2.3
+
+0.20.2 - 2009-10-06
+-------------------
+- (Re)Enable configuration and use with OpenSSL 0.9.7g and older by disabling
+ RSA PSS methods when using such old OpenSSL, thanks to Stef Walter
+
+0.20.1 - 2009-08-27
+-------------------
+- Fix regression in httpslib.ProxyHTTPSConnection, by Miloslav Trmac
+
+0.20 - 2009-08-10
+-----------------
+- Deprecated M2Crypto.PGP subpackage since nobody seems to be using it nor
+ is it being maintained (if you do use it, please let me know)
+- Added fedora_setup.sh to help work around differences on Fedora Core -based
+ distributions (RedHat, CentOS, ...); thanks to Miloslav Trmac
+- Added X509.load_request_bio and load_request_string, by Hartmut Goebel and
+ Pavel Shramov
+- Added alias X509.Request.set_subject for set_subject_name to match X509.X509,
+ by Pavel Shramov
+- OBJ_* wrappers did not work properly with OpenSSL 0.9.8a and earlier, fix by
+ Pavel Shramov
+- Added ASN1_UTCTIME.get_datetime and set_datetime, by Pavel Shramov
+- Fixed obj_obj2txt, which returned nonsense, fix by Barney Stratford
+- m2urllib did not close sockets properly, fix by Miloslav Trmac
+- Allow SSL peer certificate to have subjectAltName without dNSName and use
+ commonName for hostname check, fix by Miloslav Trmac
+- threading_locking_callback did not block on a lock when the lock
+ was held by another thread, by Miloslav Trmac
+- Allow more blocking OpenSSL functions to run without GIL, by Miloslav Trmac
+- Fixed httpslib to send only the path+query+fragment part of the URL when
+ using CONNECT proxy, by James Bowes
+- SSLServer.__init__ now takes optional bind_and_activate parameter and
+ initializes by calling SocketServer.BaseServer.__init__, which
+ are Python 2.6 compatibility fixes, by Christian
+- ftpslib now works with Python 2.6, by Theodore A. Roth
+- httpslib.ProxyHTTPSConnection needs to cast port into integer,
+ by John M. Schanck
+- Added support for RSASSA-PSS signing and verifying, by Chris Collis
+- Added support for disabling padding when using RSA encryption,
+ by Chris Collis
+- ASN1_INTEGERs can now be larger than fits in an int, for example to support
+ X509 certificates with large serial numbers,
+ patch by Mikhail Vorozhtsov and testcase by Barry G.
+- Reverted a change done in 0.17 to m2urllib2 which changed urls to include
+ host when it should stay as it was
+- httpslib no longer uses urllib; instead it uses urlparse for url parsing
+- SMIME.text_crlf and text_crlf_bio were always raising TypeError; fixed
+- EVP.load_key and load_key_bio fixed to raise EVP.EVPError and BIO.BIOError
+ instead of str (str exceptions not allowed in Python 2.6 and later)
+- SSL.Session.load_session fixed to raise SSL.SSLError instead of str
+- SMIME.load_pkcs7, load_pkcs7_bio, smime_load_pkcs7, smime_load_pkcs7_bio,
+ text_crlf, text_crlf_bio fixed to raise BIO.BIOError, SMIME.PKCS7_Error and
+ SMIME.SMIME_Error as appropriate instead of str
+- Added FIPS mode to unit tests, and used FIPS-compliant key sizes in other
+ tests, by Miloslav Trmac. Note that tests run much slower because of this!
+- Unit tests cover 80% of the code
+
+0.19.1 - 2008-10-12
+-------------------
+- Re-enable building when OpenSSL built without EC support, by Miloslav Trmac
+- Remove shebang from Engine.py since it is not executable, by Miloslav Trmac
+
+0.19 - 2008-10-05
+-----------------
+- OpenSSL OBJ_* functions wrapped by Pavel Shramov
+- OpenSSL ENGINE interface wrapped, providing support for smart cards, by
+ Martin Paljak and Pavel Shramov
+- EVP.PKey.get_rsa() now returns RSA_pub, which fixes segmentation fault
+ when trying to encrypt using public key from X509 certificate, by Ben Timby
+- httpslib.ProxyHTTPSConnection now sends the required Host header,
+ by Karl Grindley
+- Use the proxied User-Agent value in CONNECT requests, by James Antill and
+ Miloslav Trmac
+- Fixed m2urllib.build_opener when optional handlers were in use,
+ affected Python 2.5 and later, by Miloslav Trmac
+- Reverted the incorrect GIL change done in 0.18 to m2.passphrase_callback,
+ which caused a deadlock when called from mod_python for example. Thanks to
+ Michal Kochel and Keith Jackson.
+- SSL.Connection.accept() passed wrong certificate to postConnectionCheck
+ callback
+- httpslib.HTTPSConnection now raises ValueError for illegal keyword argument
+- m2.pkey_write_pem[_no_cipher] changed to use the recommended (more secure)
+ PEM_write_bio_PKCS8PrivateKey (used by PEM_write_bio_PrivateKey).
+- X509.load_cert, load_cert_bio, load_cert_der_string, new_stack_from_der,
+ load_request and load_crl now raise X509Error for invalid data. Previously
+ some of these raised a string as an error, some did not raise but caused
+ strange errors later, for example x509.verify() would return -1.
+- Fixed SSL.Connection.get_socket_read_timeout and set_socket_read_timeout on
+ 64bit platforms by adding SSL.timeout.struct_size() and using it instead of
+ hardcoded size for socket.getsockopt
+- X509_Store.load_info now returns the value from the underlying
+ m2.x509_store_load_locations call, and in case of error raises X509Error
+- Fixed SMIME.verify to raise the correct PKCS7_Error (used to raise
+ SMIME_Error) when verification fails with Python 2.6
+
+0.18.2 - 2007-10-12
+-------------------
+- typedef Py_ssize_t was insufficiently guarded, now follows PEP 353. This
+ prevented building on at least Red Hat Linux and Debian Linux (unstable).
+
+0.18.1 - 2007-10-08
+-------------------
+- Redo build fix when OpenSSL configured without Elliptic Curves (EC), see
+ also INSTALL file
+
+0.18 - 2007-07-26
+-----------------
+- Added EVP.pbkdf2 to derive key from password
+- X509_Store_Context.get1_chain added
+- Added X509_Name.__iter__, __getitem__, get_entries_by_nid which allow
+ iterating over all X509_Name_Entries or getting just all commonName entries,
+ for example
+- Added X509_Name_Entry.get_object, get_data, set_data
+- Added back PKCS7.get0_signers (was removed in 0.16)
+- X509_Extension.get_value accepts flag and indent parameters.
+- support multiple dNSName fields in subjectAltName
+- support multiple commonName fields for SSL peer hostname checking
+- Checking for erroneous returns from more OpenSSL EVP_* functions, which
+ means that certain things that used to fail silently will now raise an
+ EVP.EVPError; affected m2 functions are: digest_final, cipher_init,
+ cipher_update, cipher_final and sign_update. sign_final will now raise
+ EVP.EVPError instead of SystemError as well.
+- Fixed Pkey.verify_final to take a sign parameter
+- If a subjectAltName extension of type dNSName is present in peer certificate,
+ use only the dNSNames when checking peer certificate hostname, as specified
+ by RFC 2818. If no dNSNames are present, use subject commonName.
+- Fixed memory leaks in m2 functions ec_key_new_by_curve_name,
+ pkey_get_modulus, ecdsa_verify, threading_init and
+ X509.X509.verify, X509.X509_Stack (which manifested for example when
+ calling X509.new_stack_from_der), SSL.Connection (which manifested with some
+ connection errors or when connect was never called), twisted wrapper,
+ SSL.Connection.makefile (in BIO.IOBuffer really)
+- Fixed threading regressions introduced in 0.16,
+ by Aaron Reizes and Keith Jackson
+- Added SSL session caching support to HTTPSConnection, by Keith Jackson
+- Added the ability to save and load DER formatted X509 certificates and
+ certificate requests, by Keith Jackson
+- m2xmlrpclib.py fixed to work with Python 2.5, by Miloslav Trmac
+- 64-bit correctness fixes, by Miloslav Trmac
+- Added X509_Name.as_hash, by Thomas Uram
+- Moved --openssl option from general setup.py option to build_ext option,
+ meaning you need to do: python setup.py build build_ext --openssl=/path,
+ by Philip Kershaw
+- Fixed build problem affecting certain systems where OpenSSL was built without
+ EC support
+- M2CRYPTO_TEST_SSL_SLEEP environment variable controls how long to sleep
+ after starting the test SSL server. Default is 0.5, but 0.1 or even 0.05
+ might work with modern computers. Makes tests finish significantly faster.
+
+0.17 - 2006-12-20
+-----------------
+- setup.py has new test command to run unit tests (requires setuptools)
+- Added m2urllib2, by James Bowes (python 2.4 and later, at least for now)
+- Added CONNECT proxy for httpslib and m2urllib2, by James Bowes
+- Added PKey.get_modulus, X509.get_fingerprint, X509_Name.as_der and
+ m2.bn_to_hex, by Thomas Uram
+- Prevent Connection.makefile from freeing bio redundantly, by Thomas Uram
+- Added Err.peek_error_code, by Thomas Uram
+- Fixed m2urllib.open_https to return the response headers, otherwise code
+ that relied on that would break (for example msnlib-3.5), by Arno bakker
+- Fixed twisted wrapper to work with >16kb BIO buffers, by Martin Paljak
+- Added support for remaining ECs, by Larry Bugbee
+- Fixed DSA.save_key and DSA_.save_pub_key, by Larry Bugbee
+- SSL.Context.load_verify_locations raises ValueError if cafile and capath
+ are both None
+- Fixed X509.check_purpose() (was always raising exceptions)
+- smime_read_pkcs7 was changed to automatically call BIO_set_mem_eof_return
+ on memory BIOs because otherwise the read would fail with
+ "SMIME_Error: not enough data"
+- X509.new_extension('subjectKeyIdentifier', 'hash') raises ValueError instead
+ of crashing Python
+
+0.16 - 2006-07-05
+-----------------
+- Minimum requirements updated: Python 2.3+, OpenSSL 0.9.7+, SWIG 1.3.24+
+- Optional features from OpenSSL 0.9.8 and newer
+- Enhancements to EVP and X509 to allow proxy certificate handling,
+ by Matt Rodriguez
+- SSLBio and related additions to help do SSL with BIOs directly,
+ by Matt Rodriguez
+- Added --openssl option to build command which can be used to specify
+ where OpenSSL is installed, by Matt Rodriguez
+- Added sign and verify to RSA class, and get_rsa to PKey class,
+ by Matt Rodriguez
+- ECDSA signatures and ECDH key agreement, requires OpenSSL 0.9.8+,
+ by Arno Bakker
+- Fix non-hashable type problems in SSL._ctxmap and users,
+ by Michael Weiser
+- Fixed SSLServer.handle_error to take the correct number of
+ arguments, by Dan Williams
+- Various DSA enhancements by Larry Bugbee
+- Added sha224, sha256, sha384 and sha512, by Larry Bugbee
+- Added serialNumber, SN, surname, GN and givenName fields to X509_Name,
+ by Martin Paljak
+- m2.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT is the fourth certificate
+ verification error that will be allowed when unknown CAs are allowed
+- post connection checks in Connection.accept() and connect() fixed (these
+ were broken in 0.15)
+- Fixed EVP.Cipher to work with aes_* ciphers (used to crash Python).
+ The actual problem was in m2.bytes_to_key.
+- SMIME methods and functions raise correct exceptions
+- Raise ValueError instead of AttributeError when a non-existing hash
+ algorithm or SSL version is asked for
+- ssl_ctx_set_tmp_(dh|rsa) now return value, and the rsa version calls
+ the rsa function instead of the dh function
+- digest_update and verify_update return type changed to int, which allows
+ better error reporting; EVP.MessageDigest.update and
+ EVP.PKey.verify_update likewise changed
+- X509_Name and ASN1_String as_text (new for ASN1_String) take optional
+ parameters to control formatting.
+- Cipher_Stack, X509_Stack and X509_Extension_Stack are iterable
+- EVP.MessageDigest now properly cleans up the underlying data when the object
+ gets deleted
+- It is now possible to set and get non-nid values to X509_Name (previously
+ only set worked)
+- SSL.Connection.set_client_CA_list_from_file now uses the actual implementd
+ function instead of raising exception
+- Multithreaded SSL no longer uses the SSL_set/get_app_data to set and
+ restore thread state, but uses the standard PyGILState_STATE instead.
+- m2urllib no longer outputs the HTTP headers (there was an erronous call
+ to set_debuglevel(1))
+- Removed RCS_id, RCS_ID and _RCS_id from Python files
+- All known memory leaks fixed
+- SWIG and compiler warning fixes
+- More and better Epydoc formatted docstrings
+- More than doubled the number of unit tests, also made many demos into tests
+
+0.15 - 2005-08-17
+-----------------
+- Support OpenSSL 0.9.8, Python 2.4.1, SWIG 1.3.24
+- Fixed multiple memory leaks
+- Twisted integration
+- Safer defaults for SSL context and post connection check for clients
+- Eliminated C pointers from interfaces (some may still remain in callbacks)
+- Many cases where Python interpreter crashed have been fixed
+- Improved thread safety of many callbacks
+- And of course more of the OpenSSL API is covered, new docstrings and
+ tests have been written
+
+ Changes since 0.13
+--------------------
+- Fixed memory leak due to circular reference in SSL.Connection.
+ Thanks to Michael Dunstan. Oops, patch is ZServerSSL-specific.
+ Andre Reitz provided a generalised fix. Thanks Andre.
+- Fixed __getattr__ error in DSA. Thanks to Igor Belyi.
+- Added rand_poll, rand_screen and rand_win32_event functions to
+ M2Crypto.Rand.
+- Updated ZServerSSL files to match Zope 2.7.0 versions.
+- Integrated (overlapping) patches by Peter Teniz and Heikki Toivonen
+ covering operations on X.509-related structures that gives M2Crypto
+ PKI functionality. Thanks Peter and Heikki.
+- Peter Teniz contributed demo2004/pki/x509auth.py.
+- Created demo2004/ directory that will contain new or updated demos.
+- Added verify_[init|update|final] in _evp.i. Patch by Zachery Corbiere.
+ Thanks Zac.
+
+
+ Changes since 0.12/0.11
+-------------------------
+- Patches from Artur Frysiak <wiget@pld-linux.org>. Thanks Artur.
+ = Allow using a passphrase callback in class SMIME.
+ = Added method get0_signers to class PKCS7, which retrieves signers'
+ certificates from a PKCS7 blob.
+ = Added methods as_pem and save_pem to class X509.
+ = Added file version.py.
+ = Allow SSL.Context.load_verify_locations to accept both 'cafile' and
+ 'capath'.
+- Fixed BIO.read() not reading until EOF. Thanks to Egil Muller
+ <redhog@redhog.org> for suggestion.
+- Honour 'mode' parameter in SSL.Connection.makefile. Thanks again to Egil
+ Muller.
+- Roger Binns contributed epydoc-generated docs for M2Crypto. Thanks Roger.
+- Peter Teniz contributed patches to create X.509 requests and certificates.
+ Thanks Peter.
+- Updated Medusa to 0.54.
+- Make various OpenSSL bignum functions (written long ago) available to Python.
+
+
+ Changes since 0.11
+--------------------
+- ZServerSSL with client certificate-based authentication rides again.
+- Created Makefile for Python 2.3.
+- Modified LICENCE: changed my name to the generic "the author" in the
+ all-caps disclaimer paragraph.
+- Allow to save RSA key pair in the clear.
+- ZServerSSL for Zope 2.7.
+- Excluded RC5. IDEA was taken out several releases ago. This should
+ allow M2Crypto to build with stock OpenSSL on various Linuxen.
+- Added ssl_set_tmp_dh_callback.
+- Added ssl_set_tmp_rsa and ssl_set_tmp_rsa_callback to support weak-cipher
+ browsers.
+- ZServerSSL exports SSL_CIPHER request header (a la mod_ssl) to Zope
+ applications.
+- Perform distutils's SWIG .i search path tweaking within setup.py. setup.py
+ should now work "out of the box".
+- Added contrib/smimeplus.py, a high-level S/MIME interface, contributed by
+ Bernard Yue <bernie@3captus.com>. Thanks Bernard.
+- Added in long forms of nid's in X509_Name. Thanks to William K Volkman
+ <development@netshark.com> for patch.
+- Updated Mac OS X build instructions. Thanks to Larry Bugbee
+
+
+ Changes since 0.10
+--------------------
+- Dave Berkeley <dave@rotwang.freeserve.co.uk> contributed fixes to
+ SSL.Context-related memory leaks and code to set the size of the SSL
+ session cache.
+- Brent Chun <bnc@intel-research.net> contributed the following:
+ + Fixes to memory leaks.
+ + Code to expose X.509 certificate chain operations.
+ + Code to expose set/get operations on the SSL session cache.
+- Changed swig/ to SWIG/, for the convenience of people who don't read
+ INSTALL. Some Makefiles may break because of this. setup.py continues
+ to work, of course.
+- ZServerSSL tested with Zope 2.6.1. There is now a HOWTO.
+- Updated README and INSTALL.
+- Filled doc/ with stuff that went missing in several past releases.
+
+
+ Changes since 0.09
+--------------------
+- Updated to OpenSSL 0.9.7. Thanks to Toby Allsopp <toby@MI6.GEN.NZ> for
+ patches.
+- Added functionality to create a basic certificate request. Also
+ contributed by Toby Allsopp.
+- Finally, AES!
+
+
+ Changes since 0.08
+--------------------
+- Replaced demo/Zope/ZServer/__init__.py with the correct version
+ for Zope 2.6.0.
+- Added a sample starts.bat for ZServerSSL.
+- Incoporated a patch by prashanth@jibe.biz that handled the
+ new-in-Python-2.2.2 "strict" parameter for the various HTTP[S] connection
+ classes in httplib.py. Thanks prashanth. This fixes M2Crypto's XMLRPC
+ support for Python 2.2.2. (Apparently it was working for Python 2.2.1.)
+- Incorporated some cosmetic patches from Adam Karpierz <karpierz@zope.pl>.
+ Thanks Adam.
+
+
+ Changes since 0.07 snapshot #3
+--------------------------------
+- Updated to SWIG 1.3.17.
+- Excluded IDEA.
+- Tested with OpenSSL 0.9.6h.
+- ZServerSSL rides again for Zope 2.6.0.
+- setup.py does!
+- Removed Makefiles for Windows and Unix. (Makefile.osx remains.)
+- Included in contrib/ Isaac Salzberg's application of Mihai Ibanescu's
+ patch that allows IIS interoperability thru an authenticating proxy.
+ Thanks Isaac.
+- Included in contrib/ patch by Dave Brueck <dave@pythonaprocrypha.com>
+ that has smarter non-blocking behaviour. Thanks Dave.
+
+
+ Changes since 0.06
+-----------------------
+- test_ssl_win.py. (Requires Mark Hammond's Win32 extensions.)
+- Renamed demo/https to demo/medusa; updated Medusa to 2001 Jun release.
+- Improved _ssl.i's and M2Crypto.SSL.Connection's accept/connect methods.
+- M2Crypto.ftpslib for client-side FTP/TLS.
+- demo/medusa/ftps_server.py for server-side FTP/TLS.
+- Improved thread-safety.
+- Cleaned up echo client and servers.
+- Fixed missing import in m2urllib.
+- Fixed m2urllib to handle HTTP redirects.
+- Python 2.2 compatibility.
+- AuthCookie - secure authenticator cookies.
+
+
+ Changes since 0.05
+-----------------------
+- Handled the cases where Python callbacks raised exceptions.
+- Fixed a NULL-deref bug in _ssl.i which crashes Medusa https when IE
+ or Opera comes a-calling.
+- ZServerSSL rides again - a more robust ZServerSSL for Zope 2.3.0.
+- Added the MIME type 'application/x-x509-ca-cert' to
+ demo/ssl/https_srv.py. This facilitates installing self-generated
+ certificates into your browser.
+- ZSmime and GuardedFile bundled.
+- Documentation! A HOWTO on operating your own CA.
+- Documentation! A HOWTO on S/MIME. Examples are in demo/smime.howto.
+- Python 2.1 compatibility.
+- Fixed demo/https/https_server.py's CPU-spinning. (As per ZServerSSL.)
+- Fixed m2urllib's unexpected eof - demo/ssl/urllib_cli.py now works.
+- Renamed xmlrpclib2.py to m2xmlrpclib.py.
+- Kludged SSL.ssl_dispatcher to do blocking connect()'s: see
+ demo/ssl/https_cli_async.py.
+- SWIG 1.3.6 does! Thanks to Keith Jackson <krjackson@lbl.gov>.
+
+
+ Changes since 0.04
+-----------------------
+- Fixed a silly reversed-logic bug in M2Crypto.SSL.Connection.setblocking().
+- Fixed yet more memory leaks. Thanks to Ray Suorsa <res@loudcloud.com>.
+- Build instructions for Borland BC++ 5.5 free compiler suite.
+- Bundles the June 2000 unencumbered release of Medusa.
+- SSL callback thread-safety. Thanks again to Ray Suorsa for insights and
+ patches.
+- Renamed M2Crypto.M2Crypto to M2Crypto.m2 to prevent package/module loading
+ confusion.
+- SSL.Session and a demo in demo/ssl/sess.py.
+- https_srv.py, an enhanced, https version of SimpleHTTPServer.py.
+- Interface change: SMIME.load_pkcs7_bio() is renamed
+ SMIME.smime_load_pkcs7_bio(), similarly SMIME.load_pkcs7() to
+ SMIME.smime_load_pkcs7(); these load PKCS7 objects generated by S/MIME.
+- Interface change: SMIME.load_pkcs7_bio() now loads a PKCS7 PEM file, i.e., a
+ file of the format "-----BEGIN PKCS7-----".
+- Works with both Python 2.0 and Python 1.5.2.
+- OpenSSL 0.9.6. (Possibly incompatible with earlier OpenSSL releases.)
+- Unit tests with PyUnit.
+- Improved C code:
+ = Custom Python exceptions.
+ = Diligent error checking.
+ = Fixed memory leaks.
+- Renamed M2Crypto.urllib2 to M2Crypto.m2urllib.
+- HTTPS clients of Python 1.5.2's and Python 2.0's httplib and urllib.
+
+
+ Changes since 0.03
+-----------------------
+- SSL certificate-based authentication with Python callback.
+- More robust SSL.Connection - raises exceptions, not dumps core.
+- Fixed (some) memory leaks and multiple-free()s.
+- Cleaned up EVP.HMAC and EVP.PKey.
+- More X.509 certificate manipulation.
+- An interface to create SSL sessions.
+- Unified SSL read() and write() for synchronous and asynchronous operation.
+- S/MIME and PKCS #7.
+- Integrated with OpenSSL 0.9.5.
+- Enhanced the PRNG interface.
+
+
+ Changes since 0.02
+-----------------------
+1. Ephemeral DH for SSL.
+2. ThreadingSSLServer now does.
+3. XMLrpc over https.
+4. ZServerSSL for Zope 2.1.3.
+5. Encrypting monitor for Zope 2.1.3.
+6. Beginnings of PGP2 support.
+7. Replaced eval() calls with other (hopefully) safe ones.
+8. Miscellaneous enhancements and bug fixes.
+
+
+ Changes since 0.01
+-----------------------
+1. Beginnings of SSL support.
+
+ For building servers, blocking i/o:
+ - An SSLServer modeled after SocketServer.
+ - A ForkingSSLServer that seems to work well.
+ - A ThreadingSSLServer that runs one thread at a time. (!) ;-)
+
+ For building servers, nonblocking i/o:
+ - An ssl_dispatcher modeled after asyncore.dispatcher.
+
+ A HTTPS server based on Medusa.
+
+ For client-side web programming:
+ - httpslib
+ - urllib2
+
+
+2. Support for some BIO objects.
+3. Reduced per-module name space pollution.
+4. Have Swig check for NULL pointers: reduced .i cut-&-paste.
+5. Standardise on MPINT for passing big integers between Python and OpenSSL.
+6. Removed MD5, SHA1, RIPEMD160. Just use EVP.MessageDigest.
+7. Removed HMAC. Just use EVP.HMAC.
+
+
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..f67fed4
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,226 @@
+====================
+ Installing M2Crypto
+====================
+
+:Maintainer: Heikki Toivonen
+:Web-Site: http://chandlerproject.org/Projects/MeTooCrypto
+
+.. contents::
+
+
+Pre-requisites
+----------------
+
+The following software packages are pre-requisites:
+
+- **Python 2.3 or newer**
+- **OpenSSL 0.9.7 or newer**
+- **SWIG 1.3.28 or newer**
+
+Note about OpenSSL versions early in the 0.9.7 series
+-----------------------------------------------------
+
+Early OpenSSL 0.9.7 versions require the __i386__ symbol to be defined.
+Uncomment this line in setup.py:
+
+ #'-D__i386__', # Uncomment for early OpenSSL 0.9.7 versions
+
+if you get this compile-time error:
+
+ This openssl-devel package does not work your architecture?
+
+Note about Fedora Core -based Distributions
+-----------------------------------------------------
+
+Fedora Core (and RedHat, CentOS etc.) have made changes to OpenSSL
+configuration compared to many other Linux distributions. If you can not
+build M2Crypto normally, try the fedora_setup.sh script included with
+M2Crypto sources.
+
+Installing on Unix-like systems, including Cygwin
+-------------------------------------------------
+
+::
+
+ $ tar zxf m2crypto-<version>.tar.gz
+ $ cd m2crypto-<version>
+ $ python setup.py build
+ $ python setup.py install
+
+If you have installed setuptools you can also optionally run tests like this:
+
+ $ python setup.py test
+
+This assumes OpenSSL is installed in /usr. You can provide an alternate
+OpenSSL prefix location with --openssl option to build_ext command. Other
+commands accept standard options if you need them.
+
+Some distributions, like Fedora Core, package OpenSSL headers in a different
+location from OpenSSL itself. In that case you need to tell build_ext the
+additional include location with -I option.
+
+Differences when installing on Windows
+--------------------------------------
+
+Before building from source, you need to install OpenSSL's include files,
+import libraries and DLLs. By default setup.py assumes that OpenSSL include
+files are in ``c:\pkg\openssl\include``, and the import libraries
+in ``c:\pkg\openssl\lib``. As with other platforms, you can specify a different
+OpenSSL location with --openssl option to build_ext command.
+
+Using OpenSSL 0.9.8 on Windows requires Python be built with applink.c
+(add an include statement in python.c). This is not a requirement for
+Linux or MacOSX. (applink.c is provided by OpenSSL.)
+
+
+MSVC++
+~~~~~~~~
+
+setup.py is already configured to work with MSVC++ by default.
+
+With MSVC++, the OpenSSL DLLs, as built, are named ``libeay32.dll``
+and ``ssleay32.dll``. Install these somewhere on your PATH; for example
+in ``c:\bin``, together with ``openssl.exe``.
+
+For MSVC++, the import libraries, as built by OpenSSL, are named
+``libeay32.lib`` and ``ssleay32.lib``.
+
+
+MINGW
+~~~~~~~
+
+.. NOTE::
+ The following instructions for building M2Crypto with MINGW are from
+ M2Crypto 0.12. These instructions should continue to work for this release,
+ although I have not tested them.
+
+Read Sebastien Sauvage's webpage:
+
+ http://sebsauvage.net/python/mingw.html
+
+For mingw32, the OpenSSL import libraries are named ``libeay32.a`` and
+``libssl32.a``. You may need to edit setup.py file for these.
+
+You'll also need to create ``libpython2[123].a``, depending on your version
+of Python.
+
+OpenSSL DLLs for mingw32 are named ``libeay32.dll`` and ``libssl32.dll``.
+Install these somewhere on your PATH; for example in
+``c:\bin``, together with ``openssl.exe``.
+
+Build M2Crypto:
+
+ python setup.py build -cmingw32
+ python setup.py install
+
+
+BC++
+~~~~~~
+
+.. NOTE::
+ The following instructions for building M2Crypto with MSVC++ 6.0 and
+ BC++ 5.5 free compiler suite are from M2Crypto 0.10. These instructions
+ should continue to work for this release, although I have not tested
+ them.
+
+For BC++ these files are created from the MSVC++-built ones using the
+tool ``coff2omf.exe``. I call them ``libeay32_bc.lib`` and
+``ssleay32_bc.lib``, respectively. You will need to edit setup.py file
+for these.
+
+You'll also need Python's import library, e.g., ``python22.lib``, to
+be the BC++-compatible version; i.e., create ``python22_bc.lib`` from
+``python22.lib``, save a copy of ``python22.lib`` (as ``python22_vc.lib``,
+say), then rename ``python22_bc.lib`` to ``python22.lib``.
+
+
+Now you are ready to build M2Crypto. Do one of the following::
+
+ python setup.py build
+ python setup.py build -cbcpp
+
+Then,
+
+::
+
+ python setup.py install
+
+
+MacOSX
+------
+
+Follow the standard instructions to build and install M2Crypto.
+However, should you encounter difficulties, you may want to consider
+the following possibilities.
+
+ - Distutils for Python 2.5 now provides support for universal
+ builds (ppc and i386) and Distutils requires a recent version
+ of Xcode. See http://developer.apple.com/tools/download/
+
+ - OpenSSL 0.9.7l gets installed in /usr with Apple's Security
+ Update 2006-007. If you need features in OpenSSL 0.9.8, you
+ should consider installing 0.9.8 in /usr/local. The commands
+ are:
+
+ OpenSSL:
+ ./config shared --prefix=/usr/local
+ make
+ make test
+ sudo make install [or... install_sw]
+
+ M2Crypto:
+ python setup.py build build_ext --openssl=/usr/local
+ sudo python setup.py install build_ext --openssl=/usr/local
+
+To make Universal builds, you will need to uncomment a line in setup.py:
+
+ extra_link_args = ['-Wl,-search_paths_first'],
+
+If that does not work, here is what Marc Hedlund was able to get working:
+
+ First, download OpenSSL 0.9.8d and unpack it. Edit the OpenSSL Makefiles
+ per PROBLEMS. Then:
+
+ ./config no-shared no-asm --prefix=/usr/local
+ make
+ make test
+ sudo make install
+ make clean
+ ./Configure no-shared no-asm --prefix=/usr/local darwin-ppc-cc
+ make build_libs "CC=cc -arch ppc"
+ lipo -info lib*
+ mkdir -p build/ppc
+ mv lib* build/ppc
+ make clean
+ ./Configure no-shared no-asm --prefix=/usr/local darwin-i386-cc
+ make build_libs "CC=cc -arch i386"
+ lipo -info lib*
+ mkdir -p build/i386
+ mv lib* build/i386/
+ /bin/ls -1 build/i386/ > libnames.tmp
+ mkdir universal
+
+ Create a script in the OpenSSL directory called 'make_universal', with these
+ contents:
+
+ #!/bin/sh
+ for lib in `cat libnames.tmp`; do
+ lipo -create build/*/$lib -output universal/$lib
+ done
+ exit 0
+
+ Then:
+
+ sh make_universal
+ lipo -info universal/lib*
+ sudo cp universal/lib* /usr/local/lib
+ lipo -info /usr/local/lib/lib{crypto,ssl}*
+ cd ../m2crypto-0.17
+
+ Then edit the m2crypto setup.py and uncomment the extra_link_args line at
+ the end.
+
+ python setup.py build build_ext --openssl=/usr/local
+ sudo python setup.py install build_ext --openssl=/usr/local
+
+
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 0000000..d2f636f
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,26 @@
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions copyright (c) 2004-2006 Open Source Applications Foundation.
+All rights reserved.
+
+Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam.
+All rights reserved.
+
+Copyright (c) 2008-2010 Heikki Toivonen. All rights reserved.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation.
+
+THE AUTHOR PROVIDES THIS SOFTWARE ``AS IS'' AND ANY EXPRESSED OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/M2Crypto.egg-info/PKG-INFO b/M2Crypto.egg-info/PKG-INFO
new file mode 100644
index 0000000..3972df5
--- /dev/null
+++ b/M2Crypto.egg-info/PKG-INFO
@@ -0,0 +1,23 @@
+Metadata-Version: 1.0
+Name: M2Crypto
+Version: 0.21.1
+Summary: M2Crypto: A Python crypto and SSL toolkit
+Home-page: http://chandlerproject.org/Projects/MeTooCrypto
+Author: Heikki Toivonen
+Author-email: heikki@osafoundation.org
+License: BSD-style license
+Description: M2Crypto is the most complete Python wrapper for OpenSSL featuring RSA, DSA,
+ DH, EC, HMACs, message digests, symmetric ciphers (including AES); SSL
+ functionality to implement clients and servers; HTTPS extensions to Python's
+ httplib, urllib, and xmlrpclib; unforgeable HMAC'ing AuthCookies for web
+ session management; FTP/TLS client and server; S/MIME; ZServerSSL: A HTTPS
+ server for Zope and ZSmime: An S/MIME messenger for Zope. M2Crypto can also be
+ used to provide SSL for Twisted.
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: C
+Classifier: Programming Language :: Python
+Classifier: Topic :: Security :: Cryptography
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/M2Crypto.egg-info/SOURCES.txt b/M2Crypto.egg-info/SOURCES.txt
new file mode 100644
index 0000000..dffdb58
--- /dev/null
+++ b/M2Crypto.egg-info/SOURCES.txt
@@ -0,0 +1,354 @@
+CHANGES
+INSTALL
+LICENCE
+README
+epydoc.conf
+fedora_setup.sh
+pack.py
+setup.cfg
+setup.py
+M2Crypto/ASN1.py
+M2Crypto/AuthCookie.py
+M2Crypto/BIO.py
+M2Crypto/BN.py
+M2Crypto/DH.py
+M2Crypto/DSA.py
+M2Crypto/EC.py
+M2Crypto/EVP.py
+M2Crypto/Engine.py
+M2Crypto/Err.py
+M2Crypto/RC4.py
+M2Crypto/RSA.py
+M2Crypto/Rand.py
+M2Crypto/SMIME.py
+M2Crypto/X509.py
+M2Crypto/__init__.py
+M2Crypto/callback.py
+M2Crypto/ftpslib.py
+M2Crypto/httpslib.py
+M2Crypto/m2.py
+M2Crypto/m2urllib.py
+M2Crypto/m2urllib2.py
+M2Crypto/m2xmlrpclib.py
+M2Crypto/threading.py
+M2Crypto/util.py
+M2Crypto.egg-info/PKG-INFO
+M2Crypto.egg-info/SOURCES.txt
+M2Crypto.egg-info/dependency_links.txt
+M2Crypto.egg-info/top_level.txt
+M2Crypto/PGP/PublicKey.py
+M2Crypto/PGP/PublicKeyRing.py
+M2Crypto/PGP/RSA.py
+M2Crypto/PGP/__init__.py
+M2Crypto/PGP/constants.py
+M2Crypto/PGP/packet.py
+M2Crypto/SSL/Checker.py
+M2Crypto/SSL/Cipher.py
+M2Crypto/SSL/Connection.py
+M2Crypto/SSL/Context.py
+M2Crypto/SSL/SSLServer.py
+M2Crypto/SSL/Session.py
+M2Crypto/SSL/TwistedProtocolWrapper.py
+M2Crypto/SSL/__init__.py
+M2Crypto/SSL/cb.py
+M2Crypto/SSL/ssl_dispatcher.py
+M2Crypto/SSL/timeout.py
+SWIG/Makefile
+SWIG/Makefile.mw
+SWIG/Makefile.osx
+SWIG/_aes.i
+SWIG/_asn1.i
+SWIG/_bio.i
+SWIG/_bn.i
+SWIG/_dh.i
+SWIG/_dsa.i
+SWIG/_ec.i
+SWIG/_engine.i
+SWIG/_evp.i
+SWIG/_lib.h
+SWIG/_lib.i
+SWIG/_m2crypto.def
+SWIG/_m2crypto.i
+SWIG/_objects.i
+SWIG/_pkcs7.i
+SWIG/_rand.i
+SWIG/_rc4.i
+SWIG/_rsa.i
+SWIG/_ssl.i
+SWIG/_threads.i
+SWIG/_util.i
+SWIG/_x509.i
+contrib/README
+contrib/SimpleX509create.README
+contrib/SimpleX509create.py
+contrib/dave.README
+contrib/dave.patch
+contrib/dispatcher.README
+contrib/dispatcher.py
+contrib/isaac.README
+contrib/isaac.httpslib.py
+contrib/m2crypto.spec
+contrib/smimeplus.README
+contrib/smimeplus.py
+demo/bio_mem_rw.py
+demo/dhtest.py
+demo/dsa1024pvtkey.pem
+demo/dsa_bench.py
+demo/dsatest.pem
+demo/dsatest.py
+demo/rsa.priv.pem
+demo/rsa.priv0.pem
+demo/rsa.pub.pem
+demo/rsa1024pvtkey.pem
+demo/rsa_bench.py
+demo/rsatest.py
+demo/CipherSaber/CipherSaber.py
+demo/CipherSaber/cstest1.cs1
+demo/Zope/ca.pem
+demo/Zope/dh1024.pem
+demo/Zope/server.pem
+demo/Zope/starts
+demo/Zope/starts.bat
+demo/Zope/z2s.py
+demo/Zope/z2s.py.diff
+demo/Zope/ZServer/HTTPS_Server.py
+demo/Zope/ZServer/__init__.py
+demo/Zope/ZServer/medusa/ftps_server.py
+demo/Zope/ZServer/medusa/https_server.py
+demo/Zope/lib/python/Products/GuardedFile/GuardedFile.py
+demo/Zope/lib/python/Products/GuardedFile/README.txt
+demo/Zope/lib/python/Products/GuardedFile/TODO.txt
+demo/Zope/lib/python/Products/GuardedFile/__init__.py
+demo/Zope/lib/python/Products/GuardedFile/add.dtml
+demo/Zope/lib/python/Products/GuardedFile/refresh.txt
+demo/Zope/lib/python/Products/GuardedFile/version.txt
+demo/Zope/lib/python/Products/ZSmime/README.txt
+demo/Zope/lib/python/Products/ZSmime/SmimeTag.py
+demo/Zope/lib/python/Products/ZSmime/__init__.py
+demo/Zope/lib/python/Products/ZSmime/version.txt
+demo/Zope/utilities/x509_user.py
+demo/Zope27/INSTALL.txt
+demo/Zope27/install_dir/lib/python/ZServer/HTTPS_Server.py
+demo/Zope27/install_dir/lib/python/ZServer/__init__.py.patch
+demo/Zope27/install_dir/lib/python/ZServer/component.xml.patch
+demo/Zope27/install_dir/lib/python/ZServer/datatypes.py.patch
+demo/Zope27/install_dir/lib/python/ZServer/medusa/https_server.py
+demo/Zope27/instance_home/README.txt.patch
+demo/Zope27/instance_home/etc/zope.conf.patch
+demo/Zope27/instance_home/ssl/ca.pem
+demo/Zope27/instance_home/ssl/dh1024.pem
+demo/Zope27/instance_home/ssl/server.pem
+demo/ZopeX3/INSTALL.txt
+demo/ZopeX3/install_dir/lib/python/zope/app/server/configure.zcml.patch
+demo/ZopeX3/install_dir/lib/python/zope/app/server/https.py
+demo/ZopeX3/install_dir/lib/python/zope/server/http/https_server.py
+demo/ZopeX3/install_dir/lib/python/zope/server/http/https_serverchannel.py
+demo/ZopeX3/install_dir/lib/python/zope/server/http/publisherhttps_server.py
+demo/ZopeX3/instance_home/etc/zope.conf.patch
+demo/ZopeX3/instance_home/ssl/ca.pem
+demo/ZopeX3/instance_home/ssl/dh1024.pem
+demo/ZopeX3/instance_home/ssl/server.pem
+demo/ec/ecdhtest.py
+demo/ec/ecdsa_bench.py
+demo/ec/ecdsatest.pem
+demo/ec/ecdsatest.py
+demo/ec/secp160r1pvtkey.pem
+demo/https.howto/ca.pem
+demo/https.howto/dh1024.pem
+demo/https.howto/get_https.py
+demo/https.howto/https_cli.py
+demo/https.howto/orig_https_srv.py
+demo/https.howto/server.pem
+demo/medusa/00_README
+demo/medusa/START.py
+demo/medusa/START_xmlrpc.py
+demo/medusa/asynchat.py
+demo/medusa/asyncore.py
+demo/medusa/auth_handler.py
+demo/medusa/ca.pem
+demo/medusa/counter.py
+demo/medusa/default_handler.py
+demo/medusa/dh1024.pem
+demo/medusa/filesys.py
+demo/medusa/ftp_server.py
+demo/medusa/ftps_server.py
+demo/medusa/http_date.py
+demo/medusa/http_server.py
+demo/medusa/https_server.py
+demo/medusa/index.html
+demo/medusa/logger.py
+demo/medusa/m_syslog.py
+demo/medusa/medusa_gif.py
+demo/medusa/mime_type_table.py
+demo/medusa/poison_handler.py
+demo/medusa/producers.py
+demo/medusa/put_handler.py
+demo/medusa/redirecting_handler.py
+demo/medusa/server.pem
+demo/medusa/status_handler.py
+demo/medusa/virtual_handler.py
+demo/medusa/xmlrpc_handler.py
+demo/medusa054/00_README
+demo/medusa054/START.py
+demo/medusa054/START_xmlrpc.py
+demo/medusa054/ca.pem
+demo/medusa054/counter.py
+demo/medusa054/default_handler.py
+demo/medusa054/dh1024.pem
+demo/medusa054/filesys.py
+demo/medusa054/ftp_server.py
+demo/medusa054/ftps_server.py
+demo/medusa054/http_date.py
+demo/medusa054/http_server.py
+demo/medusa054/https_server.py
+demo/medusa054/index.html
+demo/medusa054/logger.py
+demo/medusa054/m_syslog.py
+demo/medusa054/medusa_gif.py
+demo/medusa054/poison_handler.py
+demo/medusa054/producers.py
+demo/medusa054/server.pem
+demo/medusa054/status_handler.py
+demo/medusa054/xmlrpc_handler.py
+demo/perf/memio.py
+demo/perf/sha1.py
+demo/pgp/pgpstep.py
+demo/pgp/pubring.pgp
+demo/pgp/secring.pgp
+demo/pkcs7/pkcs7-thawte.pem
+demo/pkcs7/test.py
+demo/smime/README
+demo/smime/ca.pem
+demo/smime/clear.p7
+demo/smime/client.p12
+demo/smime/client.pem
+demo/smime/client2.pem
+demo/smime/m2.se.p7
+demo/smime/ns.p7
+demo/smime/ns.se.p7
+demo/smime/opaque.p7
+demo/smime/sendsmime.py
+demo/smime/test.py
+demo/smime/unsmime.py
+demo/smime.howto/README
+demo/smime.howto/decrypt.py
+demo/smime.howto/dv.py
+demo/smime.howto/encrypt.p7
+demo/smime.howto/encrypt.py
+demo/smime.howto/recipient.pem
+demo/smime.howto/recipient_key.pem
+demo/smime.howto/se.p7
+demo/smime.howto/se.py
+demo/smime.howto/sendsmime.py
+demo/smime.howto/sign.p7
+demo/smime.howto/sign.py
+demo/smime.howto/signer.pem
+demo/smime.howto/signer_key.pem
+demo/smime.howto/verify.py
+demo/ssl/README
+demo/ssl/c.py
+demo/ssl/c_bio.py
+demo/ssl/ca.der
+demo/ssl/ca.pem
+demo/ssl/client.p12
+demo/ssl/client.pem
+demo/ssl/dh1024.pem
+demo/ssl/echo-eg.py
+demo/ssl/echo.py
+demo/ssl/echod-async.py
+demo/ssl/echod-eg1.py
+demo/ssl/echod-forking.py
+demo/ssl/echod-iterative.py
+demo/ssl/echod-thread.py
+demo/ssl/echod-threading.py
+demo/ssl/echod_lib.py
+demo/ssl/ftp_tls.py
+demo/ssl/http_cli_20.py
+demo/ssl/https_cli.py
+demo/ssl/https_cli_async.py
+demo/ssl/https_srv.py
+demo/ssl/myapp.py
+demo/ssl/s_client.py
+demo/ssl/s_server.py
+demo/ssl/server.pem
+demo/ssl/server3.py
+demo/ssl/sess.py
+demo/ssl/sess2.py
+demo/ssl/sess2.ssldump.out
+demo/ssl/socklib.py
+demo/ssl/somelib.py
+demo/ssl/ss.py
+demo/ssl/twistedsslclient.py
+demo/ssl/twistedsslserver.py
+demo/ssl/xmlrpc_cli.py
+demo/ssl/xmlrpc_srv.py
+demo/tinderbox/build_lib.py
+demo/tinderbox/killableprocess.py
+demo/tinderbox/slave.py
+demo/tinderbox/winprocess.py
+demo/x509/ca.py
+demo/x509/certdata2pem.py
+demo/x509/client2.pem
+demo/x509/demo1.py
+demo/x509/proxy_destroy.py
+demo/x509/proxy_info.py
+demo/x509/proxy_init.py
+demo/x509/proxylib.py
+demo/x509/server-expired.pem
+demo/x509/server.pem
+demo/x509/x509auth.py
+doc/ZServerSSL-HOWTO.html
+doc/howto.ca.html
+doc/howto.smime.html
+doc/howto.ssl.html
+tests/README
+tests/__init__.py
+tests/alltests.py
+tests/ca.pem
+tests/der_encoded_seq.b64
+tests/dhparams.pem
+tests/dsa.param.pem
+tests/dsa.priv.pem
+tests/dsa.pub.pem
+tests/ec.priv.pem
+tests/ec.pub.pem
+tests/fips.py
+tests/long_serial_cert.pem
+tests/pubring.pgp
+tests/recipient.pem
+tests/recipient_key.pem
+tests/rsa.priv.pem
+tests/rsa.priv2.pem
+tests/rsa.pub.pem
+tests/server.pem
+tests/signer.pem
+tests/signer_key.pem
+tests/test_asn1.py
+tests/test_authcookie.py
+tests/test_bio.py
+tests/test_bio_file.py
+tests/test_bio_iobuf.py
+tests/test_bio_membuf.py
+tests/test_bio_ssl.py
+tests/test_bn.py
+tests/test_dh.py
+tests/test_dsa.py
+tests/test_ec_curves.py
+tests/test_ecdh.py
+tests/test_ecdsa.py
+tests/test_engine.py
+tests/test_evp.py
+tests/test_obj.py
+tests/test_pgp.py
+tests/test_rand.py
+tests/test_rc4.py
+tests/test_rsa.py
+tests/test_smime.py
+tests/test_ssl.py
+tests/test_ssl_offline.py
+tests/test_ssl_win.py
+tests/test_threading.py
+tests/test_x509.py
+tests/thawte.pem
+tests/x509.der
+tests/x509.pem \ No newline at end of file
diff --git a/M2Crypto.egg-info/dependency_links.txt b/M2Crypto.egg-info/dependency_links.txt
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/M2Crypto.egg-info/dependency_links.txt
@@ -0,0 +1 @@
+
diff --git a/M2Crypto.egg-info/top_level.txt b/M2Crypto.egg-info/top_level.txt
new file mode 100644
index 0000000..fa4a704
--- /dev/null
+++ b/M2Crypto.egg-info/top_level.txt
@@ -0,0 +1 @@
+M2Crypto
diff --git a/M2Crypto/ASN1.py b/M2Crypto/ASN1.py
new file mode 100644
index 0000000..09d9e9f
--- /dev/null
+++ b/M2Crypto/ASN1.py
@@ -0,0 +1,192 @@
+"""
+M2Crypto wrapper for OpenSSL ASN1 API.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2005 OSAF. All Rights Reserved.
+"""
+
+import time, datetime
+
+import BIO
+import m2
+
+MBSTRING_FLAG = 0x1000
+MBSTRING_ASC = MBSTRING_FLAG | 1
+MBSTRING_BMP = MBSTRING_FLAG | 2
+
+
+class ASN1_Integer:
+
+ m2_asn1_integer_free = m2.asn1_integer_free
+
+ def __init__(self, asn1int, _pyfree=0):
+ self.asn1int = asn1int
+ self._pyfree = _pyfree
+
+ def __cmp__(self, other):
+ return m2.asn1_integer_cmp(self.asn1int, other.asn1int)
+
+ def __del__(self):
+ if self._pyfree:
+ self.m2_asn1_integer_free(self.asn1int)
+
+
+class ASN1_String:
+
+ m2_asn1_string_free = m2.asn1_string_free
+
+ def __init__(self, asn1str, _pyfree=0):
+ self.asn1str = asn1str
+ self._pyfree = _pyfree
+
+ def __str__(self):
+ buf = BIO.MemoryBuffer()
+ m2.asn1_string_print( buf.bio_ptr(), self.asn1str )
+ return buf.read_all()
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_asn1_string_free(self.asn1str)
+
+ def _ptr(self):
+ return self.asn1str
+
+ def as_text(self, flags=0):
+ buf = BIO.MemoryBuffer()
+ m2.asn1_string_print_ex( buf.bio_ptr(), self.asn1str, flags)
+ return buf.read_all()
+
+
+class ASN1_Object:
+
+ m2_asn1_object_free = m2.asn1_object_free
+
+ def __init__(self, asn1obj, _pyfree=0):
+ self.asn1obj = asn1obj
+ self._pyfree = _pyfree
+
+ def __del__(self):
+ if self._pyfree:
+ self.m2_asn1_object_free(self.asn1obj)
+
+ def _ptr(self):
+ return self.asn1obj
+
+class _UTC(datetime.tzinfo):
+ def tzname(self, dt):
+ return "UTC"
+
+ def dst(self, dt):
+ return datetime.timedelta(0)
+
+ def utcoffset(self, dt):
+ return datetime.timedelta(0)
+
+ def __repr__(self):
+ return "<Timezone: %s>" % self.tzname(None)
+UTC = _UTC()
+
+
+class LocalTimezone(datetime.tzinfo):
+ """ Localtimezone from datetime manual """
+ def __init__(self):
+ self._stdoffset = datetime.timedelta(seconds = -time.timezone)
+ if time.daylight:
+ self._dstoffset = datetime.timedelta(seconds = -time.altzone)
+ else:
+ self._dstoffset = self._stdoffset
+ self._dstdiff = self._dstoffset - self._stdoffset
+
+
+ def utcoffset(self, dt):
+ if self._isdst(dt):
+ return self._dstoffset
+ else:
+ return self._stdoffset
+
+ def dst(self, dt):
+ if self._isdst(dt):
+ return self._dstdiff
+ else:
+ return datetime.timedelta(0)
+
+ def tzname(self, dt):
+ return time.tzname[self._isdst(dt)]
+
+ def _isdst(self, dt):
+ tt = (dt.year, dt.month, dt.day,
+ dt.hour, dt.minute, dt.second,
+ dt.weekday(), 0, -1)
+ stamp = time.mktime(tt)
+ tt = time.localtime(stamp)
+ return tt.tm_isdst > 0
+
+
+class ASN1_UTCTIME:
+ _ssl_months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug",
+ "Sep", "Oct", "Nov", "Dec"]
+ m2_asn1_utctime_free = m2.asn1_utctime_free
+
+ def __init__(self, asn1_utctime=None, _pyfree=0):
+ if asn1_utctime is not None:
+ assert m2.asn1_utctime_type_check(asn1_utctime), "'asn1_utctime' type error'"
+ self.asn1_utctime = asn1_utctime
+ self._pyfree = _pyfree
+ else:
+ self.asn1_utctime = m2.asn1_utctime_new ()
+ self._pyfree = 1
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_asn1_utctime_free(self.asn1_utctime)
+
+ def __str__(self):
+ assert m2.asn1_utctime_type_check(self.asn1_utctime), "'asn1_utctime' type error'"
+ buf = BIO.MemoryBuffer()
+ m2.asn1_utctime_print( buf.bio_ptr(), self.asn1_utctime )
+ return buf.read_all()
+
+ def _ptr(self):
+ assert m2.asn1_utctime_type_check(self.asn1_utctime), "'asn1_utctime' type error'"
+ return self.asn1_utctime
+
+ def set_string (self, string):
+ """
+ Set time from UTC string.
+ """
+ assert m2.asn1_utctime_type_check(self.asn1_utctime), "'asn1_utctime' type error'"
+ return m2.asn1_utctime_set_string( self.asn1_utctime, string )
+
+ def set_time (self, time):
+ """
+ Set time from seconds since epoch (long).
+ """
+ assert m2.asn1_utctime_type_check(self.asn1_utctime), "'asn1_utctime' type error'"
+ return m2.asn1_utctime_set( self.asn1_utctime, time )
+
+ def get_datetime(self):
+ date = str(self)
+
+ timezone = None
+ if ' ' not in date:
+ raise ValueError("Invalid date: %s" % date)
+ month, rest = date.split(' ', 1)
+ if month not in self._ssl_months:
+ raise ValueError("Invalid date %s: Invalid month: %s" % (date, m))
+ if rest.endswith(' GMT'):
+ timezone = UTC
+ rest = rest[:-4]
+ tm = list(time.strptime(rest, "%d %H:%M:%S %Y"))[:6]
+ tm[1] = self._ssl_months.index(month) + 1
+ tm.append(0)
+ tm.append(timezone)
+ return datetime.datetime(*tm)
+
+ def set_datetime(self, date):
+ local = LocalTimezone()
+ if date.tzinfo is None:
+ date = date.replace(tzinfo=local)
+ date = date.astimezone(local)
+ return self.set_time(int(time.mktime(date.timetuple())))
diff --git a/M2Crypto/AuthCookie.py b/M2Crypto/AuthCookie.py
new file mode 100644
index 0000000..d401708
--- /dev/null
+++ b/M2Crypto/AuthCookie.py
@@ -0,0 +1,114 @@
+"""Secure Authenticator Cookies
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+# M2Crypto
+import Rand, m2
+
+# Python. Cookie is bundled with Python 2.x.
+import Cookie, binascii, re, time
+
+
+_MIX_FORMAT = 'exp=%s&data=%s&digest='
+_MIX_RE = re.compile('exp=(\d+\.\d+)&data=(.+)&digest=(\S*)')
+
+def mix(expiry, data, format=_MIX_FORMAT):
+ return format % (repr(expiry), data)
+
+def unmix(dough, regex=_MIX_RE):
+ mo = regex.match(dough)
+ if mo:
+ return float(mo.group(1)), mo.group(2)
+ else:
+ return None
+
+def unmix3(dough, regex=_MIX_RE):
+ mo = regex.match(dough)
+ if mo:
+ return float(mo.group(1)), mo.group(2), mo.group(3)
+ else:
+ return None
+
+
+_TOKEN = '_M2AUTH_'
+
+class AuthCookieJar:
+
+ _keylen = 20
+
+ def __init__(self):
+ self._key = Rand.rand_bytes(self._keylen)
+
+ def _hmac(self, key, data):
+ return binascii.b2a_base64(m2.hmac(key, data, m2.sha1()))[:-1]
+
+ def makeCookie(self, expiry, data):
+ dough = mix(expiry, data)
+ return AuthCookie(expiry, data, dough, self._hmac(self._key, dough))
+
+ def isGoodCookie(self, cookie):
+ assert isinstance(cookie, AuthCookie)
+ if cookie.isExpired():
+ return 0
+ c = self.makeCookie(cookie._expiry, cookie._data)
+ return (c._expiry == cookie._expiry) \
+ and (c._data == cookie._data) \
+ and (c._mac == cookie._mac) \
+ and (c.output() == cookie.output())
+
+ def isGoodCookieString(self, cookie_str):
+ c = Cookie.SmartCookie()
+ c.load(cookie_str)
+ if not c.has_key(_TOKEN):
+ return 0
+ undough = unmix3(c[_TOKEN].value)
+ if undough is None:
+ return 0
+ exp, data, mac = undough
+ c2 = self.makeCookie(exp, data)
+ return (not c2.isExpired()) and (c2._mac == mac)
+
+
+class AuthCookie:
+
+ def __init__(self, expiry, data, dough, mac):
+ self._expiry = expiry
+ self._data = data
+ self._mac = mac
+ self._cookie = Cookie.SmartCookie()
+ self._cookie[_TOKEN] = '%s%s' % (dough, mac)
+ self._name = '%s%s' % (dough, mac) # XXX WebKit only.
+
+ def expiry(self):
+ """Return the cookie's expiry time."""
+ return self._expiry
+
+ def data(self):
+ """Return the data portion of the cookie."""
+ return self._data
+
+ def mac(self):
+ """Return the cookie's MAC."""
+ return self._mac
+
+ def output(self):
+ """Return the cookie's output in "Set-Cookie" format."""
+ return self._cookie.output()
+
+ def value(self):
+ """Return the cookie's output minus the "Set-Cookie: " portion.
+ """
+ return self._cookie[_TOKEN].value
+
+ def isExpired(self):
+ """Return 1 if the cookie has expired, 0 otherwise."""
+ return (time.time() > self._expiry)
+
+ # XXX Following methods are for WebKit only. These should be pushed
+ # to WKAuthCookie.
+ def name(self):
+ return self._name
+
+ def headerValue(self):
+ return self.value()
+
diff --git a/M2Crypto/BIO.py b/M2Crypto/BIO.py
new file mode 100644
index 0000000..11dbce4
--- /dev/null
+++ b/M2Crypto/BIO.py
@@ -0,0 +1,286 @@
+"""M2Crypto wrapper for OpenSSL BIO API.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+import m2
+
+# Deprecated
+from m2 import bio_do_handshake as bio_do_ssl_handshake
+
+from cStringIO import StringIO
+
+class BIOError(Exception): pass
+
+m2.bio_init(BIOError)
+
+class BIO:
+
+ """Abstract object interface to the BIO API."""
+
+ m2_bio_free = m2.bio_free
+
+ def __init__(self, bio=None, _pyfree=0, _close_cb=None):
+ self.bio = bio
+ self._pyfree = _pyfree
+ self._close_cb = _close_cb
+ self.closed = 0
+ self.write_closed = 0
+
+ def __del__(self):
+ if self._pyfree:
+ self.m2_bio_free(self.bio)
+
+ def _ptr(self):
+ return self.bio
+
+ # Deprecated.
+ bio_ptr = _ptr
+
+ def fileno(self):
+ return m2.bio_get_fd(self.bio)
+
+ def readable(self):
+ return not self.closed
+
+ def read(self, size=None):
+ if not self.readable():
+ raise IOError, 'cannot read'
+ if size is None:
+ buf = StringIO()
+ while 1:
+ data = m2.bio_read(self.bio, 4096)
+ if not data: break
+ buf.write(data)
+ return buf.getvalue()
+ elif size == 0:
+ return ''
+ elif size < 0:
+ raise ValueError, 'read count is negative'
+ else:
+ return m2.bio_read(self.bio, size)
+
+ def readline(self, size=4096):
+ if not self.readable():
+ raise IOError, 'cannot read'
+ buf = m2.bio_gets(self.bio, size)
+ return buf
+
+ def readlines(self, sizehint='ignored'):
+ if not self.readable():
+ raise IOError, 'cannot read'
+ lines=[]
+ while 1:
+ buf=m2.bio_gets(self.bio, 4096)
+ if buf is None:
+ break
+ lines.append(buf)
+ return lines
+
+ def writeable(self):
+ return (not self.closed) and (not self.write_closed)
+
+ def write(self, data):
+ if not self.writeable():
+ raise IOError, 'cannot write'
+ return m2.bio_write(self.bio, data)
+
+ def write_close(self):
+ self.write_closed = 1
+
+ def flush(self):
+ m2.bio_flush(self.bio)
+
+ def reset(self):
+ """
+ Sets the bio to its initial state
+ """
+ return m2.bio_reset(self.bio)
+
+ def close(self):
+ self.closed = 1
+ if self._close_cb:
+ self._close_cb()
+
+ def should_retry(self):
+ """
+ Can the call be attempted again, or was there an error
+ ie do_handshake
+
+ """
+ return m2.bio_should_retry(self.bio)
+
+ def should_read(self):
+ """
+ Returns whether the cause of the condition is the bio
+ should read more data
+ """
+ return m2.bio_should_read(self.bio)
+
+ def should_write(self):
+ """
+ Returns whether the cause of the condition is the bio
+ should write more data
+ """
+ return m2.bio_should_write(self.bio)
+
+class MemoryBuffer(BIO):
+
+ """
+ Object interface to BIO_s_mem.
+
+ Empirical testing suggests that this class performs less well than cStringIO,
+ because cStringIO is implemented in C, whereas this class is implemented in
+ Python. Thus, the recommended practice is to use cStringIO for regular work and
+ convert said cStringIO object to a MemoryBuffer object only when necessary.
+ """
+
+ def __init__(self, data=None):
+ BIO.__init__(self)
+ self.bio = m2.bio_new(m2.bio_s_mem())
+ self._pyfree = 1
+ if data is not None:
+ m2.bio_write(self.bio, data)
+
+ def __len__(self):
+ return m2.bio_ctrl_pending(self.bio)
+
+ def read(self, size=0):
+ if not self.readable():
+ raise IOError, 'cannot read'
+ if size:
+ return m2.bio_read(self.bio, size)
+ else:
+ return m2.bio_read(self.bio, m2.bio_ctrl_pending(self.bio))
+
+ # Backwards-compatibility.
+ getvalue = read_all = read
+
+ def write_close(self):
+ self.write_closed = 1
+ m2.bio_set_mem_eof_return(self.bio, 0)
+
+ close = write_close
+
+
+class File(BIO):
+
+ """
+ Object interface to BIO_s_fp.
+
+ This class interfaces Python to OpenSSL functions that expect BIO *. For
+ general file manipulation in Python, use Python's builtin file object.
+ """
+
+ def __init__(self, pyfile, close_pyfile=1):
+ BIO.__init__(self, _pyfree=1)
+ self.pyfile = pyfile
+ self.close_pyfile = close_pyfile
+ self.bio = m2.bio_new_fp(pyfile, 0)
+
+ def close(self):
+ self.closed = 1
+ if self.close_pyfile:
+ self.pyfile.close()
+
+def openfile(filename, mode='rb'):
+ return File(open(filename, mode))
+
+
+class IOBuffer(BIO):
+
+ """
+ Object interface to BIO_f_buffer.
+
+ Its principal function is to be BIO_push()'ed on top of a BIO_f_ssl, so
+ that makefile() of said underlying SSL socket works.
+ """
+
+ m2_bio_pop = m2.bio_pop
+ m2_bio_free = m2.bio_free
+
+ def __init__(self, under_bio, mode='rwb', _pyfree=1):
+ BIO.__init__(self, _pyfree=_pyfree)
+ self.io = m2.bio_new(m2.bio_f_buffer())
+ self.bio = m2.bio_push(self.io, under_bio._ptr())
+ # This reference keeps the underlying BIO alive while we're not closed.
+ self._under_bio = under_bio
+ if 'w' in mode:
+ self.write_closed = 0
+ else:
+ self.write_closed = 1
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_bio_pop(self.bio)
+ self.m2_bio_free(self.io)
+
+ def close(self):
+ BIO.close(self)
+
+
+class CipherStream(BIO):
+
+ """
+ Object interface to BIO_f_cipher.
+ """
+
+ SALT_LEN = m2.PKCS5_SALT_LEN
+
+ m2_bio_pop = m2.bio_pop
+ m2_bio_free = m2.bio_free
+
+ def __init__(self, obio):
+ BIO.__init__(self, _pyfree=1)
+ self.obio = obio
+ self.bio = m2.bio_new(m2.bio_f_cipher())
+ self.closed = 0
+
+ def __del__(self):
+ if not getattr(self, 'closed', 1):
+ self.close()
+
+ def close(self):
+ self.m2_bio_pop(self.bio)
+ self.m2_bio_free(self.bio)
+ self.closed = 1
+
+ def write_close(self):
+ self.obio.write_close()
+
+ def set_cipher(self, algo, key, iv, op):
+ cipher = getattr(m2, algo, None)
+ if cipher is None:
+ raise ValueError, ('unknown cipher', algo)
+ m2.bio_set_cipher(self.bio, cipher(), key, iv, op)
+ m2.bio_push(self.bio, self.obio._ptr())
+
+
+class SSLBio(BIO):
+ """
+ Object interface to BIO_f_ssl
+ """
+ def __init__(self, _pyfree=1):
+ BIO.__init__(self, _pyfree)
+ self.bio = m2.bio_new(m2.bio_f_ssl())
+ self.closed = 0
+
+
+ def set_ssl(self, conn, close_flag=m2.bio_noclose):
+ """
+ Sets the bio to the SSL pointer which is
+ contained in the connection object.
+ """
+ self._pyfree = 0
+ m2.bio_set_ssl(self.bio, conn.ssl, close_flag)
+ if close_flag == m2.bio_noclose:
+ conn.set_ssl_close_flag(m2.bio_close)
+
+ def do_handshake(self):
+ """
+ Do the handshake.
+
+ Return 1 if the handshake completes
+ Return 0 or a negative number if there is a problem
+ """
+ return m2.bio_do_handshake(self.bio)
+
diff --git a/M2Crypto/BN.py b/M2Crypto/BN.py
new file mode 100755
index 0000000..ec741c0
--- /dev/null
+++ b/M2Crypto/BN.py
@@ -0,0 +1,47 @@
+"""
+M2Crypto wrapper for OpenSSL BN (BIGNUM) API.
+
+Copyright (c) 2005 Open Source Applications Foundation. All rights reserved.
+"""
+
+import m2
+
+def rand(bits, top=-1, bottom=0):
+ """
+ Generate cryptographically strong random number.
+
+ @param bits: Length of random number in bits.
+ @param top: If -1, the most significant bit can be 0. If 0, the most
+ significant bit is 1, and if 1, the two most significant
+ bits will be 1.
+ @param bottom: If bottom is true, the number will be odd.
+ """
+ return m2.bn_rand(bits, top, bottom)
+
+
+def rand_range(range):
+ """
+ Generate a random number in a range.
+
+ @param range: Upper limit for range.
+ @return: A random number in the range [0, range)
+ """
+ return m2.bn_rand_range(range)
+
+
+def randfname(length):
+ """
+ Return a random filename, which is simply a string where all
+ the characters are from the set [a-zA-Z0-9].
+
+ @param length: Length of filename to return.
+ @type length: int
+ @return: random filename string
+ """
+ letters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890'
+ lettersLen = len(letters)
+ fname = []
+ for x in range(length):
+ fname += [letters[m2.bn_rand_range(lettersLen)]]
+
+ return ''.join(fname)
diff --git a/M2Crypto/DH.py b/M2Crypto/DH.py
new file mode 100644
index 0000000..4d454ef
--- /dev/null
+++ b/M2Crypto/DH.py
@@ -0,0 +1,96 @@
+"""M2Crypto wrapper for OpenSSL DH API.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from util import genparam_callback
+import BIO, Err, m2
+
+class DHError(Exception): pass
+
+m2.dh_init(DHError)
+
+class DH:
+
+ """
+ Object interface to the Diffie-Hellman key exchange
+ protocol.
+ """
+
+ m2_dh_free = m2.dh_free
+
+ def __init__(self, dh, _pyfree=0):
+ assert m2.dh_type_check(dh)
+ self.dh = dh
+ self._pyfree = _pyfree
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_dh_free(self.dh)
+
+ def __len__(self):
+ assert m2.dh_type_check(self.dh), "'dh' type error"
+ return m2.dh_size(self.dh)
+
+ def __getattr__(self, name):
+ if name in ('p', 'g', 'pub', 'priv'):
+ method = getattr(m2, 'dh_get_%s' % (name,))
+ assert m2.dh_type_check(self.dh), "'dh' type error"
+ return method(self.dh)
+ else:
+ raise AttributeError
+
+ def __setattr__(self, name, value):
+ if name in ('p', 'g'):
+ raise DHError, 'set (p, g) via set_params()'
+ elif name in ('pub','priv'):
+ raise DHError, 'generate (pub, priv) via gen_key()'
+ else:
+ self.__dict__[name] = value
+
+ def _ptr(self):
+ return self.dh
+
+ def check_params(self):
+ assert m2.dh_type_check(self.dh), "'dh' type error"
+ return m2.dh_check(self.dh)
+
+ def gen_key(self):
+ assert m2.dh_type_check(self.dh), "'dh' type error"
+ m2.dh_generate_key(self.dh)
+
+ def compute_key(self, pubkey):
+ assert m2.dh_type_check(self.dh), "'dh' type error"
+ return m2.dh_compute_key(self.dh, pubkey)
+
+ def print_params(self, bio):
+ assert m2.dh_type_check(self.dh), "'dh' type error"
+ return m2.dhparams_print(bio._ptr(), self.dh)
+
+
+def gen_params(plen, g, callback=genparam_callback):
+ return DH(m2.dh_generate_parameters(plen, g, callback), 1)
+
+
+def load_params(file):
+ bio = BIO.openfile(file)
+ return load_params_bio(bio)
+
+
+def load_params_bio(bio):
+ return DH(m2.dh_read_parameters(bio._ptr()), 1)
+
+
+def set_params(p, g):
+ dh = m2.dh_new()
+ m2.dh_set_p(dh, p)
+ m2.dh_set_g(dh, g)
+ return DH(dh, 1)
+
+
+#def free_params(cptr):
+# m2.dh_free(cptr)
+
+
+DH_GENERATOR_2 = m2.DH_GENERATOR_2
+DH_GENERATOR_5 = m2.DH_GENERATOR_5
+
diff --git a/M2Crypto/DSA.py b/M2Crypto/DSA.py
new file mode 100644
index 0000000..db1f2ff
--- /dev/null
+++ b/M2Crypto/DSA.py
@@ -0,0 +1,439 @@
+"""
+ M2Crypto wrapper for OpenSSL DSA API.
+
+ Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+
+ Portions created by Open Source Applications Foundation (OSAF) are
+ Copyright (C) 2004 OSAF. All Rights Reserved.
+"""
+
+import sys
+import util, BIO, m2
+
+class DSAError(Exception): pass
+
+m2.dsa_init(DSAError)
+
+class DSA:
+
+ """
+ This class is a context supporting DSA key and parameter
+ values, signing and verifying.
+
+ Simple example::
+
+ from M2Crypto import EVP, DSA, util
+
+ message = 'Kilroy was here!'
+ md = EVP.MessageDigest('sha1')
+ md.update(message)
+ digest = md.final()
+
+ dsa = DSA.gen_params(1024)
+ dsa.gen_key()
+ r, s = dsa.sign(digest)
+ good = dsa.verify(digest, r, s)
+ if good:
+ print ' ** success **'
+ else:
+ print ' ** verification failed **'
+ """
+
+ m2_dsa_free = m2.dsa_free
+
+ def __init__(self, dsa, _pyfree=0):
+ """
+ Use one of the factory functions to create an instance.
+ """
+ assert m2.dsa_type_check(dsa), "'dsa' type error"
+ self.dsa = dsa
+ self._pyfree = _pyfree
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_dsa_free(self.dsa)
+
+ def __len__(self):
+ """
+ Return the key length.
+
+ @rtype: int
+ @return: the DSA key length in bits
+ """
+ assert m2.dsa_type_check(self.dsa), "'dsa' type error"
+ return m2.dsa_keylen(self.dsa)
+
+ def __getattr__(self, name):
+ """
+ Return specified DSA parameters and key values.
+
+ @type name: str
+ @param name: name of variable to be returned. Must be
+ one of 'p', 'q', 'g', 'pub', 'priv'.
+ @rtype: str
+ @return: value of specified variable (a "byte string")
+ """
+ if name in ['p', 'q', 'g', 'pub', 'priv']:
+ method = getattr(m2, 'dsa_get_%s' % (name,))
+ assert m2.dsa_type_check(self.dsa), "'dsa' type error"
+ return method(self.dsa)
+ else:
+ raise AttributeError
+
+ def __setattr__(self, name, value):
+ if name in ['p', 'q', 'g']:
+ raise DSAError('set (p, q, g) via set_params()')
+ elif name in ['pub','priv']:
+ raise DSAError('generate (pub, priv) via gen_key()')
+ else:
+ self.__dict__[name] = value
+
+ def set_params(self, p, q, g):
+ """
+ Set new parameters.
+
+ @warning: This does not change the private key, so it may be
+ unsafe to use this method. It is better to use
+ gen_params function to create a new DSA object.
+ """
+ m2.dsa_set_p(self.dsa, p)
+ m2.dsa_set_q(self.dsa, q)
+ m2.dsa_set_g(self.dsa, g)
+
+ def gen_key(self):
+ """
+ Generate a key pair.
+ """
+ assert m2.dsa_type_check(self.dsa), "'dsa' type error"
+ m2.dsa_gen_key(self.dsa)
+
+ def save_params(self, filename):
+ """
+ Save the DSA parameters to a file.
+
+ @type filename: str
+ @param filename: Save the DSA parameters to this file.
+ @return: 1 (true) if successful
+ """
+ bio = BIO.openfile(filename, 'wb')
+ ret = m2.dsa_write_params_bio(self.dsa, bio._ptr())
+ bio.close()
+ return ret
+
+ def save_params_bio(self, bio):
+ """
+ Save DSA parameters to a BIO object.
+
+ @type bio: M2Crypto.BIO object
+ @param bio: Save DSA parameters to this object.
+ @return: 1 (true) if successful
+ """
+ return m2.dsa_write_params_bio(self.dsa, bio._ptr())
+
+ def save_key(self, filename, cipher='aes_128_cbc',
+ callback=util.passphrase_callback):
+ """
+ Save the DSA key pair to a file.
+
+ @type filename: str
+ @param filename: Save the DSA key pair to this file.
+ @type cipher: str
+ @param cipher: name of symmetric key algorithm and mode
+ to encrypt the private key.
+ @return: 1 (true) if successful
+ """
+ bio = BIO.openfile(filename, 'wb')
+ ret = self.save_key_bio(bio, cipher, callback)
+ bio.close()
+ return ret
+
+ def save_key_bio(self, bio, cipher='aes_128_cbc',
+ callback=util.passphrase_callback):
+ """
+ Save DSA key pair to a BIO object.
+
+ @type bio: M2Crypto.BIO object
+ @param bio: Save DSA parameters to this object.
+ @type cipher: str
+ @param cipher: name of symmetric key algorithm and mode
+ to encrypt the private key.
+ @return: 1 (true) if successful
+ """
+ if cipher is None:
+ return m2.dsa_write_key_bio_no_cipher(self.dsa,
+ bio._ptr(), callback)
+ else:
+ ciph = getattr(m2, cipher, None)
+ if ciph is None:
+ raise DSAError('no such cipher: %s' % cipher)
+ else:
+ ciph = ciph()
+ return m2.dsa_write_key_bio(self.dsa, bio._ptr(), ciph, callback)
+
+ def save_pub_key(self, filename):
+ """
+ Save the DSA public key (with parameters) to a file.
+
+ @type filename: str
+ @param filename: Save DSA public key (with parameters)
+ to this file.
+ @return: 1 (true) if successful
+ """
+ bio = BIO.openfile(filename, 'wb')
+ ret = self.save_pub_key_bio(bio)
+ bio.close()
+ return ret
+
+ def save_pub_key_bio(self, bio):
+ """
+ Save DSA public key (with parameters) to a BIO object.
+
+ @type bio: M2Crypto.BIO object
+ @param bio: Save DSA public key (with parameters)
+ to this object.
+ @return: 1 (true) if successful
+ """
+ return m2.dsa_write_pub_key_bio(self.dsa, bio._ptr())
+
+ def sign(self, digest):
+ """
+ Sign the digest.
+
+ @type digest: str
+ @param digest: SHA-1 hash of message (same as output
+ from MessageDigest, a "byte string")
+ @rtype: tuple
+ @return: DSA signature, a tuple of two values, r and s,
+ both "byte strings".
+ """
+ assert self.check_key(), 'key is not initialised'
+ return m2.dsa_sign(self.dsa, digest)
+
+ def verify(self, digest, r, s):
+ """
+ Verify a newly calculated digest against the signature
+ values r and s.
+
+ @type digest: str
+ @param digest: SHA-1 hash of message (same as output
+ from MessageDigest, a "byte string")
+ @type r: str
+ @param r: r value of the signature, a "byte string"
+ @type s: str
+ @param s: s value of the signature, a "byte string"
+ @rtype: int
+ @return: 1 (true) if verify succeeded, 0 if failed
+ """
+ assert self.check_key(), 'key is not initialised'
+ return m2.dsa_verify(self.dsa, digest, r, s)
+
+ def sign_asn1(self, digest):
+ assert self.check_key(), 'key is not initialised'
+ return m2.dsa_sign_asn1(self.dsa, digest)
+
+ def verify_asn1(self, digest, blob):
+ assert self.check_key(), 'key is not initialised'
+ return m2.dsa_verify_asn1(self.dsa, digest, blob)
+
+ def check_key(self):
+ """
+ Check to be sure the DSA object has a valid private key.
+
+ @rtype: int
+ @return: 1 (true) if a valid private key
+ """
+ assert m2.dsa_type_check(self.dsa), "'dsa' type error"
+ return m2.dsa_check_key(self.dsa)
+
+
+
+class DSA_pub(DSA):
+
+ """
+ This class is a DSA context that only supports a public key
+ and verification. It does NOT support a private key or
+ signing.
+
+ """
+
+ def sign(self, *argv):
+ raise DSAError('DSA_pub object has no private key')
+
+ sign_asn1 = sign
+
+ def check_key(self):
+ return m2.dsa_check_pub_key(self.dsa)
+
+ save_key = DSA.save_pub_key
+
+ save_key_bio = DSA.save_pub_key_bio
+
+#---------------------------------------------------------------
+# factories and other functions
+
+def gen_params(bits, callback=util.genparam_callback):
+ """
+ Factory function that generates DSA parameters and
+ instantiates a DSA object from the output.
+
+ @type bits: int
+ @param bits: The length of the prime to be generated. If
+ 'bits' < 512, it is set to 512.
+ @type callback: function
+ @param callback: A Python callback object that will be
+ invoked during parameter generation; it usual
+ purpose is to provide visual feedback.
+ @rtype: DSA
+ @return: instance of DSA.
+ """
+ dsa = m2.dsa_generate_parameters(bits, callback)
+ if dsa is None:
+ raise DSAError('problem generating DSA parameters')
+ return DSA(dsa, 1)
+
+def set_params(p, q, g):
+ """
+ Factory function that instantiates a DSA object with DSA
+ parameters.
+
+ @type p: str
+ @param p: value of p, a "byte string"
+ @type q: str
+ @param q: value of q, a "byte string"
+ @type g: str
+ @param g: value of g, a "byte string"
+ @rtype: DSA
+ @return: instance of DSA.
+ """
+ dsa = m2.dsa_new()
+ m2.dsa_set_p(dsa, p)
+ m2.dsa_set_q(dsa, q)
+ m2.dsa_set_g(dsa, g)
+ return DSA(dsa, 1)
+
+def load_params(file, callback=util.passphrase_callback):
+ """
+ Factory function that instantiates a DSA object with DSA
+ parameters from a file.
+
+ @type file: str
+ @param file: Names the file (a path) that contains the PEM
+ representation of the DSA parameters.
+ @type callback: A Python callable
+ @param callback: A Python callback object that will be
+ invoked if the DSA parameters file is
+ passphrase-protected.
+ @rtype: DSA
+ @return: instance of DSA.
+ """
+ bio = BIO.openfile(file)
+ ret = load_params_bio(bio, callback)
+ bio.close()
+ return ret
+
+
+def load_params_bio(bio, callback=util.passphrase_callback):
+ """
+ Factory function that instantiates a DSA object with DSA
+ parameters from a M2Crypto.BIO object.
+
+ @type bio: M2Crypto.BIO object
+ @param bio: Contains the PEM representation of the DSA
+ parameters.
+ @type callback: A Python callable
+ @param callback: A Python callback object that will be
+ invoked if the DSA parameters file is
+ passphrase-protected.
+ @rtype: DSA
+ @return: instance of DSA.
+ """
+ dsa = m2.dsa_read_params(bio._ptr(), callback)
+ if dsa is None:
+ raise DSAError('problem loading DSA parameters')
+ return DSA(dsa, 1)
+
+
+def load_key(file, callback=util.passphrase_callback):
+ """
+ Factory function that instantiates a DSA object from a
+ PEM encoded DSA key pair.
+
+ @type file: str
+ @param file: Names the file (a path) that contains the PEM
+ representation of the DSA key pair.
+ @type callback: A Python callable
+ @param callback: A Python callback object that will be
+ invoked if the DSA key pair is
+ passphrase-protected.
+ @rtype: DSA
+ @return: instance of DSA.
+ """
+ bio = BIO.openfile(file)
+ ret = load_key_bio(bio, callback)
+ bio.close()
+ return ret
+
+
+def load_key_bio(bio, callback=util.passphrase_callback):
+ """
+ Factory function that instantiates a DSA object from a
+ PEM encoded DSA key pair.
+
+ @type bio: M2Crypto.BIO object
+ @param bio: Contains the PEM representation of the DSA
+ key pair.
+ @type callback: A Python callable
+ @param callback: A Python callback object that will be
+ invoked if the DSA key pair is
+ passphrase-protected.
+ @rtype: DSA
+ @return: instance of DSA.
+ """
+ dsa = m2.dsa_read_key(bio._ptr(), callback)
+ if not dsa:
+ raise DSAError('problem loading DSA key pair')
+ return DSA(dsa, 1)
+
+
+def load_pub_key(file, callback=util.passphrase_callback):
+ """
+ Factory function that instantiates a DSA_pub object using
+ a DSA public key contained in PEM file. The PEM file
+ must contain the parameters in addition to the public key.
+
+ @type file: str
+ @param file: Names the file (a path) that contains the PEM
+ representation of the DSA public key.
+ @type callback: A Python callable
+ @param callback: A Python callback object that will be
+ invoked should the DSA public key be
+ passphrase-protected.
+ @rtype: DSA_pub
+ @return: instance of DSA_pub.
+ """
+ bio = BIO.openfile(file)
+ ret = load_pub_key_bio(bio, callback)
+ bio.close()
+ return ret
+
+
+def load_pub_key_bio(bio, callback=util.passphrase_callback):
+ """
+ Factory function that instantiates a DSA_pub object using
+ a DSA public key contained in PEM format. The PEM
+ must contain the parameters in addition to the public key.
+
+ @type bio: M2Crypto.BIO object
+ @param bio: Contains the PEM representation of the DSA
+ public key (with params).
+ @type callback: A Python callable
+ @param callback: A Python callback object that will be
+ invoked should the DSA public key be
+ passphrase-protected.
+ @rtype: DSA_pub
+ @return: instance of DSA_pub.
+ """
+ dsapub = m2.dsa_read_pub_key(bio._ptr(), callback)
+ if not dsapub:
+ raise DSAError('problem loading DSA public key')
+ return DSA_pub(dsapub, 1)
diff --git a/M2Crypto/EC.py b/M2Crypto/EC.py
new file mode 100644
index 0000000..b1ed43e
--- /dev/null
+++ b/M2Crypto/EC.py
@@ -0,0 +1,335 @@
+"""
+M2Crypto wrapper for OpenSSL ECDH/ECDSA API.
+
+@requires: OpenSSL 0.9.8 or newer
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+
+Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam.
+All rights reserved."""
+
+import util, BIO, m2
+
+class ECError(Exception): pass
+
+m2.ec_init(ECError)
+
+# Curve identifier constants
+NID_secp112r1 = m2.NID_secp112r1
+NID_secp112r2 = m2.NID_secp112r2
+NID_secp128r1 = m2.NID_secp128r1
+NID_secp128r2 = m2.NID_secp128r2
+NID_secp160k1 = m2.NID_secp160k1
+NID_secp160r1 = m2.NID_secp160r1
+NID_secp160r2 = m2.NID_secp160r2
+NID_secp192k1 = m2.NID_secp192k1
+NID_secp224k1 = m2.NID_secp224k1
+NID_secp224r1 = m2.NID_secp224r1
+NID_secp256k1 = m2.NID_secp256k1
+NID_secp384r1 = m2.NID_secp384r1
+NID_secp521r1 = m2.NID_secp521r1
+NID_sect113r1 = m2.NID_sect113r1
+NID_sect113r2 = m2.NID_sect113r2
+NID_sect131r1 = m2.NID_sect131r1
+NID_sect131r2 = m2.NID_sect131r2
+NID_sect163k1 = m2.NID_sect163k1
+NID_sect163r1 = m2.NID_sect163r1
+NID_sect163r2 = m2.NID_sect163r2
+NID_sect193r1 = m2.NID_sect193r1
+NID_sect193r2 = m2.NID_sect193r2
+NID_sect233k1 = m2.NID_sect233k1 # default for secg.org TLS test server
+NID_sect233r1 = m2.NID_sect233r1
+NID_sect239k1 = m2.NID_sect239k1
+NID_sect283k1 = m2.NID_sect283k1
+NID_sect283r1 = m2.NID_sect283r1
+NID_sect409k1 = m2.NID_sect409k1
+NID_sect409r1 = m2.NID_sect409r1
+NID_sect571k1 = m2.NID_sect571k1
+NID_sect571r1 = m2.NID_sect571r1
+
+NID_X9_62_prime192v1 = m2.NID_X9_62_prime192v1
+NID_X9_62_prime192v2 = m2.NID_X9_62_prime192v2
+NID_X9_62_prime192v3 = m2.NID_X9_62_prime192v3
+NID_X9_62_prime239v1 = m2.NID_X9_62_prime239v1
+NID_X9_62_prime239v2 = m2.NID_X9_62_prime239v2
+NID_X9_62_prime239v3 = m2.NID_X9_62_prime239v3
+NID_X9_62_prime256v1 = m2.NID_X9_62_prime256v1
+NID_X9_62_c2pnb163v1 = m2.NID_X9_62_c2pnb163v1
+NID_X9_62_c2pnb163v2 = m2.NID_X9_62_c2pnb163v2
+NID_X9_62_c2pnb163v3 = m2.NID_X9_62_c2pnb163v3
+NID_X9_62_c2pnb176v1 = m2.NID_X9_62_c2pnb176v1
+NID_X9_62_c2tnb191v1 = m2.NID_X9_62_c2tnb191v1
+NID_X9_62_c2tnb191v2 = m2.NID_X9_62_c2tnb191v2
+NID_X9_62_c2tnb191v3 = m2.NID_X9_62_c2tnb191v3
+NID_X9_62_c2pnb208w1 = m2.NID_X9_62_c2pnb208w1
+NID_X9_62_c2tnb239v1 = m2.NID_X9_62_c2tnb239v1
+NID_X9_62_c2tnb239v2 = m2.NID_X9_62_c2tnb239v2
+NID_X9_62_c2tnb239v3 = m2.NID_X9_62_c2tnb239v3
+NID_X9_62_c2pnb272w1 = m2.NID_X9_62_c2pnb272w1
+NID_X9_62_c2pnb304w1 = m2.NID_X9_62_c2pnb304w1
+NID_X9_62_c2tnb359v1 = m2.NID_X9_62_c2tnb359v1
+NID_X9_62_c2pnb368w1 = m2.NID_X9_62_c2pnb368w1
+NID_X9_62_c2tnb431r1 = m2.NID_X9_62_c2tnb431r1
+
+NID_wap_wsg_idm_ecid_wtls1 = m2.NID_wap_wsg_idm_ecid_wtls1
+NID_wap_wsg_idm_ecid_wtls3 = m2.NID_wap_wsg_idm_ecid_wtls3
+NID_wap_wsg_idm_ecid_wtls4 = m2.NID_wap_wsg_idm_ecid_wtls4
+NID_wap_wsg_idm_ecid_wtls5 = m2.NID_wap_wsg_idm_ecid_wtls5
+NID_wap_wsg_idm_ecid_wtls6 = m2.NID_wap_wsg_idm_ecid_wtls6
+NID_wap_wsg_idm_ecid_wtls7 = m2.NID_wap_wsg_idm_ecid_wtls7
+NID_wap_wsg_idm_ecid_wtls8 = m2.NID_wap_wsg_idm_ecid_wtls8
+NID_wap_wsg_idm_ecid_wtls9 = m2.NID_wap_wsg_idm_ecid_wtls9
+NID_wap_wsg_idm_ecid_wtls10 = m2.NID_wap_wsg_idm_ecid_wtls10
+NID_wap_wsg_idm_ecid_wtls11 = m2.NID_wap_wsg_idm_ecid_wtls11
+NID_wap_wsg_idm_ecid_wtls12 = m2.NID_wap_wsg_idm_ecid_wtls12
+
+# The following two curves, according to OpenSSL, have a
+# "Questionable extension field!" and are not supported by
+# the OpenSSL inverse function. ECError: no inverse.
+# As such they cannot be used for signing. They might,
+# however, be usable for encryption but that has not
+# been tested. Until thir usefulness can be established,
+# they are not supported at this time.
+# NID_ipsec3 = m2.NID_ipsec3
+# NID_ipsec4 = m2.NID_ipsec4
+
+
+class EC:
+
+ """
+ Object interface to a EC key pair.
+ """
+
+ m2_ec_key_free = m2.ec_key_free
+
+ def __init__(self, ec, _pyfree=0):
+ assert m2.ec_key_type_check(ec), "'ec' type error"
+ self.ec = ec
+ self._pyfree = _pyfree
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_ec_key_free(self.ec)
+
+ def __len__(self):
+ assert m2.ec_key_type_check(self.ec), "'ec' type error"
+ return m2.ec_key_keylen(self.ec)
+
+ def gen_key(self):
+ """
+ Generates the key pair from its parameters. Use::
+ keypair = EC.gen_params(curve)
+ keypair.gen_key()
+ to create an EC key pair.
+ """
+ assert m2.ec_key_type_check(self.ec), "'ec' type error"
+ m2.ec_key_gen_key(self.ec)
+
+ def pub(self):
+ # Don't let python free
+ return EC_pub(self.ec, 0)
+
+ def sign_dsa(self, digest):
+ """
+ Sign the given digest using ECDSA. Returns a tuple (r,s), the two
+ ECDSA signature parameters.
+ """
+ assert self._check_key_type(), "'ec' type error"
+ return m2.ecdsa_sign(self.ec, digest)
+
+ def verify_dsa(self, digest, r, s):
+ """
+ Verify the given digest using ECDSA. r and s are the ECDSA
+ signature parameters.
+ """
+ assert self._check_key_type(), "'ec' type error"
+ return m2.ecdsa_verify(self.ec, digest, r, s)
+
+ def sign_dsa_asn1(self, digest):
+ assert self._check_key_type(), "'ec' type error"
+ return m2.ecdsa_sign_asn1(self.ec, digest)
+
+ def verify_dsa_asn1(self, digest, blob):
+ assert self._check_key_type(), "'ec' type error"
+ return m2.ecdsa_verify_asn1(self.ec, digest, blob)
+
+ def compute_dh_key(self,pub_key):
+ """
+ Compute the ECDH shared key of this key pair and the given public
+ key object. They must both use the same curve. Returns the
+ shared key in binary as a buffer object. No Key Derivation Function is
+ applied.
+ """
+ assert self.check_key(), 'key is not initialised'
+ return m2.ecdh_compute_key(self.ec, pub_key.ec)
+
+ def save_key_bio(self, bio, cipher='aes_128_cbc', callback=util.passphrase_callback):
+ """
+ Save the key pair to an M2Crypto.BIO.BIO object in PEM format.
+
+ @type bio: M2Crypto.BIO.BIO
+ @param bio: M2Crypto.BIO.BIO object to save key to.
+
+ @type cipher: string
+ @param cipher: Symmetric cipher to protect the key. The default
+ cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+ in the clear.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ to acquire a passphrase with which to protect the key.
+ The default is util.passphrase_callback.
+ """
+ if cipher is None:
+ return m2.ec_key_write_bio_no_cipher(self.ec, bio._ptr(), callback)
+ else:
+ ciph = getattr(m2, cipher, None)
+ if ciph is None:
+ raise ValueError('not such cipher %s' % cipher)
+ return m2.ec_key_write_bio(self.ec, bio._ptr(), ciph(), callback)
+
+ def save_key(self, file, cipher='aes_128_cbc', callback=util.passphrase_callback):
+ """
+ Save the key pair to a file in PEM format.
+
+ @type file: string
+ @param file: Name of file to save key to.
+
+ @type cipher: string
+ @param cipher: Symmetric cipher to protect the key. The default
+ cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+ in the clear.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ to acquire a passphrase with which to protect the key.
+ The default is util.passphrase_callback.
+ """
+ bio = BIO.openfile(file, 'wb')
+ return self.save_key_bio(bio, cipher, callback)
+
+ def save_pub_key_bio(self, bio):
+ """
+ Save the public key to an M2Crypto.BIO.BIO object in PEM format.
+
+ @type bio: M2Crypto.BIO.BIO
+ @param bio: M2Crypto.BIO.BIO object to save key to.
+ """
+ return m2.ec_key_write_pubkey(self.ec, bio._ptr())
+
+ def save_pub_key(self, file):
+ """
+ Save the public key to a file in PEM format.
+
+ @type file: string
+ @param file: Name of file to save key to.
+ """
+ bio = BIO.openfile(file, 'wb')
+ return m2.ec_key_write_pubkey(self.ec, bio._ptr())
+
+ def _check_key_type(self):
+ return m2.ec_key_type_check(self.ec)
+
+ def check_key(self):
+ assert m2.ec_key_type_check(self.ec), "'ec' type error"
+ return m2.ec_key_check_key(self.ec)
+
+
+class EC_pub(EC):
+
+ """
+ Object interface to an EC public key.
+ ((don't like this implementation inheritance))
+ """
+ def __init__(self,ec,_pyfree=0):
+ EC.__init__(self,ec,_pyfree)
+ self.der = None
+
+ def get_der(self):
+ """
+ Returns the public key in DER format as a buffer object.
+ """
+ assert self.check_key(), 'key is not initialised'
+ if self.der is None:
+ self.der = m2.ec_key_get_public_der(self.ec)
+ return self.der
+
+ save_key = EC.save_pub_key
+
+ save_key_bio = EC.save_pub_key_bio
+
+
+def gen_params(curve):
+ """
+ Factory function that generates EC parameters and
+ instantiates a EC object from the output.
+
+ @param curve: This is the OpenSSL nid of the curve to use.
+ """
+ return EC(m2.ec_key_new_by_curve_name(curve), 1)
+
+
+def load_key(file, callback=util.passphrase_callback):
+ """
+ Factory function that instantiates a EC object.
+
+ @param file: Names the file that contains the PEM representation
+ of the EC key pair.
+
+ @param callback: Python callback object that will be invoked
+ if the EC key pair is passphrase-protected.
+ """
+ bio = BIO.openfile(file)
+ return load_key_bio(bio, callback)
+
+
+def load_key_bio(bio, callback=util.passphrase_callback):
+ """
+ Factory function that instantiates a EC object.
+
+ @param bio: M2Crypto.BIO object that contains the PEM
+ representation of the EC key pair.
+
+ @param callback: Python callback object that will be invoked
+ if the EC key pair is passphrase-protected.
+ """
+ return EC(m2.ec_key_read_bio(bio._ptr(), callback), 1)
+
+def load_pub_key(file):
+ """
+ Load an EC public key from file.
+
+ @type file: string
+ @param file: Name of file containing EC public key in PEM format.
+
+ @rtype: M2Crypto.EC.EC_pub
+ @return: M2Crypto.EC.EC_pub object.
+ """
+ bio = BIO.openfile(file)
+ return load_pub_key_bio(bio)
+
+
+def load_pub_key_bio(bio):
+ """
+ Load an EC public key from an M2Crypto.BIO.BIO object.
+
+ @type bio: M2Crypto.BIO.BIO
+ @param bio: M2Crypto.BIO.BIO object containing EC public key in PEM
+ format.
+
+ @rtype: M2Crypto.EC.EC_pub
+ @return: M2Crypto.EC.EC_pub object.
+ """
+ ec = m2.ec_key_read_pubkey(bio._ptr())
+ if ec is None:
+ ec_error()
+ return EC_pub(ec, 1)
+
+def ec_error():
+ raise ECError, m2.err_reason_error_string(m2.err_get_error())
+
+def pub_key_from_der(der):
+ """
+ Create EC_pub from DER.
+ """
+ return EC_pub(m2.ec_key_from_pubkey_der(der), 1)
diff --git a/M2Crypto/EVP.py b/M2Crypto/EVP.py
new file mode 100644
index 0000000..cb92380
--- /dev/null
+++ b/M2Crypto/EVP.py
@@ -0,0 +1,408 @@
+"""M2Crypto wrapper for OpenSSL EVP API.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions Copyright (c) 2004-2007 Open Source Applications Foundation.
+Author: Heikki Toivonen
+"""
+
+from M2Crypto import Err, util, BIO, RSA
+import m2
+
+class EVPError(Exception): pass
+
+m2.evp_init(EVPError)
+
+
+def pbkdf2(password, salt, iter, keylen):
+ """
+ Derive a key from password using PBKDF2 algorithm specified in RFC 2898.
+
+ @param password: Derive the key from this password.
+ @type password: str
+ @param salt: Salt.
+ @type salt: str
+ @param iter: Number of iterations to perform.
+ @type iter: int
+ @param keylen: Length of key to produce.
+ @type keylen: int
+ @return: Key.
+ @rtype: str
+ """
+ return m2.pkcs5_pbkdf2_hmac_sha1(password, salt, iter, keylen)
+
+class MessageDigest:
+ """
+ Message Digest
+ """
+ m2_md_ctx_free = m2.md_ctx_free
+
+ def __init__(self, algo):
+ md = getattr(m2, algo, None)
+ if md is None:
+ raise ValueError, ('unknown algorithm', algo)
+ self.md=md()
+ self.ctx=m2.md_ctx_new()
+ m2.digest_init(self.ctx, self.md)
+
+ def __del__(self):
+ if getattr(self, 'ctx', None):
+ self.m2_md_ctx_free(self.ctx)
+
+ def update(self, data):
+ """
+ Add data to be digested.
+
+ @return: -1 for Python error, 1 for success, 0 for OpenSSL failure.
+ """
+ return m2.digest_update(self.ctx, data)
+
+ def final(self):
+ return m2.digest_final(self.ctx)
+
+ # Deprecated.
+ digest = final
+
+
+class HMAC:
+
+ m2_hmac_ctx_free = m2.hmac_ctx_free
+
+ def __init__(self, key, algo='sha1'):
+ md = getattr(m2, algo, None)
+ if md is None:
+ raise ValueError, ('unknown algorithm', algo)
+ self.md=md()
+ self.ctx=m2.hmac_ctx_new()
+ m2.hmac_init(self.ctx, key, self.md)
+
+ def __del__(self):
+ if getattr(self, 'ctx', None):
+ self.m2_hmac_ctx_free(self.ctx)
+
+ def reset(self, key):
+ m2.hmac_init(self.ctx, key, self.md)
+
+ def update(self, data):
+ m2.hmac_update(self.ctx, data)
+
+ def final(self):
+ return m2.hmac_final(self.ctx)
+
+ digest=final
+
+def hmac(key, data, algo='sha1'):
+ md = getattr(m2, algo, None)
+ if md is None:
+ raise ValueError, ('unknown algorithm', algo)
+ return m2.hmac(key, data, md())
+
+
+class Cipher:
+
+ m2_cipher_ctx_free = m2.cipher_ctx_free
+
+ def __init__(self, alg, key, iv, op, key_as_bytes=0, d='md5', salt='12345678', i=1, padding=1):
+ cipher = getattr(m2, alg, None)
+ if cipher is None:
+ raise ValueError, ('unknown cipher', alg)
+ self.cipher=cipher()
+ if key_as_bytes:
+ kmd = getattr(m2, d, None)
+ if kmd is None:
+ raise ValueError, ('unknown message digest', d)
+ key = m2.bytes_to_key(self.cipher, kmd(), key, salt, iv, i)
+ self.ctx=m2.cipher_ctx_new()
+ m2.cipher_init(self.ctx, self.cipher, key, iv, op)
+ self.set_padding(padding)
+ del key
+
+ def __del__(self):
+ if getattr(self, 'ctx', None):
+ self.m2_cipher_ctx_free(self.ctx)
+
+ def update(self, data):
+ return m2.cipher_update(self.ctx, data)
+
+ def final(self):
+ return m2.cipher_final(self.ctx)
+
+ def set_padding(self, padding=1):
+ return m2.cipher_set_padding(self.ctx, padding)
+
+
+class PKey:
+ """
+ Public Key
+ """
+
+ m2_pkey_free = m2.pkey_free
+ m2_md_ctx_free = m2.md_ctx_free
+
+ def __init__(self, pkey=None, _pyfree=0, md='sha1'):
+ if pkey is not None:
+ self.pkey = pkey
+ self._pyfree = _pyfree
+ else:
+ self.pkey = m2.pkey_new()
+ self._pyfree = 1
+ self._set_context(md)
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_pkey_free(self.pkey)
+ if getattr(self, 'ctx', None):
+ self.m2_md_ctx_free(self.ctx)
+
+ def _ptr(self):
+ return self.pkey
+
+ def _set_context(self, md):
+ mda = getattr(m2, md, None)
+ if mda is None:
+ raise ValueError, ('unknown message digest', md)
+ self.md = mda()
+ self.ctx = m2.md_ctx_new()
+
+ def reset_context(self, md='sha1'):
+ """
+ Reset internal message digest context.
+
+ @type md: string
+ @param md: The message digest algorithm.
+ """
+ self._set_context(md)
+
+ def sign_init(self):
+ """
+ Initialise signing operation with self.
+ """
+ m2.sign_init(self.ctx, self.md)
+
+ def sign_update(self, data):
+ """
+ Feed data to signing operation.
+
+ @type data: string
+ @param data: Data to be signed.
+ """
+ m2.sign_update(self.ctx, data)
+
+ def sign_final(self):
+ """
+ Return signature.
+
+ @rtype: string
+ @return: The signature.
+ """
+ return m2.sign_final(self.ctx, self.pkey)
+
+ # Deprecated
+ update = sign_update
+ final = sign_final
+
+ def verify_init(self):
+ """
+ Initialise signature verification operation with self.
+ """
+ m2.verify_init(self.ctx, self.md)
+
+ def verify_update(self, data):
+ """
+ Feed data to verification operation.
+
+ @type data: string
+ @param data: Data to be verified.
+ @return: -1 on Python error, 1 for success, 0 for OpenSSL error
+ """
+ return m2.verify_update(self.ctx, data)
+
+ def verify_final(self, sign):
+ """
+ Return result of verification.
+
+ @param sign: Signature to use for verification
+ @rtype: int
+ @return: Result of verification: 1 for success, 0 for failure, -1 on
+ other error.
+ """
+ return m2.verify_final(self.ctx, sign, self.pkey)
+
+ def assign_rsa(self, rsa, capture=1):
+ """
+ Assign the RSA key pair to self.
+
+ @type rsa: M2Crypto.RSA.RSA
+ @param rsa: M2Crypto.RSA.RSA object to be assigned to self.
+
+ @type capture: boolean
+ @param capture: If true (default), this PKey object will own the RSA
+ object, meaning that once the PKey object gets
+ deleted it is no longer safe to use the RSA object.
+
+ @rtype: int
+ @return: Return 1 for success and 0 for failure.
+ """
+ if capture:
+ ret = m2.pkey_assign_rsa(self.pkey, rsa.rsa)
+ if ret:
+ rsa._pyfree = 0
+ else:
+ ret = m2.pkey_set1_rsa(self.pkey, rsa.rsa)
+ return ret
+
+ def get_rsa(self):
+ """
+ Return the underlying RSA key if that is what the EVP
+ instance is holding.
+ """
+ rsa_ptr = m2.pkey_get1_rsa(self.pkey)
+ if rsa_ptr is None:
+ raise ValueError("PKey instance is not holding a RSA key")
+
+ rsa = RSA.RSA_pub(rsa_ptr, 1)
+ return rsa
+
+ def save_key(self, file, cipher='aes_128_cbc', callback=util.passphrase_callback):
+ """
+ Save the key pair to a file in PEM format.
+
+ @type file: string
+ @param file: Name of file to save key to.
+
+ @type cipher: string
+ @param cipher: Symmetric cipher to protect the key. The default
+ cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+ in the clear.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ to acquire a passphrase with which to protect the key.
+ The default is util.passphrase_callback.
+ """
+ bio = BIO.openfile(file, 'wb')
+ return self.save_key_bio(bio, cipher, callback)
+
+ def save_key_bio(self, bio, cipher='aes_128_cbc', callback=util.passphrase_callback):
+ """
+ Save the key pair to the M2Crypto.BIO object 'bio' in PEM format.
+
+ @type bio: M2Crypto.BIO
+ @param bio: M2Crypto.BIO object to save key to.
+
+ @type cipher: string
+ @param cipher: Symmetric cipher to protect the key. The default
+ cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+ in the clear.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ to acquire a passphrase with which to protect the key.
+ The default is util.passphrase_callback.
+ """
+ if cipher is None:
+ return m2.pkey_write_pem_no_cipher(self.pkey, bio._ptr(), callback)
+ else:
+ proto = getattr(m2, cipher, None)
+ if proto is None:
+ raise ValueError, 'no such cipher %s' % cipher
+ return m2.pkey_write_pem(self.pkey, bio._ptr(), proto(), callback)
+
+ def as_pem(self, cipher='aes_128_cbc', callback=util.passphrase_callback):
+ """
+ Return key in PEM format in a string.
+
+ @type cipher: string
+ @param cipher: Symmetric cipher to protect the key. The default
+ cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+ in the clear.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ to acquire a passphrase with which to protect the key.
+ The default is util.passphrase_callback.
+ """
+ bio = BIO.MemoryBuffer()
+ self.save_key_bio(bio, cipher, callback)
+ return bio.read_all()
+
+ def as_der(self):
+ """
+ Return key in DER format in a string
+ """
+ buf = m2.pkey_as_der(self.pkey)
+ bio = BIO.MemoryBuffer(buf)
+ return bio.read_all()
+
+ def size(self):
+ """
+ Return the size of the key in bytes.
+ """
+ return m2.pkey_size(self.pkey)
+
+ def get_modulus(self):
+ """
+ Return the modulus in hex format.
+ """
+ return m2.pkey_get_modulus(self.pkey)
+
+
+def load_key(file, callback=util.passphrase_callback):
+ """
+ Load an M2Crypto.EVP.PKey from file.
+
+ @type file: string
+ @param file: Name of file containing the key in PEM format.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ to acquire a passphrase with which to protect the key.
+
+ @rtype: M2Crypto.EVP.PKey
+ @return: M2Crypto.EVP.PKey object.
+ """
+ bio = m2.bio_new_file(file, 'r')
+ if bio is None:
+ raise BIO.BIOError(Err.get_error())
+ cptr = m2.pkey_read_pem(bio, callback)
+ m2.bio_free(bio)
+ if cptr is None:
+ raise EVPError(Err.get_error())
+ return PKey(cptr, 1)
+
+def load_key_bio(bio, callback=util.passphrase_callback):
+ """
+ Load an M2Crypto.EVP.PKey from an M2Crypto.BIO object.
+
+ @type bio: M2Crypto.BIO
+ @param bio: M2Crypto.BIO object containing the key in PEM format.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ to acquire a passphrase with which to protect the key.
+
+ @rtype: M2Crypto.EVP.PKey
+ @return: M2Crypto.EVP.PKey object.
+ """
+ cptr = m2.pkey_read_pem(bio._ptr(), callback)
+ if cptr is None:
+ raise EVPError(Err.get_error())
+ return PKey(cptr, 1)
+
+def load_key_string(string, callback=util.passphrase_callback):
+ """
+ Load an M2Crypto.EVP.PKey from a string.
+
+ @type string: string
+ @param string: String containing the key in PEM format.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ to acquire a passphrase with which to protect the key.
+
+ @rtype: M2Crypto.EVP.PKey
+ @return: M2Crypto.EVP.PKey object.
+ """
+ bio = BIO.MemoryBuffer(string)
+ return load_key_bio( bio, callback)
+
diff --git a/M2Crypto/Engine.py b/M2Crypto/Engine.py
new file mode 100644
index 0000000..d6b879b
--- /dev/null
+++ b/M2Crypto/Engine.py
@@ -0,0 +1,119 @@
+# vim: sts=4 sw=4 et
+"""
+M2Crypto wrapper for OpenSSL ENGINE API.
+
+Pavel Shramov
+IMEC MSU
+"""
+
+from M2Crypto import m2, EVP, X509, Err
+
+class EngineError(Exception): pass
+
+m2.engine_init_error(EngineError)
+
+class Engine:
+ """Wrapper for ENGINE object."""
+
+ m2_engine_free = m2.engine_free
+
+ def __init__(self, id = None, _ptr = None, _pyfree = 1):
+ """Create new Engine from ENGINE pointer or obtain by id"""
+ if not _ptr and not id:
+ raise ValueError("No engine id specified")
+ self._ptr = _ptr
+ if not self._ptr:
+ self._ptr = m2.engine_by_id(id)
+ if not self._ptr:
+ raise ValueError("Unknown engine: %s" % id)
+ self._pyfree = _pyfree
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_engine_free(self._ptr)
+
+ def init(self):
+ """Obtain a functional reference to the engine.
+
+ @return: 0 on error, non-zero on success."""
+ return m2.engine_init(self._ptr)
+
+ def finish(self):
+ """Release a functional and structural reference to the engine."""
+ return m2.engine_finish(self._ptr)
+
+ def ctrl_cmd_string(self, cmd, arg, optional = 0):
+ """Call ENGINE_ctrl_cmd_string"""
+ if not m2.engine_ctrl_cmd_string(self._ptr, cmd, arg, optional):
+ raise EngineError(Err.get_error())
+
+ def get_name(self):
+ """Return engine name"""
+ return m2.engine_get_name(self._ptr)
+
+ def get_id(self):
+ """Return engine id"""
+ return m2.engine_get_id(self._ptr)
+
+ def set_default(self, methods = m2.ENGINE_METHOD_ALL):
+ """Use this engine as default for methods specified in argument
+ Possible values are bitwise OR of m2.ENGINE_METHOD_*"""
+ return m2.engine_set_default(self._ptr, methods)
+
+ def _engine_load_key(self, func, name, pin = None):
+ """Helper function for loading keys"""
+ ui = m2.ui_openssl()
+ cbd = m2.engine_pkcs11_data_new(pin)
+ try:
+ kptr = func(self._ptr, name, ui, cbd)
+ if not kptr:
+ raise EngineError(Err.get_error())
+ key = EVP.PKey(kptr, _pyfree = 1)
+ finally:
+ m2.engine_pkcs11_data_free(cbd)
+ return key
+
+ def load_private_key(self, name, pin = None):
+ """Load private key with engine methods (e.g from smartcard).
+ If pin is not set it will be asked
+ """
+ return self._engine_load_key(m2.engine_load_private_key, name, pin)
+
+ def load_public_key(self, name, pin = None):
+ """Load public key with engine methods (e.g from smartcard)."""
+ return self._engine_load_key(m2.engine_load_public_key, name, pin)
+
+ def load_certificate(self, name):
+ """Load certificate from engine (e.g from smartcard).
+ NOTE: This function may be not implemented by engine!"""
+ cptr = m2.engine_load_certificate(self._ptr, name)
+ if not cptr:
+ raise EngineError("Certificate or card not found")
+ return X509.X509(cptr, _pyfree = 1)
+
+
+def load_dynamic_engine(id, sopath):
+ """Load and return dymanic engine from sopath and assign id to it"""
+ m2.engine_load_dynamic()
+ e = Engine('dynamic')
+ e.ctrl_cmd_string("SO_PATH", sopath)
+ e.ctrl_cmd_string("ID", id)
+ e.ctrl_cmd_string("LIST_ADD", "1")
+ e.ctrl_cmd_string("LOAD", None)
+ return e
+
+
+def load_dynamic():
+ """Load dynamic engine"""
+ m2.engine_load_dynamic()
+
+
+def load_openssl():
+ """Load openssl engine"""
+ m2.engine_load_openssl()
+
+
+def cleanup():
+ """If you load any engines, you need to clean up after your application
+ is finished with the engines."""
+ m2.engine_cleanup()
diff --git a/M2Crypto/Err.py b/M2Crypto/Err.py
new file mode 100644
index 0000000..3588f76
--- /dev/null
+++ b/M2Crypto/Err.py
@@ -0,0 +1,49 @@
+"""M2Crypto wrapper for OpenSSL Error API.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import BIO
+import m2
+
+def get_error():
+ err=BIO.MemoryBuffer()
+ m2.err_print_errors(err.bio_ptr())
+ return err.getvalue()
+
+def get_error_code():
+ return m2.err_get_error()
+
+def peek_error_code():
+ return m2.err_peek_error()
+
+def get_error_lib(err):
+ return m2.err_lib_error_string(err)
+
+def get_error_func(err):
+ return m2.err_func_error_string(err)
+
+def get_error_reason(err):
+ return m2.err_reason_error_string(err)
+
+def get_x509_verify_error(err):
+ return m2.x509_get_verify_error(err)
+
+class SSLError(Exception):
+ def __init__(self, err, client_addr):
+ self.err = err
+ self.client_addr = client_addr
+
+ def __str__(self):
+ if (isinstance(self.client_addr, unicode)):
+ s = self.client_addr.encode('utf8')
+ else:
+ s = self.client_addr
+ return "%s: %s: %s" % \
+ (m2.err_func_error_string(self.err), \
+ s, \
+ m2.err_reason_error_string(self.err))
+
+class M2CryptoError(Exception):
+ pass
+
+
diff --git a/M2Crypto/PGP/PublicKey.py b/M2Crypto/PGP/PublicKey.py
new file mode 100644
index 0000000..f892ade
--- /dev/null
+++ b/M2Crypto/PGP/PublicKey.py
@@ -0,0 +1,58 @@
+"""M2Crypto PGP2.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from constants import *
+from packet import *
+import RSA
+
+class PublicKey:
+ def __init__(self, pubkey_pkt):
+ import warnings
+ warnings.warn('Deprecated. No maintainer for PGP. If you use this, please inform M2Crypto maintainer.', DeprecationWarning)
+
+ self._pubkey_pkt = pubkey_pkt
+ self._pubkey = RSA.new_pub_key((pubkey_pkt._e, pubkey_pkt._n))
+ self._userid = {}
+ self._signature = {}
+
+ def keyid(self):
+ return self._pubkey.n[-8:]
+
+ def add_userid(self, u_pkt):
+ assert isinstance(u_pkt, userid_packet)
+ self._userid[u_pkt.userid()] = u_pkt
+
+ def remove_userid(self, userid):
+ del self._userid[userid]
+
+ def add_signature(self, userid, s_pkt):
+ assert isinstance(s_pkt, signature_packet)
+ assert self._userid.has_key(userid)
+ if self._signature.has_key(userid):
+ self._signature.append(s_pkt)
+ else:
+ self._signature = [s_pkt]
+
+ def __getitem__(self, id):
+ return self._userid[id]
+
+ def __setitem__(self, *args):
+ raise NotImplementedError
+
+ def __delitem__(self, id):
+ del self._userid[id]
+ if self._signature[id]:
+ del self._signature[id]
+
+ def write(self, stream):
+ pass
+
+ def encrypt(self, ptxt):
+ # XXX Munge ptxt into pgp format.
+ return self._pubkey.public_encrypt(ptxt, RSA.pkcs1_padding)
+
+ def decrypt(self, ctxt):
+ # XXX Munge ctxt into pgp format.
+ return self._pubkey.public_encrypt(ctxt, RSA.pkcs1_padding)
+
diff --git a/M2Crypto/PGP/PublicKeyRing.py b/M2Crypto/PGP/PublicKeyRing.py
new file mode 100644
index 0000000..a8447b3
--- /dev/null
+++ b/M2Crypto/PGP/PublicKeyRing.py
@@ -0,0 +1,81 @@
+"""M2Crypto PGP2.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from constants import *
+from packet import *
+from PublicKey import *
+
+class PublicKeyRing:
+ def __init__(self, keyring):
+ import warnings
+ warnings.warn('Deprecated. No maintainer for PGP. If you use this, please inform M2Crypto maintainer.', DeprecationWarning)
+
+ self._keyring = keyring
+ self._userid = {}
+ self._keyid = {}
+ self._spurious = []
+ self._pubkey = []
+
+ def load(self):
+ curr_pub = None
+ curr_index = -1
+
+ ps = packet_stream(self._keyring)
+ while 1:
+ pkt = ps.read()
+
+ if pkt is None:
+ break
+
+ elif isinstance(pkt, public_key_packet):
+ curr_index = curr_index + 1
+ curr_pub = PublicKey(pkt)
+ self._pubkey.append(curr_pub)
+ #self._keyid[curr_pub.keyid()] = (curr_pub, curr_index)
+
+ elif isinstance(pkt, userid_packet):
+ if curr_pub is None:
+ self._spurious.append(pkt)
+ else:
+ curr_pub.add_userid(pkt)
+ self._userid[pkt.userid()] = (curr_pub, curr_index)
+
+ elif isinstance(pkt, signature_packet):
+ if curr_pub is None:
+ self._spurious.append(pkt)
+ else:
+ curr_pub.add_signature(pkt)
+
+ else:
+ self._spurious.append(pkt)
+
+ ps.close()
+
+ def __getitem__(self, id):
+ return self._userid[id][0]
+
+ def __setitem__(self, *args):
+ raise NotImplementedError
+
+ def __delitem__(self, id):
+ pkt, idx = self._userid[id]
+ del self._pubkey[idx]
+ del self._userid[idx]
+ pkt, idx = self._keyid[id]
+ del self._keyid[idx]
+
+ def spurious(self):
+ return tuple(self._spurious)
+
+ def save(self, keyring):
+ for p in self._pubkey:
+ pp = p.pack()
+ keyring.write(pp)
+
+
+def load_pubring(filename='pubring.pgp'):
+ pkr = PublicKeyRing(open(filename, 'rb'))
+ pkr.load()
+ return pkr
+
diff --git a/M2Crypto/PGP/RSA.py b/M2Crypto/PGP/RSA.py
new file mode 100644
index 0000000..153193d
--- /dev/null
+++ b/M2Crypto/PGP/RSA.py
@@ -0,0 +1,36 @@
+"""M2Crypto PGP2 RSA.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import sys
+from M2Crypto import m2, RSA
+_RSA = RSA
+del RSA
+
+
+class RSA(_RSA.RSA):
+ pass
+
+
+class RSA_pub(_RSA.RSA_pub):
+ pass
+
+
+def new_pub_key((e, n)):
+ """
+ Factory function that instantiates an RSA_pub object from a (e, n) tuple.
+
+ 'e' is the RSA public exponent; it is a string in OpenSSL's binary format,
+ i.e., a number of bytes in big-endian.
+
+ 'n' is the RSA composite of primes; it is a string in OpenSSL's binary format,
+ i.e., a number of bytes in big-endian.
+ """
+ import warnings
+ warnings.warn('Deprecated. No maintainer for PGP. If you use this, please inform M2Crypto maintainer.', DeprecationWarning)
+
+ rsa = m2.rsa_new()
+ m2.rsa_set_e_bin(rsa, e)
+ m2.rsa_set_n_bin(rsa, n)
+ return RSA_pub(rsa, 1)
+
diff --git a/M2Crypto/PGP/__init__.py b/M2Crypto/PGP/__init__.py
new file mode 100644
index 0000000..27fd669
--- /dev/null
+++ b/M2Crypto/PGP/__init__.py
@@ -0,0 +1,14 @@
+"""M2Crypto PGP2.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from constants import *
+
+from packet import public_key_packet, trust_packet, userid_packet,\
+ comment_packet, signature_packet, private_key_packet, cke_packet,\
+ pke_packet, literal_packet, packet_stream
+
+from PublicKey import *
+from PublicKeyRing import *
+
+
diff --git a/M2Crypto/PGP/constants.py b/M2Crypto/PGP/constants.py
new file mode 100644
index 0000000..9a7810f
--- /dev/null
+++ b/M2Crypto/PGP/constants.py
@@ -0,0 +1,19 @@
+"""M2Crypto PGP2.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+CTB_TAG = 128
+
+CTB_PKE = 1
+CTB_SIGNATURE = 2
+CTB_MESSAGE_DIGETS = 3
+CTB_PRIVATE_KEY = 5
+CTB_PUBLIC_KEY = 6
+CTB_COMPRESSED_DATA = 8
+CTB_CKE = 9
+CTB_LITERAL_DATA = 11
+CTB_TRUST = 12
+CTB_USERID = 13
+CTB_COMMENT = 14
+
+
diff --git a/M2Crypto/PGP/packet.py b/M2Crypto/PGP/packet.py
new file mode 100644
index 0000000..d662c84
--- /dev/null
+++ b/M2Crypto/PGP/packet.py
@@ -0,0 +1,379 @@
+"""M2Crypto PGP2.
+
+This module implements PGP packets per RFC1991 and various source distributions.
+
+Each packet type is represented by a class; packet classes derive from
+the abstract 'packet' class.
+
+The 'message digest' packet type, mentioned but not documented in RFC1991,
+is not implemented.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# XXX Work-in-progress.
+
+# Be liberal in what you accept.
+# Be conservative in what you send.
+# Be lazy in what you eval.
+
+import struct, time
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+from M2Crypto import EVP, RSA
+from M2Crypto.util import octx_to_num
+
+from constants import *
+
+_OK_VERSION = ('\002', '\003')
+_OK_VALIDITY = ('\000',)
+_OK_PKC = ('\001',)
+
+
+class packet:
+ def __init__(self, ctb, body=None):
+ import warnings
+ warnings.warn('Deprecated. No maintainer for PGP. If you use this, please inform M2Crypto maintainer.', DeprecationWarning)
+
+ self.ctb = ctb
+ if body is not None:
+ self.body = StringIO(body)
+ else:
+ self.body = None
+
+ def validate(self):
+ return 1
+
+ def pack(self):
+ raise NotImplementedError, '%s.pack(): abstract method' % (self.__class__,)
+
+ def version(self):
+ if hasattr(self, '_version'):
+ return ord(self._version)
+ else:
+ return None
+
+ def timestamp(self):
+ if hasattr(self, '_timestamp'):
+ return struct.unpack('>L', self._timestamp)[0]
+ else:
+ return None
+
+ def validity(self):
+ if hasattr(self, '_validity'):
+ return struct.unpack('>H', self._validity)[0]
+ else:
+ return None
+
+ def pkc(self):
+ if hasattr(self, '_pkc'):
+ return self._pkc
+ else:
+ return None
+
+ def _llf(self, lenf):
+ if lenf < 256:
+ return (0, chr(lenf))
+ elif lenf < 65536:
+ return (1, struct.pack('>H', lenf))
+ else:
+ assert lenf < 2L**32
+ return (2, struct.pack('>L', lenf))
+
+ def _ctb(self, llf):
+ ctbv = _FACTORY[self.__class__]
+ return chr((1 << 7) | (ctbv << 2) | llf)
+
+
+class public_key_packet(packet):
+ def __init__(self, ctb, body=None):
+ packet.__init__(self, ctb, body)
+ if self.body is not None:
+ self._version = self.body.read(1)
+ self._timestamp = self.body.read(4)
+ self._validity = self.body.read(2)
+ self._pkc = self.body.read(1)
+
+ self._nlen = self.body.read(2)
+ nlen = (struct.unpack('>H', self._nlen)[0] + 7) / 8
+ self._n = self.body.read(nlen)
+
+ self._elen = self.body.read(2)
+ elen = (struct.unpack('>H', self._elen)[0] + 7) / 8
+ self._e = self.body.read(elen)
+
+ def pack(self):
+ if self.body is None:
+ self.body = StringIO()
+ self.body.write(self._version)
+ self.body.write(self._timestamp)
+ self.body.write(self._validity)
+ self.body.write(self._pkc)
+ self.body.write(self._nlen)
+ self.body.write(self._n)
+ self.body.write(self._elen)
+ self.body.write(self._e)
+ self.body = self.body.getvalue()
+ llf, lenf = self._llf(len(self.body))
+ ctb = self._ctb(llf)
+ return '%s%s%s' % (ctb, lenf, self.body)
+
+ def pubkey(self):
+ return self._pubkey.pub()
+
+
+class trust_packet(packet):
+ # This implementation neither interprets nor emits trust packets.
+ def __init__(self, ctb, body=None):
+ packet.__init__(self, ctb, body)
+ if body is not None:
+ self.trust = self.body.read(1)
+
+
+class userid_packet(packet):
+ def __init__(self, ctb, body=None):
+ packet.__init__(self, ctb, body)
+ if body is not None:
+ self._userid = body
+
+ def pack(self):
+ if self.body is None:
+ self.body = StringIO()
+ self.body.write(chr(len(self._userid)))
+ self.body.write(self._userid)
+ self.body = self.body.getvalue()
+ return self.ctb + self.body
+
+ def userid(self):
+ return self._userid
+
+
+class comment_packet(packet):
+ def __init__(self, ctb, body=None):
+ packet.__init__(self, ctb, body)
+ if body is not None:
+ self.comment = self.body.getvalue()
+
+ def pack(self):
+ if self.body is None:
+ self.body = StringIO()
+ self.body.write(chr(len(self.comment)))
+ self.body.write(self.comment)
+ self.body = self.body.getvalue()
+ return self.ctb + self.body
+
+
+class signature_packet(packet):
+ def __init__(self, ctb, body=None):
+ packet.__init__(self, ctb, body)
+ if body is not None:
+ self._version = self.body.read(1)
+ self._len_md_stuff = self.body.read(1)
+ self._classification = self.body.read(1)
+ self._timestamp = self.body.read(4)
+ self._keyid = self.body.read(8)
+ self._pkc = self.body.read(1)
+ self._md_algo = self.body.read(1)
+ self._md_chksum = self.body.read(2)
+ self._sig = self.body.read()
+
+ def pack(self):
+ if self.body is None:
+ self.body = StringIO()
+ self.body.write(self._version)
+ self.body.write(self._len_md_stuff)
+ self.body.write(self._classification)
+ self.body.write(self._timestamp)
+ self.body.write(self._keyid)
+ self.body.write(self._pkc)
+ self.body.write(self._md_algo)
+ self.body.write(self._md_chksum)
+ self.body.write(self._sig)
+ self.body = self.body.getvalue()
+ llf, lenf = self._llf(len(body))
+ self.ctb = self.ctb | llf
+ return '%s%s%s' % (self.ctb, lenf, self.body)
+
+
+ def validate(self):
+ if self._version not in _OK_VERSION:
+ return None
+ if self._len_md_stuff != '\005':
+ return None
+
+
+class private_key_packet(packet):
+ def __init__(self, ctb, body=None):
+ packet.__init__(self, ctb, body)
+ if body is not None:
+ self._version = self.body.read(1)
+ self._timestamp = self.body.read(4)
+ self._validity = self.body.read(2)
+ self._pkc = self.body.read(1)
+
+ self._nlen = self.body.read(2)
+ nlen = (struct.unpack('>H', self._nlen)[0] + 7) / 8
+ self._n = self.body.read(nlen)
+
+ self._elen = self.body.read(2)
+ elen = (struct.unpack('>H', self._elen)[0] + 7) / 8
+ self._e = self.body.read(elen)
+
+ self._cipher = self.body.read(1)
+ if self._cipher == '\001':
+ self._iv = self.body.read(8)
+ else:
+ self._iv = None
+
+ for param in ['d', 'p', 'q', 'u']:
+ _plen = self.body.read(2)
+ setattr(self, '_'+param+'len', _plen)
+ plen = (struct.unpack('>H', _plen)[0] + 7) / 8
+ setattr(self, '_'+param, self.body.read(plen))
+
+ self._cksum = self.body.read(2)
+
+ def is_encrypted(self):
+ return ord(self._cipher)
+
+
+class cke_packet(packet):
+ def __init__(self, ctb, body=None):
+ packet.__init__(self, ctb, body)
+ if body is not None:
+ self._iv = self.body.read(8)
+ self._cksum = self.body.read(2)
+ self._ctxt = self.body.read()
+
+
+class pke_packet(packet):
+ def __init__(self, ctb, body=None):
+ packet.__init__(self, ctb, body)
+ if body is not None:
+ self._version = self.body.read(1)
+ self._keyid = self.body.read(8)
+ self._pkc = ord(self.body.read(1))
+
+ deklen = (struct.unpack('>H', self.body.read(2))[0] + 7 ) / 8
+ self._dek = octx_to_num(self.body.read(deklen))
+
+
+class literal_packet(packet):
+ def __init__(self, ctb, body=None):
+ packet.__init__(self, ctb, body)
+ if body is not None:
+ self.fmode = self.body.read(1)
+ fnlen = self.body.read(1)
+ self.fname = self.body.read(fnlen)
+ self.ftime = self.body.read(4)
+ #self.data = self.body.read()
+
+
+class compressed_packet(packet):
+ def __init__(self, ctb, stream):
+ packet.__init__(self, ctb, '')
+ if body is not None:
+ self.algo = stream.read(1)
+ # This reads the entire stream into memory.
+ self.data = stream.read()
+
+ def validate(self):
+ return (self.algo == '\001')
+
+ def uncompress(self):
+ import zlib
+ decomp = zlib.decompressobj(-13) # RFC 2440, pg 61.
+ # This doubles the memory usage.
+ stream = StringIO(decomp.decompress(self.data))
+ return stream
+
+
+_FACTORY = {
+ 1 : pke_packet,
+ 2 : signature_packet,
+ #3 : message_digest_packet, # XXX not implemented
+ 5 : private_key_packet,
+ 6 : public_key_packet,
+ #8 : compressed_packet, # special case
+ 9 : cke_packet,
+ 11 : literal_packet,
+ 12 : trust_packet,
+ 13 : userid_packet,
+ 14 : comment_packet,
+ pke_packet : 1,
+ signature_packet : 2,
+ #3 : message_digest_packet,
+ private_key_packet : 5,
+ public_key_packet : 6,
+ #8 : compressed_packet,
+ cke_packet : 9,
+ literal_packet : 11,
+ trust_packet : 12,
+ userid_packet : 13,
+ comment_packet : 14
+}
+
+
+class packet_stream:
+ def __init__(self, input):
+ self.stream = input
+ self.under_current = None
+ self._count = 0
+
+ def close(self):
+ self.stream.close()
+ if self.under_current is not None:
+ self.under_current.close()
+
+ def read(self, keep_trying=0):
+ while 1:
+ ctb0 = self.stream.read(1)
+ if not ctb0:
+ return None
+ ctb = ord(ctb0)
+ if is_ctb(ctb):
+ break
+ elif keep_trying:
+ continue
+ else:
+ raise XXXError
+ ctbt = (ctb & 0x3c) >> 2
+
+ if ctbt == CTB_COMPRESSED_DATA:
+ self.under_current = self.stream
+ cp = compressed_packet(ctb0, self.stream)
+ self.stream = cp.uncompress()
+ return self.read()
+
+ # Decode the length of following data. See RFC for details.
+ llf = ctb & 3
+ if llf == 0:
+ lenf = ord(self.stream.read(1))
+ elif llf == 1:
+ lenf = struct.unpack('>H', self.stream.read(2))[0]
+ elif llf == 2:
+ lenf = struct.unpack('>L', self.stream.read(4))[0]
+ else: # llf == 3
+ raise XXXError, 'impossible case'
+
+ body = self.stream.read(lenf)
+ if not body or (len(body) != lenf):
+ raise XXXError, 'corrupted packet'
+
+ self._count = self.stream.tell()
+ try:
+ return _FACTORY[ctbt](ctb0, body)
+ except KeyError:
+ return packet(ctb0, body)
+
+ def count(self):
+ return self._count
+
+def is_ctb(ctb):
+ return ctb & 0xc0
+
+def make_ctb(value, llf):
+ return chr((1 << 7) | (value << 2) | llf)
diff --git a/M2Crypto/RC4.py b/M2Crypto/RC4.py
new file mode 100644
index 0000000..1b5d408
--- /dev/null
+++ b/M2Crypto/RC4.py
@@ -0,0 +1,31 @@
+"""M2Crypto wrapper for OpenSSL RC4 API.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from m2 import rc4_new, rc4_free, rc4_set_key, rc4_update
+
+class RC4:
+
+ """Object interface to the stream cipher RC4."""
+
+ rc4_free = rc4_free
+
+ def __init__(self, key=None):
+ self.cipher = rc4_new()
+ if key:
+ rc4_set_key(self.cipher, key)
+
+ def __del__(self):
+ if getattr(self, 'cipher', None):
+ self.rc4_free(self.cipher)
+
+ def set_key(self, key):
+ rc4_set_key(self.cipher, key)
+
+ def update(self, data):
+ return rc4_update(self.cipher, data)
+
+ def final(self):
+ return ''
+
+
diff --git a/M2Crypto/RSA.py b/M2Crypto/RSA.py
new file mode 100644
index 0000000..2dff160
--- /dev/null
+++ b/M2Crypto/RSA.py
@@ -0,0 +1,448 @@
+"""M2Crypto wrapper for OpenSSL RSA API.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+import sys
+import util, BIO, Err, m2
+
+class RSAError(Exception): pass
+
+m2.rsa_init(RSAError)
+
+no_padding = m2.no_padding
+pkcs1_padding = m2.pkcs1_padding
+sslv23_padding = m2.sslv23_padding
+pkcs1_oaep_padding = m2.pkcs1_oaep_padding
+
+
+class RSA:
+ """
+ RSA Key Pair.
+ """
+
+ m2_rsa_free = m2.rsa_free
+
+ def __init__(self, rsa, _pyfree=0):
+ assert m2.rsa_type_check(rsa), "'rsa' type error"
+ self.rsa = rsa
+ self._pyfree = _pyfree
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_rsa_free(self.rsa)
+
+ def __len__(self):
+ return m2.rsa_size(self.rsa) << 3
+
+ def __getattr__(self, name):
+ if name == 'e':
+ return m2.rsa_get_e(self.rsa)
+ elif name == 'n':
+ return m2.rsa_get_n(self.rsa)
+ else:
+ raise AttributeError
+
+ def pub(self):
+ assert self.check_key(), 'key is not initialised'
+ return m2.rsa_get_e(self.rsa), m2.rsa_get_n(self.rsa)
+
+ def public_encrypt(self, data, padding):
+ assert self.check_key(), 'key is not initialised'
+ return m2.rsa_public_encrypt(self.rsa, data, padding)
+
+ def public_decrypt(self, data, padding):
+ assert self.check_key(), 'key is not initialised'
+ return m2.rsa_public_decrypt(self.rsa, data, padding)
+
+ def private_encrypt(self, data, padding):
+ assert self.check_key(), 'key is not initialised'
+ return m2.rsa_private_encrypt(self.rsa, data, padding)
+
+ def private_decrypt(self, data, padding):
+ assert self.check_key(), 'key is not initialised'
+ return m2.rsa_private_decrypt(self.rsa, data, padding)
+
+ def save_key_bio(self, bio, cipher='aes_128_cbc', callback=util.passphrase_callback):
+ """
+ Save the key pair to an M2Crypto.BIO.BIO object in PEM format.
+
+ @type bio: M2Crypto.BIO.BIO
+ @param bio: M2Crypto.BIO.BIO object to save key to.
+
+ @type cipher: string
+ @param cipher: Symmetric cipher to protect the key. The default
+ cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+ in the clear.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ to acquire a passphrase with which to protect the key.
+ The default is util.passphrase_callback.
+ """
+ if cipher is None:
+ return m2.rsa_write_key_no_cipher(self.rsa, bio._ptr(), callback)
+ else:
+ ciph = getattr(m2, cipher, None)
+ if ciph is None:
+ raise RSAError, 'not such cipher %s' % cipher
+ else:
+ ciph = ciph()
+ return m2.rsa_write_key(self.rsa, bio._ptr(), ciph, callback)
+
+ def save_key(self, file, cipher='aes_128_cbc', callback=util.passphrase_callback):
+ """
+ Save the key pair to a file in PEM format.
+
+ @type file: string
+ @param file: Name of file to save key to.
+
+ @type cipher: string
+ @param cipher: Symmetric cipher to protect the key. The default
+ cipher is 'aes_128_cbc'. If cipher is None, then the key is saved
+ in the clear.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ to acquire a passphrase with which to protect the key.
+ The default is util.passphrase_callback.
+ """
+ bio = BIO.openfile(file, 'wb')
+ return self.save_key_bio(bio, cipher, callback)
+
+ save_pem = save_key
+
+ def as_pem(self, cipher='aes_128_cbc', callback=util.passphrase_callback):
+ """
+ Returns the key(pair) as a string in PEM format.
+ """
+ bio = BIO.MemoryBuffer()
+ self.save_key_bio(bio, cipher, callback)
+ return bio.read()
+
+ def save_key_der_bio(self, bio):
+ """
+ Save the key pair to an M2Crypto.BIO.BIO object in DER format.
+
+ @type bio: M2Crypto.BIO.BIO
+ @param bio: M2Crypto.BIO.BIO object to save key to.
+ """
+ return m2.rsa_write_key_der(self.rsa, bio._ptr())
+
+ def save_key_der(self, file):
+ """
+ Save the key pair to a file in DER format.
+
+ @type file: str
+ @param file: Filename to save key to
+ """
+ bio = BIO.openfile(file, 'wb')
+ return self.save_key_der_bio(bio)
+
+ def save_pub_key_bio(self, bio):
+ """
+ Save the public key to an M2Crypto.BIO.BIO object in PEM format.
+
+ @type bio: M2Crypto.BIO.BIO
+ @param bio: M2Crypto.BIO.BIO object to save key to.
+ """
+ return m2.rsa_write_pub_key(self.rsa, bio._ptr())
+
+ def save_pub_key(self, file):
+ """
+ Save the public key to a file in PEM format.
+
+ @type file: string
+ @param file: Name of file to save key to.
+ """
+ bio = BIO.openfile(file, 'wb')
+ return m2.rsa_write_pub_key(self.rsa, bio._ptr())
+
+ def check_key(self):
+ return m2.rsa_check_key(self.rsa)
+
+ def sign_rsassa_pss(self, digest, algo='sha1', salt_length=20):
+ """
+ Signs a digest with the private key using RSASSA-PSS
+
+ @requires: OpenSSL 0.9.7h or later.
+
+ @type digest: str
+ @param digest: A digest created by using the digest method
+
+ @type salt_length: int
+ @param salt_length: The length of the salt to use
+
+ @type algo: str
+ @param algo: The hash algorithm to use
+
+ @return: a string which is the signature
+ """
+ hash = getattr(m2, algo, None)
+ if hash is None:
+ raise ValueError('not such hash algorithm %s' % hash_algo)
+
+ signature = m2.rsa_padding_add_pkcs1_pss(self.rsa, digest, hash(), salt_length)
+
+ return self.private_encrypt(signature, m2.no_padding)
+
+ def verify_rsassa_pss(self, data, signature, algo='sha1', salt_length=20):
+ """
+ Verifies the signature RSASSA-PSS
+
+ @requires: OpenSSL 0.9.7h or later.
+
+ @type data: str
+ @param data: Data that has been signed
+
+ @type signature: str
+ @param signature: The signature signed with RSASSA-PSS
+
+ @type salt_length: int
+ @param salt_length: The length of the salt that was used
+
+ @type algo: str
+ @param algo: The hash algorithm to use
+
+ @return: 1 or 0, depending on whether the signature was
+ verified or not.
+ """
+ hash = getattr(m2, algo, None)
+ if hash is None:
+ raise ValueError('not such hash algorithm %s' % hash_algo)
+
+ plain_signature = self.public_decrypt(signature, m2.no_padding)
+
+ return m2.rsa_verify_pkcs1_pss(self.rsa, data, plain_signature, hash(), salt_length)
+
+ def sign(self, digest, algo='sha1'):
+ """
+ Signs a digest with the private key
+
+ @type digest: str
+ @param digest: A digest created by using the digest method
+
+ @type algo: str
+ @param algo: The method that created the digest.
+ Legal values are 'sha1','sha224', 'sha256', 'ripemd160',
+ and 'md5'.
+
+ @return: a string which is the signature
+ """
+ digest_type = getattr(m2, 'NID_' + algo, None)
+ if digest_type is None:
+ raise ValueError, ('unknown algorithm', algo)
+
+ return m2.rsa_sign(self.rsa, digest, digest_type)
+
+ def verify(self, data, signature, algo='sha1'):
+ """
+ Verifies the signature with the public key
+
+ @type data: str
+ @param data: Data that has been signed
+
+ @type signature: str
+ @param signature: The signature signed with the private key
+
+ @type algo: str
+ @param algo: The method use to create digest from the data
+ before it was signed. Legal values are 'sha1','sha224',
+ 'sha256', 'ripemd160', and 'md5'.
+
+ @return: True or False, depending on whether the signature was
+ verified.
+ """
+ digest_type = getattr(m2, 'NID_' + algo, None)
+ if digest_type is None:
+ raise ValueError, ('unknown algorithm', algo)
+
+ return m2.rsa_verify(self.rsa, data, signature, digest_type)
+
+
+class RSA_pub(RSA):
+
+ """
+ Object interface to an RSA public key.
+ """
+
+ def __setattr__(self, name, value):
+ if name in ['e', 'n']:
+ raise RSAError, \
+ 'use factory function new_pub_key() to set (e, n)'
+ else:
+ self.__dict__[name] = value
+
+ def private_encrypt(self, *argv):
+ raise RSAError, 'RSA_pub object has no private key'
+
+ def private_decrypt(self, *argv):
+ raise RSAError, 'RSA_pub object has no private key'
+
+ def save_key(self, file, *args, **kw):
+ """
+ Save public key to file.
+ """
+ return self.save_pub_key(file)
+
+ def save_key_bio(self, bio, *args, **kw):
+ """
+ Save public key to BIO.
+ """
+ return self.save_pub_key_bio(bio)
+
+ #save_key_der
+
+ #save_key_der_bio
+
+ def check_key(self):
+ return m2.rsa_check_pub_key(self.rsa)
+
+
+def rsa_error():
+ raise RSAError, m2.err_reason_error_string(m2.err_get_error())
+
+
+def keygen_callback(p, n, out=sys.stdout):
+ """
+ Default callback for gen_key().
+ """
+ ch = ['.','+','*','\n']
+ out.write(ch[p])
+ out.flush()
+
+
+def gen_key(bits, e, callback=keygen_callback):
+ """
+ Generate an RSA key pair.
+
+ @type bits: int
+ @param bits: Key length, in bits.
+
+ @type e: int
+ @param e: The RSA public exponent.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ during key generation; its usual purpose is to provide visual
+ feedback. The default callback is keygen_callback.
+
+ @rtype: M2Crypto.RSA.RSA
+ @return: M2Crypto.RSA.RSA object.
+ """
+ return RSA(m2.rsa_generate_key(bits, e, callback), 1)
+
+
+def load_key(file, callback=util.passphrase_callback):
+ """
+ Load an RSA key pair from file.
+
+ @type file: string
+ @param file: Name of file containing RSA public key in PEM format.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ to acquire a passphrase with which to unlock the key.
+ The default is util.passphrase_callback.
+
+ @rtype: M2Crypto.RSA.RSA
+ @return: M2Crypto.RSA.RSA object.
+ """
+ bio = BIO.openfile(file)
+ return load_key_bio(bio, callback)
+
+
+def load_key_bio(bio, callback=util.passphrase_callback):
+ """
+ Load an RSA key pair from an M2Crypto.BIO.BIO object.
+
+ @type bio: M2Crypto.BIO.BIO
+ @param bio: M2Crypto.BIO.BIO object containing RSA key pair in PEM
+ format.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ to acquire a passphrase with which to unlock the key.
+ The default is util.passphrase_callback.
+
+ @rtype: M2Crypto.RSA.RSA
+ @return: M2Crypto.RSA.RSA object.
+ """
+ rsa = m2.rsa_read_key(bio._ptr(), callback)
+ if rsa is None:
+ rsa_error()
+ return RSA(rsa, 1)
+
+
+def load_key_string(string, callback=util.passphrase_callback):
+ """
+ Load an RSA key pair from a string.
+
+ @type string: string
+ @param string: String containing RSA key pair in PEM format.
+
+ @type callback: Python callable
+ @param callback: A Python callable object that is invoked
+ to acquire a passphrase with which to unlock the key.
+ The default is util.passphrase_callback.
+
+ @rtype: M2Crypto.RSA.RSA
+ @return: M2Crypto.RSA.RSA object.
+ """
+ bio = BIO.MemoryBuffer(string)
+ return load_key_bio(bio, callback)
+
+
+def load_pub_key(file):
+ """
+ Load an RSA public key from file.
+
+ @type file: string
+ @param file: Name of file containing RSA public key in PEM format.
+
+ @rtype: M2Crypto.RSA.RSA_pub
+ @return: M2Crypto.RSA.RSA_pub object.
+ """
+ bio = BIO.openfile(file)
+ return load_pub_key_bio(bio)
+
+
+def load_pub_key_bio(bio):
+ """
+ Load an RSA public key from an M2Crypto.BIO.BIO object.
+
+ @type bio: M2Crypto.BIO.BIO
+ @param bio: M2Crypto.BIO.BIO object containing RSA public key in PEM
+ format.
+
+ @rtype: M2Crypto.RSA.RSA_pub
+ @return: M2Crypto.RSA.RSA_pub object.
+ """
+ rsa = m2.rsa_read_pub_key(bio._ptr())
+ if rsa is None:
+ rsa_error()
+ return RSA_pub(rsa, 1)
+
+
+def new_pub_key((e, n)):
+ """
+ Instantiate an RSA_pub object from an (e, n) tuple.
+
+ @type e: string
+ @param e: The RSA public exponent; it is a string in OpenSSL's MPINT
+ format - 4-byte big-endian bit-count followed by the appropriate
+ number of bits.
+
+ @type n: string
+ @param n: The RSA composite of primes; it is a string in OpenSSL's MPINT
+ format - 4-byte big-endian bit-count followed by the appropriate
+ number of bits.
+
+ @rtype: M2Crypto.RSA.RSA_pub
+ @return: M2Crypto.RSA.RSA_pub object.
+ """
+ rsa = m2.rsa_new()
+ m2.rsa_set_e(rsa, e)
+ m2.rsa_set_n(rsa, n)
+ return RSA_pub(rsa, 1)
+
+
diff --git a/M2Crypto/Rand.py b/M2Crypto/Rand.py
new file mode 100644
index 0000000..1e6a918
--- /dev/null
+++ b/M2Crypto/Rand.py
@@ -0,0 +1,17 @@
+"""M2Crypto wrapper for OpenSSL PRNG. Requires OpenSSL 0.9.5 and above.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['rand_seed', 'rand_add', 'load_file', 'save_file', 'rand_bytes',
+ 'rand_pseudo_bytes']
+
+import m2
+
+rand_seed = m2.rand_seed
+rand_add = m2.rand_add
+load_file = m2.rand_load_file
+save_file = m2.rand_save_file
+rand_bytes = m2.rand_bytes
+rand_pseudo_bytes = m2.rand_pseudo_bytes
+
+
diff --git a/M2Crypto/SMIME.py b/M2Crypto/SMIME.py
new file mode 100644
index 0000000..556cd8f
--- /dev/null
+++ b/M2Crypto/SMIME.py
@@ -0,0 +1,245 @@
+"""M2Crypto wrapper for OpenSSL S/MIME API.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import BIO, EVP, X509, Err, util
+import m2
+
+PKCS7_TEXT = m2.PKCS7_TEXT
+PKCS7_NOCERTS = m2.PKCS7_NOCERTS
+PKCS7_NOSIGS = m2.PKCS7_NOSIGS
+PKCS7_NOCHAIN = m2.PKCS7_NOCHAIN
+PKCS7_NOINTERN = m2.PKCS7_NOINTERN
+PKCS7_NOVERIFY = m2.PKCS7_NOVERIFY
+PKCS7_DETACHED = m2.PKCS7_DETACHED
+PKCS7_BINARY = m2.PKCS7_BINARY
+PKCS7_NOATTR = m2.PKCS7_NOATTR
+
+PKCS7_SIGNED = m2.PKCS7_SIGNED
+PKCS7_ENVELOPED = m2.PKCS7_ENVELOPED
+PKCS7_SIGNED_ENVELOPED = m2.PKCS7_SIGNED_ENVELOPED # Deprecated
+PKCS7_DATA = m2.PKCS7_DATA
+
+class PKCS7_Error(Exception): pass
+
+m2.pkcs7_init(PKCS7_Error)
+
+class PKCS7:
+
+ m2_pkcs7_free = m2.pkcs7_free
+
+ def __init__(self, pkcs7=None, _pyfree=0):
+ if pkcs7 is not None:
+ self.pkcs7 = pkcs7
+ self._pyfree = _pyfree
+ else:
+ self.pkcs7 = m2.pkcs7_new()
+ self._pyfree = 1
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_pkcs7_free(self.pkcs7)
+
+ def _ptr(self):
+ return self.pkcs7
+
+ def type(self, text_name=0):
+ if text_name:
+ return m2.pkcs7_type_sn(self.pkcs7)
+ else:
+ return m2.pkcs7_type_nid(self.pkcs7)
+
+ def write(self, bio):
+ return m2.pkcs7_write_bio(self.pkcs7, bio._ptr())
+
+ def write_der(self, bio):
+ return m2.pkcs7_write_bio_der(self.pkcs7, bio._ptr())
+
+ def get0_signers(self, certs, flags = 0):
+ return X509.X509_Stack(m2.pkcs7_get0_signers(self.pkcs7,
+ certs.stack, flags), 1)
+
+
+def load_pkcs7(p7file):
+ bio = m2.bio_new_file(p7file, 'r')
+ if bio is None:
+ raise BIO.BIOError(Err.get_error())
+
+ try:
+ p7_ptr = m2.pkcs7_read_bio(bio)
+ finally:
+ m2.bio_free(bio)
+
+ if p7_ptr is None:
+ raise PKCS7_Error(Err.get_error())
+ return PKCS7(p7_ptr, 1)
+
+
+def load_pkcs7_bio(p7_bio):
+ p7_ptr = m2.pkcs7_read_bio(p7_bio._ptr())
+ if p7_ptr is None:
+ raise PKCS7_Error(Err.get_error())
+ return PKCS7(p7_ptr, 1)
+
+
+def smime_load_pkcs7(p7file):
+ bio = m2.bio_new_file(p7file, 'r')
+ if bio is None:
+ raise BIO.BIOError(Err.get_error())
+
+ try:
+ p7_ptr, bio_ptr = m2.smime_read_pkcs7(bio)
+ finally:
+ m2.bio_free(bio)
+
+ if p7_ptr is None:
+ raise SMIME_Error(Err.get_error())
+ if bio_ptr is None:
+ return PKCS7(p7_ptr, 1), None
+ else:
+ return PKCS7(p7_ptr, 1), BIO.BIO(bio_ptr, 1)
+
+
+def smime_load_pkcs7_bio(p7_bio):
+ p7_ptr, bio_ptr = m2.smime_read_pkcs7(p7_bio._ptr())
+ if p7_ptr is None:
+ raise SMIME_Error(Err.get_error())
+ if bio_ptr is None:
+ return PKCS7(p7_ptr, 1), None
+ else:
+ return PKCS7(p7_ptr, 1), BIO.BIO(bio_ptr, 1)
+
+
+class Cipher:
+
+ """
+ Object interface to EVP_CIPHER without all the frills of M2Crypto.EVP.Cipher.
+ """
+
+ def __init__(self, algo):
+ cipher = getattr(m2, algo, None)
+ if cipher is None:
+ raise ValueError, ('unknown cipher', algo)
+ self.cipher = cipher()
+
+ def _ptr(self):
+ return self.cipher
+
+
+class SMIME_Error(Exception): pass
+
+m2.smime_init(SMIME_Error)
+
+class SMIME:
+ def load_key(self, keyfile, certfile=None, callback=util.passphrase_callback):
+ if certfile is None:
+ certfile = keyfile
+ self.pkey = EVP.load_key(keyfile, callback)
+ self.x509 = X509.load_cert(certfile)
+
+ def load_key_bio(self, keybio, certbio=None, callback=util.passphrase_callback):
+ if certbio is None:
+ certbio = keybio
+ self.pkey = EVP.load_key_bio(keybio, callback)
+ self.x509 = X509.load_cert_bio(certbio)
+
+ def set_x509_stack(self, stack):
+ assert isinstance(stack, X509.X509_Stack)
+ self.x509_stack = stack
+
+ def set_x509_store(self, store):
+ assert isinstance(store, X509.X509_Store)
+ self.x509_store = store
+
+ def set_cipher(self, cipher):
+ assert isinstance(cipher, Cipher)
+ self.cipher = cipher
+
+ def unset_key(self):
+ del self.pkey
+ del self.x509
+
+ def unset_x509_stack(self):
+ del self.x509_stack
+
+ def unset_x509_store(self):
+ del self.x509_store
+
+ def unset_cipher(self):
+ del self.cipher
+
+ def encrypt(self, data_bio, flags=0):
+ if not hasattr(self, 'cipher'):
+ raise SMIME_Error, 'no cipher: use set_cipher()'
+ if not hasattr(self, 'x509_stack'):
+ raise SMIME_Error, 'no recipient certs: use set_x509_stack()'
+ pkcs7 = m2.pkcs7_encrypt(self.x509_stack._ptr(), data_bio._ptr(), self.cipher._ptr(), flags)
+ if pkcs7 is None:
+ raise SMIME_Error(Err.get_error())
+ return PKCS7(pkcs7, 1)
+
+ def decrypt(self, pkcs7, flags=0):
+ if not hasattr(self, 'pkey'):
+ raise SMIME_Error, 'no private key: use load_key()'
+ if not hasattr(self, 'x509'):
+ raise SMIME_Error, 'no certificate: load_key() used incorrectly?'
+ blob = m2.pkcs7_decrypt(pkcs7._ptr(), self.pkey._ptr(), self.x509._ptr(), flags)
+ if blob is None:
+ raise SMIME_Error(Err.get_error())
+ return blob
+
+ def sign(self, data_bio, flags=0):
+ if not hasattr(self, 'pkey'):
+ raise SMIME_Error, 'no private key: use load_key()'
+ if hasattr(self, 'x509_stack'):
+ pkcs7 = m2.pkcs7_sign1(self.x509._ptr(), self.pkey._ptr(),
+ self.x509_stack._ptr(), data_bio._ptr(), flags)
+ if pkcs7 is None:
+ raise SMIME_Error(Err.get_error())
+ return PKCS7(pkcs7, 1)
+ else:
+ pkcs7 = m2.pkcs7_sign0(self.x509._ptr(), self.pkey._ptr(),
+ data_bio._ptr(), flags)
+ if pkcs7 is None:
+ raise SMIME_Error(Err.get_error())
+ return PKCS7(pkcs7, 1)
+
+ def verify(self, pkcs7, data_bio=None, flags=0):
+ if not hasattr(self, 'x509_stack'):
+ raise SMIME_Error, 'no signer certs: use set_x509_stack()'
+ if not hasattr(self, 'x509_store'):
+ raise SMIME_Error, 'no x509 cert store: use set_x509_store()'
+ assert isinstance(pkcs7, PKCS7), 'pkcs7 not an instance of PKCS7'
+ p7 = pkcs7._ptr()
+ if data_bio is None:
+ blob = m2.pkcs7_verify0(p7, self.x509_stack._ptr(), self.x509_store._ptr(), flags)
+ else:
+ blob = m2.pkcs7_verify1(p7, self.x509_stack._ptr(), self.x509_store._ptr(), data_bio._ptr(), flags)
+ if blob is None:
+ raise SMIME_Error(Err.get_error())
+ return blob
+
+ def write(self, out_bio, pkcs7, data_bio=None, flags=0):
+ assert isinstance(pkcs7, PKCS7)
+ if data_bio is None:
+ return m2.smime_write_pkcs7(out_bio._ptr(), pkcs7._ptr(), flags)
+ else:
+ return m2.smime_write_pkcs7_multi(out_bio._ptr(), pkcs7._ptr(), data_bio._ptr(), flags)
+
+
+def text_crlf(text):
+ bio_in = BIO.MemoryBuffer(text)
+ bio_out = BIO.MemoryBuffer()
+ if m2.smime_crlf_copy(bio_in._ptr(), bio_out._ptr()):
+ return bio_out.read()
+ else:
+ raise SMIME_Error(Err.get_error())
+
+
+def text_crlf_bio(bio_in):
+ bio_out = BIO.MemoryBuffer()
+ if m2.smime_crlf_copy(bio_in._ptr(), bio_out._ptr()):
+ return bio_out
+ else:
+ raise SMIME_Error(Err.get_error())
+
diff --git a/M2Crypto/SSL/Checker.py b/M2Crypto/SSL/Checker.py
new file mode 100644
index 0000000..5218663
--- /dev/null
+++ b/M2Crypto/SSL/Checker.py
@@ -0,0 +1,224 @@
+"""
+SSL peer certificate checking routines
+
+Copyright (c) 2004-2007 Open Source Applications Foundation.
+All rights reserved.
+
+Copyright 2008 Heikki Toivonen. All rights reserved.
+"""
+
+__all__ = ['SSLVerificationError', 'NoCertificate', 'WrongCertificate',
+ 'WrongHost', 'Checker']
+
+from M2Crypto import util, EVP, m2
+import re
+
+class SSLVerificationError(Exception):
+ pass
+
+class NoCertificate(SSLVerificationError):
+ pass
+
+class WrongCertificate(SSLVerificationError):
+ pass
+
+class WrongHost(SSLVerificationError):
+ def __init__(self, expectedHost, actualHost, fieldName='commonName'):
+ """
+ This exception will be raised if the certificate returned by the
+ peer was issued for a different host than we tried to connect to.
+ This could be due to a server misconfiguration or an active attack.
+
+ @param expectedHost: The name of the host we expected to find in the
+ certificate.
+ @param actualHost: The name of the host we actually found in the
+ certificate.
+ @param fieldName: The field name where we noticed the error. This
+ should be either 'commonName' or 'subjectAltName'.
+ """
+ if fieldName not in ('commonName', 'subjectAltName'):
+ raise ValueError('Unknown fieldName, should be either commonName or subjectAltName')
+
+ SSLVerificationError.__init__(self)
+ self.expectedHost = expectedHost
+ self.actualHost = actualHost
+ self.fieldName = fieldName
+
+ def __str__(self):
+ s = 'Peer certificate %s does not match host, expected %s, got %s' \
+ % (self.fieldName, self.expectedHost, self.actualHost)
+ if isinstance(s, unicode):
+ s = s.encode('utf8')
+ return s
+
+
+class Checker:
+
+ numericIpMatch = re.compile('^[0-9]+(\.[0-9]+)*$')
+
+ def __init__(self, host=None, peerCertHash=None, peerCertDigest='sha1'):
+ self.host = host
+ self.fingerprint = peerCertHash
+ self.digest = peerCertDigest
+
+ def __call__(self, peerCert, host=None):
+ if peerCert is None:
+ raise NoCertificate('peer did not return certificate')
+
+ if host is not None:
+ self.host = host
+
+ if self.fingerprint:
+ if self.digest not in ('sha1', 'md5'):
+ raise ValueError('unsupported digest "%s"' %(self.digest))
+
+ if (self.digest == 'sha1' and len(self.fingerprint) != 40) or \
+ (self.digest == 'md5' and len(self.fingerprint) != 32):
+ raise WrongCertificate('peer certificate fingerprint length does not match')
+
+ der = peerCert.as_der()
+ md = EVP.MessageDigest(self.digest)
+ md.update(der)
+ digest = md.final()
+ if util.octx_to_num(digest) != int(self.fingerprint, 16):
+ raise WrongCertificate('peer certificate fingerprint does not match')
+
+ if self.host:
+ hostValidationPassed = False
+ self.useSubjectAltNameOnly = False
+
+ # subjectAltName=DNS:somehost[, ...]*
+ try:
+ subjectAltName = peerCert.get_ext('subjectAltName').get_value()
+ if self._splitSubjectAltName(self.host, subjectAltName):
+ hostValidationPassed = True
+ elif self.useSubjectAltNameOnly:
+ raise WrongHost(expectedHost=self.host,
+ actualHost=subjectAltName,
+ fieldName='subjectAltName')
+ except LookupError:
+ pass
+
+ # commonName=somehost[, ...]*
+ if not hostValidationPassed:
+ hasCommonName = False
+ commonNames = ''
+ for entry in peerCert.get_subject().get_entries_by_nid(m2.NID_commonName):
+ hasCommonName = True
+ commonName = entry.get_data().as_text()
+ if not commonNames:
+ commonNames = commonName
+ else:
+ commonNames += ',' + commonName
+ if self._match(self.host, commonName):
+ hostValidationPassed = True
+ break
+
+ if not hasCommonName:
+ raise WrongCertificate('no commonName in peer certificate')
+
+ if not hostValidationPassed:
+ raise WrongHost(expectedHost=self.host,
+ actualHost=commonNames,
+ fieldName='commonName')
+
+ return True
+
+ def _splitSubjectAltName(self, host, subjectAltName):
+ """
+ >>> check = Checker()
+ >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:my.example.com')
+ True
+ >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:*.example.com')
+ True
+ >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*.example.com')
+ True
+ >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*ample.com')
+ False
+ >>> check.useSubjectAltNameOnly
+ True
+ >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*ample.com, othername:<unsupported>')
+ False
+ >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*ample.com, DNS:my.example.org')
+ False
+ >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:m*ample.com, DNS:my.example.com')
+ True
+ >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='DNS:my.example.com, DNS:my.example.org')
+ True
+ >>> check.useSubjectAltNameOnly
+ True
+ >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='')
+ False
+ >>> check._splitSubjectAltName(host='my.example.com', subjectAltName='othername:<unsupported>')
+ False
+ >>> check.useSubjectAltNameOnly
+ False
+ """
+ self.useSubjectAltNameOnly = False
+ for certHost in subjectAltName.split(','):
+ certHost = certHost.lower().strip()
+ if certHost[:4] == 'dns:':
+ self.useSubjectAltNameOnly = True
+ if self._match(host, certHost[4:]):
+ return True
+ return False
+
+
+ def _match(self, host, certHost):
+ """
+ >>> check = Checker()
+ >>> check._match(host='my.example.com', certHost='my.example.com')
+ True
+ >>> check._match(host='my.example.com', certHost='*.example.com')
+ True
+ >>> check._match(host='my.example.com', certHost='m*.example.com')
+ True
+ >>> check._match(host='my.example.com', certHost='m*.EXAMPLE.com')
+ True
+ >>> check._match(host='my.example.com', certHost='m*ample.com')
+ False
+ >>> check._match(host='my.example.com', certHost='*.*.com')
+ False
+ >>> check._match(host='1.2.3.4', certHost='1.2.3.4')
+ True
+ >>> check._match(host='1.2.3.4', certHost='*.2.3.4')
+ False
+ >>> check._match(host='1234', certHost='1234')
+ True
+ """
+ # XXX See RFC 2818 and 3280 for matching rules, this is may not
+ # XXX yet be complete.
+
+ host = host.lower()
+ certHost = certHost.lower()
+
+ if host == certHost:
+ return True
+
+ if certHost.count('*') > 1:
+ # Not sure about this, but being conservative
+ return False
+
+ if self.numericIpMatch.match(host) or \
+ self.numericIpMatch.match(certHost.replace('*', '')):
+ # Not sure if * allowed in numeric IP, but think not.
+ return False
+
+ if certHost.find('\\') > -1:
+ # Not sure about this, maybe some encoding might have these.
+ # But being conservative for now, because regex below relies
+ # on this.
+ return False
+
+ # Massage certHost so that it can be used in regex
+ certHost = certHost.replace('.', '\.')
+ certHost = certHost.replace('*', '[^\.]*')
+ if re.compile('^%s$' %(certHost)).match(host):
+ return True
+
+ return False
+
+
+if __name__ == '__main__':
+ import doctest
+ doctest.testmod()
diff --git a/M2Crypto/SSL/Cipher.py b/M2Crypto/SSL/Cipher.py
new file mode 100644
index 0000000..2d2c088
--- /dev/null
+++ b/M2Crypto/SSL/Cipher.py
@@ -0,0 +1,44 @@
+"""SSL Ciphers
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['Cipher', 'Cipher_Stack']
+
+from M2Crypto import m2
+
+class Cipher:
+ def __init__(self, cipher):
+ self.cipher=cipher
+
+ def __len__(self):
+ return m2.ssl_cipher_get_bits(self.cipher)
+
+ def __repr__(self):
+ return "%s-%s" % (self.name(), len(self))
+
+ def __str__(self):
+ return "%s-%s" % (self.name(), len(self))
+
+ def version(self):
+ return m2.ssl_cipher_get_version(self.cipher)
+
+ def name(self):
+ return m2.ssl_cipher_get_name(self.cipher)
+
+
+class Cipher_Stack:
+ def __init__(self, stack):
+ self.stack=stack
+
+ def __len__(self):
+ return m2.sk_ssl_cipher_num(self.stack)
+
+ def __getitem__(self, idx):
+ if not 0 <= idx < m2.sk_ssl_cipher_num(self.stack):
+ raise IndexError('index out of range')
+ v=m2.sk_ssl_cipher_value(self.stack, idx)
+ return Cipher(v)
+
+ def __iter__(self):
+ for i in xrange(m2.sk_ssl_cipher_num(self.stack)):
+ yield self[i]
diff --git a/M2Crypto/SSL/Connection.py b/M2Crypto/SSL/Connection.py
new file mode 100644
index 0000000..ef6a2c8
--- /dev/null
+++ b/M2Crypto/SSL/Connection.py
@@ -0,0 +1,361 @@
+"""SSL Connection aka socket
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2004-2007 OSAF. All Rights Reserved.
+
+Copyright 2008 Heikki Toivonen. All rights reserved.
+"""
+
+__all__ = ['Connection',
+ 'timeout', # XXX Not really, but for documentation purposes
+ ]
+
+# Python
+import socket
+
+# M2Crypto
+from Cipher import Cipher, Cipher_Stack
+from Session import Session
+from M2Crypto import BIO, X509, m2
+import timeout
+import Checker
+
+#SSLError = getattr(__import__('M2Crypto.SSL', globals(), locals(), 'SSLError'), 'SSLError')
+from M2Crypto.SSL import SSLError
+
+def _serverPostConnectionCheck(*args, **kw):
+ return 1
+
+class Connection:
+
+ """An SSL connection."""
+
+ clientPostConnectionCheck = Checker.Checker()
+ serverPostConnectionCheck = _serverPostConnectionCheck
+
+ m2_bio_free = m2.bio_free
+ m2_ssl_free = m2.ssl_free
+
+ def __init__(self, ctx, sock=None):
+ self.ctx = ctx
+ self.ssl = m2.ssl_new(self.ctx.ctx)
+ if sock is not None:
+ self.socket = sock
+ else:
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ self._fileno = self.socket.fileno()
+
+ self.blocking = self.socket.gettimeout()
+
+ self.ssl_close_flag = m2.bio_noclose
+
+
+ def __del__(self):
+ if getattr(self, 'sslbio', None):
+ self.m2_bio_free(self.sslbio)
+ if getattr(self, 'sockbio', None):
+ self.m2_bio_free(self.sockbio)
+ if self.ssl_close_flag == m2.bio_noclose and getattr(self, 'ssl', None):
+ self.m2_ssl_free(self.ssl)
+ self.socket.close()
+
+ def close(self):
+ m2.ssl_shutdown(self.ssl)
+
+ def clear(self):
+ """
+ If there were errors in this connection, call clear() rather
+ than close() to end it, so that bad sessions will be cleared
+ from cache.
+ """
+ return m2.ssl_clear(self.ssl)
+
+ def set_shutdown(self, mode):
+ m2.ssl_set_shutdown1(self.ssl, mode)
+
+ def get_shutdown(self):
+ return m2.ssl_get_shutdown(self.ssl)
+
+ def bind(self, addr):
+ self.socket.bind(addr)
+
+ def listen(self, qlen=5):
+ self.socket.listen(qlen)
+
+ def ssl_get_error(self, ret):
+ return m2.ssl_get_error(self.ssl, ret)
+
+ def set_bio(self, readbio, writebio):
+ """
+ Explicitly set read and write bios
+ """
+ m2.ssl_set_bio(self.ssl, readbio._ptr(), writebio._ptr())
+
+ def set_client_CA_list_from_file(self, cafile):
+ """
+ Set the acceptable client CA list. If the client
+ returns a certificate, it must have been issued by
+ one of the CAs listed in cafile.
+
+ Makes sense only for servers.
+
+ @param cafile: Filename from which to load the CA list.
+ """
+ m2.ssl_set_client_CA_list_from_file(self.ssl, cafile)
+
+ def set_client_CA_list_from_context(self):
+ """
+ Set the acceptable client CA list. If the client
+ returns a certificate, it must have been issued by
+ one of the CAs listed in context.
+
+ Makes sense only for servers.
+ """
+ m2.ssl_set_client_CA_list_from_context(self.ssl, self.ctx.ctx)
+
+ def setup_addr(self, addr):
+ self.addr = addr
+
+ def set_ssl_close_flag(self, flag):
+ """
+ By default, SSL struct will be freed in __del__. Call with
+ m2.bio_close to override this default.
+ """
+ if flag not in (m2.bio_close, m2.bio_noclose):
+ raise ValueError("flag must be m2.bio_close or m2.bio_noclose")
+ self.ssl_close_flag = flag
+
+ def setup_ssl(self):
+ # Make a BIO_s_socket.
+ self.sockbio = m2.bio_new_socket(self.socket.fileno(), 0)
+ # Link SSL struct with the BIO_socket.
+ m2.ssl_set_bio(self.ssl, self.sockbio, self.sockbio)
+ # Make a BIO_f_ssl.
+ self.sslbio = m2.bio_new(m2.bio_f_ssl())
+ # Link BIO_f_ssl with the SSL struct.
+ m2.bio_set_ssl(self.sslbio, self.ssl, m2.bio_noclose)
+
+ def _setup_ssl(self, addr):
+ """Deprecated"""
+ self.setup_addr(addr)
+ self.setup_ssl()
+
+ def set_accept_state(self):
+ m2.ssl_set_accept_state(self.ssl)
+
+ def accept_ssl(self):
+ return m2.ssl_accept(self.ssl)
+
+ def accept(self):
+ """Accept an SSL connection. The return value is a pair (ssl, addr) where
+ ssl is a new SSL connection object and addr is the address bound to
+ the other end of the SSL connection."""
+ sock, addr = self.socket.accept()
+ ssl = Connection(self.ctx, sock)
+ ssl.addr = addr
+ ssl.setup_ssl()
+ ssl.set_accept_state()
+ ssl.accept_ssl()
+ check = getattr(self, 'postConnectionCheck', self.serverPostConnectionCheck)
+ if check is not None:
+ if not check(ssl.get_peer_cert(), ssl.addr[0]):
+ raise Checker.SSLVerificationError, 'post connection check failed'
+ return ssl, addr
+
+ def set_connect_state(self):
+ m2.ssl_set_connect_state(self.ssl)
+
+ def connect_ssl(self):
+ return m2.ssl_connect(self.ssl)
+
+ def connect(self, addr):
+ self.socket.connect(addr)
+ self.addr = addr
+ self.setup_ssl()
+ self.set_connect_state()
+ ret = self.connect_ssl()
+ check = getattr(self, 'postConnectionCheck', self.clientPostConnectionCheck)
+ if check is not None:
+ if not check(self.get_peer_cert(), self.addr[0]):
+ raise Checker.SSLVerificationError, 'post connection check failed'
+ return ret
+
+ def shutdown(self, how):
+ m2.ssl_set_shutdown(self.ssl, how)
+
+ def renegotiate(self):
+ """Renegotiate this connection's SSL parameters."""
+ return m2.ssl_renegotiate(self.ssl)
+
+ def pending(self):
+ """Return the numbers of octets that can be read from the
+ connection."""
+ return m2.ssl_pending(self.ssl)
+
+ def _write_bio(self, data):
+ return m2.ssl_write(self.ssl, data)
+
+ def _write_nbio(self, data):
+ return m2.ssl_write_nbio(self.ssl, data)
+
+ def _read_bio(self, size=1024):
+ if size <= 0:
+ raise ValueError, 'size <= 0'
+ return m2.ssl_read(self.ssl, size)
+
+ def _read_nbio(self, size=1024):
+ if size <= 0:
+ raise ValueError, 'size <= 0'
+ return m2.ssl_read_nbio(self.ssl, size)
+
+ def write(self, data):
+ if self.blocking:
+ return self._write_bio(data)
+ return self._write_nbio(data)
+ sendall = send = write
+
+ def read(self, size=1024):
+ if self.blocking:
+ return self._read_bio(size)
+ return self._read_nbio(size)
+ recv = read
+
+ def setblocking(self, mode):
+ """Set this connection's underlying socket to _mode_."""
+ self.socket.setblocking(mode)
+ self.blocking = mode
+
+ def fileno(self):
+ return self.socket.fileno()
+
+ def getsockopt(self, *args):
+ return apply(self.socket.getsockopt, args)
+
+ def setsockopt(self, *args):
+ return apply(self.socket.setsockopt, args)
+
+ def get_context(self):
+ """Return the SSL.Context object associated with this
+ connection."""
+ return m2.ssl_get_ssl_ctx(self.ssl)
+
+ def get_state(self):
+ """Return the SSL state of this connection."""
+ return m2.ssl_get_state(self.ssl)
+
+ def verify_ok(self):
+ return (m2.ssl_get_verify_result(self.ssl) == m2.X509_V_OK)
+
+ def get_verify_mode(self):
+ """Return the peer certificate verification mode."""
+ return m2.ssl_get_verify_mode(self.ssl)
+
+ def get_verify_depth(self):
+ """Return the peer certificate verification depth."""
+ return m2.ssl_get_verify_depth(self.ssl)
+
+ def get_verify_result(self):
+ """Return the peer certificate verification result."""
+ return m2.ssl_get_verify_result(self.ssl)
+
+ def get_peer_cert(self):
+ """Return the peer certificate; if the peer did not provide
+ a certificate, return None."""
+ c=m2.ssl_get_peer_cert(self.ssl)
+ if c is None:
+ return None
+ # Need to free the pointer coz OpenSSL doesn't.
+ return X509.X509(c, 1)
+
+ def get_peer_cert_chain(self):
+ """Return the peer certificate chain; if the peer did not provide
+ a certificate chain, return None.
+
+ @warning: The returned chain will be valid only for as long as the
+ connection object is alive. Once the connection object gets freed,
+ the chain will be freed as well.
+ """
+ c=m2.ssl_get_peer_cert_chain(self.ssl)
+ if c is None:
+ return None
+ # No need to free the pointer coz OpenSSL does.
+ return X509.X509_Stack(c)
+
+ def get_cipher(self):
+ """Return an M2Crypto.SSL.Cipher object for this connection; if the
+ connection has not been initialised with a cipher suite, return None."""
+ c=m2.ssl_get_current_cipher(self.ssl)
+ if c is None:
+ return None
+ return Cipher(c)
+
+ def get_ciphers(self):
+ """Return an M2Crypto.SSL.Cipher_Stack object for this connection; if the
+ connection has not been initialised with cipher suites, return None."""
+ c=m2.ssl_get_ciphers(self.ssl)
+ if c is None:
+ return None
+ return Cipher_Stack(c)
+
+ def get_cipher_list(self, idx=0):
+ """Return the cipher suites for this connection as a string object."""
+ return m2.ssl_get_cipher_list(self.ssl, idx)
+
+ def set_cipher_list(self, cipher_list):
+ """Set the cipher suites for this connection."""
+ return m2.ssl_set_cipher_list(self.ssl, cipher_list)
+
+ def makefile(self, mode='rb', bufsize='ignored'):
+ r = 'r' in mode or '+' in mode
+ w = 'w' in mode or 'a' in mode or '+' in mode
+ b = 'b' in mode
+ m2mode = ['', 'r'][r] + ['', 'w'][w] + ['', 'b'][b]
+ # XXX Need to dup().
+ bio = BIO.BIO(self.sslbio, _close_cb=self.close)
+ m2.bio_do_handshake(bio._ptr())
+ return BIO.IOBuffer(bio, m2mode, _pyfree=0)
+
+ def getsockname(self):
+ return self.socket.getsockname()
+
+ def getpeername(self):
+ return self.socket.getpeername()
+
+ def set_session_id_ctx(self, id):
+ ret = m2.ssl_set_session_id_context(self.ssl, id)
+ if not ret:
+ raise SSLError(m2.err_reason_error_string(m2.err_get_error()))
+
+ def get_session(self):
+ sess = m2.ssl_get_session(self.ssl)
+ return Session(sess)
+
+ def set_session(self, session):
+ m2.ssl_set_session(self.ssl, session._ptr())
+
+ def get_default_session_timeout(self):
+ return m2.ssl_get_default_session_timeout(self.ssl)
+
+ def get_socket_read_timeout(self):
+ return timeout.struct_to_timeout(self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeout.struct_size()))
+
+ def get_socket_write_timeout(self):
+ return timeout.struct_to_timeout(self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_SNDTIMEO, timeout.struct_size()))
+
+ def set_socket_read_timeout(self, timeo):
+ assert isinstance(timeo, timeout.timeout)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_RCVTIMEO, timeo.pack())
+
+ def set_socket_write_timeout(self, timeo):
+ assert isinstance(timeo, timeout.timeout)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDTIMEO, timeo.pack())
+
+ def get_version(self):
+ "Return the TLS/SSL protocol version for this connection."
+ return m2.ssl_get_version(self.ssl)
+
+ def set_post_connection_check_callback(self, postConnectionCheck):
+ self.postConnectionCheck = postConnectionCheck
diff --git a/M2Crypto/SSL/Context.py b/M2Crypto/SSL/Context.py
new file mode 100644
index 0000000..b8d159f
--- /dev/null
+++ b/M2Crypto/SSL/Context.py
@@ -0,0 +1,254 @@
+"""SSL Context
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['map', 'Context']
+
+from weakref import WeakValueDictionary
+
+# M2Crypto
+import cb
+from M2Crypto import util, BIO, Err, RSA, m2, X509
+
+class _ctxmap:
+ singleton = None
+ def __init__(self):
+ self.map = WeakValueDictionary()
+
+ def __getitem__(self, key):
+ return self.map[key]
+
+ def __setitem__(self, key, value):
+ self.map[key] = value
+
+ def __delitem__(self, key):
+ del self.map[key]
+
+def map():
+ if _ctxmap.singleton is None:
+ _ctxmap.singleton = _ctxmap()
+ return _ctxmap.singleton
+
+
+class Context:
+
+ """'Context' for SSL connections."""
+
+ m2_ssl_ctx_free = m2.ssl_ctx_free
+
+ def __init__(self, protocol='sslv23', weak_crypto=None):
+ proto = getattr(m2, protocol + '_method', None)
+ if proto is None:
+ raise ValueError, "no such protocol '%s'" % protocol
+ self.ctx = m2.ssl_ctx_new(proto())
+ self.allow_unknown_ca = 0
+ map()[long(self.ctx)] = self
+ m2.ssl_ctx_set_cache_size(self.ctx, 128L)
+ if weak_crypto is None:
+ if protocol == 'sslv23':
+ self.set_options(m2.SSL_OP_ALL | m2.SSL_OP_NO_SSLv2)
+ self.set_cipher_list('ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH')
+
+ def __del__(self):
+ if getattr(self, 'ctx', None):
+ self.m2_ssl_ctx_free(self.ctx)
+
+ def close(self):
+ del map()[long(self.ctx)]
+
+ def load_cert(self, certfile, keyfile=None, callback=util.passphrase_callback):
+ """Load certificate and private key into the context.
+
+ @param certfile: File that contains the PEM-encoded certificate.
+ @type certfile: str
+ @param keyfile: File that contains the PEM-encoded private key.
+ Default value of None indicates that the private key
+ is to be found in 'certfile'.
+ @type keyfile: str
+
+ @param callback: Callable object to be invoked if the private key is
+ passphrase-protected. Default callback provides a
+ simple terminal-style input for the passphrase.
+ """
+ m2.ssl_ctx_passphrase_callback(self.ctx, callback)
+ m2.ssl_ctx_use_cert(self.ctx, certfile)
+ if not keyfile:
+ keyfile = certfile
+ m2.ssl_ctx_use_privkey(self.ctx, keyfile)
+ if not m2.ssl_ctx_check_privkey(self.ctx):
+ raise ValueError, 'public/private key mismatch'
+
+ def load_cert_chain(self, certchainfile, keyfile=None, callback=util.passphrase_callback):
+ """Load certificate chain and private key into the context.
+
+ @param certchainfile: File object containing the PEM-encoded
+ certificate chain.
+ @type certchainfile: str
+ @param keyfile: File object containing the PEM-encoded private
+ key. Default value of None indicates that the
+ private key is to be found in 'certchainfile'.
+ @type keyfile: str
+
+ @param callback: Callable object to be invoked if the private key
+ is passphrase-protected. Default callback
+ provides a simple terminal-style input for the
+ passphrase.
+ """
+ m2.ssl_ctx_passphrase_callback(self.ctx, callback)
+ m2.ssl_ctx_use_cert_chain(self.ctx, certchainfile)
+ if not keyfile:
+ keyfile = certchainfile
+ m2.ssl_ctx_use_privkey(self.ctx, keyfile)
+ if not m2.ssl_ctx_check_privkey(self.ctx):
+ raise ValueError, 'public/private key mismatch'
+
+ def set_client_CA_list_from_file(self, cafile):
+ """Load CA certs into the context. These CA certs are sent to the
+ peer during *SSLv3 certificate request*.
+
+ @param cafile: File object containing one or more PEM-encoded CA
+ certificates concatenated together.
+ @type cafile: str
+ """
+ m2.ssl_ctx_set_client_CA_list_from_file(self.ctx, cafile)
+
+ # Deprecated.
+ load_client_CA = load_client_ca = set_client_CA_list_from_file
+
+ def load_verify_locations(self, cafile=None, capath=None):
+ """Load CA certs into the context. These CA certs are used during
+ verification of the peer's certificate.
+
+ @param cafile: File containing one or more PEM-encoded CA certificates
+ concatenated together.
+ @type cafile: str
+ @param capath: Directory containing PEM-encoded CA certificates
+ (one certificate per file).
+ @type capath: str
+ """
+ if cafile is None and capath is None:
+ raise ValueError("cafile and capath can not both be None.")
+ return m2.ssl_ctx_load_verify_locations(self.ctx, cafile, capath)
+
+ # Deprecated.
+ load_verify_info = load_verify_locations
+
+ def set_session_id_ctx(self, id):
+ ret = m2.ssl_ctx_set_session_id_context(self.ctx, id)
+ if not ret:
+ raise Err.SSLError(Err.get_error_code(), '')
+
+ def set_allow_unknown_ca(self, ok):
+ """Set the context to accept/reject a peer certificate if the
+ certificate's CA is unknown.
+
+ @param ok: True to accept, False to reject.
+ @type ok: boolean
+ """
+ self.allow_unknown_ca = ok
+
+ def get_allow_unknown_ca(self):
+ """Get the context's setting that accepts/rejects a peer
+ certificate if the certificate's CA is unknown.
+ """
+ return self.allow_unknown_ca
+
+ def set_verify(self, mode, depth, callback=None):
+ """
+ Set verify options. Most applications will need to call this
+ method with the right options to make a secure SSL connection.
+
+ @param mode: The verification mode to use. Typically at least
+ SSL.verify_peer is used. Clients would also typically
+ add SSL.verify_fail_if_no_peer_cert.
+ @type mode: int
+ @param depth: The maximum allowed depth of the certificate chain
+ returned by the peer.
+ @type depth: int
+ @param callback: Callable that can be used to specify custom
+ verification checks.
+ """
+ if callback is None:
+ m2.ssl_ctx_set_verify_default(self.ctx, mode)
+ else:
+ m2.ssl_ctx_set_verify(self.ctx, mode, callback)
+ m2.ssl_ctx_set_verify_depth(self.ctx, depth)
+
+ def get_verify_mode(self):
+ return m2.ssl_ctx_get_verify_mode(self.ctx)
+
+ def get_verify_depth(self):
+ return m2.ssl_ctx_get_verify_depth(self.ctx)
+
+ def set_tmp_dh(self, dhpfile):
+ """Load ephemeral DH parameters into the context.
+
+ @param dhpfile: File object containing the PEM-encoded DH
+ parameters.
+ @type dhpfile: str
+ """
+ f = BIO.openfile(dhpfile)
+ dhp = m2.dh_read_parameters(f.bio_ptr())
+ return m2.ssl_ctx_set_tmp_dh(self.ctx, dhp)
+
+ def set_tmp_dh_callback(self, callback=None):
+ if callback is not None:
+ m2.ssl_ctx_set_tmp_dh_callback(self.ctx, callback)
+
+ def set_tmp_rsa(self, rsa):
+ """Load ephemeral RSA key into the context.
+
+ @param rsa: M2Crypto.RSA.RSA instance.
+ """
+ if isinstance(rsa, RSA.RSA):
+ return m2.ssl_ctx_set_tmp_rsa(self.ctx, rsa.rsa)
+ else:
+ raise TypeError, "Expected an instance of RSA.RSA, got %s." % (rsa,)
+
+ def set_tmp_rsa_callback(self, callback=None):
+ if callback is not None:
+ m2.ssl_ctx_set_tmp_rsa_callback(self.ctx, callback)
+
+ def set_info_callback(self, callback=cb.ssl_info_callback):
+ """
+ Set a callback function that can be used to get state information
+ about the SSL connections that are created from this context.
+
+ @param callback: Callback function. The default prints information to
+ stderr.
+ """
+ m2.ssl_ctx_set_info_callback(self.ctx, callback)
+
+ def set_cipher_list(self, cipher_list):
+ return m2.ssl_ctx_set_cipher_list(self.ctx, cipher_list)
+
+ def add_session(self, session):
+ return m2.ssl_ctx_add_session(self.ctx, session._ptr())
+
+ def remove_session(self, session):
+ return m2.ssl_ctx_remove_session(self.ctx, session._ptr())
+
+ def get_session_timeout(self):
+ return m2.ssl_ctx_get_session_timeout(self.ctx)
+
+ def set_session_timeout(self, timeout):
+ return m2.ssl_ctx_set_session_timeout(self.ctx, timeout)
+
+ def set_session_cache_mode(self, mode):
+ return m2.ssl_ctx_set_session_cache_mode(self.ctx, mode)
+
+ def get_session_cache_mode(self):
+ return m2.ssl_ctx_get_session_cache_mode(self.ctx)
+
+ def set_options(self, op):
+ return m2.ssl_ctx_set_options(self.ctx, op)
+
+ def get_cert_store(self):
+ """
+ Get the certificate store associated with this context.
+
+ @warning: The store is NOT refcounted, and as such can not be relied
+ to be valid once the context goes away or is changed.
+ """
+ return X509.X509_Store(m2.ssl_ctx_get_cert_store(self.ctx))
+
diff --git a/M2Crypto/SSL/SSLServer.py b/M2Crypto/SSL/SSLServer.py
new file mode 100644
index 0000000..d894d3b
--- /dev/null
+++ b/M2Crypto/SSL/SSLServer.py
@@ -0,0 +1,53 @@
+"""SSLServer
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['SSLServer', 'ForkingSSLServer', 'ThreadingSSLServer']
+
+# Python
+import socket, SocketServer
+
+# M2Crypto
+from Connection import Connection
+from M2Crypto.SSL import SSLError
+from M2Crypto import m2
+
+
+class SSLServer(SocketServer.TCPServer):
+ def __init__(self, server_address, RequestHandlerClass, ssl_context, bind_and_activate=True):
+ """
+ Superclass says: Constructor. May be extended, do not override.
+ This class says: Ho-hum.
+ """
+ SocketServer.BaseServer.__init__(self, server_address, RequestHandlerClass)
+ self.ssl_ctx=ssl_context
+ self.socket=Connection(self.ssl_ctx)
+ if bind_and_activate:
+ self.server_bind()
+ self.server_activate()
+
+ def handle_request(self):
+ request = None
+ client_address = None
+ try:
+ request, client_address = self.get_request()
+ if self.verify_request(request, client_address):
+ self.process_request(request, client_address)
+ except SSLError:
+ self.handle_error(request, client_address)
+
+ def handle_error(self, request, client_address):
+ print '-'*40
+ import traceback
+ traceback.print_exc()
+ print '-'*40
+
+
+class ForkingSSLServer(SocketServer.ForkingMixIn, SSLServer):
+ pass
+
+
+class ThreadingSSLServer(SocketServer.ThreadingMixIn, SSLServer):
+ pass
+
+
diff --git a/M2Crypto/SSL/Session.py b/M2Crypto/SSL/Session.py
new file mode 100644
index 0000000..1edf5b0
--- /dev/null
+++ b/M2Crypto/SSL/Session.py
@@ -0,0 +1,58 @@
+"""SSL Session
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['Session', 'load_session']
+
+from M2Crypto import BIO, Err, m2
+
+class Session:
+
+ m2_ssl_session_free = m2.ssl_session_free
+
+ def __init__(self, session, _pyfree=0):
+ assert session is not None
+ self.session = session
+ self._pyfree = _pyfree
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_ssl_session_free(self.session)
+
+ def _ptr(self):
+ return self.session
+
+ def as_text(self):
+ buf = BIO.MemoryBuffer()
+ m2.ssl_session_print(buf.bio_ptr(), self.session)
+ return buf.read_all()
+
+ def as_der(self):
+ buf = BIO.MemoryBuffer()
+ m2.i2d_ssl_session(buf.bio_ptr(), self.session)
+ return buf.read_all()
+
+ def write_bio(self, bio):
+ return m2.ssl_session_write_bio(bio.bio_ptr(), self.session)
+
+ def get_time(self):
+ return m2.ssl_session_get_time(self.session)
+
+ def set_time(self, t):
+ return m2.ssl_session_set_time(self.session, t)
+
+ def get_timeout(self):
+ return m2.ssl_session_get_timeout(self.session)
+
+ def set_timeout(self, t):
+ return m2.ssl_session_set_timeout(self.session, t)
+
+
+def load_session(pemfile):
+ f = BIO.openfile(pemfile)
+ cptr = m2.ssl_session_read_pem(f.bio_ptr())
+ f.close()
+ if cptr is None:
+ from M2Crypto.SSL import SSLError
+ raise SSLError(Err.get_error())
+ return Session(cptr, 1)
diff --git a/M2Crypto/SSL/TwistedProtocolWrapper.py b/M2Crypto/SSL/TwistedProtocolWrapper.py
new file mode 100644
index 0000000..930dd32
--- /dev/null
+++ b/M2Crypto/SSL/TwistedProtocolWrapper.py
@@ -0,0 +1,410 @@
+"""
+Make Twisted use M2Crypto for SSL
+
+Copyright (c) 2004-2007 Open Source Applications Foundation.
+All rights reserved.
+"""
+
+__all__ = ['connectSSL', 'connectTCP', 'listenSSL', 'listenTCP',
+ 'TLSProtocolWrapper']
+
+import twisted.protocols.policies as policies
+import twisted.internet.reactor
+from twisted.protocols.policies import ProtocolWrapper
+from twisted.internet.interfaces import ITLSTransport
+from zope.interface import implements
+
+import M2Crypto # for M2Crypto.BIO.BIOError
+from M2Crypto import m2, X509
+from M2Crypto.SSL import Checker
+
+
+def _alwaysSucceedsPostConnectionCheck(peerX509, expectedHost):
+ return 1
+
+
+def connectSSL(host, port, factory, contextFactory, timeout=30,
+ bindAddress=None,
+ reactor=twisted.internet.reactor,
+ postConnectionCheck=Checker.Checker()):
+ """
+ A convenience function to start an SSL/TLS connection using Twisted.
+
+ See IReactorSSL interface in Twisted.
+ """
+ wrappingFactory = policies.WrappingFactory(factory)
+ wrappingFactory.protocol = lambda factory, wrappedProtocol: \
+ TLSProtocolWrapper(factory,
+ wrappedProtocol,
+ startPassThrough=0,
+ client=1,
+ contextFactory=contextFactory,
+ postConnectionCheck=postConnectionCheck)
+ return reactor.connectTCP(host, port, wrappingFactory, timeout, bindAddress)
+
+
+def connectTCP(host, port, factory, timeout=30, bindAddress=None,
+ reactor=twisted.internet.reactor,
+ postConnectionCheck=Checker.Checker()):
+ """
+ A convenience function to start a TCP connection using Twisted.
+
+ NOTE: You must call startTLS(ctx) to go into SSL/TLS mode.
+
+ See IReactorTCP interface in Twisted.
+ """
+ wrappingFactory = policies.WrappingFactory(factory)
+ wrappingFactory.protocol = lambda factory, wrappedProtocol: \
+ TLSProtocolWrapper(factory,
+ wrappedProtocol,
+ startPassThrough=1,
+ client=1,
+ contextFactory=None,
+ postConnectionCheck=postConnectionCheck)
+ return reactor.connectTCP(host, port, wrappingFactory, timeout, bindAddress)
+
+
+def listenSSL(port, factory, contextFactory, backlog=5, interface='',
+ reactor=twisted.internet.reactor,
+ postConnectionCheck=_alwaysSucceedsPostConnectionCheck):
+ """
+ A convenience function to listen for SSL/TLS connections using Twisted.
+
+ See IReactorSSL interface in Twisted.
+ """
+ wrappingFactory = policies.WrappingFactory(factory)
+ wrappingFactory.protocol = lambda factory, wrappedProtocol: \
+ TLSProtocolWrapper(factory,
+ wrappedProtocol,
+ startPassThrough=0,
+ client=0,
+ contextFactory=contextFactory,
+ postConnectionCheck=postConnectionCheck)
+ return reactor.listenTCP(port, wrappingFactory, backlog, interface)
+
+
+def listenTCP(port, factory, backlog=5, interface='',
+ reactor=twisted.internet.reactor,
+ postConnectionCheck=None):
+ """
+ A convenience function to listen for TCP connections using Twisted.
+
+ NOTE: You must call startTLS(ctx) to go into SSL/TLS mode.
+
+ See IReactorTCP interface in Twisted.
+ """
+ wrappingFactory = policies.WrappingFactory(factory)
+ wrappingFactory.protocol = lambda factory, wrappedProtocol: \
+ TLSProtocolWrapper(factory,
+ wrappedProtocol,
+ startPassThrough=1,
+ client=0,
+ contextFactory=None,
+ postConnectionCheck=postConnectionCheck)
+ return reactor.listenTCP(port, wrappingFactory, backlog, interface)
+
+
+class _BioProxy:
+ """
+ The purpose of this class is to eliminate the __del__ method from
+ TLSProtocolWrapper, and thus letting it be garbage collected.
+ """
+
+ m2_bio_free_all = m2.bio_free_all
+
+ def __init__(self, bio):
+ self.bio = bio
+
+ def _ptr(self):
+ return self.bio
+
+ def __del__(self):
+ if self.bio is not None:
+ self.m2_bio_free_all(self.bio)
+
+
+class _SSLProxy:
+ """
+ The purpose of this class is to eliminate the __del__ method from
+ TLSProtocolWrapper, and thus letting it be garbage collected.
+ """
+
+ m2_ssl_free = m2.ssl_free
+
+ def __init__(self, ssl):
+ self.ssl = ssl
+
+ def _ptr(self):
+ return self.ssl
+
+ def __del__(self):
+ if self.ssl is not None:
+ self.m2_ssl_free(self.ssl)
+
+
+class TLSProtocolWrapper(ProtocolWrapper):
+ """
+ A SSL/TLS protocol wrapper to be used with Twisted. Typically
+ you would not use this class directly. Use connectTCP,
+ connectSSL, listenTCP, listenSSL functions defined above,
+ which will hook in this class.
+ """
+
+ implements(ITLSTransport)
+
+ def __init__(self, factory, wrappedProtocol, startPassThrough, client,
+ contextFactory, postConnectionCheck):
+ """
+ @param factory:
+ @param wrappedProtocol:
+ @param startPassThrough: If true we won't encrypt at all. Need to
+ call startTLS() later to switch to SSL/TLS.
+ @param client: True if this should be a client protocol.
+ @param contextFactory: Factory that creates SSL.Context objects.
+ The called function is getContext().
+ @param postConnectionCheck: The post connection check callback that
+ will be called just after connection has
+ been established but before any real data
+ has been exchanged. The first argument to
+ this function is an X509 object, the second
+ is the expected host name string.
+ """
+ #ProtocolWrapper.__init__(self, factory, wrappedProtocol)
+ #XXX: Twisted 2.0 has a new addition where the wrappingFactory is
+ # set as the factory of the wrappedProtocol. This is an issue
+ # as the wrap should be transparent. What we want is
+ # the factory of the wrappedProtocol to be the wrappedFactory and
+ # not the outer wrappingFactory. This is how it was implemented in
+ # Twisted 1.3
+ self.factory = factory
+ self.wrappedProtocol = wrappedProtocol
+
+ # wrappedProtocol == client/server instance
+ # factory.wrappedFactory == client/server factory
+
+ self.data = '' # Clear text to encrypt and send
+ self.encrypted = '' # Encrypted data we need to decrypt and pass on
+ self.tlsStarted = 0 # SSL/TLS mode or pass through
+ self.checked = 0 # Post connection check done or not
+ self.isClient = client
+ self.helloDone = 0 # True when hello has been sent
+ if postConnectionCheck is None:
+ self.postConnectionCheck = _alwaysSucceedsPostConnectionCheck
+ else:
+ self.postConnectionCheck = postConnectionCheck
+
+ if not startPassThrough:
+ self.startTLS(contextFactory.getContext())
+
+ def clear(self):
+ """
+ Clear this instance, after which it is ready for reuse.
+ """
+ if getattr(self, 'tlsStarted', 0):
+ self.sslBio = None
+ self.ssl = None
+ self.internalBio = None
+ self.networkBio = None
+ self.data = ''
+ self.encrypted = ''
+ self.tlsStarted = 0
+ self.checked = 0
+ self.isClient = 1
+ self.helloDone = 0
+ # We can reuse self.ctx and it will be deleted automatically
+ # when this instance dies
+
+ def startTLS(self, ctx):
+ """
+ Start SSL/TLS. If this is not called, this instance just passes data
+ through untouched.
+ """
+ # NOTE: This method signature must match the startTLS() method Twisted
+ # expects transports to have. This will be called automatically
+ # by Twisted in STARTTLS situations, for example with SMTP.
+ if self.tlsStarted:
+ raise Exception, 'TLS already started'
+
+ self.ctx = ctx
+
+ self.internalBio = m2.bio_new(m2.bio_s_bio())
+ m2.bio_set_write_buf_size(self.internalBio, 0)
+ self.networkBio = _BioProxy(m2.bio_new(m2.bio_s_bio()))
+ m2.bio_set_write_buf_size(self.networkBio._ptr(), 0)
+ m2.bio_make_bio_pair(self.internalBio, self.networkBio._ptr())
+
+ self.sslBio = _BioProxy(m2.bio_new(m2.bio_f_ssl()))
+
+ self.ssl = _SSLProxy(m2.ssl_new(self.ctx.ctx))
+
+ if self.isClient:
+ m2.ssl_set_connect_state(self.ssl._ptr())
+ else:
+ m2.ssl_set_accept_state(self.ssl._ptr())
+
+ m2.ssl_set_bio(self.ssl._ptr(), self.internalBio, self.internalBio)
+ m2.bio_set_ssl(self.sslBio._ptr(), self.ssl._ptr(), m2.bio_noclose)
+
+ # Need this for writes that are larger than BIO pair buffers
+ mode = m2.ssl_get_mode(self.ssl._ptr())
+ m2.ssl_set_mode(self.ssl._ptr(),
+ mode |
+ m2.SSL_MODE_ENABLE_PARTIAL_WRITE |
+ m2.SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER)
+
+ self.tlsStarted = 1
+
+ def write(self, data):
+ if not self.tlsStarted:
+ ProtocolWrapper.write(self, data)
+ return
+
+ try:
+ encryptedData = self._encrypt(data)
+ ProtocolWrapper.write(self, encryptedData)
+ self.helloDone = 1
+ except M2Crypto.BIO.BIOError, e:
+ # See http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS
+ # for the error codes returned by SSL_get_verify_result.
+ e.args = (m2.ssl_get_verify_result(self.ssl._ptr()), e.args[0])
+ raise e
+
+ def writeSequence(self, data):
+ if not self.tlsStarted:
+ ProtocolWrapper.writeSequence(self, ''.join(data))
+ return
+
+ self.write(''.join(data))
+
+ def loseConnection(self):
+ # XXX Do we need to do m2.ssl_shutdown(self.ssl._ptr())?
+ ProtocolWrapper.loseConnection(self)
+
+ def connectionMade(self):
+ ProtocolWrapper.connectionMade(self)
+ if self.tlsStarted and self.isClient and not self.helloDone:
+ self._clientHello()
+
+ def dataReceived(self, data):
+ if not self.tlsStarted:
+ ProtocolWrapper.dataReceived(self, data)
+ return
+
+ self.encrypted += data
+
+ try:
+ while 1:
+ decryptedData = self._decrypt()
+
+ self._check()
+
+ encryptedData = self._encrypt()
+ ProtocolWrapper.write(self, encryptedData)
+
+ ProtocolWrapper.dataReceived(self, decryptedData)
+
+ if decryptedData == '' and encryptedData == '':
+ break
+ except M2Crypto.BIO.BIOError, e:
+ # See http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS
+ # for the error codes returned by SSL_get_verify_result.
+ e.args = (m2.ssl_get_verify_result(self.ssl._ptr()), e.args[0])
+ raise e
+
+ def connectionLost(self, reason):
+ self.clear()
+ ProtocolWrapper.connectionLost(self, reason)
+
+ def _check(self):
+ if not self.checked and m2.ssl_is_init_finished(self.ssl._ptr()):
+ x509 = m2.ssl_get_peer_cert(self.ssl._ptr())
+ if x509 is not None:
+ x509 = X509.X509(x509, 1)
+ if self.isClient:
+ host = self.transport.addr[0]
+ else:
+ host = self.transport.getPeer().host
+ if not self.postConnectionCheck(x509, host):
+ raise Checker.SSLVerificationError, 'post connection check'
+ self.checked = 1
+
+ def _clientHello(self):
+ try:
+ # We rely on OpenSSL implicitly starting with client hello
+ # when we haven't yet established an SSL connection
+ encryptedData = self._encrypt(clientHello=1)
+ ProtocolWrapper.write(self, encryptedData)
+ self.helloDone = 1
+ except M2Crypto.BIO.BIOError, e:
+ # See http://www.openssl.org/docs/apps/verify.html#DIAGNOSTICS
+ # for the error codes returned by SSL_get_verify_result.
+ e.args = (m2.ssl_get_verify_result(self.ssl._ptr()), e.args[0])
+ raise e
+
+ def _encrypt(self, data='', clientHello=0):
+ # XXX near mirror image of _decrypt - refactor
+ encryptedData = ''
+ self.data += data
+ # Optimizations to reduce attribute accesses
+ sslBioPtr = self.sslBio._ptr()
+ networkBio = self.networkBio._ptr()
+ m2bio_ctrl_get_write_guarantee = m2.bio_ctrl_get_write_guarantee
+ m2bio_write = m2.bio_write
+ m2bio_should_retry = m2.bio_should_retry
+ m2bio_ctrl_pending = m2.bio_ctrl_pending
+ m2bio_read = m2.bio_read
+
+ while 1:
+ g = m2bio_ctrl_get_write_guarantee(sslBioPtr)
+ if g > 0 and self.data != '' or clientHello:
+ r = m2bio_write(sslBioPtr, self.data)
+ if r <= 0:
+ assert(m2bio_should_retry(sslBioPtr))
+ else:
+ assert(self.checked)
+ self.data = self.data[r:]
+
+ pending = m2bio_ctrl_pending(networkBio)
+ if pending:
+ d = m2bio_read(networkBio, pending)
+ if d is not None: # This is strange, but d can be None
+ encryptedData += d
+ else:
+ assert(m2bio_should_retry(networkBio))
+ else:
+ break
+ return encryptedData
+
+ def _decrypt(self, data=''):
+ # XXX near mirror image of _encrypt - refactor
+ self.encrypted += data
+ decryptedData = ''
+ # Optimizations to reduce attribute accesses
+ sslBioPtr = self.sslBio._ptr()
+ networkBio = self.networkBio._ptr()
+ m2bio_ctrl_get_write_guarantee = m2.bio_ctrl_get_write_guarantee
+ m2bio_write = m2.bio_write
+ m2bio_should_retry = m2.bio_should_retry
+ m2bio_ctrl_pending = m2.bio_ctrl_pending
+ m2bio_read = m2.bio_read
+
+ while 1:
+ g = m2bio_ctrl_get_write_guarantee(networkBio)
+ if g > 0 and self.encrypted != '':
+ r = m2bio_write(networkBio, self.encrypted)
+ if r <= 0:
+ assert(m2bio_should_retry(networkBio))
+ else:
+ self.encrypted = self.encrypted[r:]
+
+ pending = m2bio_ctrl_pending(sslBioPtr)
+ if pending:
+ d = m2bio_read(sslBioPtr, pending)
+ if d is not None: # This is strange, but d can be None
+ decryptedData += d
+ else:
+ assert(m2bio_should_retry(sslBioPtr))
+ else:
+ break
+
+ return decryptedData
diff --git a/M2Crypto/SSL/__init__.py b/M2Crypto/SSL/__init__.py
new file mode 100644
index 0000000..a013569
--- /dev/null
+++ b/M2Crypto/SSL/__init__.py
@@ -0,0 +1,28 @@
+"""M2Crypto SSL services.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+# M2Crypto
+from M2Crypto import m2
+
+class SSLError(Exception): pass
+m2.ssl_init(SSLError)
+
+# M2Crypto.SSL
+from Cipher import Cipher, Cipher_Stack
+from Context import Context
+from Connection import Connection
+from SSLServer import SSLServer, ForkingSSLServer, ThreadingSSLServer
+from ssl_dispatcher import ssl_dispatcher
+from timeout import timeout
+
+verify_none = m2.SSL_VERIFY_NONE
+verify_peer = m2.SSL_VERIFY_PEER
+verify_fail_if_no_peer_cert = m2.SSL_VERIFY_FAIL_IF_NO_PEER_CERT
+verify_client_once = m2.SSL_VERIFY_CLIENT_ONCE
+
+SSL_SENT_SHUTDOWN = m2.SSL_SENT_SHUTDOWN
+SSL_RECEIVED_SHUTDOWN = m2.SSL_RECEIVED_SHUTDOWN
+
+op_all = m2.SSL_OP_ALL
+op_no_sslv2 = m2.SSL_OP_NO_SSLv2
diff --git a/M2Crypto/SSL/cb.py b/M2Crypto/SSL/cb.py
new file mode 100644
index 0000000..c1710cc
--- /dev/null
+++ b/M2Crypto/SSL/cb.py
@@ -0,0 +1,83 @@
+"""SSL callbacks
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['unknown_issuer', 'ssl_verify_callback_stub', 'ssl_verify_callback',
+ 'ssl_verify_callback_allow_unknown_ca', 'ssl_info_callback']
+
+# Python
+import sys
+
+# M2Crypto
+import Context
+from M2Crypto import m2
+
+def ssl_verify_callback_stub(ssl_ctx_ptr, x509_ptr, errnum, errdepth, ok):
+ # Deprecated
+ return ok
+
+unknown_issuer = [
+ m2.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
+ m2.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
+ m2.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE,
+ m2.X509_V_ERR_CERT_UNTRUSTED,
+ ]
+
+def ssl_verify_callback(ssl_ctx_ptr, x509_ptr, errnum, errdepth, ok):
+ # Deprecated
+ ssl_ctx = Context.map()[long(ssl_ctx_ptr)]
+ if errnum in unknown_issuer:
+ if ssl_ctx.get_allow_unknown_ca():
+ sys.stderr.write("policy: %s: permitted...\n" % (m2.x509_get_verify_error(errnum)))
+ sys.stderr.flush()
+ ok = 1
+ # CRL checking goes here...
+ if ok:
+ if ssl_ctx.get_verify_depth() >= errdepth:
+ ok = 1
+ else:
+ ok = 0
+ return ok
+
+def ssl_verify_callback_allow_unknown_ca(ok, store):
+ errnum = store.get_error()
+ if errnum in unknown_issuer:
+ ok = 1
+ return ok
+
+# Cribbed from OpenSSL's apps/s_cb.c.
+def ssl_info_callback(where, ret, ssl_ptr):
+
+ w = where & ~m2.SSL_ST_MASK
+ if (w & m2.SSL_ST_CONNECT):
+ state = "SSL connect"
+ elif (w & m2.SSL_ST_ACCEPT):
+ state = "SSL accept"
+ else:
+ state = "SSL state unknown"
+
+ if (where & m2.SSL_CB_LOOP):
+ sys.stderr.write("LOOP: %s: %s\n" % (state, m2.ssl_get_state_v(ssl_ptr)))
+ sys.stderr.flush()
+ return
+
+ if (where & m2.SSL_CB_EXIT):
+ if not ret:
+ sys.stderr.write("FAILED: %s: %s\n" % (state, m2.ssl_get_state_v(ssl_ptr)))
+ sys.stderr.flush()
+ else:
+ sys.stderr.write("INFO: %s: %s\n" % (state, m2.ssl_get_state_v(ssl_ptr)))
+ sys.stderr.flush()
+ return
+
+ if (where & m2.SSL_CB_ALERT):
+ if (where & m2.SSL_CB_READ):
+ w = 'read'
+ else:
+ w = 'write'
+ sys.stderr.write("ALERT: %s: %s: %s\n" % \
+ (w, m2.ssl_get_alert_type_v(ret), m2.ssl_get_alert_desc_v(ret)))
+ sys.stderr.flush()
+ return
+
+
diff --git a/M2Crypto/SSL/ssl_dispatcher.py b/M2Crypto/SSL/ssl_dispatcher.py
new file mode 100644
index 0000000..1e8c875
--- /dev/null
+++ b/M2Crypto/SSL/ssl_dispatcher.py
@@ -0,0 +1,36 @@
+"""SSL dispatcher
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+__all__ = ['ssl_dispatcher']
+
+# Python
+import asyncore, socket
+
+# M2Crypto
+from Connection import Connection
+from M2Crypto import Err, m2
+
+
+class ssl_dispatcher(asyncore.dispatcher):
+
+ def create_socket(self, ssl_context):
+ self.family_and_type=socket.AF_INET, socket.SOCK_STREAM
+ self.ssl_ctx=ssl_context
+ self.socket=Connection(self.ssl_ctx)
+ #self.socket.setblocking(0)
+ self.add_channel()
+
+ def connect(self, addr):
+ self.socket.setblocking(1)
+ self.socket.connect(addr)
+ self.socket.setblocking(0)
+
+ def recv(self, buffer_size=4096):
+ """Receive data over SSL."""
+ return self.socket.recv(buffer_size)
+
+ def send(self, buffer):
+ """Send data over SSL."""
+ return self.socket.send(buffer)
+
diff --git a/M2Crypto/SSL/timeout.py b/M2Crypto/SSL/timeout.py
new file mode 100644
index 0000000..d76556d
--- /dev/null
+++ b/M2Crypto/SSL/timeout.py
@@ -0,0 +1,30 @@
+"""Support for SSL socket timeouts.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+
+Copyright 2008 Heikki Toivonen. All rights reserved.
+"""
+
+__all__ = ['DEFAULT_TIMEOUT', 'timeout', 'struct_to_timeout', 'struct_size']
+
+import struct
+from M2Crypto import m2
+
+DEFAULT_TIMEOUT = 600
+
+class timeout:
+
+ def __init__(self, sec=DEFAULT_TIMEOUT, microsec=0):
+ self.sec = sec
+ self.microsec = microsec
+
+ def pack(self):
+ return struct.pack('ll', self.sec, self.microsec)
+
+
+def struct_to_timeout(binstr):
+ (s, ms) = struct.unpack('ll', binstr)
+ return timeout(s, ms)
+
+def struct_size():
+ return struct.calcsize('ll')
diff --git a/M2Crypto/X509.py b/M2Crypto/X509.py
new file mode 100644
index 0000000..eef83fe
--- /dev/null
+++ b/M2Crypto/X509.py
@@ -0,0 +1,1104 @@
+"""M2Crypto wrapper for OpenSSL X509 API.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2004-2007 OSAF. All Rights Reserved.
+Author: Heikki Toivonen
+"""
+
+# M2Crypto
+from M2Crypto import ASN1, BIO, Err, EVP, util
+import m2
+
+FORMAT_DER = 0
+FORMAT_PEM = 1
+
+class X509Error(Exception): pass
+
+m2.x509_init(X509Error)
+
+V_OK = m2.X509_V_OK
+
+def new_extension(name, value, critical=0, _pyfree=1):
+ """
+ Create new X509_Extension instance.
+ """
+ if name == 'subjectKeyIdentifier' and \
+ value.strip('0123456789abcdefABCDEF:') is not '':
+ raise ValueError('value must be precomputed hash')
+ lhash = m2.x509v3_lhash()
+ ctx = m2.x509v3_set_conf_lhash(lhash)
+ x509_ext_ptr = m2.x509v3_ext_conf(lhash, ctx, name, value)
+ x509_ext = X509_Extension(x509_ext_ptr, _pyfree)
+ x509_ext.set_critical(critical)
+ return x509_ext
+
+
+class X509_Extension:
+ """
+ X509 Extension
+ """
+
+ m2_x509_extension_free = m2.x509_extension_free
+
+ def __init__(self, x509_ext_ptr=None, _pyfree=1):
+ self.x509_ext = x509_ext_ptr
+ self._pyfree = _pyfree
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0) and self.x509_ext:
+ self.m2_x509_extension_free(self.x509_ext)
+
+ def _ptr(self):
+ return self.x509_ext
+
+ def set_critical(self, critical=1):
+ """
+ Mark this extension critical or noncritical. By default an
+ extension is not critical.
+
+ @type critical: int
+ @param critical: Nonzero sets this extension as critical.
+ Calling this method without arguments will
+ set this extension to critical.
+ """
+ return m2.x509_extension_set_critical(self.x509_ext, critical)
+
+ def get_critical(self):
+ """
+ Return whether or not this is a critical extension.
+
+ @rtype: int
+ @return: Nonzero if this is a critical extension.
+ """
+ return m2.x509_extension_get_critical(self.x509_ext)
+
+ def get_name(self):
+ """
+ Get the extension name, for example 'subjectAltName'.
+ """
+ return m2.x509_extension_get_name(self.x509_ext)
+
+ def get_value(self, flag=0, indent=0):
+ """
+ Get the extension value, for example 'DNS:www.example.com'.
+
+ @param flag: Flag to control what and how to print.
+ @param indent: How many spaces to print before actual value.
+ """
+ buf=BIO.MemoryBuffer()
+ m2.x509_ext_print(buf.bio_ptr(), self.x509_ext, flag, indent)
+ return buf.read_all()
+
+
+class X509_Extension_Stack:
+ """
+ X509 Extension Stack
+
+ @warning: Do not modify the underlying OpenSSL stack
+ except through this interface, or use any OpenSSL functions that do so
+ indirectly. Doing so will get the OpenSSL stack and the internal pystack
+ of this class out of sync, leading to python memory leaks, exceptions
+ or even python crashes!
+ """
+
+ m2_sk_x509_extension_free = m2.sk_x509_extension_free
+
+ def __init__(self, stack=None, _pyfree=0):
+ if stack is not None:
+ self.stack = stack
+ self._pyfree = _pyfree
+ num = m2.sk_x509_extension_num(self.stack)
+ for i in range(num):
+ self.pystack.append(X509_Extension(m2.sk_x509_extension_value(self.stack, i),
+ _pyfree=_pyfree))
+ else:
+ self.stack = m2.sk_x509_extension_new_null()
+ self._pyfree = 1
+ self.pystack = [] # This must be kept in sync with self.stack
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_sk_x509_extension_free(self.stack)
+
+ def __len__(self):
+ assert m2.sk_x509_extension_num(self.stack) == len(self.pystack)
+ return len(self.pystack)
+
+ def __getitem__(self, idx):
+ return self.pystack[idx]
+
+ def __iter__(self):
+ return iter(self.pystack)
+
+ def _ptr(self):
+ return self.stack
+
+ def push(self, x509_ext):
+ """
+ Push X509_Extension object onto the stack.
+
+ @type x509_ext: M2Crypto.X509.X509_Extension
+ @param x509_ext: X509_Extension object to be pushed onto the stack.
+ @return: The number of extensions on the stack.
+ """
+ self.pystack.append(x509_ext)
+ ret = m2.sk_x509_extension_push(self.stack, x509_ext._ptr())
+ assert ret == len(self.pystack)
+ return ret
+
+ def pop(self):
+ """
+ Pop X509_Extension object from the stack.
+
+ @return: X509_Extension popped
+ """
+ x509_ext_ptr = m2.sk_x509_extension_pop(self.stack)
+ if x509_ext_ptr is None:
+ assert len(self.pystack) == 0
+ return None
+ return self.pystack.pop()
+
+
+class X509_Name_Entry:
+ """
+ X509 Name Entry
+ """
+
+ m2_x509_name_entry_free = m2.x509_name_entry_free
+
+ def __init__(self, x509_name_entry, _pyfree=0):
+ self.x509_name_entry = x509_name_entry
+ self._pyfree = _pyfree
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_x509_name_entry_free(self.x509_name_entry)
+
+ def _ptr(self):
+ return self.x509_name_entry
+
+ def set_object(self, asn1obj):
+ return m2.x509_name_entry_set_object(self.x509_name_entry,
+ asn1obj._ptr())
+
+ def set_data(self, data, type=ASN1.MBSTRING_ASC):
+ return m2.x509_name_entry_set_data(self.x509_name_entry,
+ type, data)
+
+ def get_object(self):
+ return ASN1.ASN1_Object(m2.x509_name_entry_get_object(self.x509_name_entry))
+
+ def get_data(self):
+ return ASN1.ASN1_String(m2.x509_name_entry_get_data(self.x509_name_entry))
+
+ def create_by_txt( self, field, type, entry, len):
+ return m2.x509_name_entry_create_by_txt(self.x509_name_entry._ptr(),
+ field, type, entry, len)
+
+
+class X509_Name:
+ """
+ X509 Name
+ """
+
+ nid = {'C' : m2.NID_countryName,
+ 'SP' : m2.NID_stateOrProvinceName,
+ 'ST' : m2.NID_stateOrProvinceName,
+ 'stateOrProvinceName' : m2.NID_stateOrProvinceName,
+ 'L' : m2.NID_localityName,
+ 'localityName' : m2.NID_localityName,
+ 'O' : m2.NID_organizationName,
+ 'organizationName' : m2.NID_organizationName,
+ 'OU' : m2.NID_organizationalUnitName,
+ 'organizationUnitName' : m2.NID_organizationalUnitName,
+ 'CN' : m2.NID_commonName,
+ 'commonName' : m2.NID_commonName,
+ 'Email' : m2.NID_pkcs9_emailAddress,
+ 'emailAddress' : m2.NID_pkcs9_emailAddress,
+ 'serialNumber' : m2.NID_serialNumber,
+ 'SN' : m2.NID_surname,
+ 'surname' : m2.NID_surname,
+ 'GN' : m2.NID_givenName,
+ 'givenName' : m2.NID_givenName
+ }
+
+ m2_x509_name_free = m2.x509_name_free
+
+ def __init__(self, x509_name=None, _pyfree=0):
+ if x509_name is not None:
+ assert m2.x509_name_type_check(x509_name), "'x509_name' type error"
+ self.x509_name = x509_name
+ self._pyfree = _pyfree
+ else:
+ self.x509_name = m2.x509_name_new ()
+ self._pyfree = 1
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_x509_name_free(self.x509_name)
+
+ def __str__(self):
+ assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error"
+ return m2.x509_name_oneline(self.x509_name)
+
+ def __getattr__(self, attr):
+ if attr in self.nid:
+ assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error"
+ return m2.x509_name_by_nid(self.x509_name, self.nid[attr])
+
+ if attr in self.__dict__:
+ return self.__dict__[attr]
+
+ raise AttributeError, (self, attr)
+
+ def __setattr__(self, attr, value):
+ if attr in self.nid:
+ assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error"
+ return m2.x509_name_set_by_nid(self.x509_name, self.nid[attr], value)
+
+ self.__dict__[attr] = value
+
+ def __len__(self):
+ return m2.x509_name_entry_count(self.x509_name)
+
+ def __getitem__(self, idx):
+ if not 0 <= idx < self.entry_count():
+ raise IndexError("index out of range")
+ return X509_Name_Entry(m2.x509_name_get_entry(self.x509_name, idx))
+
+ def __iter__(self):
+ for i in xrange(self.entry_count()):
+ yield self[i]
+
+ def _ptr(self):
+ #assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error"
+ return self.x509_name
+
+ def add_entry_by_txt(self, field, type, entry, len, loc, set):
+ return m2.x509_name_add_entry_by_txt(self.x509_name, field, type,
+ entry, len, loc, set )
+
+ def entry_count( self ):
+ return m2.x509_name_entry_count( self.x509_name )
+
+ def get_entries_by_nid(self, nid):
+ ret = []
+ lastpos = -1
+
+ while True:
+ lastpos = m2.x509_name_get_index_by_nid(self.x509_name, nid,
+ lastpos)
+ if lastpos == -1:
+ break
+
+ ret.append(self[lastpos])
+
+ return ret
+
+ def as_text(self, indent=0, flags=m2.XN_FLAG_COMPAT):
+ """
+ as_text returns the name as a string.
+
+ @param indent: Each line in multiline format is indented
+ by this many spaces.
+ @param flags: Flags that control how the output should be formatted.
+ """
+ assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error"
+ buf=BIO.MemoryBuffer()
+ m2.x509_name_print_ex(buf.bio_ptr(), self.x509_name, indent, flags)
+ return buf.read_all()
+
+ def as_der(self):
+ assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error"
+ return m2.x509_name_get_der(self.x509_name)
+
+ def as_hash(self):
+ assert m2.x509_name_type_check(self.x509_name), "'x509_name' type error"
+ return m2.x509_name_hash(self.x509_name)
+
+class X509:
+ """
+ X.509 Certificate
+ """
+
+ m2_x509_free = m2.x509_free
+
+ def __init__(self, x509=None, _pyfree=0):
+ if x509 is not None:
+ assert m2.x509_type_check(x509), "'x509' type error"
+ self.x509 = x509
+ self._pyfree = _pyfree
+ else:
+ self.x509 = m2.x509_new ()
+ self._pyfree = 1
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_x509_free(self.x509)
+
+ def _ptr(self):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return self.x509
+
+ def as_text(self):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ buf=BIO.MemoryBuffer()
+ m2.x509_print(buf.bio_ptr(), self.x509)
+ return buf.read_all()
+
+ def as_der(self):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return m2.i2d_x509(self.x509)
+
+ def as_pem(self):
+ buf=BIO.MemoryBuffer()
+ m2.x509_write_pem(buf.bio_ptr(), self.x509)
+ return buf.read_all()
+
+ def save_pem(self, filename):
+ """
+ save_pem
+ """
+ bio=BIO.openfile(filename, 'wb')
+ return m2.x509_write_pem(bio.bio_ptr(), self.x509)
+
+ def save(self, filename, format=FORMAT_PEM):
+ """
+ Saves X.509 certificate to a file. Default output
+ format is PEM.
+
+ @type filename: string
+ @param filename: Name of the file the cert will be saved to.
+ @type format: int
+ @param format: Controls what output format is used to save the cert.
+ Either FORMAT_PEM or FORMAT_DER to save in PEM or DER format.
+ Raises a ValueError if an unknow format is used.
+ """
+ bio = BIO.openfile(filename, 'wb')
+ if format == FORMAT_PEM:
+ return m2.x509_write_pem(bio.bio_ptr(), self.x509)
+ elif format == FORMAT_DER:
+ return m2.i2d_x509_bio(bio.bio_ptr(), self.x509)
+ else:
+ raise ValueError("Unknown filetype. Must be either FORMAT_PEM or FORMAT_DER")
+
+ def set_version(self, version):
+ """
+ Set version.
+
+ @type version: int
+ @param version: Version number.
+ @rtype: int
+ @return: Returns 0 on failure.
+ """
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return m2.x509_set_version(self.x509, version)
+
+ def set_not_before(self, asn1_utctime):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return m2.x509_set_not_before(self.x509, asn1_utctime._ptr())
+
+ def set_not_after(self, asn1_utctime):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return m2.x509_set_not_after(self.x509, asn1_utctime._ptr())
+
+ def set_subject_name(self, name):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return m2.x509_set_subject_name(self.x509, name.x509_name)
+
+ def set_issuer_name(self, name):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return m2.x509_set_issuer_name(self.x509, name.x509_name)
+
+ def get_version(self):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return m2.x509_get_version(self.x509)
+
+ def get_serial_number(self):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ asn1_integer = m2.x509_get_serial_number(self.x509)
+ return m2.asn1_integer_get(asn1_integer)
+
+ def set_serial_number(self, serial):
+ """
+ Set serial number.
+
+ @type serial: int
+ @param serial: Serial number.
+ """
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ # This "magically" changes serial since asn1_integer
+ # is C pointer to x509's internal serial number.
+ asn1_integer = m2.x509_get_serial_number(self.x509)
+ return m2.asn1_integer_set(asn1_integer, serial)
+ # XXX Or should I do this?
+ #asn1_integer = m2.asn1_integer_new()
+ #m2.asn1_integer_set(asn1_integer, serial)
+ #return m2.x509_set_serial_number(self.x509, asn1_integer)
+
+ def get_not_before(self):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return ASN1.ASN1_UTCTIME(m2.x509_get_not_before(self.x509))
+
+ def get_not_after(self):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return ASN1.ASN1_UTCTIME(m2.x509_get_not_after(self.x509))
+
+ def get_pubkey(self):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return EVP.PKey(m2.x509_get_pubkey(self.x509), _pyfree=1)
+
+ def set_pubkey(self, pkey):
+ """
+ Set the public key for the certificate
+
+ @type pkey: EVP_PKEY
+ @param pkey: Public key
+ """
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return m2.x509_set_pubkey(self.x509, pkey.pkey)
+
+ def get_issuer(self):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return X509_Name(m2.x509_get_issuer_name(self.x509))
+
+ def set_issuer(self, name):
+ """
+ Set issuer name.
+
+ @type name: X509_Name
+ @param name: subjectName field.
+ """
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return m2.x509_set_issuer_name(self.x509, name.x509_name)
+
+ def get_subject(self):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return X509_Name(m2.x509_get_subject_name(self.x509))
+
+ def set_subject(self, name):
+ """
+ Set subject name.
+
+ @type name: X509_Name
+ @param name: subjectName field.
+ """
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return m2.x509_set_subject_name(self.x509, name.x509_name)
+
+ def add_ext(self, ext):
+ """
+ Add X509 extension to this certificate.
+
+ @type ext: X509_Extension
+ @param ext: Extension
+ """
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ return m2.x509_add_ext(self.x509, ext.x509_ext, -1)
+
+ def get_ext(self, name):
+ """
+ Get X509 extension by name.
+
+ @type name: Name of the extension
+ @param name: str
+ @return: X509_Extension
+ """
+ # Optimizations to reduce attribute accesses
+ m2x509_get_ext = m2.x509_get_ext
+ m2x509_extension_get_name = m2.x509_extension_get_name
+ x509 = self.x509
+
+ for i in range(m2.x509_get_ext_count(x509)):
+ extPtr = m2x509_get_ext(x509, i)
+ if m2x509_extension_get_name(extPtr) == name:
+ return X509_Extension(extPtr, _pyfree=0)
+
+ raise LookupError
+
+ def get_ext_at(self, index):
+ """
+ Get X509 extension by index.
+
+ @type index: Name of the extension
+ @param index: int
+ @return: X509_Extension
+ """
+ if index < 0 or index >= self.get_ext_count():
+ raise IndexError
+
+ return X509_Extension(m2.x509_get_ext(self.x509, index),
+ _pyfree=0)
+
+ def get_ext_count(self):
+ """
+ Get X509 extension count.
+ """
+ return m2.x509_get_ext_count(self.x509)
+
+ def sign(self, pkey, md):
+ """
+ Sign the certificate.
+
+ @type pkey: EVP_PKEY
+ @param pkey: Public key
+ @type md: str
+ @param md: Message digest algorithm to use for signing,
+ for example 'sha1'.
+ """
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ mda = getattr(m2, md, None)
+ if mda is None:
+ raise ValueError, ('unknown message digest', md)
+ return m2.x509_sign(self.x509, pkey.pkey, mda())
+
+ def verify(self, pkey=None):
+ assert m2.x509_type_check(self.x509), "'x509' type error"
+ if pkey:
+ return m2.x509_verify(self.x509, pkey.pkey)
+ else:
+ return m2.x509_verify(self.x509, self.get_pubkey().pkey)
+
+ def check_ca(self):
+ """
+ Check if the certificate is a Certificate Authority (CA) certificate.
+
+ @return: 0 if the certificate is not CA, nonzero otherwise.
+
+ @requires: OpenSSL 0.9.8 or newer
+ """
+ return m2.x509_check_ca(self.x509)
+
+ def check_purpose(self, id, ca):
+ """
+ Check if the certificate's purpose matches the asked purpose.
+
+ @param id: Purpose id. See X509_PURPOSE_* constants.
+ @param ca: 1 if the certificate should be CA, 0 otherwise.
+ @return: 0 if the certificate purpose does not match, nonzero otherwise.
+ """
+ return m2.x509_check_purpose(self.x509, id, ca)
+
+ def get_fingerprint(self, md='md5'):
+ """
+ Get the fingerprint of the certificate.
+
+ @param md: Message digest algorithm to use.
+ @return: String containing the fingerprint in hex format.
+ """
+ der = self.as_der()
+ md = EVP.MessageDigest(md)
+ md.update(der)
+ digest = md.final()
+ return hex(util.octx_to_num(digest))[2:-1].upper()
+
+def load_cert(file, format=FORMAT_PEM):
+ """
+ Load certificate from file.
+
+ @type file: string
+ @param file: Name of file containing certificate in either DER or PEM format.
+ @type format: int, either FORMAT_PEM or FORMAT_DER
+ @param format: Describes the format of the file to be loaded, either PEM or DER.
+
+ @rtype: M2Crypto.X509.X509
+ @return: M2Crypto.X509.X509 object.
+ """
+ bio = BIO.openfile(file)
+ if format == FORMAT_PEM:
+ return load_cert_bio(bio)
+ elif format == FORMAT_DER:
+ cptr = m2.d2i_x509(bio._ptr())
+ if cptr is None:
+ raise X509Error(Err.get_error())
+ return X509(cptr, _pyfree=1)
+ else:
+ raise ValueError("Unknown format. Must be either FORMAT_DER or FORMAT_PEM")
+
+def load_cert_bio(bio, format=FORMAT_PEM):
+ """
+ Load certificate from a bio.
+
+ @type bio: M2Crypto.BIO.BIO
+ @param bio: BIO pointing at a certificate in either DER or PEM format.
+ @type format: int, either FORMAT_PEM or FORMAT_DER
+ @param format: Describes the format of the cert to be loaded, either PEM or DER.
+
+ @rtype: M2Crypto.X509.X509
+ @return: M2Crypto.X509.X509 object.
+ """
+ if format == FORMAT_PEM:
+ cptr = m2.x509_read_pem(bio._ptr())
+ elif format == FORMAT_DER:
+ cptr = m2.d2i_x509(bio._ptr())
+ else:
+ raise ValueError("Unknown format. Must be either FORMAT_DER or FORMAT_PEM")
+ if cptr is None:
+ raise X509Error(Err.get_error())
+ return X509(cptr, _pyfree=1)
+
+def load_cert_string(string, format=FORMAT_PEM):
+ """
+ Load certificate from a string.
+
+ @type string: string
+ @param string: String containing a certificate in either DER or PEM format.
+ @type format: int, either FORMAT_PEM or FORMAT_DER
+ @param format: Describes the format of the cert to be loaded, either PEM or DER.
+
+ @rtype: M2Crypto.X509.X509
+ @return: M2Crypto.X509.X509 object.
+ """
+ bio = BIO.MemoryBuffer(string)
+ return load_cert_bio(bio, format)
+
+def load_cert_der_string(string):
+ """
+ Load certificate from a string.
+
+ @type string: string
+ @param string: String containing a certificate in DER format.
+
+ @rtype: M2Crypto.X509.X509
+ @return: M2Crypto.X509.X509 object.
+ """
+ bio = BIO.MemoryBuffer(string)
+ cptr = m2.d2i_x509(bio._ptr())
+ if cptr is None:
+ raise X509Error(Err.get_error())
+ return X509(cptr, _pyfree=1)
+
+class X509_Store_Context:
+ """
+ X509 Store Context
+ """
+
+ m2_x509_store_ctx_free = m2.x509_store_ctx_free
+
+ def __init__(self, x509_store_ctx, _pyfree=0):
+ self.ctx = x509_store_ctx
+ self._pyfree = _pyfree
+
+ def __del__(self):
+ if self._pyfree:
+ self.m2_x509_store_ctx_free(self.ctx)
+
+ def _ptr(self):
+ return self.ctx
+
+ def get_current_cert(self):
+ """
+ Get current X.509 certificate.
+
+ @warning: The returned certificate is NOT refcounted, so you can not
+ rely on it being valid once the store context goes away or is modified.
+ """
+ return X509(m2.x509_store_ctx_get_current_cert(self.ctx), _pyfree=0)
+
+ def get_error(self):
+ """
+ Get error code.
+ """
+ return m2.x509_store_ctx_get_error(self.ctx)
+
+ def get_error_depth(self):
+ """
+ Get error depth.
+ """
+ return m2.x509_store_ctx_get_error_depth(self.ctx)
+
+ def get1_chain(self):
+ """
+ Get certificate chain.
+
+ @return: Reference counted (i.e. safe to use even after the store
+ context goes away) stack of certificates in the chain.
+ @rtype: X509_Stack
+ """
+ return X509_Stack(m2.x509_store_ctx_get1_chain(self.ctx), 1, 1)
+
+
+class X509_Store:
+ """
+ X509 Store
+ """
+
+ m2_x509_store_free = m2.x509_store_free
+
+ def __init__(self, store=None, _pyfree=0):
+ if store is not None:
+ self.store = store
+ self._pyfree = _pyfree
+ else:
+ self.store = m2.x509_store_new()
+ self._pyfree = 1
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_x509_store_free(self.store)
+
+ def _ptr(self):
+ return self.store
+
+ def load_info(self, file):
+ ret = m2.x509_store_load_locations(self.store, file)
+ if ret < 1:
+ raise X509Error(Err.get_error())
+ return ret
+
+ load_locations = load_info
+
+ def add_x509(self, x509):
+ assert isinstance(x509, X509)
+ return m2.x509_store_add_cert(self.store, x509._ptr())
+
+ add_cert = add_x509
+
+
+class X509_Stack:
+ """
+ X509 Stack
+
+ @warning: Do not modify the underlying OpenSSL stack
+ except through this interface, or use any OpenSSL functions that do so
+ indirectly. Doing so will get the OpenSSL stack and the internal pystack
+ of this class out of sync, leading to python memory leaks, exceptions
+ or even python crashes!
+ """
+
+ m2_sk_x509_free = m2.sk_x509_free
+
+ def __init__(self, stack=None, _pyfree=0, _pyfree_x509=0):
+ if stack is not None:
+ self.stack = stack
+ self._pyfree = _pyfree
+ self.pystack = [] # This must be kept in sync with self.stack
+ num = m2.sk_x509_num(self.stack)
+ for i in range(num):
+ self.pystack.append(X509(m2.sk_x509_value(self.stack, i),
+ _pyfree=_pyfree_x509))
+ else:
+ self.stack = m2.sk_x509_new_null()
+ self._pyfree = 1
+ self.pystack = [] # This must be kept in sync with self.stack
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_sk_x509_free(self.stack)
+
+ def __len__(self):
+ assert m2.sk_x509_num(self.stack) == len(self.pystack)
+ return len(self.pystack)
+
+ def __getitem__(self, idx):
+ return self.pystack[idx]
+
+ def __iter__(self):
+ return iter(self.pystack)
+
+ def _ptr(self):
+ return self.stack
+
+ def push(self, x509):
+ """
+ push an X509 certificate onto the stack.
+
+ @param x509: X509 object.
+ @return: The number of X509 objects currently on the stack.
+ """
+ assert isinstance(x509, X509)
+ self.pystack.append(x509)
+ ret = m2.sk_x509_push(self.stack, x509._ptr())
+ assert ret == len(self.pystack)
+ return ret
+
+ def pop(self):
+ """
+ pop a certificate from the stack.
+
+ @return: X509 object that was popped, or None if there is nothing
+ to pop.
+ """
+ x509_ptr = m2.sk_x509_pop(self.stack)
+ if x509_ptr is None:
+ assert len(self.pystack) == 0
+ return None
+ return self.pystack.pop()
+
+ def as_der(self):
+ """
+ Return the stack as a DER encoded string
+ """
+ return m2.get_der_encoding_stack(self.stack)
+
+
+def new_stack_from_der(der_string):
+ """
+ Create a new X509_Stack from DER string.
+
+ @return: X509_Stack
+ """
+ stack_ptr = m2.make_stack_from_der_sequence(der_string)
+ if stack_ptr is None:
+ raise X509Error(Err.get_error())
+ return X509_Stack(stack_ptr, 1, 1)
+
+
+class Request:
+ """
+ X509 Certificate Request.
+ """
+
+ m2_x509_req_free = m2.x509_req_free
+
+ def __init__(self, req=None, _pyfree=0):
+ if req is not None:
+ self.req = req
+ self._pyfree = _pyfree
+ else:
+ self.req = m2.x509_req_new()
+ self._pyfree = 1
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_x509_req_free(self.req)
+
+ def as_text(self):
+ buf=BIO.MemoryBuffer()
+ m2.x509_req_print(buf.bio_ptr(), self.req)
+ return buf.read_all()
+
+ def as_pem(self):
+ buf=BIO.MemoryBuffer()
+ m2.x509_req_write_pem(buf.bio_ptr(), self.req)
+ return buf.read_all()
+
+ def as_der(self):
+ buf = BIO.MemoryBuffer()
+ m2.i2d_x509_req_bio(buf.bio_ptr(), self.req)
+ return buf.read_all()
+
+ def save_pem(self, filename):
+ bio=BIO.openfile(filename, 'wb')
+ return m2.x509_req_write_pem(bio.bio_ptr(), self.req)
+
+ def save(self, filename, format=FORMAT_PEM):
+ """
+ Saves X.509 certificate request to a file. Default output
+ format is PEM.
+
+ @type filename: string
+ @param filename: Name of the file the request will be saved to.
+ @type format: int
+ @param format: Controls what output format is used to save the request.
+ Either FORMAT_PEM or FORMAT_DER to save in PEM or DER format.
+ Raises ValueError if an unknown format is used.
+ """
+ bio = BIO.openfile(filename, 'wb')
+ if format == FORMAT_PEM:
+ return m2.x509_req_write_pem(bio.bio_ptr(), self.req)
+ elif format == FORMAT_DER:
+ return m2.i2d_x509_req_bio(bio.bio_ptr(), self.req)
+ else:
+ raise ValueError("Unknown filetype. Must be either FORMAT_DER or FORMAT_PEM")
+
+ def get_pubkey(self):
+ """
+ Get the public key for the request.
+
+ @rtype: EVP_PKEY
+ @return: Public key from the request.
+ """
+ return EVP.PKey(m2.x509_req_get_pubkey(self.req), _pyfree=1)
+
+ def set_pubkey(self, pkey):
+ """
+ Set the public key for the request.
+
+ @type pkey: EVP_PKEY
+ @param pkey: Public key
+
+ @rtype: int
+ @return: Return 1 for success and 0 for failure.
+ """
+ return m2.x509_req_set_pubkey( self.req, pkey.pkey )
+
+ def get_version(self):
+ """
+ Get version.
+
+ @rtype: int
+ @return: Returns version.
+ """
+ return m2.x509_req_get_version(self.req)
+
+ def set_version(self, version):
+ """
+ Set version.
+
+ @type version: int
+ @param version: Version number.
+ @rtype: int
+ @return: Returns 0 on failure.
+ """
+ return m2.x509_req_set_version( self.req, version )
+
+ def get_subject(self):
+ return X509_Name(m2.x509_req_get_subject_name( self.req ))
+
+ def set_subject_name(self, name):
+ """
+ Set subject name.
+
+ @type name: X509_Name
+ @param name: subjectName field.
+ """
+ return m2.x509_req_set_subject_name( self.req, name.x509_name )
+
+ set_subject = set_subject_name
+
+ def add_extensions(self, ext_stack):
+ """
+ Add X509 extensions to this request.
+
+ @type ext_stack: X509_Extension_Stack
+ @param ext_stack: Stack of extensions to add.
+ """
+ return m2.x509_req_add_extensions(self.req, ext_stack._ptr())
+
+ def verify(self, pkey):
+ return m2.x509_req_verify(self.req, pkey.pkey)
+
+ def sign(self, pkey, md):
+ mda = getattr(m2, md, None)
+ if mda is None:
+ raise ValueError, ('unknown message digest', md)
+ return m2.x509_req_sign(self.req, pkey.pkey, mda())
+
+
+def load_request(file, format=FORMAT_PEM):
+ """
+ Load certificate request from file.
+
+ @type file: string
+ @param file: Name of file containing certificate request in either PEM or DER format.
+ @type format: int, either FORMAT_PEM or FORMAT_DER
+ @param format: Describes the format of the file to be loaded, either PEM or DER.
+
+ @rtype: M2Crypto.X509.Request
+ @return: M2Crypto.X509.Request object.
+ """
+ f=BIO.openfile(file)
+ if format == FORMAT_PEM:
+ cptr=m2.x509_req_read_pem(f.bio_ptr())
+ elif format == FORMAT_DER:
+ cptr = m2.d2i_x509_req(f.bio_ptr())
+ else:
+ raise ValueError("Unknown filetype. Must be either FORMAT_PEM or FORMAT_DER")
+ f.close()
+ if cptr is None:
+ raise X509Error(Err.get_error())
+ return Request(cptr, 1)
+
+def load_request_bio(bio, format=FORMAT_PEM):
+ """
+ Load certificate request from a bio.
+
+ @type bio: M2Crypto.BIO.BIO
+ @param bio: BIO pointing at a certificate request in either DER or PEM format.
+ @type format: int, either FORMAT_PEM or FORMAT_DER
+ @param format: Describes the format of the request to be loaded, either PEM or DER.
+
+ @rtype: M2Crypto.X509.Request
+ @return: M2Crypto.X509.Request object.
+ """
+ if format == FORMAT_PEM:
+ cptr = m2.x509_req_read_pem(bio._ptr())
+ elif format == FORMAT_DER:
+ cptr = m2.d2i_x509_req(bio._ptr())
+ else:
+ raise ValueError("Unknown format. Must be either FORMAT_DER or FORMAT_PEM")
+ if cptr is None:
+ raise X509Error(Err.get_error())
+ return Request(cptr, _pyfree=1)
+
+def load_request_string(string, format=FORMAT_PEM):
+ """
+ Load certificate request from a string.
+
+ @type string: string
+ @param string: String containing a certificate request in either DER or PEM format.
+ @type format: int, either FORMAT_PEM or FORMAT_DER
+ @param format: Describes the format of the request to be loaded, either PEM or DER.
+
+ @rtype: M2Crypto.X509.Request
+ @return: M2Crypto.X509.Request object.
+ """
+ bio = BIO.MemoryBuffer(string)
+ return load_request_bio(bio, format)
+
+def load_request_der_string(string):
+ """
+ Load certificate request from a string.
+
+ @type string: string
+ @param string: String containing a certificate request in DER format.
+
+ @rtype: M2Crypto.X509.Request
+ @return: M2Crypto.X509.Request object.
+ """
+ bio = BIO.MemoryBuffer(string)
+ return load_request_bio(bio, FORMAT_DER)
+
+
+class CRL:
+ """
+ X509 Certificate Revocation List
+ """
+
+ m2_x509_crl_free = m2.x509_crl_free
+
+ def __init__(self, crl=None, _pyfree=0):
+ if crl is not None:
+ self.crl = crl
+ self._pyfree = _pyfree
+ else:
+ self.crl = m2.x509_crl_new()
+ self._pyfree = 1
+
+ def __del__(self):
+ if getattr(self, '_pyfree', 0):
+ self.m2_x509_crl_free(self.crl)
+
+ def as_text(self):
+ """
+ Return CRL in PEM format in a string.
+
+ @rtype: string
+ @return: String containing the CRL in PEM format.
+ """
+ buf=BIO.MemoryBuffer()
+ m2.x509_crl_print(buf.bio_ptr(), self.crl)
+ return buf.read_all()
+
+
+def load_crl(file):
+ """
+ Load CRL from file.
+
+ @type file: string
+ @param file: Name of file containing CRL in PEM format.
+
+ @rtype: M2Crypto.X509.CRL
+ @return: M2Crypto.X509.CRL object.
+ """
+ f=BIO.openfile(file)
+ cptr=m2.x509_crl_read_pem(f.bio_ptr())
+ f.close()
+ if cptr is None:
+ raise X509Error(Err.get_error())
+ return CRL(cptr, 1)
+
+
diff --git a/M2Crypto/__init__.py b/M2Crypto/__init__.py
new file mode 100644
index 0000000..e7acfe7
--- /dev/null
+++ b/M2Crypto/__init__.py
@@ -0,0 +1,60 @@
+"""
+M2Crypto is the most complete Python wrapper for OpenSSL featuring RSA, DSA,
+DH, EC, HMACs, message digests, symmetric ciphers (including AES); SSL
+functionality to implement clients and servers; HTTPS extensions to
+Python's httplib, urllib, and xmlrpclib; unforgeable HMAC'ing AuthCookies
+for web session management; FTP/TLS client and server; S/MIME; ZServerSSL:
+A HTTPS server for Zope and ZSmime: An S/MIME messenger for Zope.
+M2Crypto can also be used to provide SSL for Twisted. Smartcards supported
+through the Engine interface.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2004-2007 OSAF. All Rights Reserved.
+
+Copyright 2008-2011 Heikki Toivonen. All rights reserved.
+"""
+
+version_info = (0, 21, 1)
+version = '.'.join([str(_v) for _v in version_info])
+
+import __m2crypto
+import m2
+import ASN1
+import AuthCookie
+import BIO
+import BN
+import Rand
+import DH
+import DSA
+if m2.OPENSSL_VERSION_NUMBER >= 0x90800F and m2.OPENSSL_NO_EC == 0:
+ import EC
+import Err
+import Engine
+import EVP
+import RSA
+import RC4
+import SMIME
+import SSL
+import X509
+import PGP
+import m2urllib
+# Backwards compatibility.
+urllib2 = m2urllib
+
+import sys
+if sys.version_info >= (2,4):
+ import m2urllib2
+del sys
+
+import ftpslib
+import httpslib
+import m2xmlrpclib
+import threading
+import util
+
+encrypt=1
+decrypt=0
+
+__m2crypto.lib_init()
diff --git a/M2Crypto/callback.py b/M2Crypto/callback.py
new file mode 100644
index 0000000..46613ca
--- /dev/null
+++ b/M2Crypto/callback.py
@@ -0,0 +1,9 @@
+"""Deprecated, use the util module instead.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import warnings
+
+warnings.warn('Use the util module instead', DeprecationWarning)
+
+from util import genparam_callback, passphrase_callback
diff --git a/M2Crypto/ftpslib.py b/M2Crypto/ftpslib.py
new file mode 100644
index 0000000..b7d82fd
--- /dev/null
+++ b/M2Crypto/ftpslib.py
@@ -0,0 +1,94 @@
+"""M2Crypto client-side FTP/TLS.
+
+This implementation complies with draft-murray-auth-ftp-ssl-07.txt.
+
+Example:
+
+>>> from M2Crypto import ftpslib
+>>> f = ftpslib.FTP_TLS()
+>>> f.connect('', 9021)
+'220 spinnaker.dyndns.org M2Crypto (Medusa) FTP/TLS server v0.07 ready.'
+>>> f.auth_tls()
+>>> f.set_pasv(0)
+>>> f.login('ftp', 'ngps@')
+'230 Ok.'
+>>> f.retrlines('LIST')
+-rw-rw-r-- 1 0 198 2326 Jul 3 1996 apache_pb.gif
+drwxrwxr-x 7 0 198 1536 Oct 10 2000 manual
+drwxrwxr-x 2 0 198 512 Oct 31 2000 modpy
+drwxrwxr-x 2 0 198 512 Oct 31 2000 bobo
+drwxr-xr-x 2 0 198 14336 May 28 15:54 postgresql
+drwxr-xr-x 4 100 198 512 May 16 17:19 home
+drwxr-xr-x 7 100 100 3584 Sep 23 2000 openacs
+drwxr-xr-x 10 0 0 512 Aug 5 2000 python1.5
+-rw-r--r-- 1 100 198 326 Jul 29 03:29 index.html
+drwxr-xr-x 12 0 0 512 May 31 17:08 python2.1
+'226 Transfer complete'
+>>> f.quit()
+'221 Goodbye.'
+>>>
+
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# Python
+from ftplib import *
+from ftplib import parse150, parse227
+from ftplib import error_reply, error_temp, error_perm, error_proto
+import socket, time
+
+# M2Crypto
+import SSL
+
+DEFAULT_PROTOCOL='sslv23'
+
+class FTP_TLS(FTP):
+
+ """Python OO interface to client-side FTP/TLS."""
+
+ def __init__(self, host=None, ssl_ctx=None):
+ """Initialise the client. If 'host' is supplied, connect to it."""
+ if ssl_ctx is not None:
+ self.ssl_ctx = ssl_ctx
+ else:
+ self.ssl_ctx = SSL.Context(DEFAULT_PROTOCOL)
+ FTP.__init__(self, host)
+ self.prot = 0
+
+ def auth_tls(self):
+ """Secure the control connection per AUTH TLS, aka AUTH TLS-C."""
+ self.voidcmd('AUTH TLS')
+ s = SSL.Connection(self.ssl_ctx, self.sock)
+ s.setup_ssl()
+ s.set_connect_state()
+ s.connect_ssl()
+ self.sock = s
+ self.file = self.sock.makefile()
+
+ def auth_ssl(self):
+ """Secure the control connection per AUTH SSL, aka AUTH TLS-P."""
+ raise NotImplementedError
+
+ def prot_p(self):
+ """Set up secure data connection."""
+ self.voidcmd('PBSZ 0')
+ self.voidcmd('PROT P')
+ self.prot = 1
+
+ def prot_c(self):
+ """Set up data connection in the clear."""
+ self.voidcmd('PROT C')
+ self.prot = 0
+
+ def ntransfercmd(self, cmd, rest=None):
+ """Initiate a data transfer."""
+ conn, size = FTP.ntransfercmd(self, cmd, rest)
+ if self.prot:
+ conn = SSL.Connection(self.ssl_ctx, conn)
+ conn.setup_ssl()
+ conn.set_connect_state()
+ conn.set_session(self.sock.get_session())
+ conn.connect_ssl()
+ return conn, size
+
+
diff --git a/M2Crypto/httpslib.py b/M2Crypto/httpslib.py
new file mode 100644
index 0000000..c1bfd78
--- /dev/null
+++ b/M2Crypto/httpslib.py
@@ -0,0 +1,212 @@
+"""M2Crypto support for Python's httplib.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+import string, sys
+import socket
+from urlparse import urlsplit, urlunsplit
+import base64
+
+from httplib import *
+from httplib import HTTPS_PORT # This is not imported with just '*'
+import SSL
+
+class HTTPSConnection(HTTPConnection):
+
+ """
+ This class allows communication via SSL using M2Crypto.
+ """
+
+ default_port = HTTPS_PORT
+
+ def __init__(self, host, port=None, strict=None, **ssl):
+ self.session = None
+ keys = ssl.keys()
+ try:
+ keys.remove('key_file')
+ except ValueError:
+ pass
+ try:
+ keys.remove('cert_file')
+ except ValueError:
+ pass
+ try:
+ keys.remove('ssl_context')
+ except ValueError:
+ pass
+ if keys:
+ raise ValueError('unknown keyword argument')
+ try:
+ self.ssl_ctx = ssl['ssl_context']
+ assert isinstance(self.ssl_ctx, SSL.Context), self.ssl_ctx
+ except KeyError:
+ self.ssl_ctx = SSL.Context('sslv23')
+ HTTPConnection.__init__(self, host, port, strict)
+
+ def connect(self):
+ self.sock = SSL.Connection(self.ssl_ctx)
+ if self.session:
+ self.sock.set_session(self.session)
+ self.sock.connect((self.host, self.port))
+
+ def close(self):
+ # This kludges around line 545 of httplib.py,
+ # which closes the connection in this object;
+ # the connection remains open in the response
+ # object.
+ #
+ # M2Crypto doesn't close-here-keep-open-there,
+ # so, in effect, we don't close until the whole
+ # business is over and gc kicks in.
+ #
+ # XXX Long-running callers beware leakage.
+ #
+ # XXX 05-Jan-2002: This module works with Python 2.2,
+ # XXX but I've not investigated if the above conditions
+ # XXX remain.
+ pass
+
+ def get_session(self):
+ return self.sock.get_session()
+
+ def set_session(self, session):
+ self.session = session
+
+
+class HTTPS(HTTP):
+
+ _connection_class = HTTPSConnection
+
+ def __init__(self, host='', port=None, strict=None, **ssl):
+ HTTP.__init__(self, host, port, strict)
+ try:
+ self.ssl_ctx = ssl['ssl_context']
+ except KeyError:
+ self.ssl_ctx = SSL.Context('sslv23')
+ assert isinstance(self._conn, HTTPSConnection)
+ self._conn.ssl_ctx = self.ssl_ctx
+
+
+class ProxyHTTPSConnection(HTTPSConnection):
+
+ """
+ An HTTPS Connection that uses a proxy and the CONNECT request.
+
+ When the connection is initiated, CONNECT is first sent to the proxy (along
+ with authorization headers, if supplied). If successful, an SSL connection
+ will be established over the socket through the proxy and to the target
+ host.
+
+ Finally, the actual request is sent over the SSL connection tunneling
+ through the proxy.
+ """
+
+ _ports = {'http' : 80, 'https' : 443}
+ _AUTH_HEADER = "Proxy-Authorization"
+ _UA_HEADER = "User-Agent"
+
+ def __init__(self, host, port=None, strict=None, username=None,
+ password=None, **ssl):
+ """
+ Create the ProxyHTTPSConnection object.
+
+ host and port are the hostname and port number of the proxy server.
+ """
+ HTTPSConnection.__init__(self, host, port, strict, **ssl)
+
+ self._username = username
+ self._password = password
+ self._proxy_auth = None
+ self._proxy_UA = None
+
+ def putrequest(self, method, url, skip_host=0, skip_accept_encoding=0):
+ #putrequest is called before connect, so can interpret url and get
+ #real host/port to be used to make CONNECT request to proxy
+ proto, netloc, path, query, fragment = urlsplit(url)
+ if not proto:
+ raise ValueError, "unknown URL type: %s" % url
+
+ #get host & port
+ try:
+ username_password, host_port = netloc.split('@')
+ except ValueError:
+ host_port = netloc
+
+ try:
+ host, port = host_port.split(':')
+ except ValueError:
+ host = host_port
+ #try to get port from proto
+ try:
+ port = self._ports[proto]
+ except KeyError:
+ raise ValueError, "unknown protocol for: %s" % url
+
+ self._real_host = host
+ self._real_port = int(port)
+ rest = urlunsplit((None, None, path, query, fragment))
+ if sys.version_info < (2,4):
+ HTTPSConnection.putrequest(self, method, rest, skip_host)
+ else:
+ HTTPSConnection.putrequest(self, method, rest, skip_host, skip_accept_encoding)
+
+ def putheader(self, header, value):
+ # Store the auth header if passed in.
+ if header.lower() == self._UA_HEADER.lower():
+ self._proxy_UA = value
+ if header.lower() == self._AUTH_HEADER.lower():
+ self._proxy_auth = value
+ else:
+ HTTPSConnection.putheader(self, header, value)
+
+ def endheaders(self):
+ # We've recieved all of hte headers. Use the supplied username
+ # and password for authorization, possibly overriding the authstring
+ # supplied in the headers.
+ if not self._proxy_auth:
+ self._proxy_auth = self._encode_auth()
+
+ HTTPSConnection.endheaders(self)
+
+ def connect(self):
+ HTTPConnection.connect(self)
+
+ #send proxy CONNECT request
+ self.sock.sendall(self._get_connect_msg())
+ response = HTTPResponse(self.sock)
+ response.begin()
+
+ code = response.status
+ if code != 200:
+ #proxy returned and error, abort connection, and raise exception
+ self.close()
+ raise socket.error, "Proxy connection failed: %d" % code
+
+ self._start_ssl()
+
+ def _get_connect_msg(self):
+ """ Return an HTTP CONNECT request to send to the proxy. """
+ msg = "CONNECT %s:%d HTTP/1.1\r\n" % (self._real_host, self._real_port)
+ msg = msg + "Host: %s:%d\r\n" % (self._real_host, self._real_port)
+ if self._proxy_UA:
+ msg = msg + "%s: %s\r\n" % (self._UA_HEADER, self._proxy_UA)
+ if self._proxy_auth:
+ msg = msg + "%s: %s\r\n" % (self._AUTH_HEADER, self._proxy_auth)
+ msg = msg + "\r\n"
+ return msg
+
+ def _start_ssl(self):
+ """ Make this connection's socket SSL-aware. """
+ self.sock = SSL.Connection(self.ssl_ctx, self.sock)
+ self.sock.setup_ssl()
+ self.sock.set_connect_state()
+ self.sock.connect_ssl()
+
+ def _encode_auth(self):
+ """ Encode the username and password for use in the auth header. """
+ if not (self._username and self._password):
+ return None
+ # Authenticated proxy
+ userpass = "%s:%s" % (self._username, self._password)
+ enc_userpass = base64.encodestring(userpass).replace("\n", "")
+ return "Basic %s" % enc_userpass
diff --git a/M2Crypto/m2.py b/M2Crypto/m2.py
new file mode 100644
index 0000000..e4bb695
--- /dev/null
+++ b/M2Crypto/m2.py
@@ -0,0 +1,31 @@
+"""M2Crypto low level OpenSSL wrapper functions.
+
+m2 is the low level wrapper for OpenSSL functions. Typically you would not
+need to use these directly, since these will be called by the higher level
+objects you should try to use instead.
+
+Naming conventions: All functions wrapped by m2 are all lower case,
+words separated by underscores.
+
+Examples:
+
+OpenSSL M2Crypto
+
+X509_get_version m2.x509_get_version
+X509_get_notBefore m2.x509_get_not_before
+X509_REQ_verify m2.x509_req_verify
+
+Exceptions to naming rules:
+
+XXX TDB
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2004 OSAF. All Rights Reserved.
+"""
+
+from __m2crypto import *
+lib_init()
+
+
diff --git a/M2Crypto/m2urllib.py b/M2Crypto/m2urllib.py
new file mode 100644
index 0000000..d951eb2
--- /dev/null
+++ b/M2Crypto/m2urllib.py
@@ -0,0 +1,70 @@
+"""M2Crypto enhancement to Python's urllib for handling
+'https' url's.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import string, sys, urllib
+from urllib import *
+
+import SSL
+import httpslib
+
+DEFAULT_PROTOCOL='sslv23'
+
+def open_https(self, url, data=None, ssl_context=None):
+ if ssl_context is not None and isinstance(ssl_context, SSL.Context):
+ self.ctx = ssl_context
+ else:
+ self.ctx = SSL.Context(DEFAULT_PROTOCOL)
+ user_passwd = None
+ if type(url) is type(""):
+ host, selector = splithost(url)
+ if host:
+ user_passwd, host = splituser(host)
+ host = unquote(host)
+ realhost = host
+ else:
+ host, selector = url
+ urltype, rest = splittype(selector)
+ url = rest
+ user_passwd = None
+ if string.lower(urltype) != 'http':
+ realhost = None
+ else:
+ realhost, rest = splithost(rest)
+ if realhost:
+ user_passwd, realhost = splituser(realhost)
+ if user_passwd:
+ selector = "%s://%s%s" % (urltype, realhost, rest)
+ #print "proxy via http:", host, selector
+ if not host: raise IOError, ('http error', 'no host given')
+ if user_passwd:
+ import base64
+ auth = string.strip(base64.encodestring(user_passwd))
+ else:
+ auth = None
+ # Start here!
+ h = httpslib.HTTPSConnection(host=host, ssl_context=self.ctx)
+ #h.set_debuglevel(1)
+ # Stop here!
+ if data is not None:
+ h.putrequest('POST', selector)
+ h.putheader('Content-type', 'application/x-www-form-urlencoded')
+ h.putheader('Content-length', '%d' % len(data))
+ else:
+ h.putrequest('GET', selector)
+ if auth: h.putheader('Authorization', 'Basic %s' % auth)
+ for args in self.addheaders: apply(h.putheader, args)
+ h.endheaders()
+ if data is not None:
+ h.send(data + '\r\n')
+ # Here again!
+ resp = h.getresponse()
+ fp = resp.fp
+ return urllib.addinfourl(fp, resp.msg, "https:" + url)
+ # Stop again.
+
+# Minor brain surgery.
+URLopener.open_https = open_https
+
+
diff --git a/M2Crypto/m2urllib2.py b/M2Crypto/m2urllib2.py
new file mode 100644
index 0000000..e500410
--- /dev/null
+++ b/M2Crypto/m2urllib2.py
@@ -0,0 +1,152 @@
+"""
+M2Crypto enhancement to Python's urllib2 for handling
+'https' url's.
+
+Code from urllib2 is Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007
+Python Software Foundation; All Rights Reserved
+
+Summary of changes:
+ - Use an HTTPSProxyConnection if the request is going through a proxy.
+ - Add the SSL context to the https connection when performing https_open.
+ - Add the M2Crypto HTTPSHandler when building a default opener.
+"""
+
+import socket
+from urllib2 import *
+import urlparse
+
+import SSL
+import httpslib
+
+
+class _closing_fileobject(socket._fileobject):
+ '''socket._fileobject that propagates self.close() to the socket.
+
+ Python 2.5 provides this as socket._fileobject(sock, close=True).
+ '''
+
+ def __init__(self, sock):
+ socket._fileobject.__init__(self, sock)
+
+ def close(self):
+ sock = self._sock
+ socket._fileobject.close(self)
+ sock.close()
+
+class HTTPSHandler(AbstractHTTPHandler):
+ def __init__(self, ssl_context = None):
+ AbstractHTTPHandler.__init__(self)
+
+ if ssl_context is not None:
+ assert isinstance(ssl_context, SSL.Context), ssl_context
+ self.ctx = ssl_context
+ else:
+ self.ctx = SSL.Context()
+
+ # Copied from urllib2, so we can set the ssl context.
+ def https_open(self, req):
+ """Return an addinfourl object for the request, using http_class.
+
+ http_class must implement the HTTPConnection API from httplib.
+ The addinfourl return value is a file-like object. It also
+ has methods and attributes including:
+ - info(): return a mimetools.Message object for the headers
+ - geturl(): return the original request URL
+ - code: HTTP status code
+ """
+ host = req.get_host()
+ if not host:
+ raise URLError('no host given')
+
+ # Our change: Check to see if we're using a proxy.
+ # Then create an appropriate ssl-aware connection.
+ full_url = req.get_full_url()
+ target_host = urlparse.urlparse(full_url)[1]
+
+ if (target_host != host):
+ h = httpslib.ProxyHTTPSConnection(host = host, ssl_context = self.ctx)
+ else:
+ h = httpslib.HTTPSConnection(host = host, ssl_context = self.ctx)
+ # End our change
+ h.set_debuglevel(self._debuglevel)
+
+ headers = dict(req.headers)
+ headers.update(req.unredirected_hdrs)
+ # We want to make an HTTP/1.1 request, but the addinfourl
+ # class isn't prepared to deal with a persistent connection.
+ # It will try to read all remaining data from the socket,
+ # which will block while the server waits for the next request.
+ # So make sure the connection gets closed after the (only)
+ # request.
+ headers["Connection"] = "close"
+ try:
+ h.request(req.get_method(), req.get_selector(), req.data, headers)
+ r = h.getresponse()
+ except socket.error, err: # XXX what error?
+ raise URLError(err)
+
+ # Pick apart the HTTPResponse object to get the addinfourl
+ # object initialized properly.
+
+ # Wrap the HTTPResponse object in socket's file object adapter
+ # for Windows. That adapter calls recv(), so delegate recv()
+ # to read(). This weird wrapping allows the returned object to
+ # have readline() and readlines() methods.
+
+ # XXX It might be better to extract the read buffering code
+ # out of socket._fileobject() and into a base class.
+
+ r.recv = r.read
+ fp = _closing_fileobject(r)
+
+ resp = addinfourl(fp, r.msg, req.get_full_url())
+ resp.code = r.status
+ resp.msg = r.reason
+ return resp
+
+
+ https_request = AbstractHTTPHandler.do_request_
+
+
+# Copied from urllib2 with modifications for ssl
+def build_opener(ssl_context = None, *handlers):
+ """Create an opener object from a list of handlers.
+
+ The opener will use several default handlers, including support
+ for HTTP and FTP.
+
+ If any of the handlers passed as arguments are subclasses of the
+ default handlers, the default handlers will not be used.
+ """
+ import types
+ def isclass(obj):
+ return isinstance(obj, types.ClassType) or hasattr(obj, "__bases__")
+
+ opener = OpenerDirector()
+ default_classes = [ProxyHandler, UnknownHandler, HTTPHandler,
+ HTTPDefaultErrorHandler, HTTPRedirectHandler,
+ FTPHandler, FileHandler, HTTPErrorProcessor]
+ skip = []
+ for klass in default_classes:
+ for check in handlers:
+ if isclass(check):
+ if issubclass(check, klass):
+ skip.append(klass)
+ elif isinstance(check, klass):
+ skip.append(klass)
+ for klass in skip:
+ default_classes.remove(klass)
+
+ for klass in default_classes:
+ opener.add_handler(klass())
+
+ # Add the HTTPS handler with ssl_context
+ if HTTPSHandler not in skip:
+ opener.add_handler(HTTPSHandler(ssl_context))
+
+
+ for h in handlers:
+ if isclass(h):
+ h = h()
+ opener.add_handler(h)
+ return opener
diff --git a/M2Crypto/m2xmlrpclib.py b/M2Crypto/m2xmlrpclib.py
new file mode 100644
index 0000000..bb50a01
--- /dev/null
+++ b/M2Crypto/m2xmlrpclib.py
@@ -0,0 +1,65 @@
+"""M2Crypto enhancement to xmlrpclib.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import base64, string, sys
+
+from xmlrpclib import *
+import M2Crypto
+import SSL, httpslib, m2urllib
+
+__version__ = M2Crypto.version
+
+class SSL_Transport(Transport):
+
+ user_agent = "M2Crypto_XMLRPC/%s - %s" % (__version__, Transport.user_agent)
+
+ def __init__(self, ssl_context=None, *args, **kw):
+ if getattr(Transport, '__init__', None) is not None:
+ Transport.__init__(self, *args, **kw)
+ if ssl_context is None:
+ self.ssl_ctx=SSL.Context('sslv23')
+ else:
+ self.ssl_ctx=ssl_context
+
+ def request(self, host, handler, request_body, verbose=0):
+ # Handle username and password.
+ user_passwd, host_port = m2urllib.splituser(host)
+ _host, _port = m2urllib.splitport(host_port)
+ h = httpslib.HTTPS(_host, int(_port), ssl_context=self.ssl_ctx)
+ if verbose:
+ h.set_debuglevel(1)
+
+ # What follows is as in xmlrpclib.Transport. (Except the authz bit.)
+ h.putrequest("POST", handler)
+
+ # required by HTTP/1.1
+ h.putheader("Host", _host)
+
+ # required by XML-RPC
+ h.putheader("User-Agent", self.user_agent)
+ h.putheader("Content-Type", "text/xml")
+ h.putheader("Content-Length", str(len(request_body)))
+
+ # Authorisation.
+ if user_passwd is not None:
+ auth=string.strip(base64.encodestring(user_passwd))
+ h.putheader('Authorization', 'Basic %s' % auth)
+
+ h.endheaders()
+
+ if request_body:
+ h.send(request_body)
+
+ errcode, errmsg, headers = h.getreply()
+
+ if errcode != 200:
+ raise ProtocolError(
+ host + handler,
+ errcode, errmsg,
+ headers
+ )
+
+ self.verbose = verbose
+ return self.parse_response(h.getfile())
+
diff --git a/M2Crypto/threading.py b/M2Crypto/threading.py
new file mode 100644
index 0000000..4cb8149
--- /dev/null
+++ b/M2Crypto/threading.py
@@ -0,0 +1,20 @@
+"""
+M2Crypto threading support, required for multithreaded applications.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# M2Crypto
+import m2
+
+def init():
+ """
+ Initialize threading support.
+ """
+ m2.threading_init()
+
+def cleanup():
+ """
+ End and cleanup threading support.
+ """
+ m2.threading_cleanup()
+
diff --git a/M2Crypto/util.py b/M2Crypto/util.py
new file mode 100644
index 0000000..12103fc
--- /dev/null
+++ b/M2Crypto/util.py
@@ -0,0 +1,71 @@
+"""
+ M2Crypto utility routines.
+
+ Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+
+ Portions created by Open Source Applications Foundation (OSAF) are
+ Copyright (C) 2004 OSAF. All Rights Reserved.
+"""
+
+import sys
+import m2
+
+class UtilError(Exception): pass
+
+m2.util_init(UtilError)
+
+def h2b(s):
+ import array, string
+ ar=array.array('c')
+ start=0
+ if s[:2]=='0x':
+ start=2
+ for i in range(start, len(s), 2):
+ num=string.atoi("%s"%(s[i:i+2],), 16)
+ ar.append(chr(num))
+ return ar.tostring()
+
+def pkcs5_pad(data, blklen=8):
+ pad=(8-(len(data)%8))
+ return data+chr(pad)*pad
+
+def pkcs7_pad(data, blklen):
+ if blklen>255:
+ raise ValueError, 'illegal block size'
+ pad=(blklen-(len(data)%blklen))
+ return data+chr(pad)*pad
+
+def octx_to_num(x):
+ v = 0L
+ lx = len(x)
+ for i in range(lx):
+ v = v + ord(x[i]) * (256L ** (lx-i-1))
+ return v
+
+def genparam_callback(p, n, out=sys.stdout):
+ ch = ['.','+','*','\n']
+ out.write(ch[p])
+ out.flush()
+
+def quiet_genparam_callback(p, n, out):
+ pass
+
+def passphrase_callback(v, prompt1='Enter passphrase:',
+ prompt2='Verify passphrase:'):
+ from getpass import getpass
+ while 1:
+ try:
+ p1=getpass(prompt1)
+ if v:
+ p2=getpass(prompt2)
+ if p1==p2:
+ break
+ else:
+ break
+ except KeyboardInterrupt:
+ return None
+ return p1
+
+def no_passphrase_callback(*args):
+ return ''
+
diff --git a/PKG-INFO b/PKG-INFO
new file mode 100644
index 0000000..3972df5
--- /dev/null
+++ b/PKG-INFO
@@ -0,0 +1,23 @@
+Metadata-Version: 1.0
+Name: M2Crypto
+Version: 0.21.1
+Summary: M2Crypto: A Python crypto and SSL toolkit
+Home-page: http://chandlerproject.org/Projects/MeTooCrypto
+Author: Heikki Toivonen
+Author-email: heikki@osafoundation.org
+License: BSD-style license
+Description: M2Crypto is the most complete Python wrapper for OpenSSL featuring RSA, DSA,
+ DH, EC, HMACs, message digests, symmetric ciphers (including AES); SSL
+ functionality to implement clients and servers; HTTPS extensions to Python's
+ httplib, urllib, and xmlrpclib; unforgeable HMAC'ing AuthCookies for web
+ session management; FTP/TLS client and server; S/MIME; ZServerSSL: A HTTPS
+ server for Zope and ZSmime: An S/MIME messenger for Zope. M2Crypto can also be
+ used to provide SSL for Twisted.
+Platform: any
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: C
+Classifier: Programming Language :: Python
+Classifier: Topic :: Security :: Cryptography
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
diff --git a/README b/README
new file mode 100644
index 0000000..99affa0
--- /dev/null
+++ b/README
@@ -0,0 +1,61 @@
+=========
+ M2Crypto
+=========
+
+:Maintainer: Heikki Toivonen
+:Web-Site: http://chandlerproject.org/Projects/MeTooCrypto
+
+
+M2Crypto = Python + OpenSSL + SWIG
+------------------------------------
+
+M2Crypto is a crypto and SSL toolkit for Python.
+
+M2 stands for "me, too!"
+
+M2Crypto comes with the following:
+
+- **RSA**, **DSA**, **DH**, **HMACs**, **message digests**,
+ **symmetric ciphers** including **AES**,
+
+- **SSL** functionality to implement **clients and servers**.
+
+- **Example SSL client and server programs**, which are variously
+ **threading**, **forking** or based on **non-blocking socket IO**.
+
+- **HTTPS** extensions to Python's **httplib, urllib and xmlrpclib**.
+
+- Unforgeable HMAC'ing **AuthCookies** for **web session management**.
+
+- **FTP/TLS** client and server.
+
+- **S/MIME v2**.
+
+- **ZServerSSL**: A **HTTPS server for Zope**.
+
+- **ZSmime**: An S/MIME messenger for **Zope**.
+
+- And much more.
+
+M2Crypto is released under a very liberal BSD-style licence. See
+LICENCE for details.
+
+To install, see the file INSTALL.
+
+Look at the tests and demos for example use. Recommended reading before
+deploying in production is "Network Security with OpenSSL" by John Viega,
+Matt Messier and Pravir Chandra, ISBN 059600270X.
+
+Note these caveats:
+
+- Possible memory leaks, because some objects need to be freed on the
+ Python side and other objects on the C side, and these may change
+ between OpenSSL versions. (Multiple free's lead to crashes very
+ quickly, so these should be relatively rare.)
+
+- No memory locking/clearing for keys, passphrases, etc. because AFAIK
+ Python does not provide the features needed. On the C (OpenSSL) side
+ things are cleared when the Python objects are deleted.
+
+
+Have fun! Your feedback is welcome.
diff --git a/SWIG/Makefile b/SWIG/Makefile
new file mode 100644
index 0000000..7d73a3b
--- /dev/null
+++ b/SWIG/Makefile
@@ -0,0 +1,25 @@
+# $Id: Makefile 299 2005-06-09 17:32:28Z heikki $
+
+CFLAGS = -DTHREADING -g
+INCLUDE = -I/usr/local/include -I.
+LIBS = -L/usr/local/lib -lssl -lcrypto
+#if PYTHON_VERSION
+PYVER = $(PYTHON_VERSION)
+#else
+PYVER = 2.3
+#endif
+PYINCLUDE = -DHAVE_CONFIG_H -I/usr/local/include/python$(PYVER) \
+ -I/usr/local/lib/python$(PYVER)/config
+PYLIB = /usr/local/lib/python$(PYVER)/config
+
+all: _m2crypto
+
+_m2crypto: _m2crypto.i
+ swig -python -shadow _m2crypto.i
+ cc -c -fpic $(CFLAGS) $(INCLUDE) $(PYINCLUDE) _m2crypto_wrap.c
+ ld -Bshareable -o __m2crypto.so _m2crypto_wrap.o $(LIBS)
+ cp _m2crypto.py __m2crypto.so ../M2Crypto
+
+clean:
+ rm -f *_wrap* *.o *.so _*.py *.pyc
+
diff --git a/SWIG/Makefile.mw b/SWIG/Makefile.mw
new file mode 100644
index 0000000..02f5ae5
--- /dev/null
+++ b/SWIG/Makefile.mw
@@ -0,0 +1,34 @@
+# $Id: Makefile.mw 299 2005-06-09 17:32:28Z heikki $
+
+# Python
+PYFLAGS=-D__WIN32__ -DHAVE_CONFIG_H -Ic:/pkg/py23/include
+PYLIB=c:/pkg/py23/libs/libpython23.a
+PYINCLUDE=-Ic:/pkg/py23/include
+
+# OpenSSL
+SSLINCLUDE=-Ic:/pkg/openssl/include
+SSLLIB=c:/pkg/openssl/lib/libssl32.a c:/pkg/openssl/lib/libeay32.a
+
+# Windoze
+INCLUDE=$(PYINCLUDE) $(SSLINCLUDE) -I.
+LIBS=$(PYLIB) $(SSLLIB)
+
+SWIG=c:/pkg/swig/swig.exe
+SWIGFLAGS=-shadow -python #-verbose
+
+CP=cp
+
+all: swig
+
+swig: _m2crypto.i
+ $(SWIG) $(SWIGFLAGS) _m2crypto.i
+ gcc -c -DTHREADING -g $(INCLUDE) _m2crypto_wrap.c
+ dllwrap --dllname __m2crypto.pyd --driver-name gcc \
+ --def _m2crypto.def -o __m2crypto.pyd _m2crypto_wrap.o \
+ -s --entry _DllMain@12 --target=i386-mingw32 $(LIBS)
+ $(CP) _m2crypto.py ..\M2Crypto
+ $(CP) __m2crypto.pyd ..\M2Crypto
+
+clean:
+ del *wrap* *.o *.dll *.exp *.ilk *.pdb *.lib _*.py *.pyc
+
diff --git a/SWIG/Makefile.osx b/SWIG/Makefile.osx
new file mode 100644
index 0000000..a8508a6
--- /dev/null
+++ b/SWIG/Makefile.osx
@@ -0,0 +1,32 @@
+# $Id: Makefile.osx 299 2005-06-09 17:32:28Z heikki $
+
+# 2003-10-26, ngps: Beware any mixup of tabs and spaces caused by me.
+
+WHICHOPENSSL = /usr/local
+
+CFLAGS = -DTHREADING -DHAVE_CONFIG -g -O2
+INCLUDE = -I. -I/Library/Frameworks/Python.framework/Headers
+LIBS = $(WHICHOPENSSL)/lib/libssl.a \
+ $(WHICHOPENSSL)/lib/libcrypto.a \
+ /Library/Frameworks/Python.framework/python
+
+all: __m2crypto.so
+
+_m2crypto_wrap.c: _m2crypto.i
+ swig -shadow -python _m2crypto.i
+
+_m2crypto_wrap.o: _m2crypto_wrap.c
+ cc -c $(CFLAGS) $(INCLUDES) _m2crypto_wrap.c
+
+__m2crypto.so: _m2crypto_wrap.o
+ cc -bundle _m2crypto_wrap.o $(LIBS) -lcc_dynamic -o __m2crypto.so
+ cp __m2crypto.so ../M2Crypto
+
+clean:
+ rm -f *_wrap* *.o *.so _*.py *.pyc
+
+versions:
+ python -c "import sys, os; \
+ print os.popen('gcc --version').readlines()[0],; \
+ print 'Python '+sys.version.split()[0]; \
+ print os.popen('$(WHICHOPENSSL)/bin/openssl version').readlines()[0]"
diff --git a/SWIG/_aes.i b/SWIG/_aes.i
new file mode 100644
index 0000000..be0af87
--- /dev/null
+++ b/SWIG/_aes.i
@@ -0,0 +1,85 @@
+/* Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved. */
+/* $Id: _aes.i 721 2010-02-13 06:30:33Z heikki $ */
+
+%{
+#include <openssl/evp.h>
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+#include <openssl/aes.h>
+#endif
+
+/*
+// 2004-10-10, ngps:
+// CTR mode is not included in the default OpenSSL build.
+// To use the AES CTR ciphers, link with your own copy of OpenSSL.
+*/
+#ifdef HAVE_AES_CTR
+extern EVP_CIPHER const *EVP_aes_128_ctr(void);
+extern EVP_CIPHER const *EVP_aes_192_ctr(void);
+extern EVP_CIPHER const *EVP_aes_256_ctr(void);
+#endif
+%}
+
+%apply Pointer NONNULL { AES_KEY * };
+
+%constant int AES_BLOCK_SIZE = AES_BLOCK_SIZE;
+
+%inline %{
+AES_KEY *aes_new(void) {
+ AES_KEY *key;
+
+ if (!(key = (AES_KEY *)PyMem_Malloc(sizeof(AES_KEY))))
+ PyErr_SetString(PyExc_MemoryError, "aes_new");
+ return key;
+}
+
+void AES_free(AES_KEY *key) {
+ PyMem_Free((void *)key);
+}
+
+/*
+// op == 0: decrypt
+// otherwise: encrypt (Python code will supply the value 1.)
+*/
+PyObject *AES_set_key(AES_KEY *key, PyObject *value, int bits, int op) {
+ const void *vbuf;
+ Py_ssize_t vlen;
+
+ if (PyObject_AsReadBuffer(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (op == 0)
+ AES_set_encrypt_key(vbuf, bits, key);
+ else
+ AES_set_decrypt_key(vbuf, bits, key);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+/*
+// op == 0: decrypt
+// otherwise: encrypt (Python code will supply the value 1.)
+*/
+PyObject *AES_crypt(const AES_KEY *key, PyObject *in, int outlen, int op) {
+ const void *buf;
+ Py_ssize_t len;
+ unsigned char *out;
+
+ if (PyObject_AsReadBuffer(in, &buf, &len) == -1)
+ return NULL;
+
+ if (!(out=(unsigned char *)PyMem_Malloc(outlen))) {
+ PyErr_SetString(PyExc_MemoryError, "AES_crypt");
+ return NULL;
+ }
+ if (op == 0)
+ AES_encrypt((const unsigned char *)in, out, key);
+ else
+ AES_decrypt((const unsigned char *)in, out, key);
+ return PyString_FromStringAndSize((char*)out, outlen);
+}
+
+int AES_type_check(AES_KEY *key) {
+ return 1;
+}
+%}
diff --git a/SWIG/_asn1.i b/SWIG/_asn1.i
new file mode 100644
index 0000000..6dab7ff
--- /dev/null
+++ b/SWIG/_asn1.i
@@ -0,0 +1,201 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved. */
+/*
+** Portions created by Open Source Applications Foundation (OSAF) are
+** Copyright (C) 2004 OSAF. All Rights Reserved.
+*/
+/* $Id: _asn1.i 696 2009-07-28 03:43:19Z heikki $ */
+
+%{
+#include <openssl/asn1.h>
+%}
+
+%apply Pointer NONNULL { BIO * };
+%apply Pointer NONNULL { ASN1_OBJECT * };
+%apply Pointer NONNULL { ASN1_STRING * };
+%apply Pointer NONNULL { ASN1_INTEGER * };
+%apply Pointer NONNULL { ASN1_UTCTIME * };
+
+%rename(asn1_object_new) ASN1_OBJECT_new;
+extern ASN1_OBJECT *ASN1_OBJECT_new( void );
+%rename(asn1_object_create) ASN1_OBJECT_create;
+extern ASN1_OBJECT *ASN1_OBJECT_create( int, unsigned char *, int, const char *, const char *);
+%rename(asn1_object_free) ASN1_OBJECT_free;
+extern void ASN1_OBJECT_free( ASN1_OBJECT *);
+%rename(i2d_asn1_object) i2d_ASN1_OBJECT;
+extern int i2d_ASN1_OBJECT( ASN1_OBJECT *, unsigned char **);
+%rename(c2i_asn1_object) c2i_ASN1_OBJECT;
+extern ASN1_OBJECT *c2i_ASN1_OBJECT( ASN1_OBJECT **, CONST098 unsigned char **, long);
+%rename(d2i_asn1_object) d2i_ASN1_OBJECT;
+extern ASN1_OBJECT *d2i_ASN1_OBJECT( ASN1_OBJECT **, CONST098 unsigned char **, long);
+
+%rename(asn1_bit_string_new) ASN1_BIT_STRING_new;
+extern ASN1_BIT_STRING *ASN1_BIT_STRING_new( void );
+
+%rename(asn1_string_new) ASN1_STRING_new;
+extern ASN1_STRING *ASN1_STRING_new( void );
+%rename(asn1_string_free) ASN1_STRING_free;
+extern void ASN1_STRING_free( ASN1_STRING *);
+
+%typemap(in) (const void *, int) {
+ if (PyString_Check($input)) {
+ Py_ssize_t len;
+
+ $1 = PyString_AsString($input);
+ len = PyString_Size($input);
+ if (len > INT_MAX) {
+ PyErr_SetString(PyExc_ValueError, "object too large");
+ return NULL;
+ }
+ $2 = len;
+ } else {
+ PyErr_SetString(PyExc_TypeError, "expected string");
+ return NULL;
+ }
+}
+
+%rename(asn1_string_set) ASN1_STRING_set;
+extern int ASN1_STRING_set( ASN1_STRING *, const void *, int);
+
+%typemap(in) (const void *, int);
+
+%rename(asn1_string_print) ASN1_STRING_print;
+%threadallow ASN1_STRING_print;
+extern int ASN1_STRING_print(BIO *, ASN1_STRING *);
+%threadallow ASN1_STRING_print_ex;
+%rename(asn1_string_print_ex) ASN1_STRING_print_ex;
+extern int ASN1_STRING_print_ex(BIO *, ASN1_STRING *, unsigned long);
+
+%rename(asn1_utctime_new) ASN1_UTCTIME_new;
+extern ASN1_UTCTIME *ASN1_UTCTIME_new( void );
+%rename(asn1_utctime_free) ASN1_UTCTIME_free;
+extern void ASN1_UTCTIME_free(ASN1_UTCTIME *);
+%rename(asn1_utctime_check) ASN1_UTCTIME_check;
+extern int ASN1_UTCTIME_check(ASN1_UTCTIME *);
+%rename(asn1_utctime_set) ASN1_UTCTIME_set;
+extern ASN1_UTCTIME *ASN1_UTCTIME_set(ASN1_UTCTIME *, long);
+%rename(asn1_utctime_set_string) ASN1_UTCTIME_set_string;
+extern int ASN1_UTCTIME_set_string(ASN1_UTCTIME *, CONST098 char *);
+%rename(asn1_utctime_print) ASN1_UTCTIME_print;
+%threadallow ASN1_UTCTIME_print;
+extern int ASN1_UTCTIME_print(BIO *, ASN1_UTCTIME *);
+
+%rename(asn1_integer_new) ASN1_INTEGER_new;
+extern ASN1_INTEGER *ASN1_INTEGER_new( void );
+%rename(asn1_integer_free) ASN1_INTEGER_free;
+extern void ASN1_INTEGER_free( ASN1_INTEGER *);
+%rename(asn1_integer_cmp) ASN1_INTEGER_cmp;
+extern int ASN1_INTEGER_cmp(ASN1_INTEGER *, ASN1_INTEGER *);
+
+%constant int ASN1_STRFLGS_ESC_2253 = 1;
+%constant int ASN1_STRFLGS_ESC_CTRL = 2;
+%constant int ASN1_STRFLGS_ESC_MSB = 4;
+%constant int ASN1_STRFLGS_ESC_QUOTE = 8;
+%constant int ASN1_STRFLGS_UTF8_CONVERT = 0x10;
+%constant int ASN1_STRFLGS_DUMP_UNKNOWN = 0x100;
+%constant int ASN1_STRFLGS_DUMP_DER = 0x200;
+%constant int ASN1_STRFLGS_RFC2253 = (ASN1_STRFLGS_ESC_2253 | \
+ ASN1_STRFLGS_ESC_CTRL | \
+ ASN1_STRFLGS_ESC_MSB | \
+ ASN1_STRFLGS_UTF8_CONVERT | \
+ ASN1_STRFLGS_DUMP_UNKNOWN | \
+ ASN1_STRFLGS_DUMP_DER);
+
+%inline %{
+/* ASN1_UTCTIME_set_string () is a macro */
+int asn1_utctime_type_check(ASN1_UTCTIME *ASN1_UTCTIME) {
+ return 1;
+}
+
+PyObject *asn1_integer_get(ASN1_INTEGER *asn1) {
+ BIGNUM *bn;
+ PyObject *ret;
+ char *hex;
+
+ bn = ASN1_INTEGER_to_BN(asn1, NULL);
+
+ if (!bn){
+ PyErr_SetString(
+ PyExc_RuntimeError, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+
+ hex = BN_bn2hex(bn);
+
+ if (!hex){
+ PyErr_SetString(
+ PyExc_RuntimeError, ERR_reason_error_string(ERR_get_error()));
+ BN_free(bn);
+ return NULL;
+ }
+
+ BN_free(bn);
+
+ ret = PyLong_FromString(hex, NULL, 16);
+
+ OPENSSL_free(hex);
+
+ return ret;
+}
+
+int asn1_integer_set(ASN1_INTEGER *asn1, PyObject *value) {
+ BIGNUM *bn = NULL;
+ PyObject *fmt, *args, *hex;
+
+ if (PyInt_Check(value))
+ return ASN1_INTEGER_set(asn1, PyInt_AS_LONG(value));
+
+ if (!PyLong_Check(value)){
+ PyErr_SetString(PyExc_TypeError, "expected int or long");
+ return 0;
+ }
+
+ fmt = PyString_FromString("%x");
+
+ if (!fmt)
+ return 0;
+
+ args = PyTuple_New(1);
+
+ if (!args){
+ Py_DECREF(fmt);
+ PyErr_SetString(PyExc_RuntimeError, "PyTuple_New() failed");
+ return 0;
+ }
+
+ Py_INCREF(value);
+ PyTuple_SET_ITEM(args, 0, value);
+ hex = PyString_Format(fmt, args);
+
+ if (!hex){
+ PyErr_SetString(PyExc_RuntimeError, "PyString_Format() failed");
+ Py_DECREF(fmt);
+ Py_DECREF(args);
+ return 0;
+ }
+
+ Py_DECREF(fmt);
+ Py_DECREF(args);
+
+ if (BN_hex2bn(&bn, PyString_AsString(hex)) <= 0){
+ PyErr_SetString(
+ PyExc_RuntimeError, ERR_reason_error_string(ERR_get_error()));
+ Py_DECREF(hex);
+ return 0;
+ }
+
+ Py_DECREF(hex);
+
+ if (!BN_to_ASN1_INTEGER(bn, asn1)){
+ PyErr_SetString(
+ PyExc_RuntimeError, ERR_reason_error_string(ERR_get_error()));
+ BN_free(bn);
+ return 0;
+ }
+
+ BN_free(bn);
+
+ return 1;
+}
+
+%}
diff --git a/SWIG/_bio.i b/SWIG/_bio.i
new file mode 100644
index 0000000..441ba06
--- /dev/null
+++ b/SWIG/_bio.i
@@ -0,0 +1,227 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999 Ng Pheng Siong. All rights reserved.
+ *
+ * Portions created by Open Source Applications Foundation (OSAF) are
+ * Copyright (C) 2004-2005 OSAF. All Rights Reserved.
+ * Author: Heikki Toivonen
+*/
+/* $Id: _bio.i 695 2009-07-24 06:37:01Z heikki $ */
+
+%{
+#include <openssl/bio.h>
+%}
+
+%apply Pointer NONNULL { BIO * };
+%apply Pointer NONNULL { BIO_METHOD * };
+
+%rename(bio_s_bio) BIO_s_bio;
+extern BIO_METHOD *BIO_s_bio(void);
+%rename(bio_s_mem) BIO_s_mem;
+extern BIO_METHOD *BIO_s_mem(void);
+%rename(bio_s_socket) BIO_s_socket;
+extern BIO_METHOD *BIO_s_socket(void);
+%rename(bio_f_ssl) BIO_f_ssl;
+extern BIO_METHOD *BIO_f_ssl(void);
+%rename(bio_f_buffer) BIO_f_buffer;
+extern BIO_METHOD *BIO_f_buffer(void);
+%rename(bio_f_cipher) BIO_f_cipher;
+extern BIO_METHOD *BIO_f_cipher(void);
+
+%rename(bio_new) BIO_new;
+extern BIO *BIO_new(BIO_METHOD *);
+%rename(bio_new_socket) BIO_new_socket;
+extern BIO *BIO_new_socket(int, int);
+%rename(bio_new_fd) BIO_new_fd;
+extern BIO *BIO_new_fd(int, int);
+%rename(bio_new_fp) BIO_new_fp;
+extern BIO *BIO_new_fp(FILE *, int);
+%rename(bio_new_file) BIO_new_file;
+extern BIO *BIO_new_file(const char *, const char *);
+%rename(bio_free) BIO_free;
+%threadallow BIO_free;
+extern int BIO_free(BIO *);
+%rename(bio_free_all) BIO_free_all;
+%threadallow BIO_free_all;
+extern void BIO_free_all(BIO *);
+%rename(bio_dup_chain) BIO_dup_chain;
+extern BIO *BIO_dup_chain(BIO *);
+
+%rename(bio_push) BIO_push;
+extern BIO *BIO_push(BIO *, BIO *);
+%rename(bio_pop) BIO_pop;
+extern BIO *BIO_pop(BIO *);
+
+%constant int bio_noclose = BIO_NOCLOSE;
+%constant int bio_close = BIO_CLOSE;
+%constant int BIO_FLAGS_READ = 0x01;
+%constant int BIO_FLAGS_WRITE = 0x02;
+%constant int BIO_FLAGS_IO_SPECIAL = 0x04;
+%constant int BIO_FLAGS_RWS = (BIO_FLAGS_READ|BIO_FLAGS_WRITE|BIO_FLAGS_IO_SPECIAL);
+%constant int BIO_FLAGS_SHOULD_RETRY = 0x08;
+%constant int BIO_FLAGS_MEM_RDONLY = 0x200;
+
+%inline %{
+static PyObject *_bio_err;
+
+void bio_init(PyObject *bio_err) {
+ Py_INCREF(bio_err);
+ _bio_err = bio_err;
+}
+
+PyObject *bio_read(BIO *bio, int num) {
+ PyObject *blob;
+ void *buf;
+ int r;
+
+ if (!(buf = PyMem_Malloc(num))) {
+ PyErr_SetString(PyExc_MemoryError, "bio_read");
+ return NULL;
+ }
+ Py_BEGIN_ALLOW_THREADS
+ r = BIO_read(bio, buf, num);
+ Py_END_ALLOW_THREADS
+ if (r < 0) {
+ PyMem_Free(buf);
+ if (ERR_peek_error()) {
+ PyErr_SetString(_bio_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ blob = PyString_FromStringAndSize(buf, r);
+ PyMem_Free(buf);
+ return blob;
+}
+
+PyObject *bio_gets(BIO *bio, int num) {
+ PyObject *blob;
+ void *buf;
+ int r;
+
+ if (!(buf = PyMem_Malloc(num))) {
+ PyErr_SetString(PyExc_MemoryError, "bio_gets");
+ return NULL;
+ }
+ Py_BEGIN_ALLOW_THREADS
+ r = BIO_gets(bio, buf, num);
+ Py_END_ALLOW_THREADS
+ if (r < 0) {
+ PyMem_Free(buf);
+ if (ERR_peek_error()) {
+ PyErr_SetString(_bio_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ blob = PyString_FromStringAndSize(buf, r);
+ PyMem_Free(buf);
+ return blob;
+}
+
+int bio_write(BIO *bio, PyObject *from) {
+ const void *fbuf;
+ int flen, ret;
+
+ if (m2_PyObject_AsReadBufferInt(from, &fbuf, &flen) == -1)
+ return -1;
+
+ Py_BEGIN_ALLOW_THREADS
+ ret = BIO_write(bio, fbuf, flen);
+ Py_END_ALLOW_THREADS
+ if (ret < 0) {
+ if (ERR_peek_error()) {
+ PyErr_SetString(_bio_err, ERR_reason_error_string(ERR_get_error()));
+ }
+ }
+ return ret;
+}
+
+/* XXX Casting size_t to int. */
+int bio_ctrl_pending(BIO *bio) {
+ return (int)BIO_ctrl_pending(bio);
+}
+
+int bio_ctrl_wpending(BIO *bio) {
+ return (int)BIO_ctrl_wpending(bio);
+}
+
+int bio_ctrl_get_write_guarantee(BIO *a) {
+ return BIO_ctrl_get_write_guarantee(a);
+}
+
+int bio_reset(BIO *bio) {
+ return (int)BIO_reset(bio);
+}
+%}
+
+%threadallow bio_flush;
+%inline %{
+int bio_flush(BIO *bio) {
+ return (int)BIO_flush(bio);
+}
+
+int bio_seek(BIO *bio, int offset) {
+ return (int)BIO_seek(bio, offset);
+}
+
+void bio_set_flags(BIO *bio, int flags) {
+ BIO_set_flags(bio, flags);
+}
+
+int bio_get_flags(BIO *bio) {
+ return BIO_get_flags(bio);
+}
+
+PyObject *bio_set_cipher(BIO *b, EVP_CIPHER *c, PyObject *key, PyObject *iv, int op) {
+ const void *kbuf, *ibuf;
+ Py_ssize_t klen, ilen;
+
+ if ((PyObject_AsReadBuffer(key, &kbuf, &klen) == -1)
+ || (PyObject_AsReadBuffer(iv, &ibuf, &ilen) == -1))
+ return NULL;
+
+ BIO_set_cipher(b, (const EVP_CIPHER *)c,
+ (unsigned char *)kbuf, (unsigned char *)ibuf, op);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+int bio_set_mem_eof_return(BIO *b, int v) {
+ return (int)BIO_set_mem_eof_return(b, v);
+}
+
+int bio_get_fd(BIO *bio) {
+ return BIO_get_fd(bio, NULL);
+}
+%}
+
+%threadallow bio_do_handshake;
+%inline %{
+int bio_do_handshake(BIO *bio) {
+ return BIO_do_handshake(bio);
+}
+
+/* macro */
+int bio_make_bio_pair(BIO* b1, BIO* b2) {
+ return BIO_make_bio_pair(b1, b2);
+}
+
+int bio_set_write_buf_size(BIO* b, size_t size) {
+ return BIO_set_write_buf_size(b, size);
+}
+
+int bio_should_retry(BIO* a) {
+ return BIO_should_retry(a);
+}
+
+int bio_should_read(BIO* a) {
+ return BIO_should_read(a);
+}
+
+int bio_should_write(BIO* a) {
+ return BIO_should_write(a);
+}
+%}
+
diff --git a/SWIG/_bn.i b/SWIG/_bn.i
new file mode 100755
index 0000000..a3b73c4
--- /dev/null
+++ b/SWIG/_bn.i
@@ -0,0 +1,114 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 2005-2006 Open Source Applications Foundation. All rights reserved. */
+
+/* We are converting between the Python arbitrarily long integer and
+ * the BIGNUM arbitrarily long integer by converting to and from
+ * a string representation of the number (in hexadecimal).
+ * Direct manipulation would be a possibility, but would require
+ * tighter integration with the Python and OpenSSL internals.
+ */
+
+
+%{
+#include <openssl/bn.h>
+%}
+
+
+%inline %{
+PyObject *bn_rand(int bits, int top, int bottom)
+{
+ BIGNUM rnd;
+ PyObject *ret;
+ char *randhex;
+
+ BN_init(&rnd);
+ if (!BN_rand(&rnd, bits, top, bottom)) {
+ /*Custom errors?*/
+ PyErr_SetString(PyExc_Exception, ERR_reason_error_string(ERR_get_error()));
+ BN_free(&rnd);
+ return NULL;
+ }
+
+ randhex = BN_bn2hex(&rnd);
+ if (!randhex) {
+ /*Custom errors?*/
+ PyErr_SetString(PyExc_Exception, ERR_reason_error_string(ERR_get_error()));
+ BN_free(&rnd);
+ return NULL;
+ }
+ BN_free(&rnd);
+
+ ret = PyLong_FromString(randhex, NULL, 16);
+ OPENSSL_free(randhex);
+ return ret;
+}
+
+
+PyObject *bn_rand_range(PyObject *range)
+{
+ BIGNUM rnd;
+ BIGNUM *rng = NULL;
+ PyObject *ret, *tuple;
+ PyObject *format, *rangePyString;
+ char *randhex, *rangehex;
+
+ /* Wow, it's a lot of work to convert into a hex string in C! */
+ format = PyString_FromString("%x");
+ if (!format) {
+ return NULL;
+ }
+ tuple = PyTuple_New(1);
+ if (!tuple) {
+ Py_DECREF(format);
+ PyErr_SetString(PyExc_RuntimeError, "PyTuple_New() fails");
+ return NULL;
+ }
+ Py_INCREF(range);
+ PyTuple_SET_ITEM(tuple, 0, range);
+ rangePyString = PyString_Format(format, tuple);
+ if (!rangePyString) {
+ PyErr_SetString(PyExc_Exception, "PyString_Format failed");
+ Py_DECREF(format);
+ Py_DECREF(tuple);
+ return NULL;
+ }
+ Py_DECREF(format);
+ Py_DECREF(tuple);
+ rangehex = PyString_AsString(rangePyString);
+
+ if (!BN_hex2bn(&rng, rangehex)) {
+ /*Custom errors?*/
+ PyErr_SetString(PyExc_Exception, ERR_reason_error_string(ERR_get_error()));
+ Py_DECREF(rangePyString);
+ return NULL;
+ }
+
+ Py_DECREF(rangePyString);
+
+ BN_init(&rnd);
+
+ if (!BN_rand_range(&rnd, rng)) {
+ /*Custom errors?*/
+ PyErr_SetString(PyExc_Exception, ERR_reason_error_string(ERR_get_error()));
+ BN_free(&rnd);
+ BN_free(rng);
+ return NULL;
+ }
+
+ BN_free(rng);
+
+ randhex = BN_bn2hex(&rnd);
+ if (!randhex) {
+ /*Custom errors?*/
+ PyErr_SetString(PyExc_Exception, ERR_reason_error_string(ERR_get_error()));
+ BN_free(&rnd);
+ return NULL;
+ }
+ BN_free(&rnd);
+
+ ret = PyLong_FromString(randhex, NULL, 16);
+ OPENSSL_free(randhex);
+ return ret;
+}
+
+%}
diff --git a/SWIG/_dh.i b/SWIG/_dh.i
new file mode 100644
index 0000000..675b39f
--- /dev/null
+++ b/SWIG/_dh.i
@@ -0,0 +1,186 @@
+/* Copyright (c) 1999 Ng Pheng Siong. All rights reserved. */
+/* $Id: _dh.i 695 2009-07-24 06:37:01Z heikki $ */
+
+%{
+#include <openssl/bn.h>
+#include <openssl/bio.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/dh.h>
+%}
+
+%apply Pointer NONNULL { DH * };
+
+%rename(dh_new) DH_new;
+extern DH *DH_new(void);
+%rename(dh_free) DH_free;
+extern void DH_free(DH *);
+%rename(dh_size) DH_size;
+extern int DH_size(const DH *);
+%rename(dh_generate_key) DH_generate_key;
+extern int DH_generate_key(DH *);
+%rename(dhparams_print) DHparams_print;
+%threadallow DHparams_print;
+extern int DHparams_print(BIO *, const DH *);
+
+%constant int dh_check_ok = 0;
+%constant int dh_check_p_not_prime = DH_CHECK_P_NOT_PRIME;
+%constant int dh_check_p_not_strong = DH_CHECK_P_NOT_STRONG_PRIME;
+%constant int dh_check_g_failed = DH_UNABLE_TO_CHECK_GENERATOR;
+%constant int dh_check_bad_g = DH_NOT_SUITABLE_GENERATOR;
+
+%constant DH_GENERATOR_2 = 2;
+%constant DH_GENERATOR_5 = 5;
+
+%inline %{
+static PyObject *_dh_err;
+
+void dh_init(PyObject *dh_err) {
+ Py_INCREF(dh_err);
+ _dh_err = dh_err;
+}
+
+int dh_type_check(DH *dh) {
+ /* Our getting here means we passed Swig's type checking,
+ XXX Still need to check the pointer for sanity? */
+ return 1;
+}
+%}
+
+%threadallow dh_read_parameters;
+%inline %{
+DH *dh_read_parameters(BIO *bio) {
+ return PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+}
+
+void gendh_callback(int p, int n, void *arg) {
+ PyObject *argv, *ret, *cbfunc;
+
+ cbfunc = (PyObject *)arg;
+ argv = Py_BuildValue("(ii)", p, n);
+ ret = PyEval_CallObject(cbfunc, argv);
+ PyErr_Clear();
+ Py_DECREF(argv);
+ Py_XDECREF(ret);
+}
+
+DH *dh_generate_parameters(int plen, int g, PyObject *pyfunc) {
+ DH *dh;
+
+ Py_INCREF(pyfunc);
+ dh = DH_generate_parameters(plen, g, gendh_callback, (void *)pyfunc);
+ Py_DECREF(pyfunc);
+ if (!dh)
+ PyErr_SetString(_dh_err, ERR_reason_error_string(ERR_get_error()));
+ return dh;
+}
+
+/* Note return value shenanigan. */
+int dh_check(DH *dh) {
+ int err;
+
+ return (DH_check(dh, &err)) ? 0 : err;
+}
+
+PyObject *dh_compute_key(DH *dh, PyObject *pubkey) {
+ const void *pkbuf;
+ int pklen, klen;
+ void *key;
+ BIGNUM *pk;
+ PyObject *ret;
+
+ if (m2_PyObject_AsReadBufferInt(pubkey, &pkbuf, &pklen) == -1)
+ return NULL;
+
+ if (!(pk = BN_mpi2bn((unsigned char *)pkbuf, pklen, NULL))) {
+ PyErr_SetString(_dh_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ if (!(key = PyMem_Malloc(DH_size(dh)))) {
+ BN_free(pk);
+ PyErr_SetString(PyExc_MemoryError, "dh_compute_key");
+ return NULL;
+ }
+ if ((klen = DH_compute_key((unsigned char *)key, pk, dh)) == -1) {
+ BN_free(pk);
+ PyMem_Free(key);
+ PyErr_SetString(_dh_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ ret = PyString_FromStringAndSize((const char *)key, klen);
+ BN_free(pk);
+ PyMem_Free(key);
+ return ret;
+}
+
+PyObject *dh_get_p(DH *dh) {
+ if (!dh->p) {
+ PyErr_SetString(_dh_err, "'p' is unset");
+ return NULL;
+ }
+ return bn_to_mpi(dh->p);
+}
+
+PyObject *dh_get_g(DH *dh) {
+ if (!dh->g) {
+ PyErr_SetString(_dh_err, "'g' is unset");
+ return NULL;
+ }
+ return bn_to_mpi(dh->g);
+}
+
+PyObject *dh_get_pub(DH *dh) {
+ if (!dh->pub_key) {
+ PyErr_SetString(_dh_err, "'pub' is unset");
+ return NULL;
+ }
+ return bn_to_mpi(dh->pub_key);
+}
+
+PyObject *dh_get_priv(DH *dh) {
+ if (!dh->priv_key) {
+ PyErr_SetString(_dh_err, "'priv' is unset");
+ return NULL;
+ }
+ return bn_to_mpi(dh->priv_key);
+}
+
+PyObject *dh_set_p(DH *dh, PyObject *value) {
+ BIGNUM *bn;
+ const void *vbuf;
+ int vlen;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+ PyErr_SetString(_dh_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ if (dh->p)
+ BN_free(dh->p);
+ dh->p = bn;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *dh_set_g(DH *dh, PyObject *value) {
+ BIGNUM *bn;
+ const void *vbuf;
+ int vlen;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+ PyErr_SetString(_dh_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ if (dh->g)
+ BN_free(dh->g);
+ dh->g = bn;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+%}
+
diff --git a/SWIG/_dsa.i b/SWIG/_dsa.i
new file mode 100644
index 0000000..addf33a
--- /dev/null
+++ b/SWIG/_dsa.i
@@ -0,0 +1,349 @@
+/* Copyright (c) 1999-2000 Ng Pheng Siong. All rights reserved. */
+/* $Id: _dsa.i 723 2010-02-13 06:53:13Z heikki $ */
+
+%{
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/dsa.h>
+
+PyObject *dsa_sig_get_r(DSA_SIG *dsa_sig) {
+ return bn_to_mpi(dsa_sig->r);
+}
+
+PyObject *dsa_sig_get_s(DSA_SIG *dsa_sig) {
+ return bn_to_mpi(dsa_sig->s);
+}
+%}
+
+%apply Pointer NONNULL { DSA * };
+
+%rename(dsa_new) DSA_new;
+extern DSA *DSA_new(void);
+%rename(dsa_free) DSA_free;
+extern void DSA_free(DSA *);
+%rename(dsa_size) DSA_size;
+extern int DSA_size(const DSA *); /* assert(dsa->q); */
+%rename(dsa_gen_key) DSA_generate_key;
+extern int DSA_generate_key(DSA *);
+
+%inline %{
+static PyObject *_dsa_err;
+
+void dsa_init(PyObject *dsa_err) {
+ Py_INCREF(dsa_err);
+ _dsa_err = dsa_err;
+}
+
+void genparam_callback(int p, int n, void *arg) {
+ PyObject *argv, *ret, *cbfunc;
+
+ cbfunc = (PyObject *)arg;
+ argv = Py_BuildValue("(ii)", p, n);
+ ret = PyEval_CallObject(cbfunc, argv);
+ PyErr_Clear();
+ Py_DECREF(argv);
+ Py_XDECREF(ret);
+}
+
+DSA *dsa_generate_parameters(int bits, PyObject *pyfunc) {
+ DSA *dsa;
+
+ Py_INCREF(pyfunc);
+ dsa = DSA_generate_parameters(bits, NULL, 0, NULL, NULL, genparam_callback, (void *)pyfunc);
+ Py_DECREF(pyfunc);
+ if (!dsa)
+ PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+ return dsa;
+}
+
+PyObject *dsa_get_p(DSA *dsa) {
+ if (!dsa->p) {
+ PyErr_SetString(_dsa_err, "'p' is unset");
+ return NULL;
+ }
+ return bn_to_mpi(dsa->p);
+}
+
+PyObject *dsa_get_q(DSA *dsa) {
+ if (!dsa->q) {
+ PyErr_SetString(_dsa_err, "'q' is unset");
+ return NULL;
+ }
+ return bn_to_mpi(dsa->q);
+}
+
+PyObject *dsa_get_g(DSA *dsa) {
+ if (!dsa->g) {
+ PyErr_SetString(_dsa_err, "'g' is unset");
+ return NULL;
+ }
+ return bn_to_mpi(dsa->g);
+}
+
+PyObject *dsa_get_pub(DSA *dsa) {
+ if (!dsa->pub_key) {
+ PyErr_SetString(_dsa_err, "'pub' is unset");
+ return NULL;
+ }
+ return bn_to_mpi(dsa->pub_key);
+}
+
+PyObject *dsa_get_priv(DSA *dsa) {
+ if (!dsa->priv_key) {
+ PyErr_SetString(_dsa_err, "'priv' is unset");
+ return NULL;
+ }
+ return bn_to_mpi(dsa->priv_key);
+}
+
+PyObject *dsa_set_p(DSA *dsa, PyObject *value) {
+ BIGNUM *bn;
+ const void *vbuf;
+ int vlen;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+ PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ if (dsa->p)
+ BN_free(dsa->p);
+ dsa->p = bn;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *dsa_set_q(DSA *dsa, PyObject *value) {
+ BIGNUM *bn;
+ const void *vbuf;
+ int vlen;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+ PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ if (dsa->q)
+ BN_free(dsa->q);
+ dsa->q = bn;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *dsa_set_g(DSA *dsa, PyObject *value) {
+ BIGNUM *bn;
+ const void *vbuf;
+ int vlen;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+ PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ if (dsa->g)
+ BN_free(dsa->g);
+ dsa->g = bn;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+%}
+
+%inline %{
+DSA *dsa_read_params(BIO *f, PyObject *pyfunc) {
+ DSA *ret;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ ret = PEM_read_bio_DSAparams(f, NULL, passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return ret;
+}
+%}
+
+%threadallow dsa_write_params_bio;
+%inline %{
+int dsa_write_params_bio(DSA* dsa, BIO* f) {
+ return PEM_write_bio_DSAparams(f, dsa);
+}
+%}
+
+%inline %{
+int dsa_write_key_bio(DSA* dsa, BIO* f, EVP_CIPHER *cipher, PyObject *pyfunc) {
+ int ret;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ ret = PEM_write_bio_DSAPrivateKey(f, dsa, cipher, NULL, 0,
+ passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return ret;
+}
+%}
+
+%inline %{
+int dsa_write_key_bio_no_cipher(DSA* dsa, BIO* f, PyObject *pyfunc) {
+ int ret;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ ret = PEM_write_bio_DSAPrivateKey(f, dsa, NULL, NULL, 0,
+ passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return ret;
+}
+%}
+
+%threadallow dsa_write_pub_key_bio;
+%inline %{
+int dsa_write_pub_key_bio(DSA* dsa, BIO* f) {
+ return PEM_write_bio_DSA_PUBKEY(f, dsa);
+}
+%}
+
+%inline %{
+DSA *dsa_read_key(BIO *f, PyObject *pyfunc) {
+ DSA *ret;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ ret = PEM_read_bio_DSAPrivateKey(f, NULL, passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return ret;
+}
+%}
+
+%inline %{
+DSA *dsa_read_pub_key(BIO *f, PyObject *pyfunc) {
+ DSA *ret;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ ret = PEM_read_bio_DSA_PUBKEY(f, NULL, passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return ret;
+}
+
+PyObject *dsa_sign(DSA *dsa, PyObject *value) {
+ const void *vbuf;
+ int vlen;
+ PyObject *tuple;
+ DSA_SIG *sig;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (!(sig = DSA_do_sign(vbuf, vlen, dsa))) {
+ PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ if (!(tuple = PyTuple_New(2))) {
+ DSA_SIG_free(sig);
+ PyErr_SetString(PyExc_RuntimeError, "PyTuple_New() fails");
+ return NULL;
+ }
+ PyTuple_SET_ITEM(tuple, 0, dsa_sig_get_r(sig));
+ PyTuple_SET_ITEM(tuple, 1, dsa_sig_get_s(sig));
+ DSA_SIG_free(sig);
+ return tuple;
+}
+
+int dsa_verify(DSA *dsa, PyObject *value, PyObject *r, PyObject *s) {
+ const void *vbuf, *rbuf, *sbuf;
+ int vlen, rlen, slen;
+ DSA_SIG *sig;
+ int ret;
+
+ if ((m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ || (m2_PyObject_AsReadBufferInt(r, &rbuf, &rlen) == -1)
+ || (m2_PyObject_AsReadBufferInt(s, &sbuf, &slen) == -1))
+ return -1;
+
+ if (!(sig = DSA_SIG_new())) {
+ PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+ return -1;
+ }
+ if (!(sig->r = BN_mpi2bn((unsigned char *)rbuf, rlen, NULL))) {
+ PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+ DSA_SIG_free(sig);
+ return -1;
+ }
+ if (!(sig->s = BN_mpi2bn((unsigned char *)sbuf, slen, NULL))) {
+ PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+ DSA_SIG_free(sig);
+ return -1;
+ }
+ ret = DSA_do_verify(vbuf, vlen, sig, dsa);
+ DSA_SIG_free(sig);
+ if (ret == -1)
+ PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+ return ret;
+}
+
+PyObject *dsa_sign_asn1(DSA *dsa, PyObject *value) {
+ const void *vbuf;
+ int vlen;
+ void *sigbuf;
+ unsigned int siglen;
+ PyObject *ret;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (!(sigbuf = PyMem_Malloc(DSA_size(dsa)))) {
+ PyErr_SetString(PyExc_MemoryError, "dsa_sign_asn1");
+ return NULL;
+ }
+ if (!DSA_sign(0, vbuf, vlen, (unsigned char *)sigbuf, &siglen, dsa)) {
+ PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+ PyMem_Free(sigbuf);
+ return NULL;
+ }
+ ret = PyString_FromStringAndSize(sigbuf, siglen);
+ PyMem_Free(sigbuf);
+ return ret;
+}
+
+int dsa_verify_asn1(DSA *dsa, PyObject *value, PyObject *sig) {
+ const void *vbuf;
+ void *sbuf;
+ int vlen, slen, ret;
+
+ if ((m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ || (m2_PyObject_AsReadBufferInt(sig, (const void **)&sbuf, &slen)
+ == -1))
+ return -1;
+
+ if ((ret = DSA_verify(0, vbuf, vlen, sbuf, slen, dsa)) == -1)
+ PyErr_SetString(_dsa_err, ERR_reason_error_string(ERR_get_error()));
+ return ret;
+}
+
+int dsa_check_key(DSA *dsa) {
+ return (dsa->pub_key) && (dsa->priv_key);
+}
+
+int dsa_check_pub_key(DSA *dsa) {
+ return dsa->pub_key ? 1 : 0;
+}
+
+int dsa_keylen(DSA *dsa) {
+ return BN_num_bits(dsa->p);
+}
+
+int dsa_type_check(DSA *dsa) {
+ return 1;
+}
+%}
+
diff --git a/SWIG/_ec.i b/SWIG/_ec.i
new file mode 100644
index 0000000..f0e52bd
--- /dev/null
+++ b/SWIG/_ec.i
@@ -0,0 +1,419 @@
+/* Copyright (c) 1999-2000 Ng Pheng Siong. All rights reserved.
+ Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam. All rights reserved.
+
+ Most code originally from _dsa.i, _rsa.i and _dh.i and adjusted for EC use.
+*/
+
+%include <openssl/opensslconf.h>
+
+#if OPENSSL_VERSION_NUMBER < 0x0090800fL || defined(OPENSSL_NO_EC)
+#undef OPENSSL_NO_EC
+%constant OPENSSL_NO_EC = 1;
+#else
+%constant OPENSSL_NO_EC = 0;
+
+%{
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/x509.h>
+#include <openssl/ecdsa.h>
+#include <openssl/ecdh.h>
+%}
+
+%apply Pointer NONNULL { EC_KEY * };
+
+%rename(ec_key_new) EC_KEY_new;
+extern EC_KEY *EC_KEY_new(void);
+%rename(ec_key_free) EC_KEY_free;
+extern void EC_KEY_free(EC_KEY *);
+%rename(ec_key_size) ECDSA_size;
+extern int ECDSA_size(const EC_KEY *);
+%rename(ec_key_gen_key) EC_KEY_generate_key;
+extern int EC_KEY_generate_key(EC_KEY *);
+%rename(ec_key_check_key) EC_KEY_check_key;
+extern int EC_KEY_check_key(const EC_KEY *);
+
+/* Curve identifier constants from OpenSSL */
+%constant int NID_secp112r1 = NID_secp112r1;
+%constant int NID_secp112r2 = NID_secp112r2;
+%constant int NID_secp128r1 = NID_secp128r1;
+%constant int NID_secp128r2 = NID_secp128r2;
+%constant int NID_secp160k1 = NID_secp160k1;
+%constant int NID_secp160r1 = NID_secp160r1;
+%constant int NID_secp160r2 = NID_secp160r2;
+%constant int NID_secp192k1 = NID_secp192k1;
+%constant int NID_secp224k1 = NID_secp224k1;
+%constant int NID_secp224r1 = NID_secp224r1;
+%constant int NID_secp256k1 = NID_secp256k1;
+%constant int NID_secp384r1 = NID_secp384r1;
+%constant int NID_secp521r1 = NID_secp521r1;
+%constant int NID_sect113r1 = NID_sect113r1;
+%constant int NID_sect113r2 = NID_sect113r2;
+%constant int NID_sect131r1 = NID_sect131r1;
+%constant int NID_sect131r2 = NID_sect131r2;
+%constant int NID_sect163k1 = NID_sect163k1;
+%constant int NID_sect163r1 = NID_sect163r1;
+%constant int NID_sect163r2 = NID_sect163r2;
+%constant int NID_sect193r1 = NID_sect193r1;
+%constant int NID_sect193r2 = NID_sect193r2;
+%constant int NID_sect233k1 = NID_sect233k1;
+%constant int NID_sect233r1 = NID_sect233r1;
+%constant int NID_sect239k1 = NID_sect239k1;
+%constant int NID_sect283k1 = NID_sect283k1;
+%constant int NID_sect283r1 = NID_sect283r1;
+%constant int NID_sect409k1 = NID_sect409k1;
+%constant int NID_sect409r1 = NID_sect409r1;
+%constant int NID_sect571k1 = NID_sect571k1;
+%constant int NID_sect571r1 = NID_sect571r1;
+
+%constant int NID_X9_62_prime192v1 = NID_X9_62_prime192v1;
+%constant int NID_X9_62_prime192v2 = NID_X9_62_prime192v2;
+%constant int NID_X9_62_prime192v3 = NID_X9_62_prime192v3;
+%constant int NID_X9_62_prime239v1 = NID_X9_62_prime239v1;
+%constant int NID_X9_62_prime239v2 = NID_X9_62_prime239v2;
+%constant int NID_X9_62_prime239v3 = NID_X9_62_prime239v3;
+%constant int NID_X9_62_prime256v1 = NID_X9_62_prime256v1;
+%constant int NID_X9_62_c2pnb163v1 = NID_X9_62_c2pnb163v1;
+%constant int NID_X9_62_c2pnb163v2 = NID_X9_62_c2pnb163v2;
+%constant int NID_X9_62_c2pnb163v3 = NID_X9_62_c2pnb163v3;
+%constant int NID_X9_62_c2pnb176v1 = NID_X9_62_c2pnb176v1;
+%constant int NID_X9_62_c2tnb191v1 = NID_X9_62_c2tnb191v1;
+%constant int NID_X9_62_c2tnb191v2 = NID_X9_62_c2tnb191v2;
+%constant int NID_X9_62_c2tnb191v3 = NID_X9_62_c2tnb191v3;
+%constant int NID_X9_62_c2pnb208w1 = NID_X9_62_c2pnb208w1;
+%constant int NID_X9_62_c2tnb239v1 = NID_X9_62_c2tnb239v1;
+%constant int NID_X9_62_c2tnb239v2 = NID_X9_62_c2tnb239v2;
+%constant int NID_X9_62_c2tnb239v3 = NID_X9_62_c2tnb239v3;
+%constant int NID_X9_62_c2pnb272w1 = NID_X9_62_c2pnb272w1;
+%constant int NID_X9_62_c2pnb304w1 = NID_X9_62_c2pnb304w1;
+%constant int NID_X9_62_c2tnb359v1 = NID_X9_62_c2tnb359v1;
+%constant int NID_X9_62_c2pnb368w1 = NID_X9_62_c2pnb368w1;
+%constant int NID_X9_62_c2tnb431r1 = NID_X9_62_c2tnb431r1;
+
+%constant int NID_wap_wsg_idm_ecid_wtls1 = NID_wap_wsg_idm_ecid_wtls1;
+%constant int NID_wap_wsg_idm_ecid_wtls3 = NID_wap_wsg_idm_ecid_wtls3;
+%constant int NID_wap_wsg_idm_ecid_wtls4 = NID_wap_wsg_idm_ecid_wtls4;
+%constant int NID_wap_wsg_idm_ecid_wtls5 = NID_wap_wsg_idm_ecid_wtls5;
+%constant int NID_wap_wsg_idm_ecid_wtls6 = NID_wap_wsg_idm_ecid_wtls6;
+%constant int NID_wap_wsg_idm_ecid_wtls7 = NID_wap_wsg_idm_ecid_wtls7;
+%constant int NID_wap_wsg_idm_ecid_wtls8 = NID_wap_wsg_idm_ecid_wtls8;
+%constant int NID_wap_wsg_idm_ecid_wtls9 = NID_wap_wsg_idm_ecid_wtls9;
+%constant int NID_wap_wsg_idm_ecid_wtls10 = NID_wap_wsg_idm_ecid_wtls10;
+%constant int NID_wap_wsg_idm_ecid_wtls11 = NID_wap_wsg_idm_ecid_wtls11;
+%constant int NID_wap_wsg_idm_ecid_wtls12 = NID_wap_wsg_idm_ecid_wtls12;
+
+%constant int NID_ipsec3 = NID_ipsec3;
+%constant int NID_ipsec4 = NID_ipsec4;
+
+
+%inline %{
+static PyObject *_ec_err;
+
+void ec_init(PyObject *ec_err) {
+ Py_INCREF(ec_err);
+ _ec_err = ec_err;
+}
+
+EC_KEY* ec_key_new_by_curve_name(int nid)
+{
+ EC_KEY *key;
+ EC_GROUP *group;
+ int ret =0;
+ point_conversion_form_t form = POINT_CONVERSION_UNCOMPRESSED;
+ int asn1_flag = OPENSSL_EC_NAMED_CURVE;
+
+ /* If I simply do "return EC_KEY_new_by_curve_name(nid);"
+ * I get large public keys (222 vs 84 bytes for sect233k1 curve).
+ * I don't know why that is, but 'openssl ecparam -genkey ...' sets
+ * the ASN.1 flag and the point conversion form, and gets the
+ * small pub keys. So let's do that too.
+ */
+ key = EC_KEY_new();
+ if (!key) {
+ PyErr_SetString(PyExc_MemoryError, "ec_key_new_by_curve_name");
+ return NULL;
+ }
+ group = EC_GROUP_new_by_curve_name(nid);
+ if (!group) {
+ EC_KEY_free(key);
+ PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ EC_GROUP_set_asn1_flag(group, asn1_flag);
+ EC_GROUP_set_point_conversion_form(group, form);
+ ret = EC_KEY_set_group(key, group);
+ EC_GROUP_free(group);
+ if (ret == 0)
+ {
+ /* EC_KEY_set_group only returns 0 or 1, and does not set error. */
+ PyErr_SetString(_ec_err, "cannot set key's group");
+ EC_KEY_free(key);
+ return NULL;
+ }
+
+ return key;
+}
+
+PyObject *ec_key_get_public_der(EC_KEY *key) {
+
+ unsigned char *src=NULL;
+ void *dst=NULL;
+ int src_len=0;
+ Py_ssize_t dst_len=0;
+ PyObject *pyo=NULL;
+ int ret=0;
+
+ /* Convert to binary */
+ src_len = i2d_EC_PUBKEY( key, &src );
+ if (src_len < 0)
+ {
+ PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ /* Create a PyBuffer containing a copy of the binary,
+ * to simplify memory deallocation
+ */
+ pyo = PyBuffer_New( src_len );
+ ret = PyObject_AsWriteBuffer( pyo, &dst, &dst_len );
+ assert( src_len == dst_len );
+ if (ret < 0)
+ {
+ Py_DECREF(pyo);
+ OPENSSL_free(src);
+ PyErr_SetString(_ec_err, "cannot get write buffer");
+ return NULL;
+ }
+ memcpy( dst, src, src_len );
+ OPENSSL_free(src);
+
+ return pyo;
+}
+%}
+
+%threadallow ec_key_read_pubkey;
+%inline %{
+EC_KEY *ec_key_read_pubkey(BIO *f) {
+ return PEM_read_bio_EC_PUBKEY(f, NULL, NULL, NULL);
+}
+%}
+
+%threadallow ec_key_write_pubkey;
+%inline %{
+int ec_key_write_pubkey(EC_KEY *key, BIO *f) {
+ return PEM_write_bio_EC_PUBKEY(f, key );
+}
+%}
+
+%inline %{
+EC_KEY *ec_key_read_bio(BIO *f, PyObject *pyfunc) {
+ EC_KEY *ret;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ ret = PEM_read_bio_ECPrivateKey(f, NULL, passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return ret;
+}
+%}
+
+%inline %{
+int ec_key_write_bio(EC_KEY *key, BIO *f, EVP_CIPHER *cipher, PyObject *pyfunc) {
+ int ret;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ ret = PEM_write_bio_ECPrivateKey(f, key, cipher, NULL, 0,
+ passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return ret;
+}
+%}
+
+%inline %{
+int ec_key_write_bio_no_cipher(EC_KEY *key, BIO *f, PyObject *pyfunc) {
+ int ret;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ ret = PEM_write_bio_ECPrivateKey(f, key, NULL, NULL, 0,
+ passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return ret;
+}
+
+
+PyObject *ecdsa_sig_get_r(ECDSA_SIG *ecdsa_sig) {
+ return bn_to_mpi(ecdsa_sig->r);
+}
+
+PyObject *ecdsa_sig_get_s(ECDSA_SIG *ecdsa_sig) {
+ return bn_to_mpi(ecdsa_sig->s);
+}
+
+PyObject *ecdsa_sign(EC_KEY *key, PyObject *value) {
+ const void *vbuf;
+ int vlen;
+ PyObject *tuple;
+ ECDSA_SIG *sig;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (!(sig = ECDSA_do_sign(vbuf, vlen, key))) {
+ PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ if (!(tuple = PyTuple_New(2))) {
+ ECDSA_SIG_free(sig);
+ PyErr_SetString(PyExc_RuntimeError, "PyTuple_New() fails");
+ return NULL;
+ }
+ PyTuple_SET_ITEM(tuple, 0, ecdsa_sig_get_r(sig));
+ PyTuple_SET_ITEM(tuple, 1, ecdsa_sig_get_s(sig));
+ ECDSA_SIG_free(sig);
+ return tuple;
+}
+
+int ecdsa_verify(EC_KEY *key, PyObject *value, PyObject *r, PyObject *s) {
+ const void *vbuf, *rbuf, *sbuf;
+ int vlen, rlen, slen;
+ ECDSA_SIG *sig;
+ int ret;
+
+ if ((m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ || (m2_PyObject_AsReadBufferInt(r, &rbuf, &rlen) == -1)
+ || (m2_PyObject_AsReadBufferInt(s, &sbuf, &slen) == -1))
+ return -1;
+
+ if (!(sig = ECDSA_SIG_new())) {
+ PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+ return -1;
+ }
+ if (!BN_mpi2bn((unsigned char *)rbuf, rlen, sig->r)) {
+ PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+ ECDSA_SIG_free(sig);
+ return -1;
+ }
+ if (!BN_mpi2bn((unsigned char *)sbuf, slen, sig->s)) {
+ PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+ ECDSA_SIG_free(sig);
+ return -1;
+ }
+ ret = ECDSA_do_verify(vbuf, vlen, sig, key);
+ ECDSA_SIG_free(sig);
+ if (ret == -1)
+ PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+ return ret;
+}
+
+
+PyObject *ecdsa_sign_asn1(EC_KEY *key, PyObject *value) {
+ const void *vbuf;
+ int vlen;
+ void *sigbuf;
+ unsigned int siglen;
+ PyObject *ret;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (!(sigbuf = PyMem_Malloc(ECDSA_size(key)))) {
+ PyErr_SetString(PyExc_MemoryError, "ecdsa_sign_asn1");
+ return NULL;
+ }
+ if (!ECDSA_sign(0, vbuf, vlen, (unsigned char *)sigbuf, &siglen, key)) {
+ PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+ PyMem_Free(sigbuf);
+ return NULL;
+ }
+ ret = PyString_FromStringAndSize(sigbuf, siglen);
+ PyMem_Free(sigbuf);
+ return ret;
+}
+
+
+int ecdsa_verify_asn1(EC_KEY *key, PyObject *value, PyObject *sig) {
+ const void *vbuf;
+ void *sbuf;
+ int vlen, slen, ret;
+
+ if ((m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ || (m2_PyObject_AsReadBufferInt(sig, (const void **)&sbuf, &slen)
+ == -1))
+ return -1;
+
+ if ((ret = ECDSA_verify(0, vbuf, vlen, sbuf, slen, key)) == -1)
+ PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+ return ret;
+}
+
+PyObject *ecdh_compute_key(EC_KEY *keypairA, EC_KEY *pubkeyB) {
+ int sharedkeylen;
+ void *sharedkey;
+ const EC_POINT *pkpointB;
+ PyObject *ret;
+ const EC_GROUP* groupA;
+
+ if ((pkpointB = EC_KEY_get0_public_key(pubkeyB)) == NULL)
+ {
+ PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+
+ groupA = EC_KEY_get0_group(keypairA);
+ sharedkeylen = (EC_GROUP_get_degree(groupA) + 7)/8;
+
+ if (!(sharedkey = PyMem_Malloc(sharedkeylen))) {
+ PyErr_SetString(PyExc_MemoryError, "ecdh_compute_key");
+ return NULL;
+ }
+ if ((sharedkeylen = ECDH_compute_key((unsigned char *)sharedkey, sharedkeylen, pkpointB, keypairA, NULL)) == -1) {
+ PyMem_Free(sharedkey);
+ PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+
+ ret = PyString_FromStringAndSize((const char *)sharedkey, sharedkeylen);
+ PyMem_Free(sharedkey);
+
+ return ret;
+}
+
+
+EC_KEY* ec_key_from_pubkey_der(PyObject *pubkey) {
+ const void *keypairbuf;
+ Py_ssize_t keypairbuflen;
+ const unsigned char *tempBuf;
+ EC_KEY *keypair;
+
+ if (PyObject_AsReadBuffer(pubkey, &keypairbuf, &keypairbuflen) == -1)
+ {
+ return NULL;
+ }
+
+ tempBuf = (const unsigned char *)keypairbuf;
+ if ((keypair = d2i_EC_PUBKEY( NULL, &tempBuf, keypairbuflen)) == 0)
+ {
+ PyErr_SetString(_ec_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ return keypair;
+}
+
+
+// According to [SEC2] the degree of the group is defined as EC key length
+int ec_key_keylen(EC_KEY *key) {
+ const EC_GROUP *group = EC_KEY_get0_group(key);
+ return EC_GROUP_get_degree(group);
+}
+
+int ec_key_type_check(EC_KEY *key) {
+ return 1;
+}
+%}
+#endif // if OpenSSL version with EC support
+
diff --git a/SWIG/_engine.i b/SWIG/_engine.i
new file mode 100644
index 0000000..b55720f
--- /dev/null
+++ b/SWIG/_engine.i
@@ -0,0 +1,207 @@
+/*
+ * -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: syntax=c sts=4 sw=4
+ *
+ * ENGINE functions from engine(3SSL).
+ *
+ * Pavel Shramov
+ * IMEC MSU
+ */
+%{
+#include <openssl/engine.h>
+#include <openssl/ui.h>
+#include <stdio.h>
+%}
+
+%apply Pointer NONNULL { ENGINE * };
+%apply Pointer NONNULL { const ENGINE * };
+%apply Pointer NONNULL { const char * };
+
+/*
+ * Functions to load different engines
+ */
+%rename(engine_load_builtin_engines) ENGINE_load_builtin_engines;
+extern void ENGINE_load_builtin_engines(void);
+
+%rename(engine_load_dynamic) ENGINE_load_dynamic;
+extern void ENGINE_load_dynamic(void);
+
+%rename(engine_load_openssl) ENGINE_load_openssl;
+extern void ENGINE_load_openssl(void);
+
+%rename(engine_cleanup) ENGINE_cleanup;
+extern void ENGINE_cleanup(void);
+
+/*
+ * Engine allocation functions
+ */
+%rename(engine_new) ENGINE_new;
+extern ENGINE * ENGINE_new();
+
+%rename(engine_by_id) ENGINE_by_id;
+extern ENGINE * ENGINE_by_id(const char *);
+
+%rename(engine_free) ENGINE_free;
+extern int ENGINE_free(ENGINE *);
+
+%rename(engine_init) ENGINE_init;
+extern int ENGINE_init(ENGINE *);
+
+%rename(engine_finish) ENGINE_finish;
+extern int ENGINE_finish(ENGINE *);
+
+/*
+ * Engine id/name functions
+ */
+%rename(engine_get_id) ENGINE_get_id;
+extern const char * ENGINE_get_id(const ENGINE *);
+
+%rename(engine_get_name) ENGINE_get_name;
+extern const char * ENGINE_get_name(const ENGINE *);
+
+/*
+ * Engine control functions
+ * Control argument may be NULL (e.g for LOAD command)
+ */
+%clear const char *;
+%rename(engine_ctrl_cmd_string) ENGINE_ctrl_cmd_string;
+extern int ENGINE_ctrl_cmd_string(ENGINE *e, const char *NONNULL,
+ const char *arg, int cmd_optional);
+
+%apply Pointer NONNULL { const char * };
+
+/*
+ * UI methods.
+ * XXX: UI_OpenSSL method is static and UI_destroy_method is not needed.
+ */
+%rename(ui_openssl) UI_OpenSSL;
+extern UI_METHOD * UI_OpenSSL();
+
+/*
+%rename(ui_destroy_method) UI_destroy_method;
+extern void UI_destroy_method(UI_METHOD *ui_method);
+ */
+
+%clear const char *;
+%inline %{
+
+/*
+ * Code from engine-pkcs11 1.4.0 in engine-pkcs11.c
+ *
+
+99 static char *get_pin(UI_METHOD * ui_method, void *callback_data, char *sc_pin,
+100 int maxlen)
+101 {
+102 UI *ui;
+103 struct {
+104 const void *password;
+105 const char *prompt_info;
+106 } *mycb = callback_data;
+107
+108 if (mycb->password) {
+109 sc_pin = set_pin(mycb->password);
+110 return sc_pin;
+111 }
+
+ *
+ * So callback_data need to be always provided and have fixed type.
+ * UI method still may be NULL.
+ *
+ * Following functions allocate and free callback data structure with
+ * optional password set.
+ */
+
+typedef struct {
+ char * password;
+ char * prompt;
+} _cbd_t;
+
+void * engine_pkcs11_data_new(const char *pin) {
+ _cbd_t * cb = (_cbd_t *) PyMem_Malloc(sizeof(_cbd_t));
+ if (!cb) {
+ PyErr_SetString(PyExc_MemoryError, "engine_pkcs11_data_new");
+ return NULL;
+ }
+ cb->password = NULL;
+ if (pin) {
+ size_t size = strlen(pin);
+ cb->password = (char *) PyMem_Malloc(size + 1);
+ if (!cb->password) {
+ PyErr_SetString(PyExc_MemoryError, "engine_pkcs11_data_new");
+ PyMem_Free(cb);
+ return NULL;
+ }
+ memcpy(cb->password, pin, size + 1);
+ }
+ cb->prompt = NULL;
+ return cb;
+}
+
+void engine_pkcs11_data_free(void * vcb) {
+ _cbd_t * cb = (_cbd_t *) vcb;
+ if (!cb)
+ return;
+ if (cb->password)
+ PyMem_Free(cb->password);
+ PyMem_Free(cb);
+}
+
+%}
+%apply Pointer NONNULL { const char * };
+
+/*
+ * Engine key/cert load functions.
+ * See above notice about callback_data.
+ */
+%rename(engine_load_private_key) ENGINE_load_private_key;
+extern EVP_PKEY *ENGINE_load_private_key(ENGINE *e, const char *key_id,
+ UI_METHOD *ui_method, void *callback_data);
+%rename(engine_load_public_key) ENGINE_load_public_key;
+extern EVP_PKEY *ENGINE_load_public_key(ENGINE *e, const char *key_id,
+ UI_METHOD *ui_method, void *callback_data);
+
+/*
+ * This function may be not implemented in engine.
+ * pkcs11 engine has this control.
+ */
+%inline %{
+static PyObject *_engine_err;
+
+void engine_init_error(PyObject *engine_err) {
+ Py_INCREF(engine_err);
+ _engine_err = engine_err;
+}
+
+X509 * engine_load_certificate(ENGINE *e, const char * slot) {
+ struct {
+ const char * slot;
+ X509 * cert;
+ } cbd;
+ cbd.slot = slot;
+ cbd.cert = NULL;
+ if (!ENGINE_ctrl_cmd(e, "LOAD_CERT_CTRL", 0, &cbd, NULL, 0)) {
+ PyErr_SetString(_engine_err, "cannot load certificate");
+ return NULL;
+ }
+ return cbd.cert;
+}
+%}
+
+/* These flags are used to control combinations of algorithm (methods)
+ * by bitwise "OR"ing. */
+%constant int ENGINE_METHOD_RSA = 0x0001;
+%constant int ENGINE_METHOD_DSA = 0x0002;
+%constant int ENGINE_METHOD_DH = 0x0004;
+%constant int ENGINE_METHOD_RAND = 0x0008;
+%constant int ENGINE_METHOD_ECDH = 0x0010;
+%constant int ENGINE_METHOD_ECDSA = 0x0020;
+%constant int ENGINE_METHOD_CIPHERS = 0x0040;
+%constant int ENGINE_METHOD_DIGESTS = 0x0080;
+%constant int ENGINE_METHOD_STORE = 0x0100;
+/* Obvious all-or-nothing cases. */
+%constant int ENGINE_METHOD_ALL = 0xFFFF;
+%constant int ENGINE_METHOD_NONE = 0x0000;
+
+%rename(engine_set_default) ENGINE_set_default;
+extern int ENGINE_set_default(ENGINE *e, unsigned int flags);
+
diff --git a/SWIG/_evp.i b/SWIG/_evp.i
new file mode 100644
index 0000000..0593eed
--- /dev/null
+++ b/SWIG/_evp.i
@@ -0,0 +1,596 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+Copyright (c) 1999 Ng Pheng Siong. All rights reserved.
+
+Portions Copyright (c) 2004-2007 Open Source Applications Foundation.
+Author: Heikki Toivonen
+
+Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+
+*/
+
+%include <openssl/opensslconf.h>
+
+%{
+#include <assert.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/rsa.h>
+#include <openssl/opensslv.h>
+%}
+
+%apply Pointer NONNULL { EVP_MD_CTX * };
+%apply Pointer NONNULL { EVP_MD * };
+%apply Pointer NONNULL { EVP_PKEY * };
+%apply Pointer NONNULL { HMAC_CTX * };
+%apply Pointer NONNULL { EVP_CIPHER_CTX * };
+%apply Pointer NONNULL { EVP_CIPHER * };
+%apply Pointer NONNULL { RSA * };
+
+%rename(md5) EVP_md5;
+extern const EVP_MD *EVP_md5(void);
+%rename(sha1) EVP_sha1;
+extern const EVP_MD *EVP_sha1(void);
+%rename(ripemd160) EVP_ripemd160;
+extern const EVP_MD *EVP_ripemd160(void);
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+%rename(sha224) EVP_sha224;
+extern const EVP_MD *EVP_sha224(void);
+%rename(sha256) EVP_sha256;
+extern const EVP_MD *EVP_sha256(void);
+%rename(sha384) EVP_sha384;
+extern const EVP_MD *EVP_sha384(void);
+%rename(sha512) EVP_sha512;
+extern const EVP_MD *EVP_sha512(void);
+#endif
+
+%rename(digest_init) EVP_DigestInit;
+extern int EVP_DigestInit(EVP_MD_CTX *, const EVP_MD *);
+
+%rename(des_ecb) EVP_des_ecb;
+extern const EVP_CIPHER *EVP_des_ecb(void);
+%rename(des_ede_ecb) EVP_des_ede;
+extern const EVP_CIPHER *EVP_des_ede(void);
+%rename(des_ede3_ecb) EVP_des_ede3;
+extern const EVP_CIPHER *EVP_des_ede3(void);
+%rename(des_cbc) EVP_des_cbc;
+extern const EVP_CIPHER *EVP_des_cbc(void);
+%rename(des_ede_cbc) EVP_des_ede_cbc;
+extern const EVP_CIPHER *EVP_des_ede_cbc(void);
+%rename(des_ede3_cbc) EVP_des_ede3_cbc;
+extern const EVP_CIPHER *EVP_des_ede3_cbc(void);
+%rename(des_cfb) EVP_des_cfb;
+extern const EVP_CIPHER *EVP_des_cfb(void);
+%rename(des_ede_cfb) EVP_des_ede_cfb;
+extern const EVP_CIPHER *EVP_des_ede_cfb(void);
+%rename(des_ede3_cfb) EVP_des_ede3_cfb;
+extern const EVP_CIPHER *EVP_des_ede3_cfb(void);
+%rename(des_ofb) EVP_des_ofb;
+extern const EVP_CIPHER *EVP_des_ofb(void);
+%rename(des_ede_ofb) EVP_des_ede_ofb;
+extern const EVP_CIPHER *EVP_des_ede_ofb(void);
+%rename(des_ede3_ofb) EVP_des_ede3_ofb;
+extern const EVP_CIPHER *EVP_des_ede3_ofb(void);
+%rename(bf_ecb) EVP_bf_ecb;
+extern const EVP_CIPHER *EVP_bf_ecb(void);
+%rename(bf_cbc) EVP_bf_cbc;
+extern const EVP_CIPHER *EVP_bf_cbc(void);
+%rename(bf_cfb) EVP_bf_cfb;
+extern const EVP_CIPHER *EVP_bf_cfb(void);
+%rename(bf_ofb) EVP_bf_ofb;
+extern const EVP_CIPHER *EVP_bf_ofb(void);
+/*
+%rename(idea_ecb) extern const EVP_CIPHER *EVP_idea_ecb(void);
+%rename(idea_cbc) extern const EVP_CIPHER *EVP_idea_cbc(void);
+%rename(idea_cfb) extern const EVP_CIPHER *EVP_idea_cfb(void);
+%rename(idea_ofb) extern const EVP_CIPHER *EVP_idea_ofb(void);
+*/
+%rename(cast5_ecb) EVP_cast5_ecb;
+extern const EVP_CIPHER *EVP_cast5_ecb(void);
+%rename(cast5_cbc) EVP_cast5_cbc;
+extern const EVP_CIPHER *EVP_cast5_cbc(void);
+%rename(cast5_cfb) EVP_cast5_cfb;
+extern const EVP_CIPHER *EVP_cast5_cfb(void);
+%rename(cast5_ofb) EVP_cast5_ofb;
+extern const EVP_CIPHER *EVP_cast5_ofb(void);
+/*
+%rename(rc5_ecb) extern const EVP_CIPHER *EVP_rc5_32_12_16_ecb(void);
+%rename(rc5_cbc) extern const EVP_CIPHER *EVP_rc5_32_12_16_cbc(void);
+%rename(rc5_cfb) extern const EVP_CIPHER *EVP_rc5_32_12_16_cfb(void);
+%rename(rc5_ofb) extern const EVP_CIPHER *EVP_rc5_32_12_16_ofb(void);
+*/
+%rename(rc4) EVP_rc4;
+extern const EVP_CIPHER *EVP_rc4(void);
+%rename(rc2_40_cbc) EVP_rc2_40_cbc;
+extern const EVP_CIPHER *EVP_rc2_40_cbc(void);
+%rename(aes_128_ecb) EVP_aes_128_ecb;
+extern const EVP_CIPHER *EVP_aes_128_ecb(void);
+%rename(aes_128_cbc) EVP_aes_128_cbc;
+extern const EVP_CIPHER *EVP_aes_128_cbc(void);
+%rename(aes_128_cfb) EVP_aes_128_cfb;
+extern const EVP_CIPHER *EVP_aes_128_cfb(void);
+%rename(aes_128_ofb) EVP_aes_128_ofb;
+extern const EVP_CIPHER *EVP_aes_128_ofb(void);
+%rename(aes_192_ecb) EVP_aes_192_ecb;
+extern const EVP_CIPHER *EVP_aes_192_ecb(void);
+%rename(aes_192_cbc) EVP_aes_192_cbc;
+extern const EVP_CIPHER *EVP_aes_192_cbc(void);
+%rename(aes_192_cfb) EVP_aes_192_cfb;
+extern const EVP_CIPHER *EVP_aes_192_cfb(void);
+%rename(aes_192_ofb) EVP_aes_192_ofb;
+extern const EVP_CIPHER *EVP_aes_192_ofb(void);
+%rename(aes_256_ecb) EVP_aes_256_ecb;
+extern const EVP_CIPHER *EVP_aes_256_ecb(void);
+%rename(aes_256_cbc) EVP_aes_256_cbc;
+extern const EVP_CIPHER *EVP_aes_256_cbc(void);
+%rename(aes_256_cfb) EVP_aes_256_cfb;
+extern const EVP_CIPHER *EVP_aes_256_cfb(void);
+%rename(aes_256_ofb) EVP_aes_256_ofb;
+extern const EVP_CIPHER *EVP_aes_256_ofb(void);
+
+%rename(cipher_set_padding) EVP_CIPHER_CTX_set_padding;
+extern int EVP_CIPHER_CTX_set_padding(EVP_CIPHER_CTX *, int);
+
+%rename(pkey_new) EVP_PKEY_new;
+extern EVP_PKEY *EVP_PKEY_new(void);
+%rename(pkey_free) EVP_PKEY_free;
+extern void EVP_PKEY_free(EVP_PKEY *);
+%rename(pkey_assign) EVP_PKEY_assign;
+extern int EVP_PKEY_assign(EVP_PKEY *, int, char *);
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL && !defined(OPENSSL_NO_EC)
+%rename(pkey_assign_ec) EVP_PKEY_assign_EC_KEY;
+extern int EVP_PKEY_assign_EC_KEY(EVP_PKEY *, EC_KEY *);
+#endif
+%rename(pkey_set1_rsa) EVP_PKEY_set1_RSA;
+extern int EVP_PKEY_set1_RSA(EVP_PKEY *, RSA *);
+%rename(pkey_get1_rsa) EVP_PKEY_get1_RSA;
+extern RSA* EVP_PKEY_get1_RSA(EVP_PKEY *);
+%rename(sign_init) EVP_SignInit;
+extern int EVP_SignInit(EVP_MD_CTX *, const EVP_MD *);
+%rename(verify_init) EVP_VerifyInit;
+extern int EVP_VerifyInit(EVP_MD_CTX *, const EVP_MD *);
+%rename(pkey_size) EVP_PKEY_size;
+extern int EVP_PKEY_size(EVP_PKEY *);
+
+%inline %{
+#define PKCS5_SALT_LEN 8
+
+static PyObject *_evp_err;
+
+void evp_init(PyObject *evp_err) {
+ Py_INCREF(evp_err);
+ _evp_err = evp_err;
+}
+
+PyObject *pkcs5_pbkdf2_hmac_sha1(PyObject *pass,
+ PyObject *salt,
+ int iter,
+ int keylen) {
+ unsigned char key[EVP_MAX_KEY_LENGTH];
+ unsigned char *saltbuf;
+ char *passbuf;
+ PyObject *ret;
+ int passlen, saltlen;
+
+ if (m2_PyObject_AsReadBufferInt(pass, (const void **)&passbuf,
+ &passlen) == -1)
+ return NULL;
+ if (m2_PyObject_AsReadBufferInt(salt, (const void **)&saltbuf,
+ &saltlen) == -1)
+ return NULL;
+
+ PKCS5_PBKDF2_HMAC_SHA1(passbuf, passlen, saltbuf, saltlen, iter,
+ keylen, key);
+ ret = PyString_FromStringAndSize((char*)key, keylen);
+ OPENSSL_cleanse(key, keylen);
+ return ret;
+}
+
+EVP_MD_CTX *md_ctx_new(void) {
+ EVP_MD_CTX *ctx;
+
+ if (!(ctx = EVP_MD_CTX_create())) {
+ PyErr_SetString(PyExc_MemoryError, "md_ctx_new");
+ }
+ return ctx;
+}
+
+void md_ctx_free(EVP_MD_CTX *ctx) {
+ EVP_MD_CTX_destroy(ctx);
+}
+
+int digest_update(EVP_MD_CTX *ctx, PyObject *blob) {
+ const void *buf;
+ Py_ssize_t len;
+
+ if (PyObject_AsReadBuffer(blob, &buf, &len) == -1)
+ return -1;
+
+ return EVP_DigestUpdate(ctx, buf, len);
+}
+
+PyObject *digest_final(EVP_MD_CTX *ctx) {
+ void *blob;
+ int blen;
+ PyObject *ret;
+
+ if (!(blob = PyMem_Malloc(ctx->digest->md_size))) {
+ PyErr_SetString(PyExc_MemoryError, "digest_final");
+ return NULL;
+ }
+ if (!EVP_DigestFinal(ctx, blob, (unsigned int *)&blen)) {
+ PyMem_Free(blob);
+ PyErr_SetString(_evp_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ ret = PyString_FromStringAndSize(blob, blen);
+ PyMem_Free(blob);
+ return ret;
+}
+
+HMAC_CTX *hmac_ctx_new(void) {
+ HMAC_CTX *ctx;
+
+ if (!(ctx = (HMAC_CTX *)PyMem_Malloc(sizeof(HMAC_CTX)))) {
+ PyErr_SetString(PyExc_MemoryError, "hmac_ctx_new");
+ return NULL;
+ }
+ HMAC_CTX_init(ctx);
+ return ctx;
+}
+
+void hmac_ctx_free(HMAC_CTX *ctx) {
+ HMAC_CTX_cleanup(ctx);
+ PyMem_Free((void *)ctx);
+}
+
+PyObject *hmac_init(HMAC_CTX *ctx, PyObject *key, const EVP_MD *md) {
+ const void *kbuf;
+ int klen;
+
+ if (m2_PyObject_AsReadBufferInt(key, &kbuf, &klen) == -1)
+ return NULL;
+
+ HMAC_Init(ctx, kbuf, klen, md);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *hmac_update(HMAC_CTX *ctx, PyObject *blob) {
+ const void *buf;
+ Py_ssize_t len;
+
+ if (PyObject_AsReadBuffer(blob, &buf, &len) == -1)
+ return NULL;
+
+ HMAC_Update(ctx, buf, len);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *hmac_final(HMAC_CTX *ctx) {
+ void *blob;
+ int blen;
+ PyObject *ret;
+
+ if (!(blob = PyMem_Malloc(ctx->md->md_size))) {
+ PyErr_SetString(PyExc_MemoryError, "hmac_final");
+ return NULL;
+ }
+ HMAC_Final(ctx, blob, (unsigned int *)&blen);
+ ret = PyString_FromStringAndSize(blob, blen);
+ PyMem_Free(blob);
+ return ret;
+}
+
+PyObject *hmac(PyObject *key, PyObject *data, const EVP_MD *md) {
+ const void *kbuf, *dbuf;
+ void *blob;
+ int klen;
+ unsigned int blen;
+ Py_ssize_t dlen;
+ PyObject *ret;
+
+ if ((m2_PyObject_AsReadBufferInt(key, &kbuf, &klen) == -1)
+ || (PyObject_AsReadBuffer(data, &dbuf, &dlen) == -1))
+ return NULL;
+
+ if (!(blob = PyMem_Malloc(EVP_MAX_MD_SIZE))) {
+ PyErr_SetString(PyExc_MemoryError, "hmac");
+ return NULL;
+ }
+ HMAC(md, kbuf, klen, dbuf, dlen, blob, &blen);
+ blob = PyMem_Realloc(blob, blen);
+ ret = PyString_FromStringAndSize(blob, blen);
+ PyMem_Free(blob);
+ return ret;
+}
+
+EVP_CIPHER_CTX *cipher_ctx_new(void) {
+ EVP_CIPHER_CTX *ctx;
+
+ if (!(ctx = (EVP_CIPHER_CTX *)PyMem_Malloc(sizeof(EVP_CIPHER_CTX)))) {
+ PyErr_SetString(PyExc_MemoryError, "cipher_ctx_new");
+ return NULL;
+ }
+ EVP_CIPHER_CTX_init(ctx);
+ return ctx;
+}
+
+void cipher_ctx_free(EVP_CIPHER_CTX *ctx) {
+ EVP_CIPHER_CTX_cleanup(ctx);
+ PyMem_Free((void *)ctx);
+}
+
+PyObject *bytes_to_key(const EVP_CIPHER *cipher, EVP_MD *md,
+ PyObject *data, PyObject *salt,
+ PyObject *iv, /* Not used */
+ int iter) {
+ unsigned char key[EVP_MAX_KEY_LENGTH];
+ const void *dbuf, *sbuf;
+ int dlen, klen;
+ Py_ssize_t slen;
+ PyObject *ret;
+
+ if ((m2_PyObject_AsReadBufferInt(data, &dbuf, &dlen) == -1)
+ || (PyObject_AsReadBuffer(salt, &sbuf, &slen) == -1))
+ return NULL;
+
+ assert((slen == 8) || (slen == 0));
+ klen = EVP_BytesToKey(cipher, md, (unsigned char *)sbuf,
+ (unsigned char *)dbuf, dlen, iter,
+ key, NULL); /* Since we are not returning IV no need to derive it */
+ ret = PyString_FromStringAndSize((char*)key, klen);
+ return ret;
+}
+
+PyObject *cipher_init(EVP_CIPHER_CTX *ctx, const EVP_CIPHER *cipher,
+ PyObject *key, PyObject *iv, int mode) {
+ const void *kbuf, *ibuf;
+ Py_ssize_t klen, ilen;
+
+ if ((PyObject_AsReadBuffer(key, &kbuf, &klen) == -1)
+ || (PyObject_AsReadBuffer(iv, &ibuf, &ilen) == -1))
+ return NULL;
+
+ if (!EVP_CipherInit(ctx, cipher, (unsigned char *)kbuf,
+ (unsigned char *)ibuf, mode)) {
+ PyErr_SetString(_evp_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *cipher_update(EVP_CIPHER_CTX *ctx, PyObject *blob) {
+ const void *buf;
+ int len, olen;
+ void *obuf;
+ PyObject *ret;
+
+ if (m2_PyObject_AsReadBufferInt(blob, &buf, &len) == -1)
+ return NULL;
+
+ if (!(obuf = PyMem_Malloc(len + EVP_CIPHER_CTX_block_size(ctx) - 1))) {
+ PyErr_SetString(PyExc_MemoryError, "cipher_update");
+ return NULL;
+ }
+ if (!EVP_CipherUpdate(ctx, obuf, &olen, (unsigned char *)buf, len)) {
+ PyMem_Free(obuf);
+ PyErr_SetString(_evp_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ ret = PyString_FromStringAndSize(obuf, olen);
+ PyMem_Free(obuf);
+ return ret;
+}
+
+PyObject *cipher_final(EVP_CIPHER_CTX *ctx) {
+ void *obuf;
+ int olen;
+ PyObject *ret;
+
+ if (!(obuf = PyMem_Malloc(ctx->cipher->block_size))) {
+ PyErr_SetString(PyExc_MemoryError, "cipher_final");
+ return NULL;
+ }
+ if (!EVP_CipherFinal(ctx, (unsigned char *)obuf, &olen)) {
+ PyMem_Free(obuf);
+ PyErr_SetString(_evp_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ ret = PyString_FromStringAndSize(obuf, olen);
+ PyMem_Free(obuf);
+ return ret;
+}
+
+PyObject *sign_update(EVP_MD_CTX *ctx, PyObject *blob) {
+ const void *buf;
+ Py_ssize_t len;
+
+ if (PyObject_AsReadBuffer(blob, &buf, &len) == -1)
+ return NULL;
+
+ if (!EVP_SignUpdate(ctx, buf, len)) {
+ PyErr_SetString(_evp_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *sign_final(EVP_MD_CTX *ctx, EVP_PKEY *pkey) {
+ PyObject *ret;
+ unsigned char *sigbuf;
+ unsigned int siglen = EVP_PKEY_size(pkey);
+
+ sigbuf = (unsigned char*)OPENSSL_malloc(siglen);
+ if (!sigbuf) {
+ PyErr_SetString(PyExc_MemoryError, "sign_final");
+ return NULL;
+ }
+
+ if (!EVP_SignFinal(ctx, sigbuf, &siglen, pkey)) {
+ OPENSSL_cleanse(sigbuf, siglen);
+ OPENSSL_free(sigbuf);
+ PyErr_SetString(_evp_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ ret = PyString_FromStringAndSize((char*)sigbuf, siglen);
+ OPENSSL_cleanse(sigbuf, siglen);
+ OPENSSL_free(sigbuf);
+ return ret;
+}
+
+int verify_update(EVP_MD_CTX *ctx, PyObject *blob) {
+ const void *buf;
+ Py_ssize_t len;
+
+ if (PyObject_AsReadBuffer(blob, &buf, &len) == -1)
+ return -1;
+
+ return EVP_VerifyUpdate(ctx, buf, len);
+}
+
+
+int verify_final(EVP_MD_CTX *ctx, PyObject *blob, EVP_PKEY *pkey) {
+ unsigned char *kbuf;
+ int len;
+
+ if (m2_PyObject_AsReadBufferInt(blob, (const void **)&kbuf, &len) == -1)
+ return -1;
+
+ return EVP_VerifyFinal(ctx, kbuf, len, pkey);
+}
+%}
+
+%inline %{
+int pkey_write_pem_no_cipher(EVP_PKEY *pkey, BIO *f, PyObject *pyfunc) {
+ int ret;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ ret = PEM_write_bio_PKCS8PrivateKey(f, pkey, NULL, NULL, 0,
+ passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return ret;
+}
+%}
+
+%inline %{
+int pkey_write_pem(EVP_PKEY *pkey, BIO *f, EVP_CIPHER *cipher, PyObject *pyfunc) {
+ int ret;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ ret = PEM_write_bio_PKCS8PrivateKey(f, pkey, cipher, NULL, 0,
+ passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return ret;
+}
+%}
+
+%inline %{
+EVP_PKEY *pkey_read_pem(BIO *f, PyObject *pyfunc) {
+ EVP_PKEY *pk;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ pk = PEM_read_bio_PrivateKey(f, NULL, passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return pk;
+}
+
+int pkey_assign_rsa(EVP_PKEY *pkey, RSA *rsa) {
+ return EVP_PKEY_assign_RSA(pkey, rsa);
+}
+
+PyObject *pkey_as_der(EVP_PKEY *pkey) {
+ unsigned char * pp = NULL;
+ int len;
+ PyObject * der;
+ len = i2d_PUBKEY(pkey, &pp);
+ if (len < 0){
+ PyErr_SetString(PyExc_ValueError, "EVP_PKEY as DER failed");
+ return NULL;
+ }
+ der = PyString_FromStringAndSize((char*)pp, len);
+ OPENSSL_free(pp);
+ return der;
+}
+
+PyObject *pkey_get_modulus(EVP_PKEY *pkey)
+{
+ RSA *rsa;
+ DSA *dsa;
+ BIO *bio;
+ BUF_MEM *bptr;
+ PyObject *ret;
+
+ switch (pkey->type) {
+ case EVP_PKEY_RSA:
+ rsa = EVP_PKEY_get1_RSA(pkey);
+
+ bio = BIO_new(BIO_s_mem());
+ if (!bio) {
+ RSA_free(rsa);
+ PyErr_SetString(PyExc_MemoryError, "pkey_get_modulus");
+ return NULL;
+ }
+
+ if (!BN_print(bio, rsa->n)) {
+ PyErr_SetString(PyExc_RuntimeError,
+ ERR_error_string(ERR_get_error(), NULL));
+ BIO_free(bio);
+ RSA_free(rsa);
+ return NULL;
+ }
+ BIO_get_mem_ptr(bio, &bptr);
+ ret = PyString_FromStringAndSize(bptr->data, bptr->length);
+ BIO_set_close(bio, BIO_CLOSE);
+ BIO_free(bio);
+ RSA_free(rsa);
+
+ break;
+
+ case EVP_PKEY_DSA:
+ dsa = EVP_PKEY_get1_DSA(pkey);
+
+ bio = BIO_new(BIO_s_mem());
+ if (!bio) {
+ DSA_free(dsa);
+ PyErr_SetString(PyExc_MemoryError, "pkey_get_modulus");
+ return NULL;
+ }
+
+ if (!BN_print(bio, dsa->pub_key)) {
+ PyErr_SetString(PyExc_RuntimeError,
+ ERR_error_string(ERR_get_error(), NULL));
+ BIO_free(bio);
+ DSA_free(dsa);
+ return NULL;
+ }
+ BIO_get_mem_ptr(bio, &bptr);
+ ret = PyString_FromStringAndSize(bptr->data, bptr->length);
+ BIO_set_close(bio, BIO_CLOSE);
+ BIO_free(bio);
+ DSA_free(dsa);
+
+ break;
+
+ default:
+ PyErr_SetString(PyExc_ValueError, "unsupported key type");
+ return NULL;
+ }
+
+ return ret;
+}
+
+
+%}
+
diff --git a/SWIG/_lib.h b/SWIG/_lib.h
new file mode 100644
index 0000000..b53bc2a
--- /dev/null
+++ b/SWIG/_lib.h
@@ -0,0 +1,27 @@
+/* Copyright (c) 1999 Ng Pheng Siong. All rights reserved. */
+/* $Id: _lib.h 593 2007-10-12 21:46:34Z heikki $ */
+
+#if PY_VERSION_HEX < 0x02050000 && !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
+
+typedef struct _blob {
+ unsigned char *data;
+ int len;
+} Blob;
+
+Blob *blob_new(int len, const char *errmsg);
+Blob *blob_copy(Blob *from, const char *errmsg);
+void blob_free(Blob *blob);
+
+static int m2_PyObject_AsReadBufferInt(PyObject *obj, const void **buffer,
+ int *buffer_len);
+static int m2_PyString_AsStringAndSizeInt(PyObject *obj, char **s, int *len);
+
+void gen_callback(int p, int n, void *arg);
+int passphrase_callback(char *buf, int num, int v, void *userdata);
+
+void lib_init(void);
+
diff --git a/SWIG/_lib.i b/SWIG/_lib.i
new file mode 100644
index 0000000..42dc180
--- /dev/null
+++ b/SWIG/_lib.i
@@ -0,0 +1,511 @@
+/* Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved. */
+/* $Id: _lib.i 695 2009-07-24 06:37:01Z heikki $ */
+
+%{
+#include <openssl/dh.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+#include <ceval.h>
+
+/* Blob interface. Deprecated. */
+
+Blob *blob_new(int len, const char *errmsg) {
+
+ Blob *blob;
+ if (!(blob=(Blob *)PyMem_Malloc(sizeof(Blob)))){
+ PyErr_SetString(PyExc_MemoryError, errmsg);
+ return NULL;
+ }
+ if (!(blob->data=(unsigned char *)PyMem_Malloc(len))) {
+ PyMem_Free(blob);
+ PyErr_SetString(PyExc_MemoryError, errmsg);
+ return NULL;
+ }
+ blob->len=len;
+ return blob;
+}
+
+Blob *blob_copy(Blob *from, const char *errmsg) {
+ Blob *blob=blob_new(from->len, errmsg);
+ if (!blob) {
+ PyErr_SetString(PyExc_MemoryError, errmsg);
+ return NULL;
+ }
+ memcpy(blob->data, from->data, from->len);
+ return blob;
+}
+
+void blob_free(Blob *blob) {
+ PyMem_Free(blob->data);
+ PyMem_Free(blob);
+}
+
+
+/* Python helpers. */
+
+%}
+%ignore m2_PyObject_AsReadBufferInt;
+%ignore m2_PyString_AsStringAndSizeInt;
+%{
+static int
+m2_PyObject_AsReadBufferInt(PyObject *obj, const void **buffer,
+ int *buffer_len)
+{
+ int ret;
+ Py_ssize_t len;
+
+ ret = PyObject_AsReadBuffer(obj, buffer, &len);
+ if (ret)
+ return ret;
+ if (len > INT_MAX) {
+ PyErr_SetString(PyExc_ValueError, "object too large");
+ return -1;
+ }
+ *buffer_len = len;
+ return 0;
+}
+
+static int
+m2_PyString_AsStringAndSizeInt(PyObject *obj, char **s, int *len)
+{
+ int ret;
+ Py_ssize_t len2;
+
+ ret = PyString_AsStringAndSize(obj, s, &len2);
+ if (ret)
+ return ret;
+ if (len2 > INT_MAX) {
+ PyErr_SetString(PyExc_ValueError, "string too large");
+ return -1;
+ }
+ *len = len2;
+ return 0;
+}
+
+
+/* C callbacks invoked by OpenSSL; these in turn call back into
+Python. */
+
+int ssl_verify_callback(int ok, X509_STORE_CTX *ctx) {
+ PyObject *argv, *ret;
+ PyObject *_x509_store_ctx_swigptr=0, *_x509_store_ctx_obj=0, *_x509_store_ctx_inst=0, *_klass=0;
+ PyObject *_x509=0, *_ssl_ctx=0;
+ SSL *ssl;
+ SSL_CTX *ssl_ctx;
+ X509 *x509;
+ int errnum, errdepth;
+ int cret;
+ int new_style_callback = 0, warning_raised_exception=0;
+ PyGILState_STATE gilstate;
+
+ ssl = (SSL *)X509_STORE_CTX_get_app_data(ctx);
+
+ gilstate = PyGILState_Ensure();
+
+ if (PyMethod_Check(ssl_verify_cb_func)) {
+ PyObject *func;
+ PyCodeObject *code;
+ func = PyMethod_Function(ssl_verify_cb_func);
+ code = (PyCodeObject *) PyFunction_GetCode(func);
+ if (code && code->co_argcount == 3) { /* XXX Python internals */
+ new_style_callback = 1;
+ }
+ } else if (PyFunction_Check(ssl_verify_cb_func)) {
+ PyCodeObject *code = (PyCodeObject *) PyFunction_GetCode(ssl_verify_cb_func);
+ if (code && code->co_argcount == 2) { /* XXX Python internals */
+ new_style_callback = 1;
+ }
+ } else {
+ /* XXX There are lots of other callable types, but we will assume
+ * XXX that any other type of callable uses the new style callback,
+ * XXX although this is not entirely safe assumption.
+ */
+ new_style_callback = 1;
+ }
+
+ if (new_style_callback) {
+ PyObject *x509mod = PyDict_GetItemString(PyImport_GetModuleDict(), "M2Crypto.X509");
+ _klass = PyObject_GetAttrString(x509mod, "X509_Store_Context");
+
+ _x509_store_ctx_swigptr = SWIG_NewPointerObj((void *)ctx, SWIGTYPE_p_X509_STORE_CTX, 0);
+ _x509_store_ctx_obj = Py_BuildValue("(Oi)", _x509_store_ctx_swigptr, 0);
+ _x509_store_ctx_inst = PyInstance_New(_klass, _x509_store_ctx_obj, NULL);
+ argv = Py_BuildValue("(iO)", ok, _x509_store_ctx_inst);
+ } else {
+ if (PyErr_Warn(PyExc_DeprecationWarning, "Old style callback, use cb_func(ok, store) instead")) {
+ warning_raised_exception = 1;
+ }
+
+ x509 = X509_STORE_CTX_get_current_cert(ctx);
+ errnum = X509_STORE_CTX_get_error(ctx);
+ errdepth = X509_STORE_CTX_get_error_depth(ctx);
+
+ ssl = (SSL *)X509_STORE_CTX_get_app_data(ctx);
+ ssl_ctx = SSL_get_SSL_CTX(ssl);
+
+ _x509 = SWIG_NewPointerObj((void *)x509, SWIGTYPE_p_X509, 0);
+ _ssl_ctx = SWIG_NewPointerObj((void *)ssl_ctx, SWIGTYPE_p_SSL_CTX, 0);
+ argv = Py_BuildValue("(OOiii)", _ssl_ctx, _x509, errnum, errdepth, ok);
+ }
+
+ if (!warning_raised_exception) {
+ ret = PyEval_CallObject(ssl_verify_cb_func, argv);
+ } else {
+ ret = 0;
+ }
+
+ if (!ret) {
+ /* Got an exception in PyEval_CallObject(), let's fail verification
+ * to be safe.
+ */
+ cret = 0;
+ } else {
+ cret = (int)PyInt_AsLong(ret);
+ }
+ Py_XDECREF(ret);
+ Py_XDECREF(argv);
+ if (new_style_callback) {
+ Py_XDECREF(_x509_store_ctx_inst);
+ Py_XDECREF(_x509_store_ctx_obj);
+ Py_XDECREF(_x509_store_ctx_swigptr);
+ Py_XDECREF(_klass);
+ } else {
+ Py_XDECREF(_x509);
+ Py_XDECREF(_ssl_ctx);
+ }
+
+ PyGILState_Release(gilstate);
+
+ return cret;
+}
+
+void ssl_info_callback(const SSL *s, int where, int ret) {
+ PyObject *argv, *retval, *_SSL;
+ PyGILState_STATE gilstate;
+
+ gilstate = PyGILState_Ensure();
+
+ _SSL = SWIG_NewPointerObj((void *)s, SWIGTYPE_p_SSL, 0);
+ argv = Py_BuildValue("(iiO)", where, ret, _SSL);
+
+ retval = PyEval_CallObject(ssl_info_cb_func, argv);
+
+ Py_XDECREF(retval);
+ Py_XDECREF(argv);
+ Py_XDECREF(_SSL);
+
+ PyGILState_Release(gilstate);
+}
+
+DH *ssl_set_tmp_dh_callback(SSL *ssl, int is_export, int keylength) {
+ PyObject *argv, *ret, *_ssl;
+ DH *dh;
+ PyGILState_STATE gilstate;
+
+ gilstate = PyGILState_Ensure();
+
+ _ssl = SWIG_NewPointerObj((void *)ssl, SWIGTYPE_p_SSL, 0);
+ argv = Py_BuildValue("(Oii)", _ssl, is_export, keylength);
+
+ ret = PyEval_CallObject(ssl_set_tmp_dh_cb_func, argv);
+
+ if ((SWIG_ConvertPtr(ret, (void **)&dh, SWIGTYPE_p_DH, SWIG_POINTER_EXCEPTION | 0)) == -1)
+ dh = NULL;
+ Py_XDECREF(ret);
+ Py_XDECREF(argv);
+ Py_XDECREF(_ssl);
+
+ PyGILState_Release(gilstate);
+
+ return dh;
+}
+
+RSA *ssl_set_tmp_rsa_callback(SSL *ssl, int is_export, int keylength) {
+ PyObject *argv, *ret, *_ssl;
+ RSA *rsa;
+ PyGILState_STATE gilstate;
+
+ gilstate = PyGILState_Ensure();
+
+ _ssl = SWIG_NewPointerObj((void *)ssl, SWIGTYPE_p_SSL, 0);
+ argv = Py_BuildValue("(Oii)", _ssl, is_export, keylength);
+
+ ret = PyEval_CallObject(ssl_set_tmp_rsa_cb_func, argv);
+
+ if ((SWIG_ConvertPtr(ret, (void **)&rsa, SWIGTYPE_p_RSA, SWIG_POINTER_EXCEPTION | 0)) == -1)
+ rsa = NULL;
+ Py_XDECREF(ret);
+ Py_XDECREF(argv);
+ Py_XDECREF(_ssl);
+
+ PyGILState_Release(gilstate);
+
+ return rsa;
+}
+
+void gen_callback(int p, int n, void *arg) {
+ PyObject *argv, *ret, *cbfunc;
+
+ PyGILState_STATE gilstate;
+ gilstate = PyGILState_Ensure();
+ cbfunc = (PyObject *)arg;
+ argv = Py_BuildValue("(ii)", p, n);
+ ret = PyEval_CallObject(cbfunc, argv);
+ Py_DECREF(argv);
+ Py_XDECREF(ret);
+ PyGILState_Release(gilstate);
+}
+
+int passphrase_callback(char *buf, int num, int v, void *arg) {
+ int i;
+ Py_ssize_t len;
+ char *str;
+ PyObject *argv, *ret, *cbfunc;
+ PyGILState_STATE gilstate;
+
+ gilstate = PyGILState_Ensure();
+ cbfunc = (PyObject *)arg;
+ argv = Py_BuildValue("(i)", v);
+ ret = PyEval_CallObject(cbfunc, argv);
+ Py_DECREF(argv);
+ if (ret == NULL) {
+ PyGILState_Release(gilstate);
+ return -1;
+ }
+ if (!PyString_Check(ret)) {
+ Py_DECREF(ret);
+ PyGILState_Release(gilstate);
+ return -1;
+ }
+ if ((len = PyString_Size(ret)) > num)
+ len = num;
+ str = PyString_AsString(ret);
+ for (i = 0; i < len; i++)
+ buf[i] = str[i];
+ Py_DECREF(ret);
+ PyGILState_Release(gilstate);
+ return len;
+}
+%}
+
+%inline %{
+void lib_init() {
+ SSLeay_add_all_algorithms();
+ ERR_load_ERR_strings();
+}
+
+/* Bignum routines that aren't not numerous enough to
+warrant a separate file. */
+
+PyObject *bn_to_mpi(BIGNUM *bn) {
+ int len;
+ unsigned char *mpi;
+ PyObject *pyo;
+
+ len = BN_bn2mpi(bn, NULL);
+ if (!(mpi=(unsigned char *)PyMem_Malloc(len))) {
+ PyErr_SetString(PyExc_RuntimeError,
+ ERR_error_string(ERR_get_error(), NULL));
+ return NULL;
+ }
+ len=BN_bn2mpi(bn, mpi);
+ pyo=PyString_FromStringAndSize((const char *)mpi, len);
+ PyMem_Free(mpi);
+ return pyo;
+}
+
+BIGNUM *mpi_to_bn(PyObject *value) {
+ const void *vbuf;
+ int vlen;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ return BN_mpi2bn(vbuf, vlen, NULL);
+}
+
+PyObject *bn_to_bin(BIGNUM *bn) {
+ int len;
+ unsigned char *bin;
+ PyObject *pyo;
+
+ len = BN_num_bytes(bn);
+ if (!(bin=(unsigned char *)PyMem_Malloc(len))) {
+ PyErr_SetString(PyExc_MemoryError, "bn_to_bin");
+ return NULL;
+ }
+ BN_bn2bin(bn, bin);
+ pyo=PyString_FromStringAndSize((const char *)bin, len);
+ PyMem_Free(bin);
+ return pyo;
+}
+
+BIGNUM *bin_to_bn(PyObject *value) {
+ const void *vbuf;
+ int vlen;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ return BN_bin2bn(vbuf, vlen, NULL);
+}
+
+PyObject *bn_to_hex(BIGNUM *bn) {
+ char *hex;
+ PyObject *pyo;
+ Py_ssize_t len;
+
+ hex = BN_bn2hex(bn);
+ if (!hex) {
+ PyErr_SetString(PyExc_RuntimeError,
+ ERR_error_string(ERR_get_error(), NULL));
+ OPENSSL_free(hex);
+ return NULL;
+ }
+ len = strlen(hex);
+ pyo=PyString_FromStringAndSize(hex, len);
+ OPENSSL_free(hex);
+ return pyo;
+}
+
+BIGNUM *hex_to_bn(PyObject *value) {
+ const void *vbuf;
+ Py_ssize_t vlen;
+ BIGNUM *bn;
+
+ if (PyObject_AsReadBuffer(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if ((bn=BN_new())==NULL) {
+ PyErr_SetString(PyExc_MemoryError, "hex_to_bn");
+ return NULL;
+ }
+ if (BN_hex2bn(&bn, (const char *)vbuf) <= 0) {
+ PyErr_SetString(PyExc_RuntimeError,
+ ERR_error_string(ERR_get_error(), NULL));
+ BN_free(bn);
+ return NULL;
+ }
+ return bn;
+}
+
+BIGNUM *dec_to_bn(PyObject *value) {
+ const void *vbuf;
+ Py_ssize_t vlen;
+ BIGNUM *bn;
+
+ if (PyObject_AsReadBuffer(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if ((bn=BN_new())==NULL) {
+ PyErr_SetString(PyExc_MemoryError, "dec_to_bn");
+ return NULL;
+ }
+ if ((BN_dec2bn(&bn, (const char *)vbuf) <= 0)) {
+ PyErr_SetString(PyExc_RuntimeError,
+ ERR_error_string(ERR_get_error(), NULL));
+ BN_free(bn);
+ return NULL;
+ }
+ return bn;
+}
+%}
+
+
+/* Various useful typemaps. */
+
+%typemap(in) Blob * {
+ Py_ssize_t len;
+
+ if (!PyString_Check($input)) {
+ PyErr_SetString(PyExc_TypeError, "expected PyString");
+ return NULL;
+ }
+ len=PyString_Size($input);
+ if (len > INT_MAX) {
+ PyErr_SetString(PyExc_ValueError, "object too large");
+ return -1;
+ }
+ $1=(Blob *)PyMem_Malloc(sizeof(Blob));
+ if (!$1) {
+ PyErr_SetString(PyExc_MemoryError, "malloc Blob");
+ return NULL;
+ }
+ $1->data=(unsigned char *)PyString_AsString($input);
+ $1->len=len;
+}
+
+%typemap(out) Blob * {
+ if ($1==NULL) {
+ Py_INCREF(Py_None);
+ $result=Py_None;
+ } else {
+ $result=PyString_FromStringAndSize((const char *)$1->data, $1->len);
+ PyMem_Free($1->data);
+ PyMem_Free($1);
+ }
+}
+
+%typemap(in) FILE * {
+ if (!PyFile_Check($input)) {
+ PyErr_SetString(PyExc_TypeError, "expected PyFile");
+ return NULL;
+ }
+ $1=PyFile_AsFile($input);
+}
+
+%typemap(in) PyObject *pyfunc {
+ if (!PyCallable_Check($input)) {
+ PyErr_SetString(PyExc_TypeError, "expected PyCallable");
+ return NULL;
+ }
+ $1=$input;
+}
+
+%typemap(in) PyObject *pyblob {
+ if (!PyString_Check($input)) {
+ PyErr_SetString(PyExc_TypeError, "expected PyString");
+ return NULL;
+ }
+ $1=$input;
+}
+
+%typemap(in) PyObject * {
+ $1=$input;
+}
+
+%typemap(out) PyObject * {
+ $result=$1;
+}
+
+%typemap(out) int {
+ $result=PyInt_FromLong($1);
+ if (PyErr_Occurred()) SWIG_fail;
+}
+
+/* Pointer checks. */
+
+%apply Pointer NONNULL { Blob * };
+
+
+/* A bunch of "straight-thru" functions. */
+
+%rename(err_print_errors_fp) ERR_print_errors_fp;
+%threadallow ERR_print_errors_fp;
+extern void ERR_print_errors_fp(FILE *);
+%rename(err_print_errors) ERR_print_errors;
+%threadallow ERR_print_errors;
+extern void ERR_print_errors(BIO *);
+%rename(err_get_error) ERR_get_error;
+extern unsigned long ERR_get_error(void);
+%rename(err_peek_error) ERR_peek_error;
+extern unsigned long ERR_peek_error(void);
+%rename(err_lib_error_string) ERR_lib_error_string;
+extern const char *ERR_lib_error_string(unsigned long);
+%rename(err_func_error_string) ERR_func_error_string;
+extern const char *ERR_func_error_string(unsigned long);
+%rename(err_reason_error_string) ERR_reason_error_string;
+extern const char *ERR_reason_error_string(unsigned long);
diff --git a/SWIG/_m2crypto.def b/SWIG/_m2crypto.def
new file mode 100644
index 0000000..753db2c
--- /dev/null
+++ b/SWIG/_m2crypto.def
@@ -0,0 +1,2 @@
+EXPORTS
+init__m2crypto
diff --git a/SWIG/_m2crypto.i b/SWIG/_m2crypto.i
new file mode 100644
index 0000000..3d779a1
--- /dev/null
+++ b/SWIG/_m2crypto.i
@@ -0,0 +1,82 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved.
+ *
+ * Portions created by Open Source Applications Foundation (OSAF) are
+ * Copyright (C) 2004-2006 OSAF. All Rights Reserved.
+ *
+ * Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+ *
+ */
+
+%module(threads=1) _m2crypto
+/* We really don't need threadblock (PyGILState_Ensure() etc.) anywhere.
+ Disable threadallow as well, only enable it for operations likely to
+ block. */
+%nothreadblock;
+%nothreadallow;
+
+%{
+#include <openssl/err.h>
+#include <openssl/rand.h>
+#include <_lib.h>
+
+#include "compile.h"
+
+static PyObject *ssl_verify_cb_func;
+static PyObject *ssl_info_cb_func;
+static PyObject *ssl_set_tmp_dh_cb_func;
+static PyObject *ssl_set_tmp_rsa_cb_func;
+%}
+
+%include <openssl/opensslv.h>
+#if OPENSSL_VERSION_NUMBER >= 0x0090707fL
+#define CONST const
+#else
+#define CONST
+#endif
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+#define CONST098 const
+#else
+#define CONST098
+#endif
+
+/* Bring in STACK_OF macro definition */
+%include <openssl/safestack.h>
+
+/* Bring in LHASH_OF macro definition */
+/* XXX Can't include lhash.h where LHASH_OF is defined, because it includes
+ XXX stdio.h etc. which we fail to include. So we have to (re)define
+ XXX LHASH_OF here instead.
+%include <openssl/lhash.h>
+*/
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+#define LHASH_OF(type) struct lhash_st_##type
+#endif
+
+%include constraints.i
+%include _threads.i
+%include _lib.i
+%include _bio.i
+%include _bn.i
+%include _rand.i
+%include _evp.i
+%include _aes.i
+%include _rc4.i
+%include _dh.i
+%include _rsa.i
+%include _dsa.i
+%include _ssl.i
+%include _x509.i
+%include _asn1.i
+%include _pkcs7.i
+%include _util.i
+%include _ec.i
+%include _engine.i
+%include _objects.i
+
+#ifdef SWIG_VERSION
+%constant int encrypt = 1;
+%constant int decrypt = 0;
+#endif
+
diff --git a/SWIG/_objects.i b/SWIG/_objects.i
new file mode 100644
index 0000000..40f5e51
--- /dev/null
+++ b/SWIG/_objects.i
@@ -0,0 +1,95 @@
+/*
+ * -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: syntax=c sts=4 sw=4
+ *
+ * ASN1_OBJECT manipulation functions from OBJ_obj2txt(3SSL).
+ *
+ * Pavel Shramov
+ * IMEC MSU
+ */
+%{
+#include <openssl/objects.h>
+%}
+
+%apply Pointer NONNULL { ASN1_OBJECT * };
+%apply Pointer NONNULL { const char * };
+
+%rename(obj_nid2obj) OBJ_nid2obj;
+extern ASN1_OBJECT * OBJ_nid2obj(int n);
+%rename(obj_nid2ln) OBJ_nid2ln;
+extern const char * OBJ_nid2ln(int n);
+%rename(obj_nid2sn) OBJ_nid2sn;
+extern const char * OBJ_nid2sn(int n);
+
+%rename(obj_obj2nid) OBJ_obj2nid;
+extern int OBJ_obj2nid(const ASN1_OBJECT *o);
+
+%rename(obj_ln2nid) OBJ_ln2nid;
+extern int OBJ_ln2nid(const char *ln);
+%rename(obj_sn2nid) OBJ_sn2nid;
+extern int OBJ_sn2nid(const char *sn);
+
+%rename(obj_txt2nid) OBJ_txt2nid;
+extern int OBJ_txt2nid(const char *s);
+
+%rename(obj_txt2obj) OBJ_txt2obj;
+extern ASN1_OBJECT * OBJ_txt2obj(const char *s, int no_name);
+
+
+%rename(_obj_obj2txt) OBJ_obj2txt;
+extern int OBJ_obj2txt(char *, int, const ASN1_OBJECT *, int);
+
+
+%inline %{
+/*
+ From the manpage for OBJ_obt2txt ():
+ BUGS
+ OBJ_obj2txt() is awkward and messy to use: it doesn’t follow the
+ convention of other OpenSSL functions where the buffer can be set
+ to NULL to determine the amount of data that should be written.
+ Instead buf must point to a valid buffer and buf_len should be set
+ to a positive value. A buffer length of 80 should be more than
+ enough to handle any OID encountered in practice.
+
+ The first call to OBJ_obj2txt () therefore passes a non-NULL dummy
+ buffer. This wart is reportedly removed in OpenSSL 0.9.8b, although
+ the manpage has not been updated.
+
+ OBJ_obj2txt always prints \0 at the end. But the return value
+ is the number of "good" bytes written. So memory is allocated for
+ len + 1 bytes but only len bytes are marshalled to python.
+*/
+PyObject *obj_obj2txt(const ASN1_OBJECT *obj, int no_name)
+{
+ int len;
+ PyObject *ret;
+ char *buf;
+ char dummy[1];
+
+ len = OBJ_obj2txt(dummy, 1, obj, no_name);
+ if (len < 0) {
+ PyErr_SetString(PyExc_RuntimeError, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ } else if (len == 0) {
+ /* XXX: For OpenSSL prior to 0.9.8b.
+
+ Changes between 0.9.8a and 0.9.8b [04 May 2006]
+ ...
+ *) Several fixes and enhancements to the OID generation code. The old code
+ sometimes allowed invalid OIDs (1.X for X >= 40 for example), couldn't
+ handle numbers larger than ULONG_MAX, truncated printing and had a
+ non standard OBJ_obj2txt() behaviour.
+ [Steve Henson]
+ */
+
+ len = 80;
+ }
+
+ buf = PyMem_Malloc(len + 1);
+ len = OBJ_obj2txt(buf, len + 1, obj, no_name);
+ ret = PyString_FromStringAndSize(buf, len);
+ PyMem_Free(buf);
+
+ return ret;
+}
+%}
diff --git a/SWIG/_pkcs7.i b/SWIG/_pkcs7.i
new file mode 100644
index 0000000..174f40a
--- /dev/null
+++ b/SWIG/_pkcs7.i
@@ -0,0 +1,241 @@
+/* Copyright (c) 2000 Ng Pheng Siong. All rights reserved.
+ * Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+*/
+/* $Id: _pkcs7.i 723 2010-02-13 06:53:13Z heikki $ */
+
+%{
+#include <openssl/bio.h>
+#include <openssl/evp.h>
+#include <openssl/objects.h>
+#include <openssl/pkcs7.h>
+%}
+
+%apply Pointer NONNULL { BIO * };
+%apply Pointer NONNULL { EVP_CIPHER * };
+%apply Pointer NONNULL { EVP_PKEY * };
+%apply Pointer NONNULL { PKCS7 * };
+%apply Pointer NONNULL { STACK_OF(X509) * };
+%apply Pointer NONNULL { X509 * };
+
+%rename(pkcs7_new) PKCS7_new;
+extern PKCS7 *PKCS7_new(void);
+%rename(pkcs7_free) PKCS7_free;
+extern void PKCS7_free(PKCS7 *);
+%rename(pkcs7_add_certificate) PKCS7_add_certificate;
+extern void PKCS7_add_certificate(PKCS7 *, X509 *);
+
+/* S/MIME operation */
+%constant int PKCS7_TEXT = 0x1;
+%constant int PKCS7_NOCERTS = 0x2;
+%constant int PKCS7_NOSIGS = 0x4;
+%constant int PKCS7_NOCHAIN = 0x8;
+%constant int PKCS7_NOINTERN = 0x10;
+%constant int PKCS7_NOVERIFY = 0x20;
+%constant int PKCS7_DETACHED = 0x40;
+%constant int PKCS7_BINARY = 0x80;
+%constant int PKCS7_NOATTR = 0x100;
+
+%constant int PKCS7_SIGNED = NID_pkcs7_signed;
+%constant int PKCS7_ENVELOPED = NID_pkcs7_enveloped;
+%constant int PKCS7_SIGNED_ENVELOPED = NID_pkcs7_signedAndEnveloped;
+%constant int PKCS7_DATA = NID_pkcs7_data;
+
+%inline %{
+static PyObject *_pkcs7_err, *_smime_err;
+
+void pkcs7_init(PyObject *pkcs7_err) {
+ Py_INCREF(pkcs7_err);
+ _pkcs7_err = pkcs7_err;
+}
+
+void smime_init(PyObject *smime_err) {
+ Py_INCREF(smime_err);
+ _smime_err = smime_err;
+}
+%}
+
+%threadallow pkcs7_encrypt;
+%inline %{
+PKCS7 *pkcs7_encrypt(STACK_OF(X509) *stack, BIO *bio, EVP_CIPHER *cipher, int flags) {
+ return PKCS7_encrypt(stack, bio, cipher, flags);
+}
+
+PyObject *pkcs7_decrypt(PKCS7 *pkcs7, EVP_PKEY *pkey, X509 *cert, int flags) {
+ int outlen;
+ char *outbuf;
+ BIO *bio;
+ PyObject *ret;
+
+ if (!(bio=BIO_new(BIO_s_mem()))) {
+ PyErr_SetString(PyExc_MemoryError, "pkcs7_decrypt");
+ return NULL;
+ }
+ if (!PKCS7_decrypt(pkcs7, pkey, cert, bio, flags)) {
+ PyErr_SetString(_pkcs7_err, ERR_reason_error_string(ERR_get_error()));
+ BIO_free(bio);
+ return NULL;
+ }
+ outlen = BIO_ctrl_pending(bio);
+ if (!(outbuf=(char *)PyMem_Malloc(outlen))) {
+ PyErr_SetString(PyExc_MemoryError, "pkcs7_decrypt");
+ BIO_free(bio);
+ return NULL;
+ }
+ BIO_read(bio, outbuf, outlen);
+ ret = PyString_FromStringAndSize(outbuf, outlen);
+ BIO_free(bio);
+ PyMem_Free(outbuf);
+ return ret;
+}
+%}
+
+%threadallow pkcs7_sign0;
+%inline %{
+PKCS7 *pkcs7_sign0(X509 *x509, EVP_PKEY *pkey, BIO *bio, int flags) {
+ return PKCS7_sign(x509, pkey, NULL, bio, flags);
+}
+%}
+
+%threadallow pkcs7_sign1;
+%inline %{
+PKCS7 *pkcs7_sign1(X509 *x509, EVP_PKEY *pkey, STACK_OF(X509) *stack, BIO *bio, int flags) {
+ return PKCS7_sign(x509, pkey, stack, bio, flags);
+}
+%}
+
+%inline %{
+PyObject *pkcs7_verify1(PKCS7 *pkcs7, STACK_OF(X509) *stack, X509_STORE *store, BIO *data, int flags) {
+ int res, outlen;
+ char *outbuf;
+ BIO *bio;
+ PyObject *ret;
+
+ if (!(bio=BIO_new(BIO_s_mem()))) {
+ PyErr_SetString(PyExc_MemoryError, "pkcs7_verify1");
+ return NULL;
+ }
+ Py_BEGIN_ALLOW_THREADS
+ res = PKCS7_verify(pkcs7, stack, store, data, bio, flags);
+ Py_END_ALLOW_THREADS
+ if (!res) {
+ PyErr_SetString(_pkcs7_err, ERR_reason_error_string(ERR_get_error()));
+ BIO_free(bio);
+ return NULL;
+ }
+ outlen = BIO_ctrl_pending(bio);
+ if (!(outbuf=(char *)PyMem_Malloc(outlen))) {
+ PyErr_SetString(PyExc_MemoryError, "pkcs7_verify1");
+ BIO_free(bio);
+ return NULL;
+ }
+ BIO_read(bio, outbuf, outlen);
+ ret = PyString_FromStringAndSize(outbuf, outlen);
+ BIO_free(bio);
+ PyMem_Free(outbuf);
+ return ret;
+}
+
+PyObject *pkcs7_verify0(PKCS7 *pkcs7, STACK_OF(X509) *stack, X509_STORE *store, int flags) {
+ return pkcs7_verify1(pkcs7, stack, store, NULL, flags);
+}
+%}
+
+%threadallow smime_write_pkcs7_multi;
+%inline %{
+int smime_write_pkcs7_multi(BIO *bio, PKCS7 *pkcs7, BIO *data, int flags) {
+ return SMIME_write_PKCS7(bio, pkcs7, data, flags | PKCS7_DETACHED);
+}
+%}
+
+%threadallow smime_write_pkcs7;
+%inline %{
+int smime_write_pkcs7(BIO *bio, PKCS7 *pkcs7, int flags) {
+ return SMIME_write_PKCS7(bio, pkcs7, NULL, flags);
+}
+
+PyObject *smime_read_pkcs7(BIO *bio) {
+ BIO *bcont = NULL;
+ PKCS7 *p7;
+ PyObject *tuple, *_p7, *_BIO;
+
+ if (BIO_method_type(bio) == BIO_TYPE_MEM) {
+ /* OpenSSL FAQ explains that this is needed for mem BIO to return EOF,
+ * like file BIO does. Might need to do this for more mem BIOs but
+ * not sure if that is safe, so starting with just this single place.
+ */
+ BIO_set_mem_eof_return(bio, 0);
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+ p7=SMIME_read_PKCS7(bio, &bcont);
+ Py_END_ALLOW_THREADS
+ if (!p7) {
+ PyErr_SetString(_smime_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ if (!(tuple=PyTuple_New(2))) {
+ PyErr_SetString(PyExc_RuntimeError, "PyTuple_New() fails");
+ return NULL;
+ }
+ _p7 = SWIG_NewPointerObj((void *)p7, SWIGTYPE_p_PKCS7, 0);
+ PyTuple_SET_ITEM(tuple, 0, _p7);
+ if (!bcont) {
+ Py_INCREF(Py_None);
+ PyTuple_SET_ITEM(tuple, 1, Py_None);
+ } else {
+ _BIO = SWIG_NewPointerObj((void *)bcont, SWIGTYPE_p_BIO, 0);
+ PyTuple_SET_ITEM(tuple, 1, _BIO);
+ }
+ return tuple;
+}
+%}
+
+%threadallow pkcs7_read_bio;
+%inline %{
+PKCS7 *pkcs7_read_bio(BIO *bio) {
+ return PEM_read_bio_PKCS7(bio, NULL, NULL, NULL);
+}
+%}
+
+%threadallow pkcs7_read_bio_der;
+%inline %{
+PKCS7 *pkcs7_read_bio_der(BIO *bio) {
+ return d2i_PKCS7_bio(bio, NULL);
+}
+%}
+
+%threadallow pkcs7_write_bio;
+%inline %{
+int pkcs7_write_bio(PKCS7 *pkcs7, BIO* bio) {
+ return PEM_write_bio_PKCS7(bio, pkcs7);
+}
+%}
+
+%threadallow pkcs7_write_bio_der;
+%inline %{
+int pkcs7_write_bio_der(PKCS7 *pkcs7, BIO *bio) {
+ return i2d_PKCS7_bio(bio, pkcs7);
+}
+
+int pkcs7_type_nid(PKCS7 *pkcs7) {
+ return OBJ_obj2nid(pkcs7->type);
+}
+
+const char *pkcs7_type_sn(PKCS7 *pkcs7) {
+ return OBJ_nid2sn(OBJ_obj2nid(pkcs7->type));
+}
+%}
+
+%threadallow smime_crlf_copy;
+%inline %{
+int smime_crlf_copy(BIO *in, BIO *out) {
+ return SMIME_crlf_copy(in, out, PKCS7_TEXT);
+}
+
+/* return STACK_OF(X509)* */
+STACK_OF(X509) *pkcs7_get0_signers(PKCS7 *p7, STACK_OF(X509) *certs, int flags) {
+ return PKCS7_get0_signers(p7, certs, flags);
+}
+
+%}
+
diff --git a/SWIG/_rand.i b/SWIG/_rand.i
new file mode 100644
index 0000000..2d4f144
--- /dev/null
+++ b/SWIG/_rand.i
@@ -0,0 +1,122 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved.
+ * Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+*/
+/* $Id: _rand.i 721 2010-02-13 06:30:33Z heikki $ */
+
+%module _rand
+
+%rename(rand_load_file) RAND_load_file;
+extern int RAND_load_file(const char *, long);
+%rename(rand_save_file) RAND_write_file;
+extern int RAND_write_file(const char *);
+%rename(rand_poll) RAND_poll;
+extern int RAND_poll(void);
+%rename(rand_status) RAND_status;
+extern int RAND_status(void);
+%rename(rand_cleanup) RAND_cleanup;
+extern void RAND_cleanup(void);
+
+%inline %{
+static PyObject *_rand_err;
+
+void rand_init(PyObject *rand_err) {
+ Py_INCREF(rand_err);
+ _rand_err = rand_err;
+}
+
+PyObject *rand_seed(PyObject *seed) {
+ const void *buf;
+ int len;
+
+ if (m2_PyObject_AsReadBufferInt(seed, &buf, &len) == -1)
+ return NULL;
+
+ RAND_seed(buf, len);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *rand_add(PyObject *blob, double entropy) {
+ const void *buf;
+ int len;
+
+ if (m2_PyObject_AsReadBufferInt(blob, &buf, &len) == -1)
+ return NULL;
+
+ RAND_add(buf, len, entropy);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *rand_bytes(int n) {
+ void *blob;
+ PyObject *obj;
+
+ if (!(blob = PyMem_Malloc(n))) {
+ PyErr_SetString(PyExc_MemoryError, "rand_bytes");
+ return NULL;
+ }
+ if (RAND_bytes(blob, n)) {
+ obj = PyString_FromStringAndSize(blob, n);
+ PyMem_Free(blob);
+ return obj;
+ } else {
+ PyMem_Free(blob);
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+}
+
+PyObject *rand_pseudo_bytes(int n) {
+ int ret;
+ unsigned char *blob;
+ PyObject *tuple;
+
+ if (!(blob=(unsigned char *)PyMem_Malloc(n))) {
+ PyErr_SetString(PyExc_MemoryError, "rand_pseudo_bytes");
+ return NULL;
+ }
+ if (!(tuple=PyTuple_New(2))) {
+ PyErr_SetString(PyExc_RuntimeError, "PyTuple_New() fails");
+ PyMem_Free(blob);
+ return NULL;
+ }
+ ret = RAND_pseudo_bytes(blob, n);
+ if (ret == -1) {
+ PyMem_Free(blob);
+ Py_DECREF(tuple);
+ Py_INCREF(Py_None);
+ return Py_None;
+ } else {
+ PyTuple_SET_ITEM(tuple, 0, PyString_FromStringAndSize((char*)blob, n));
+ PyMem_Free(blob);
+ PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong((long)ret));
+ return tuple;
+ }
+}
+
+void rand_screen(void) {
+#ifdef __WINDOWS__
+ RAND_screen();
+#endif
+}
+
+int rand_win32_event(unsigned int imsg, int wparam, long lparam) {
+#ifdef __WINDOWS__
+ return RAND_event(imsg, wparam, lparam);
+#else
+ return 0;
+#endif
+}
+%}
+
+/*
+2004-04-05, ngps: Still missing:
+ RAND_egd
+ RAND_egd_bytes
+ RAND_query_egd_bytes
+ RAND_file_name
+*/
+
+
diff --git a/SWIG/_rc4.i b/SWIG/_rc4.i
new file mode 100644
index 0000000..3259997
--- /dev/null
+++ b/SWIG/_rc4.i
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999 Ng Pheng Siong. All rights reserved. */
+/* $Id: _rc4.i 522 2007-05-08 22:21:51Z heikki $ */
+
+%{
+#include <openssl/rc4.h>
+%}
+
+%apply Pointer NONNULL { RC4_KEY * };
+
+%inline %{
+RC4_KEY *rc4_new(void) {
+ RC4_KEY *key;
+
+ if (!(key = (RC4_KEY *)PyMem_Malloc(sizeof(RC4_KEY))))
+ PyErr_SetString(PyExc_MemoryError, "rc4_new");
+ return key;
+}
+
+void rc4_free(RC4_KEY *key) {
+ PyMem_Free((void *)key);
+}
+
+PyObject *rc4_set_key(RC4_KEY *key, PyObject *value) {
+ const void *vbuf;
+ int vlen;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ RC4_set_key(key, vlen, vbuf);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *rc4_update(RC4_KEY *key, PyObject *in) {
+ PyObject *ret;
+ const void *buf;
+ Py_ssize_t len;
+ void *out;
+
+ if (PyObject_AsReadBuffer(in, &buf, &len) == -1)
+ return NULL;
+
+ if (!(out = PyMem_Malloc(len))) {
+ PyErr_SetString(PyExc_MemoryError, "expected a string object");
+ return NULL;
+ }
+ RC4(key, len, buf, out);
+ ret = PyString_FromStringAndSize(out, len);
+ PyMem_Free(out);
+ return ret;
+}
+
+int rc4_type_check(RC4_KEY *key) {
+ return 1;
+}
+%}
diff --git a/SWIG/_rsa.i b/SWIG/_rsa.i
new file mode 100644
index 0000000..537e802
--- /dev/null
+++ b/SWIG/_rsa.i
@@ -0,0 +1,452 @@
+/* Copyright (c) 1999-2000 Ng Pheng Siong. All rights reserved. */
+/* $Id: _rsa.i 723 2010-02-13 06:53:13Z heikki $ */
+
+%{
+#include <openssl/bn.h>
+#include <openssl/err.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/opensslv.h>
+%}
+
+%apply Pointer NONNULL { RSA * };
+%apply Pointer NONNULL { PyObject *pyfunc };
+
+%rename(rsa_new) RSA_new;
+extern RSA *RSA_new(void);
+%rename(rsa_free) RSA_free;
+extern void RSA_free(RSA *);
+%rename(rsa_size) RSA_size;
+extern int RSA_size(const RSA *);
+%rename(rsa_check_key) RSA_check_key;
+extern int RSA_check_key(const RSA *);
+
+%constant int no_padding = RSA_NO_PADDING;
+%constant int pkcs1_padding = RSA_PKCS1_PADDING;
+%constant int sslv23_padding = RSA_SSLV23_PADDING;
+%constant int pkcs1_oaep_padding = RSA_PKCS1_OAEP_PADDING;
+
+%constant int NID_sha1 = NID_sha1;
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+%constant int NID_sha224 = NID_sha224;
+%constant int NID_sha256 = NID_sha256;
+%constant int NID_sha384 = NID_sha384;
+%constant int NID_sha512 = NID_sha512;
+#endif
+
+%constant int NID_md5 = NID_md5;
+
+%constant int NID_ripemd160 = NID_ripemd160;
+
+%inline %{
+static PyObject *_rsa_err;
+
+void rsa_init(PyObject *rsa_err) {
+ Py_INCREF(rsa_err);
+ _rsa_err = rsa_err;
+}
+%}
+
+%inline %{
+RSA *rsa_read_key(BIO *f, PyObject *pyfunc) {
+ RSA *rsa;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ rsa = PEM_read_bio_RSAPrivateKey(f, NULL, passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return rsa;
+}
+%}
+
+%inline %{
+int rsa_write_key(RSA *rsa, BIO *f, EVP_CIPHER *cipher, PyObject *pyfunc) {
+ int ret;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ ret = PEM_write_bio_RSAPrivateKey(f, rsa, cipher, NULL, 0,
+ passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return ret;
+}
+%}
+
+%inline %{
+int rsa_write_key_no_cipher(RSA *rsa, BIO *f, PyObject *pyfunc) {
+ int ret;
+
+ Py_INCREF(pyfunc);
+ Py_BEGIN_ALLOW_THREADS
+ ret = PEM_write_bio_RSAPrivateKey(f, rsa, NULL, NULL, 0,
+ passphrase_callback, (void *)pyfunc);
+ Py_END_ALLOW_THREADS
+ Py_DECREF(pyfunc);
+ return ret;
+}
+%}
+
+%threadallow rsa_read_pub_key;
+%inline %{
+RSA *rsa_read_pub_key(BIO *f) {
+ return PEM_read_bio_RSA_PUBKEY(f, NULL, NULL, NULL);
+}
+%}
+
+%threadallow rsa_write_pub_key;
+%inline %{
+int rsa_write_pub_key(RSA *rsa, BIO *f) {
+ return PEM_write_bio_RSA_PUBKEY(f, rsa);
+}
+
+PyObject *rsa_get_e(RSA *rsa) {
+ if (!rsa->e) {
+ PyErr_SetString(_rsa_err, "'e' is unset");
+ return NULL;
+ }
+ return bn_to_mpi(rsa->e);
+}
+
+PyObject *rsa_get_n(RSA *rsa) {
+ if (!rsa->n) {
+ PyErr_SetString(_rsa_err, "'n' is unset");
+ return NULL;
+ }
+ return bn_to_mpi(rsa->n);
+}
+
+PyObject *rsa_set_e(RSA *rsa, PyObject *value) {
+ BIGNUM *bn;
+ const void *vbuf;
+ int vlen;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+ PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ if (rsa->e)
+ BN_free(rsa->e);
+ rsa->e = bn;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *rsa_set_n(RSA *rsa, PyObject *value) {
+ BIGNUM *bn;
+ const void *vbuf;
+ int vlen;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (!(bn = BN_mpi2bn((unsigned char *)vbuf, vlen, NULL))) {
+ PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ if (rsa->n)
+ BN_free(rsa->n);
+ rsa->n = bn;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *rsa_set_e_bin(RSA *rsa, PyObject *value) {
+ BIGNUM *bn;
+ const void *vbuf;
+ int vlen;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (!(bn = BN_bin2bn((unsigned char *)vbuf, vlen, NULL))) {
+ PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ if (rsa->e)
+ BN_free(rsa->e);
+ rsa->e = bn;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *rsa_set_n_bin(RSA *rsa, PyObject *value) {
+ BIGNUM *bn;
+ const void *vbuf;
+ int vlen;
+
+ if (m2_PyObject_AsReadBufferInt(value, &vbuf, &vlen) == -1)
+ return NULL;
+
+ if (!(bn = BN_bin2bn((unsigned char *)vbuf, vlen, NULL))) {
+ PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ if (rsa->n)
+ BN_free(rsa->n);
+ rsa->n = bn;
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+PyObject *rsa_private_encrypt(RSA *rsa, PyObject *from, int padding) {
+ const void *fbuf;
+ void *tbuf;
+ int flen, tlen;
+ PyObject *ret;
+
+ if (m2_PyObject_AsReadBufferInt(from, &fbuf, &flen) == -1)
+ return NULL;
+
+ if (!(tbuf = PyMem_Malloc(BN_num_bytes(rsa->n)))) {
+ PyErr_SetString(PyExc_MemoryError, "rsa_private_encrypt");
+ return NULL;
+ }
+ tlen = RSA_private_encrypt(flen, (unsigned char *)fbuf,
+ (unsigned char *)tbuf, rsa, padding);
+ if (tlen == -1) {
+ PyMem_Free(tbuf);
+ PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ ret = PyString_FromStringAndSize((const char *)tbuf, tlen);
+ PyMem_Free(tbuf);
+ return ret;
+}
+
+PyObject *rsa_public_decrypt(RSA *rsa, PyObject *from, int padding) {
+ const void *fbuf;
+ void *tbuf;
+ int flen, tlen;
+ PyObject *ret;
+
+ if (m2_PyObject_AsReadBufferInt(from, &fbuf, &flen) == -1)
+ return NULL;
+
+ if (!(tbuf = PyMem_Malloc(BN_num_bytes(rsa->n)))) {
+ PyErr_SetString(PyExc_MemoryError, "rsa_public_decrypt");
+ return NULL;
+ }
+ tlen = RSA_public_decrypt(flen, (unsigned char *)fbuf,
+ (unsigned char *)tbuf, rsa, padding);
+ if (tlen == -1) {
+ PyMem_Free(tbuf);
+ PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ ret = PyString_FromStringAndSize((const char *)tbuf, tlen);
+ PyMem_Free(tbuf);
+ return ret;
+}
+
+PyObject *rsa_public_encrypt(RSA *rsa, PyObject *from, int padding) {
+ const void *fbuf;
+ void *tbuf;
+ int flen, tlen;
+ PyObject *ret;
+
+ if (m2_PyObject_AsReadBufferInt(from, &fbuf, &flen) == -1)
+ return NULL;
+
+ if (!(tbuf = PyMem_Malloc(BN_num_bytes(rsa->n)))) {
+ PyErr_SetString(PyExc_MemoryError, "rsa_public_encrypt");
+ return NULL;
+ }
+ tlen = RSA_public_encrypt(flen, (unsigned char *)fbuf,
+ (unsigned char *)tbuf, rsa, padding);
+ if (tlen == -1) {
+ PyMem_Free(tbuf);
+ PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ ret = PyString_FromStringAndSize((const char *)tbuf, tlen);
+ PyMem_Free(tbuf);
+ return ret;
+}
+
+PyObject *rsa_private_decrypt(RSA *rsa, PyObject *from, int padding) {
+ const void *fbuf;
+ void *tbuf;
+ int flen, tlen;
+ PyObject *ret;
+
+ if (m2_PyObject_AsReadBufferInt(from, &fbuf, &flen) == -1)
+ return NULL;
+
+ if (!(tbuf = PyMem_Malloc(BN_num_bytes(rsa->n)))) {
+ PyErr_SetString(PyExc_MemoryError, "rsa_private_decrypt");
+ return NULL;
+ }
+ tlen = RSA_private_decrypt(flen, (unsigned char *)fbuf,
+ (unsigned char *)tbuf, rsa, padding);
+ if (tlen == -1) {
+ PyMem_Free(tbuf);
+ PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ ret = PyString_FromStringAndSize((const char *)tbuf, tlen);
+ PyMem_Free(tbuf);
+ return ret;
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090708fL
+PyObject *rsa_padding_add_pkcs1_pss(RSA *rsa, PyObject *digest, EVP_MD *hash, int salt_length) {
+ const void *dbuf;
+ unsigned char *tbuf;
+ int dlen, result, tlen;
+ PyObject *ret;
+
+ if (m2_PyObject_AsReadBufferInt(digest, &dbuf, &dlen) == -1)
+ return NULL;
+
+ tlen = RSA_size(rsa);
+
+ if (!(tbuf = OPENSSL_malloc(tlen))) {
+ PyErr_SetString(PyExc_MemoryError, "rsa_padding_add_pkcs1_pss");
+ return NULL;
+ }
+ result = RSA_padding_add_PKCS1_PSS(
+ rsa,
+ tbuf,
+ (unsigned char *)dbuf,
+ hash,
+ salt_length);
+
+ if (result == -1) {
+ OPENSSL_cleanse(tbuf, tlen);
+ OPENSSL_free(tbuf);
+ PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ ret = PyString_FromStringAndSize((const char *)tbuf, tlen);
+ OPENSSL_cleanse(tbuf, tlen);
+ OPENSSL_free(tbuf);
+ return ret;
+}
+
+int rsa_verify_pkcs1_pss(RSA *rsa, PyObject *digest, PyObject *signature, EVP_MD *hash, int salt_length) {
+ const void *dbuf;
+ const void *sbuf;
+ int dlen, slen, ret;
+
+ if (m2_PyObject_AsReadBufferInt(digest, &dbuf, &dlen) == -1) {
+ return 0;
+ }
+
+ if (m2_PyObject_AsReadBufferInt(signature, &sbuf, &slen) == -1) {
+ return 0;
+ }
+
+ ret = RSA_verify_PKCS1_PSS(
+ rsa,
+ (unsigned char *)dbuf,
+ hash,
+ (unsigned char *)sbuf,
+ salt_length);
+
+ return ret;
+}
+#endif
+
+PyObject *rsa_sign(RSA *rsa, PyObject *py_digest_string, int method_type) {
+ int digest_len = 0;
+ int buf_len = 0;
+ int ret = 0;
+ unsigned int real_buf_len = 0;
+ char *digest_string = NULL;
+ unsigned char * sign_buf = NULL;
+ PyObject *signature;
+
+ ret = m2_PyString_AsStringAndSizeInt(py_digest_string, &digest_string,
+ &digest_len);
+ if (ret == -1) {
+ /* PyString_AsStringAndSize raises the correct exceptions. */
+ return NULL;
+ }
+
+ buf_len = RSA_size(rsa);
+ sign_buf = (unsigned char *)PyMem_Malloc(buf_len);
+ ret = RSA_sign(method_type, (const unsigned char *)digest_string, digest_len,
+ sign_buf, &real_buf_len, rsa);
+
+ if (!ret) {
+ PyMem_Free(sign_buf);
+ PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ signature = PyString_FromStringAndSize((const char*) sign_buf, buf_len);
+ PyMem_Free(sign_buf);
+ return signature;
+}
+
+int rsa_verify(RSA *rsa, PyObject *py_verify_string, PyObject* py_sign_string, int method_type){
+ int ret = 0;
+ char * sign_string = NULL;
+ char * verify_string = NULL;
+ int verify_len = 0;
+ int sign_len = 0;
+
+ ret = m2_PyString_AsStringAndSizeInt(py_verify_string, &verify_string,
+ &verify_len);
+ if (ret == -1) {
+ /* PyString_AsStringAndSize raises the correct exceptions. */
+ return 0;
+ }
+ ret = m2_PyString_AsStringAndSizeInt(py_sign_string, &sign_string,
+ &sign_len);
+ if (ret == -1) {
+ return 0;
+ }
+
+ ret = RSA_verify(method_type, (unsigned char *) verify_string,
+ verify_len, (unsigned char *) sign_string,
+ sign_len, rsa);
+ if (!ret) {
+ PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+ }
+ return ret;
+}
+
+void genrsa_callback(int p, int n, void *arg) {
+ PyObject *argv, *ret, *cbfunc;
+
+ cbfunc = (PyObject *)arg;
+ argv = Py_BuildValue("(ii)", p, n);
+ ret = PyEval_CallObject(cbfunc, argv);
+ PyErr_Clear();
+ Py_DECREF(argv);
+ Py_XDECREF(ret);
+}
+
+RSA *rsa_generate_key(int bits, unsigned long e, PyObject *pyfunc) {
+ RSA *rsa;
+
+ Py_INCREF(pyfunc);
+ rsa = RSA_generate_key(bits, e, genrsa_callback, (void *)pyfunc);
+ Py_DECREF(pyfunc);
+ if (!rsa)
+ PyErr_SetString(_rsa_err, ERR_reason_error_string(ERR_get_error()));
+ return rsa;
+}
+
+int rsa_type_check(RSA *rsa) {
+ return 1;
+}
+
+int rsa_check_pub_key(RSA *rsa) {
+ return (rsa->e) && (rsa->n);
+}
+%}
+
+%threadallow rsa_write_key_der;
+%inline %{
+int rsa_write_key_der(RSA *rsa, BIO *bio) {
+ return i2d_RSAPrivateKey_bio(bio, rsa);
+}
+%}
+
diff --git a/SWIG/_ssl.i b/SWIG/_ssl.i
new file mode 100644
index 0000000..2373ff2
--- /dev/null
+++ b/SWIG/_ssl.i
@@ -0,0 +1,739 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved. */
+/*
+** Portions created by Open Source Applications Foundation (OSAF) are
+** Copyright (C) 2004-2005 OSAF. All Rights Reserved.
+**
+** Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+**
+*/
+/* $Id: _ssl.i 721 2010-02-13 06:30:33Z heikki $ */
+
+%{
+#include <pythread.h>
+#include <openssl/bio.h>
+#include <openssl/dh.h>
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+%}
+
+%apply Pointer NONNULL { SSL_CTX * };
+%apply Pointer NONNULL { SSL * };
+%apply Pointer NONNULL { SSL_CIPHER * };
+%apply Pointer NONNULL { STACK_OF(SSL_CIPHER) * };
+%apply Pointer NONNULL { STACK_OF(X509) * };
+%apply Pointer NONNULL { BIO * };
+%apply Pointer NONNULL { DH * };
+%apply Pointer NONNULL { RSA * };
+%apply Pointer NONNULL { EVP_PKEY *};
+%apply Pointer NONNULL { PyObject *pyfunc };
+
+%rename(ssl_get_ciphers) SSL_get_ciphers;
+extern STACK_OF(SSL_CIPHER) *SSL_get_ciphers(const SSL *ssl);
+
+%rename(ssl_get_version) SSL_get_version;
+extern const char *SSL_get_version(CONST SSL *);
+%rename(ssl_get_error) SSL_get_error;
+extern int SSL_get_error(CONST SSL *, int);
+%rename(ssl_get_state) SSL_state_string;
+extern const char *SSL_state_string(const SSL *);
+%rename(ssl_get_state_v) SSL_state_string_long;
+extern const char *SSL_state_string_long(const SSL *);
+%rename(ssl_get_alert_type) SSL_alert_type_string;
+extern const char *SSL_alert_type_string(int);
+%rename(ssl_get_alert_type_v) SSL_alert_type_string_long;
+extern const char *SSL_alert_type_string_long(int);
+%rename(ssl_get_alert_desc) SSL_alert_desc_string;
+extern const char *SSL_alert_desc_string(int);
+%rename(ssl_get_alert_desc_v) SSL_alert_desc_string_long;
+extern const char *SSL_alert_desc_string_long(int);
+
+%rename(sslv2_method) SSLv2_method;
+extern SSL_METHOD *SSLv2_method(void);
+%rename(sslv3_method) SSLv3_method;
+extern SSL_METHOD *SSLv3_method(void);
+%rename(sslv23_method) SSLv23_method;
+extern SSL_METHOD *SSLv23_method(void);
+%rename(tlsv1_method) TLSv1_method;
+extern SSL_METHOD *TLSv1_method(void);
+
+%rename(ssl_ctx_new) SSL_CTX_new;
+extern SSL_CTX *SSL_CTX_new(SSL_METHOD *);
+%rename(ssl_ctx_free) SSL_CTX_free;
+extern void SSL_CTX_free(SSL_CTX *);
+%rename(ssl_ctx_set_verify_depth) SSL_CTX_set_verify_depth;
+extern void SSL_CTX_set_verify_depth(SSL_CTX *, int);
+%rename(ssl_ctx_get_verify_depth) SSL_CTX_get_verify_depth;
+extern int SSL_CTX_get_verify_depth(CONST SSL_CTX *);
+%rename(ssl_ctx_get_verify_mode) SSL_CTX_get_verify_mode;
+extern int SSL_CTX_get_verify_mode(CONST SSL_CTX *);
+%rename(ssl_ctx_set_cipher_list) SSL_CTX_set_cipher_list;
+extern int SSL_CTX_set_cipher_list(SSL_CTX *, const char *);
+%rename(ssl_ctx_add_session) SSL_CTX_add_session;
+extern int SSL_CTX_add_session(SSL_CTX *, SSL_SESSION *);
+%rename(ssl_ctx_remove_session) SSL_CTX_remove_session;
+extern int SSL_CTX_remove_session(SSL_CTX *, SSL_SESSION *);
+%rename(ssl_ctx_set_session_timeout) SSL_CTX_set_timeout;
+extern long SSL_CTX_set_timeout(SSL_CTX *, long);
+%rename(ssl_ctx_get_session_timeout) SSL_CTX_get_timeout;
+extern long SSL_CTX_get_timeout(CONST SSL_CTX *);
+%rename(ssl_ctx_get_cert_store) SSL_CTX_get_cert_store;
+extern X509_STORE *SSL_CTX_get_cert_store(CONST SSL_CTX *);
+
+%rename(bio_new_ssl) BIO_new_ssl;
+extern BIO *BIO_new_ssl(SSL_CTX *, int);
+
+%rename(ssl_new) SSL_new;
+extern SSL *SSL_new(SSL_CTX *);
+%rename(ssl_free) SSL_free;
+%threadallow SSL_free;
+extern void SSL_free(SSL *);
+%rename(ssl_dup) SSL_dup;
+extern SSL *SSL_dup(SSL *);
+%rename(ssl_set_bio) SSL_set_bio;
+extern void SSL_set_bio(SSL *, BIO *, BIO *);
+%rename(ssl_set_accept_state) SSL_set_accept_state;
+extern void SSL_set_accept_state(SSL *);
+%rename(ssl_set_connect_state) SSL_set_connect_state;
+extern void SSL_set_connect_state(SSL *);
+%rename(ssl_get_shutdown) SSL_get_shutdown;
+extern int SSL_get_shutdown(CONST SSL *);
+%rename(ssl_set_shutdown) SSL_set_shutdown;
+extern void SSL_set_shutdown(SSL *, int);
+%rename(ssl_shutdown) SSL_shutdown;
+%threadallow SSL_shutdown;
+extern int SSL_shutdown(SSL *);
+%rename(ssl_clear) SSL_clear;
+extern int SSL_clear(SSL *);
+%rename(ssl_do_handshake) SSL_do_handshake;
+%threadallow SSL_do_handshake;
+extern int SSL_do_handshake(SSL *);
+%rename(ssl_renegotiate) SSL_renegotiate;
+%threadallow SSL_renegotiate;
+extern int SSL_renegotiate(SSL *);
+%rename(ssl_pending) SSL_pending;
+extern int SSL_pending(CONST SSL *);
+
+%rename(ssl_get_peer_cert) SSL_get_peer_certificate;
+extern X509 *SSL_get_peer_certificate(CONST SSL *);
+%rename(ssl_get_current_cipher) SSL_get_current_cipher;
+extern SSL_CIPHER *SSL_get_current_cipher(CONST SSL *);
+%rename(ssl_get_verify_mode) SSL_get_verify_mode;
+extern int SSL_get_verify_mode(CONST SSL *);
+%rename(ssl_get_verify_depth) SSL_get_verify_depth;
+extern int SSL_get_verify_depth(CONST SSL *);
+%rename(ssl_get_verify_result) SSL_get_verify_result;
+extern long SSL_get_verify_result(CONST SSL *);
+%rename(ssl_get_ssl_ctx) SSL_get_SSL_CTX;
+extern SSL_CTX *SSL_get_SSL_CTX(CONST SSL *);
+%rename(ssl_get_default_session_timeout) SSL_get_default_timeout;
+extern long SSL_get_default_timeout(CONST SSL *);
+
+%rename(ssl_set_cipher_list) SSL_set_cipher_list;
+extern int SSL_set_cipher_list(SSL *, const char *);
+%rename(ssl_get_cipher_list) SSL_get_cipher_list;
+extern const char *SSL_get_cipher_list(CONST SSL *, int);
+
+%rename(ssl_cipher_get_name) SSL_CIPHER_get_name;
+extern const char *SSL_CIPHER_get_name(CONST SSL_CIPHER *);
+%rename(ssl_cipher_get_version) SSL_CIPHER_get_version;
+extern char *SSL_CIPHER_get_version(CONST SSL_CIPHER *);
+
+%rename(ssl_get_session) SSL_get_session;
+extern SSL_SESSION *SSL_get_session(CONST SSL *);
+%rename(ssl_get1_session) SSL_get1_session;
+extern SSL_SESSION *SSL_get1_session(SSL *);
+%rename(ssl_set_session) SSL_set_session;
+extern int SSL_set_session(SSL *, SSL_SESSION *);
+%rename(ssl_session_free) SSL_SESSION_free;
+extern void SSL_SESSION_free(SSL_SESSION *);
+%rename(ssl_session_print) SSL_SESSION_print;
+%threadallow SSL_SESSION_print;
+extern int SSL_SESSION_print(BIO *, CONST SSL_SESSION *);
+%rename(ssl_session_set_timeout) SSL_SESSION_set_timeout;
+extern long SSL_SESSION_set_timeout(SSL_SESSION *, long);
+%rename(ssl_session_get_timeout) SSL_SESSION_get_timeout;
+extern long SSL_SESSION_get_timeout(CONST SSL_SESSION *);
+
+%constant int ssl_error_none = SSL_ERROR_NONE;
+%constant int ssl_error_ssl = SSL_ERROR_SSL;
+%constant int ssl_error_want_read = SSL_ERROR_WANT_READ;
+%constant int ssl_error_want_write = SSL_ERROR_WANT_WRITE;
+%constant int ssl_error_want_x509_lookup = SSL_ERROR_WANT_X509_LOOKUP;
+%constant int ssl_error_syscall = SSL_ERROR_SYSCALL;
+%constant int ssl_error_zero_return = SSL_ERROR_ZERO_RETURN;
+%constant int ssl_error_want_connect = SSL_ERROR_WANT_CONNECT;
+
+%constant int SSL_VERIFY_NONE = 0x00;
+%constant int SSL_VERIFY_PEER = 0x01;
+%constant int SSL_VERIFY_FAIL_IF_NO_PEER_CERT = 0x02;
+%constant int SSL_VERIFY_CLIENT_ONCE = 0x04;
+
+%constant int SSL_ST_CONNECT = 0x1000;
+%constant int SSL_ST_ACCEPT = 0x2000;
+%constant int SSL_ST_MASK = 0x0FFF;
+%constant int SSL_ST_INIT = (SSL_ST_CONNECT|SSL_ST_ACCEPT);
+%constant int SSL_ST_BEFORE = 0x4000;
+%constant int SSL_ST_OK = 0x03;
+%constant int SSL_ST_RENEGOTIATE = (0x04|SSL_ST_INIT);
+
+%constant int SSL_CB_LOOP = 0x01;
+%constant int SSL_CB_EXIT = 0x02;
+%constant int SSL_CB_READ = 0x04;
+%constant int SSL_CB_WRITE = 0x08;
+%constant int SSL_CB_ALERT = 0x4000; /* used in callback */
+%constant int SSL_CB_READ_ALERT = (SSL_CB_ALERT|SSL_CB_READ);
+%constant int SSL_CB_WRITE_ALERT = (SSL_CB_ALERT|SSL_CB_WRITE);
+%constant int SSL_CB_ACCEPT_LOOP = (SSL_ST_ACCEPT|SSL_CB_LOOP);
+%constant int SSL_CB_ACCEPT_EXIT = (SSL_ST_ACCEPT|SSL_CB_EXIT);
+%constant int SSL_CB_CONNECT_LOOP = (SSL_ST_CONNECT|SSL_CB_LOOP);
+%constant int SSL_CB_CONNECT_EXIT = (SSL_ST_CONNECT|SSL_CB_EXIT);
+%constant int SSL_CB_HANDSHAKE_START = 0x10;
+%constant int SSL_CB_HANDSHAKE_DONE = 0x20;
+
+%constant int SSL_SENT_SHUTDOWN = 1;
+%constant int SSL_RECEIVED_SHUTDOWN = 2;
+
+%constant int SSL_SESS_CACHE_OFF = 0x000;
+%constant int SSL_SESS_CACHE_CLIENT = 0x001;
+%constant int SSL_SESS_CACHE_SERVER = 0x002;
+%constant int SSL_SESS_CACHE_BOTH = (SSL_SESS_CACHE_CLIENT|SSL_SESS_CACHE_SERVER);
+
+%constant int SSL_OP_ALL = 0x00000FFFL;
+
+%constant int SSL_OP_NO_SSLv2 = 0x01000000L;
+%constant int SSL_OP_NO_SSLv3 = 0x02000000L;
+%constant int SSL_OP_NO_TLSv1 = 0x04000000L;
+%constant int SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS = 0x00000800L;
+
+%constant int SSL_MODE_ENABLE_PARTIAL_WRITE = SSL_MODE_ENABLE_PARTIAL_WRITE;
+%constant int SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER = SSL_MODE_ENABLE_PARTIAL_WRITE;
+%constant int SSL_MODE_AUTO_RETRY = SSL_MODE_AUTO_RETRY;
+
+%inline %{
+static PyObject *_ssl_err;
+
+void ssl_init(PyObject *ssl_err) {
+ SSL_library_init();
+ SSL_load_error_strings();
+ Py_INCREF(ssl_err);
+ _ssl_err = ssl_err;
+}
+
+void ssl_ctx_passphrase_callback(SSL_CTX *ctx, PyObject *pyfunc) {
+ SSL_CTX_set_default_passwd_cb(ctx, passphrase_callback);
+ SSL_CTX_set_default_passwd_cb_userdata(ctx, (void *)pyfunc);
+ Py_INCREF(pyfunc);
+}
+
+int ssl_ctx_use_x509(SSL_CTX *ctx, X509 *x) {
+ int i;
+
+ if (!(i = SSL_CTX_use_certificate(ctx, x))) {
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ return -1;
+ }
+ return i;
+
+}
+
+int ssl_ctx_use_cert(SSL_CTX *ctx, char *file) {
+ int i;
+
+ if (!(i = SSL_CTX_use_certificate_file(ctx, file, SSL_FILETYPE_PEM))) {
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ return -1;
+ }
+ return i;
+}
+
+int ssl_ctx_use_cert_chain(SSL_CTX *ctx, char *file) {
+ int i;
+
+ if (!(i = SSL_CTX_use_certificate_chain_file(ctx, file))) {
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ return -1;
+ }
+ return i;
+}
+
+
+int ssl_ctx_use_privkey(SSL_CTX *ctx, char *file) {
+ int i;
+
+ if (!(i = SSL_CTX_use_PrivateKey_file(ctx, file, SSL_FILETYPE_PEM))) {
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ return -1;
+ }
+ return i;
+}
+
+int ssl_ctx_use_rsa_privkey(SSL_CTX *ctx, RSA *rsakey) {
+ int i;
+
+ if (!(i = SSL_CTX_use_RSAPrivateKey(ctx, rsakey))) {
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ return -1;
+ }
+ return i;
+}
+
+int ssl_ctx_use_pkey_privkey(SSL_CTX *ctx, EVP_PKEY *pkey) {
+ int i;
+
+ if (!(i = SSL_CTX_use_PrivateKey(ctx, pkey))) {
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ return -1;
+ }
+ return i;
+}
+
+
+int ssl_ctx_check_privkey(SSL_CTX *ctx) {
+ int ret;
+
+ if (!(ret = SSL_CTX_check_private_key(ctx))) {
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ return -1;
+ }
+ return ret;
+}
+
+void ssl_ctx_set_client_CA_list_from_file(SSL_CTX *ctx, const char *ca_file) {
+ SSL_CTX_set_client_CA_list(ctx, SSL_load_client_CA_file(ca_file));
+}
+
+void ssl_ctx_set_verify_default(SSL_CTX *ctx, int mode) {
+ SSL_CTX_set_verify(ctx, mode, NULL);
+}
+
+void ssl_ctx_set_verify(SSL_CTX *ctx, int mode, PyObject *pyfunc) {
+ Py_XDECREF(ssl_verify_cb_func);
+ Py_INCREF(pyfunc);
+ ssl_verify_cb_func = pyfunc;
+ SSL_CTX_set_verify(ctx, mode, ssl_verify_callback);
+}
+
+int ssl_ctx_set_session_id_context(SSL_CTX *ctx, PyObject *sid_ctx) {
+ const void *buf;
+ int len;
+
+ if (m2_PyObject_AsReadBufferInt(sid_ctx, &buf, &len) == -1)
+ return -1;
+
+ return SSL_CTX_set_session_id_context(ctx, buf, len);
+}
+
+void ssl_ctx_set_info_callback(SSL_CTX *ctx, PyObject *pyfunc) {
+ Py_XDECREF(ssl_info_cb_func);
+ Py_INCREF(pyfunc);
+ ssl_info_cb_func = pyfunc;
+ SSL_CTX_set_info_callback(ctx, ssl_info_callback);
+}
+
+long ssl_ctx_set_tmp_dh(SSL_CTX *ctx, DH* dh) {
+ return SSL_CTX_set_tmp_dh(ctx, dh);
+}
+
+void ssl_ctx_set_tmp_dh_callback(SSL_CTX *ctx, PyObject *pyfunc) {
+ Py_XDECREF(ssl_set_tmp_dh_cb_func);
+ Py_INCREF(pyfunc);
+ ssl_set_tmp_dh_cb_func = pyfunc;
+ SSL_CTX_set_tmp_dh_callback(ctx, ssl_set_tmp_dh_callback);
+}
+
+long ssl_ctx_set_tmp_rsa(SSL_CTX *ctx, RSA* rsa) {
+ return SSL_CTX_set_tmp_rsa(ctx, rsa);
+}
+
+void ssl_ctx_set_tmp_rsa_callback(SSL_CTX *ctx, PyObject *pyfunc) {
+ Py_XDECREF(ssl_set_tmp_rsa_cb_func);
+ Py_INCREF(pyfunc);
+ ssl_set_tmp_rsa_cb_func = pyfunc;
+ SSL_CTX_set_tmp_rsa_callback(ctx, ssl_set_tmp_rsa_callback);
+}
+
+int ssl_ctx_load_verify_locations(SSL_CTX *ctx, const char *cafile, const char *capath) {
+ return SSL_CTX_load_verify_locations(ctx, cafile, capath);
+}
+
+/* SSL_CTX_set_options is a macro. */
+long ssl_ctx_set_options(SSL_CTX *ctx, long op) {
+ return SSL_CTX_set_options(ctx, op);
+}
+
+int bio_set_ssl(BIO *bio, SSL *ssl, int flag) {
+ SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
+ return BIO_ctrl(bio, BIO_C_SET_SSL, flag, (char *)ssl);
+}
+
+long ssl_set_mode(SSL *ssl, long mode) {
+ return SSL_set_mode(ssl, mode);
+}
+
+long ssl_get_mode(SSL *ssl) {
+ return SSL_get_mode(ssl);
+}
+
+void ssl_set_client_CA_list_from_file(SSL *ssl, const char *ca_file) {
+ SSL_set_client_CA_list(ssl, SSL_load_client_CA_file(ca_file));
+}
+
+void ssl_set_client_CA_list_from_context(SSL *ssl, SSL_CTX *ctx) {
+ SSL_set_client_CA_list(ssl, SSL_CTX_get_client_CA_list(ctx));
+}
+
+int ssl_set_session_id_context(SSL *ssl, PyObject *sid_ctx) {
+ const void *buf;
+ int len;
+
+ if (m2_PyObject_AsReadBufferInt(sid_ctx, &buf, &len) == -1)
+ return -1;
+
+ return SSL_set_session_id_context(ssl, buf, len);
+}
+
+int ssl_set_fd(SSL *ssl, int fd) {
+ int ret;
+
+ if (!(ret = SSL_set_fd(ssl, fd))) {
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ return -1;
+ }
+ return ret;
+}
+
+PyObject *ssl_accept(SSL *ssl) {
+ PyObject *obj = NULL;
+ int r, err;
+
+ Py_BEGIN_ALLOW_THREADS
+ r = SSL_accept(ssl);
+ Py_END_ALLOW_THREADS
+
+
+ switch (SSL_get_error(ssl, r)) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ obj = PyInt_FromLong((long)1);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ obj = PyInt_FromLong((long)0);
+ break;
+ case SSL_ERROR_SSL:
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ obj = NULL;
+ break;
+ case SSL_ERROR_SYSCALL:
+ err = ERR_get_error();
+ if (err)
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
+ else if (r == 0)
+ PyErr_SetString(_ssl_err, "unexpected eof");
+ else if (r == -1)
+ PyErr_SetFromErrno(_ssl_err);
+ obj = NULL;
+ break;
+ }
+
+
+ return obj;
+}
+
+PyObject *ssl_connect(SSL *ssl) {
+ PyObject *obj = NULL;
+ int r, err;
+
+ Py_BEGIN_ALLOW_THREADS
+ r = SSL_connect(ssl);
+ Py_END_ALLOW_THREADS
+
+
+ switch (SSL_get_error(ssl, r)) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ obj = PyInt_FromLong((long)1);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ obj = PyInt_FromLong((long)0);
+ break;
+ case SSL_ERROR_SSL:
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ obj = NULL;
+ break;
+ case SSL_ERROR_SYSCALL:
+ err = ERR_get_error();
+ if (err)
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
+ else if (r == 0)
+ PyErr_SetString(_ssl_err, "unexpected eof");
+ else if (r == -1)
+ PyErr_SetFromErrno(_ssl_err);
+ obj = NULL;
+ break;
+ }
+
+
+ return obj;
+}
+
+void ssl_set_shutdown1(SSL *ssl, int mode) {
+ SSL_set_shutdown(ssl, mode);
+}
+
+PyObject *ssl_read(SSL *ssl, int num) {
+ PyObject *obj = NULL;
+ void *buf;
+ int r, err;
+
+ if (!(buf = PyMem_Malloc(num))) {
+ PyErr_SetString(PyExc_MemoryError, "ssl_read");
+ return NULL;
+ }
+
+
+ Py_BEGIN_ALLOW_THREADS
+ r = SSL_read(ssl, buf, num);
+ Py_END_ALLOW_THREADS
+
+
+ switch (SSL_get_error(ssl, r)) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ buf = PyMem_Realloc(buf, r);
+ obj = PyString_FromStringAndSize(buf, r);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ Py_INCREF(Py_None);
+ obj = Py_None;
+ break;
+ case SSL_ERROR_SSL:
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ obj = NULL;
+ break;
+ case SSL_ERROR_SYSCALL:
+ err = ERR_get_error();
+ if (err)
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
+ else if (r == 0)
+ PyErr_SetString(_ssl_err, "unexpected eof");
+ else if (r == -1)
+ PyErr_SetFromErrno(_ssl_err);
+ obj = NULL;
+ break;
+ }
+ PyMem_Free(buf);
+
+
+ return obj;
+}
+
+PyObject *ssl_read_nbio(SSL *ssl, int num) {
+ PyObject *obj = NULL;
+ void *buf;
+ int r, err;
+
+
+ if (!(buf = PyMem_Malloc(num))) {
+ PyErr_SetString(PyExc_MemoryError, "ssl_read");
+ return NULL;
+ }
+
+
+ Py_BEGIN_ALLOW_THREADS
+ r = SSL_read(ssl, buf, num);
+ Py_END_ALLOW_THREADS
+
+
+ switch (SSL_get_error(ssl, r)) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ buf = PyMem_Realloc(buf, r);
+ obj = PyString_FromStringAndSize(buf, r);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ Py_INCREF(Py_None);
+ obj = Py_None;
+ break;
+ case SSL_ERROR_SSL:
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ obj = NULL;
+ break;
+ case SSL_ERROR_SYSCALL:
+ err = ERR_get_error();
+ if (err)
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
+ else if (r == 0)
+ PyErr_SetString(_ssl_err, "unexpected eof");
+ else if (r == -1)
+ PyErr_SetFromErrno(_ssl_err);
+ obj = NULL;
+ break;
+ }
+ PyMem_Free(buf);
+
+
+ return obj;
+}
+
+int ssl_write(SSL *ssl, PyObject *blob) {
+ const void *buf;
+ int len, r, err, ret;
+
+
+ if (m2_PyObject_AsReadBufferInt(blob, &buf, &len) == -1) {
+ return -1;
+ }
+
+
+ Py_BEGIN_ALLOW_THREADS
+ r = SSL_write(ssl, buf, len);
+ Py_END_ALLOW_THREADS
+
+
+ switch (SSL_get_error(ssl, r)) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ ret = r;
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ ret = -1;
+ break;
+ case SSL_ERROR_SSL:
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ ret = -1;
+ break;
+ case SSL_ERROR_SYSCALL:
+ err = ERR_get_error();
+ if (err)
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(ERR_get_error()));
+ else if (r == 0)
+ PyErr_SetString(_ssl_err, "unexpected eof");
+ else if (r == -1)
+ PyErr_SetFromErrno(_ssl_err);
+ default:
+ ret = -1;
+ }
+
+
+ return ret;
+}
+
+int ssl_write_nbio(SSL *ssl, PyObject *blob) {
+ const void *buf;
+ int len, r, err, ret;
+
+
+ if (m2_PyObject_AsReadBufferInt(blob, &buf, &len) == -1) {
+ return -1;
+ }
+
+
+ Py_BEGIN_ALLOW_THREADS
+ r = SSL_write(ssl, buf, len);
+ Py_END_ALLOW_THREADS
+
+
+ switch (SSL_get_error(ssl, r)) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ ret = r;
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ ret = -1;
+ break;
+ case SSL_ERROR_SSL:
+ ret = -1;
+ break;
+ case SSL_ERROR_SYSCALL:
+ err = ERR_get_error();
+ if (err)
+ PyErr_SetString(_ssl_err, ERR_reason_error_string(err));
+ else if (r == 0)
+ PyErr_SetString(_ssl_err, "unexpected eof");
+ else if (r == -1)
+ PyErr_SetFromErrno(_ssl_err);
+ default:
+ ret = -1;
+ }
+
+
+ return ret;
+}
+
+int ssl_cipher_get_bits(SSL_CIPHER *c) {
+ return SSL_CIPHER_get_bits(c, NULL);
+}
+
+int sk_ssl_cipher_num(STACK_OF(SSL_CIPHER) *stack) {
+ return sk_SSL_CIPHER_num(stack);
+}
+
+SSL_CIPHER *sk_ssl_cipher_value(STACK_OF(SSL_CIPHER) *stack, int idx) {
+ return sk_SSL_CIPHER_value(stack, idx);
+}
+
+STACK_OF(X509) *ssl_get_peer_cert_chain(SSL *ssl) {
+ return SSL_get_peer_cert_chain(ssl);
+}
+
+int sk_x509_num(STACK_OF(X509) *stack) {
+ return sk_X509_num(stack);
+}
+
+X509 *sk_x509_value(STACK_OF(X509) *stack, int idx) {
+ return sk_X509_value(stack, idx);
+}
+%}
+
+%threadallow i2d_ssl_session;
+%inline %{
+void i2d_ssl_session(BIO *bio, SSL_SESSION *sess) {
+ i2d_SSL_SESSION_bio(bio, sess);
+}
+%}
+
+%threadallow ssl_session_read_pem;
+%inline %{
+SSL_SESSION *ssl_session_read_pem(BIO *bio) {
+ return PEM_read_bio_SSL_SESSION(bio, NULL, NULL, NULL);
+}
+%}
+
+%threadallow ssl_session_write_pem;
+%inline %{
+int ssl_session_write_pem(SSL_SESSION *sess, BIO *bio) {
+ return PEM_write_bio_SSL_SESSION(bio, sess);
+}
+
+int ssl_ctx_set_session_cache_mode(SSL_CTX *ctx, int mode)
+{
+ return SSL_CTX_set_session_cache_mode(ctx, mode);
+}
+
+int ssl_ctx_get_session_cache_mode(SSL_CTX *ctx)
+{
+ return SSL_CTX_get_session_cache_mode(ctx);
+}
+
+static long ssl_ctx_set_cache_size(SSL_CTX *ctx, long arg)
+{
+ return SSL_CTX_sess_set_cache_size(ctx, arg);
+}
+
+int ssl_is_init_finished(SSL *ssl)
+{
+ return SSL_is_init_finished(ssl);
+}
+%}
+
diff --git a/SWIG/_threads.i b/SWIG/_threads.i
new file mode 100644
index 0000000..bb625df
--- /dev/null
+++ b/SWIG/_threads.i
@@ -0,0 +1,66 @@
+/* Copyright (c) 1999 Ng Pheng Siong. All rights reserved. */
+/* $Id: _threads.i 690 2009-07-22 08:32:43Z heikki $ */
+
+%{
+#include <pythread.h>
+#include <openssl/crypto.h>
+
+#ifdef THREADING
+static PyThread_type_lock lock_cs[CRYPTO_NUM_LOCKS];
+static long lock_count[CRYPTO_NUM_LOCKS];
+static int thread_mode = 0;
+#endif
+
+void threading_locking_callback(int mode, int type, const char *file, int line) {
+#ifdef THREADING
+ if (mode & CRYPTO_LOCK) {
+ PyThread_acquire_lock(lock_cs[type], WAIT_LOCK);
+ lock_count[type]++;
+ } else {
+ PyThread_release_lock(lock_cs[type]);
+ lock_count[type]--;
+ }
+#endif
+}
+
+unsigned long threading_id_callback(void) {
+#ifdef THREADING
+ return (unsigned long)PyThread_get_thread_ident();
+#else
+ return (unsigned long)0;
+#endif
+}
+%}
+
+%inline %{
+void threading_init(void) {
+#ifdef THREADING
+ int i;
+ if (!thread_mode) {
+ for (i=0; i<CRYPTO_NUM_LOCKS; i++) {
+ lock_count[i]=0;
+ lock_cs[i]=PyThread_allocate_lock();
+ }
+ CRYPTO_set_id_callback(threading_id_callback);
+ CRYPTO_set_locking_callback(threading_locking_callback);
+ }
+ thread_mode = 1;
+#endif
+}
+
+void threading_cleanup(void) {
+#ifdef THREADING
+ int i;
+ if (thread_mode) {
+ CRYPTO_set_locking_callback(NULL);
+ for (i=0; i<CRYPTO_NUM_LOCKS; i++) {
+ lock_count[i]=0;
+ PyThread_release_lock(lock_cs[i]);
+ PyThread_free_lock(lock_cs[i]);
+ }
+ }
+ thread_mode = 0;
+#endif
+}
+%}
+
diff --git a/SWIG/_util.i b/SWIG/_util.i
new file mode 100644
index 0000000..522b8c8
--- /dev/null
+++ b/SWIG/_util.i
@@ -0,0 +1,57 @@
+/* Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved.
+ * Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+*/
+/* $Id: _util.i 721 2010-02-13 06:30:33Z heikki $ */
+
+%{
+#include <openssl/x509v3.h>
+%}
+
+%inline %{
+static PyObject *_util_err;
+
+void util_init(PyObject *util_err) {
+ Py_INCREF(util_err);
+ _util_err = util_err;
+}
+
+PyObject *util_hex_to_string(PyObject *blob) {
+ PyObject *obj;
+ const void *buf;
+ char *ret;
+ Py_ssize_t len;
+
+ if (PyObject_AsReadBuffer(blob, &buf, &len) == -1)
+ return NULL;
+
+ ret = hex_to_string((unsigned char *)buf, len);
+ if (!ret) {
+ PyErr_SetString(_util_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ obj = PyString_FromString(ret);
+ OPENSSL_free(ret);
+ return obj;
+}
+
+PyObject *util_string_to_hex(PyObject *blob) {
+ PyObject *obj;
+ const void *buf;
+ unsigned char *ret;
+ Py_ssize_t len0;
+ long len;
+
+ if (PyObject_AsReadBuffer(blob, &buf, &len0) == -1)
+ return NULL;
+
+ len = len0;
+ ret = string_to_hex((char *)buf, &len);
+ if (ret == NULL) {
+ PyErr_SetString(_util_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ obj = PyString_FromStringAndSize((char*)ret, len);
+ OPENSSL_free(ret);
+ return obj;
+}
+%}
diff --git a/SWIG/_x509.i b/SWIG/_x509.i
new file mode 100644
index 0000000..0471f68
--- /dev/null
+++ b/SWIG/_x509.i
@@ -0,0 +1,674 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved. */
+/*
+** Portions created by Open Source Applications Foundation (OSAF) are
+** Copyright (C) 2004-2005 OSAF. All Rights Reserved.
+**
+** Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+**
+*/
+/* $Id: _x509.i 721 2010-02-13 06:30:33Z heikki $ */
+
+%{
+#include <openssl/x509.h>
+#include <openssl/x509v3.h>
+%}
+
+%apply Pointer NONNULL { BIO * };
+%apply Pointer NONNULL { X509 * };
+%apply Pointer NONNULL { X509_CRL * };
+%apply Pointer NONNULL { X509_REQ * };
+%apply Pointer NONNULL { X509_NAME * };
+%apply Pointer NONNULL { X509_NAME_ENTRY * };
+%apply Pointer NONNULL { EVP_PKEY * };
+
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+%rename(x509_check_ca) X509_check_ca;
+extern int X509_check_ca(X509 *);
+#endif
+
+%rename(x509_new) X509_new;
+extern X509 *X509_new( void );
+%rename(x509_dup) X509_dup;
+extern X509 *X509_dup(X509 *);
+%rename(x509_free) X509_free;
+extern void X509_free(X509 *);
+%rename(x509_crl_free) X509_CRL_free;
+extern void X509_CRL_free(X509_CRL *);
+%rename(x509_crl_new) X509_CRL_new;
+extern X509_CRL * X509_CRL_new();
+
+%rename(x509_print) X509_print;
+%threadallow X509_print;
+extern int X509_print(BIO *, X509 *);
+%rename(x509_crl_print) X509_CRL_print;
+%threadallow X509_CRL_print;
+extern int X509_CRL_print(BIO *, X509_CRL *);
+
+%rename(x509_get_serial_number) X509_get_serialNumber;
+extern ASN1_INTEGER *X509_get_serialNumber(X509 *);
+%rename(x509_set_serial_number) X509_set_serialNumber;
+extern int X509_set_serialNumber(X509 *, ASN1_INTEGER *);
+%rename(x509_get_pubkey) X509_get_pubkey;
+extern EVP_PKEY *X509_get_pubkey(X509 *);
+%rename(x509_set_pubkey) X509_set_pubkey;
+extern int X509_set_pubkey(X509 *, EVP_PKEY *);
+%rename(x509_get_issuer_name) X509_get_issuer_name;
+extern X509_NAME *X509_get_issuer_name(X509 *);
+%rename(x509_set_issuer_name) X509_set_issuer_name;
+extern int X509_set_issuer_name(X509 *, X509_NAME *);
+%rename(x509_get_subject_name) X509_get_subject_name;
+extern X509_NAME *X509_get_subject_name(X509 *);
+%rename(x509_set_subject_name) X509_set_subject_name;
+extern int X509_set_subject_name(X509 *, X509_NAME *);
+%rename(x509_cmp_current_time) X509_cmp_current_time;
+extern int X509_cmp_current_time(ASN1_UTCTIME *);
+
+
+/* From x509.h */
+/* standard trust ids */
+%constant int X509_TRUST_DEFAULT = -1;
+%constant int X509_TRUST_COMPAT = 1;
+%constant int X509_TRUST_SSL_CLIENT = 2;
+%constant int X509_TRUST_SSL_SERVER = 3;
+%constant int X509_TRUST_EMAIL = 4;
+%constant int X509_TRUST_OBJECT_SIGN = 5;
+%constant int X509_TRUST_OCSP_SIGN = 6;
+%constant int X509_TRUST_OCSP_REQUEST = 7;
+
+/* trust_flags values */
+%constant int X509_TRUST_DYNAMIC = 1;
+%constant int X509_TRUST_DYNAMIC_NAME = 2;
+
+/* check_trust return codes */
+%constant int X509_TRUST_TRUSTED = 1;
+%constant int X509_TRUST_REJECTED = 2;
+%constant int X509_TRUST_UNTRUSTED = 3;
+
+/* From x509v3.h */
+%constant int X509_PURPOSE_SSL_CLIENT = 1;
+%constant int X509_PURPOSE_SSL_SERVER = 2;
+%constant int X509_PURPOSE_NS_SSL_SERVER = 3;
+%constant int X509_PURPOSE_SMIME_SIGN = 4;
+%constant int X509_PURPOSE_SMIME_ENCRYPT = 5;
+%constant int X509_PURPOSE_CRL_SIGN = 6;
+%constant int X509_PURPOSE_ANY = 7;
+%constant int X509_PURPOSE_OCSP_HELPER = 8;
+
+%rename(x509_check_purpose) X509_check_purpose;
+extern int X509_check_purpose(X509 *, int, int);
+%rename(x509_check_trust) X509_check_trust;
+extern int X509_check_trust(X509 *, int, int);
+
+%rename(x509_write_pem) PEM_write_bio_X509;
+%threadallow PEM_write_bio_X509;
+extern int PEM_write_bio_X509(BIO *, X509 *);
+%rename(x509_write_pem_file) PEM_write_X509;
+extern int PEM_write_X509(FILE *, X509 *);
+
+%rename(x509_verify) X509_verify;
+extern int X509_verify(X509 *a, EVP_PKEY *r);
+%rename(x509_get_verify_error) X509_verify_cert_error_string;
+extern const char *X509_verify_cert_error_string(long);
+
+%constant long X509V3_EXT_UNKNOWN_MASK = (0xfL << 16);
+%constant long X509V3_EXT_DEFAULT = 0;
+%constant long X509V3_EXT_ERROR_UNKNOWN = (1L << 16);
+%constant long X509V3_EXT_PARSE_UNKNOWN = (2L << 16);
+%constant long X509V3_EXT_DUMP_UNKNOWN = (3L << 16);
+
+%rename(x509_add_ext) X509_add_ext;
+extern int X509_add_ext(X509 *, X509_EXTENSION *, int);
+%rename(x509_get_ext_count) X509_get_ext_count;
+extern int X509_get_ext_count(X509 *);
+%rename(x509_get_ext) X509_get_ext;
+extern X509_EXTENSION *X509_get_ext(X509 *, int);
+%rename(x509_ext_print) X509V3_EXT_print;
+%threadallow X509V3_EXT_print;
+extern int X509V3_EXT_print(BIO *, X509_EXTENSION *, unsigned long, int);
+
+%rename(x509_name_new) X509_NAME_new;
+extern X509_NAME *X509_NAME_new( void );
+%rename(x509_name_free) X509_NAME_free;
+extern void X509_NAME_free(X509_NAME *);
+%rename(x509_name_print) X509_NAME_print;
+%threadallow X509_NAME_print;
+extern int X509_NAME_print(BIO *, X509_NAME *, int);
+%rename(x509_name_get_entry) X509_NAME_get_entry;
+extern X509_NAME_ENTRY *X509_NAME_get_entry(X509_NAME *, int);
+%rename(x509_name_entry_count) X509_NAME_entry_count;
+extern int X509_NAME_entry_count(X509_NAME *);
+%rename(x509_name_delete_entry) X509_NAME_delete_entry;
+extern X509_NAME_ENTRY *X509_NAME_delete_entry(X509_NAME *, int);
+%rename(x509_name_add_entry) X509_NAME_add_entry;
+extern int X509_NAME_add_entry(X509_NAME *, X509_NAME_ENTRY *, int, int);
+%rename(x509_name_add_entry_by_obj) X509_NAME_add_entry_by_OBJ;
+extern int X509_NAME_add_entry_by_OBJ(X509_NAME *, ASN1_OBJECT *, int, unsigned char *, int, int, int );
+%rename(x509_name_add_entry_by_nid) X509_NAME_add_entry_by_NID;
+extern int X509_NAME_add_entry_by_NID(X509_NAME *, int, int, unsigned char *, int, int, int );
+%rename(x509_name_print_ex) X509_NAME_print_ex;
+%threadallow X509_NAME_print_ex;
+extern int X509_NAME_print_ex(BIO *, X509_NAME *, int, unsigned long);
+%rename(x509_name_print_ex_fp) X509_NAME_print_ex_fp;
+extern int X509_NAME_print_ex_fp(FILE *, X509_NAME *, int, unsigned long);
+
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+%rename(x509_name_hash) X509_NAME_hash_old;
+extern unsigned long X509_NAME_hash_old(X509_NAME *);
+#else
+%rename(x509_name_hash) X509_NAME_hash;
+extern unsigned long X509_NAME_hash(X509_NAME *);
+#endif
+
+%rename(x509_name_get_index_by_nid) X509_NAME_get_index_by_NID;
+extern int X509_NAME_get_index_by_NID(X509_NAME *, int, int);
+
+%rename(x509_name_entry_new) X509_NAME_ENTRY_new;
+extern X509_NAME_ENTRY *X509_NAME_ENTRY_new( void );
+%rename(x509_name_entry_free) X509_NAME_ENTRY_free;
+extern void X509_NAME_ENTRY_free( X509_NAME_ENTRY *);
+/*XXX This is probably bogus:*/
+%rename(x509_name_entry_create_by_nid) X509_NAME_ENTRY_create_by_NID;
+extern X509_NAME_ENTRY *X509_NAME_ENTRY_create_by_NID( X509_NAME_ENTRY **, int, int, unsigned char *, int);
+%rename(x509_name_entry_set_object) X509_NAME_ENTRY_set_object;
+extern int X509_NAME_ENTRY_set_object( X509_NAME_ENTRY *, ASN1_OBJECT *);
+%rename(x509_name_entry_get_object) X509_NAME_ENTRY_get_object;
+extern ASN1_OBJECT *X509_NAME_ENTRY_get_object(X509_NAME_ENTRY *);
+%rename(x509_name_entry_get_data) X509_NAME_ENTRY_get_data;
+extern ASN1_STRING *X509_NAME_ENTRY_get_data(X509_NAME_ENTRY *);
+
+%typemap(in) (CONST unsigned char *, int) {
+ if (PyString_Check($input)) {
+ Py_ssize_t len;
+
+ $1 = (unsigned char *)PyString_AsString($input);
+ len = PyString_Size($input);
+ if (len > INT_MAX) {
+ PyErr_SetString(PyExc_ValueError, "object too large");
+ return NULL;
+ }
+ $2 = len;
+ } else {
+ PyErr_SetString(PyExc_TypeError, "expected string");
+ return NULL;
+ }
+}
+%rename(x509_name_entry_set_data) X509_NAME_ENTRY_set_data;
+extern int X509_NAME_ENTRY_set_data(X509_NAME_ENTRY *, int, CONST unsigned char *, int);
+%typemap(in) (CONST unsigned char *, int);
+
+%rename(x509_req_new) X509_REQ_new;
+extern X509_REQ * X509_REQ_new();
+%rename(x509_req_free) X509_REQ_free;
+extern void X509_REQ_free(X509_REQ *);
+%rename(x509_req_print) X509_REQ_print;
+%threadallow X509_REQ_print;
+extern int X509_REQ_print(BIO *, X509_REQ *);
+
+%rename(x509_req_get_pubkey) X509_REQ_get_pubkey;
+extern EVP_PKEY *X509_REQ_get_pubkey(X509_REQ *);
+%rename(x509_req_set_pubkey) X509_REQ_set_pubkey;
+extern int X509_REQ_set_pubkey(X509_REQ *, EVP_PKEY *);
+%rename(x509_req_set_subject_name) X509_REQ_set_subject_name;
+extern int X509_REQ_set_subject_name(X509_REQ *, X509_NAME *);
+
+%rename(x509_req_verify) X509_REQ_verify;
+extern int X509_REQ_verify(X509_REQ *, EVP_PKEY *);
+%rename(x509_req_sign) X509_REQ_sign;
+extern int X509_REQ_sign(X509_REQ *, EVP_PKEY *, const EVP_MD *);
+
+%rename(i2d_x509_bio) i2d_X509_bio;
+%threadallow i2d_X509_bio;
+extern int i2d_X509_bio(BIO *, X509 *);
+%rename(i2d_x509_req_bio) i2d_X509_REQ_bio;
+%threadallow i2d_X509_REQ_bio;
+extern int i2d_X509_REQ_bio(BIO *, X509_REQ *);
+
+%rename(x509_store_new) X509_STORE_new;
+extern X509_STORE *X509_STORE_new(void);
+%rename(x509_store_free) X509_STORE_free;
+extern void X509_STORE_free(X509_STORE *);
+%rename(x509_store_add_cert) X509_STORE_add_cert;
+extern int X509_STORE_add_cert(X509_STORE *, X509 *);
+
+%rename(x509_store_ctx_get_current_cert) X509_STORE_CTX_get_current_cert;
+extern X509 *X509_STORE_CTX_get_current_cert(X509_STORE_CTX *);
+%rename(x509_store_ctx_get_error) X509_STORE_CTX_get_error;
+extern int X509_STORE_CTX_get_error(X509_STORE_CTX *);
+%rename(x509_store_ctx_get_error_depth) X509_STORE_CTX_get_error_depth;
+extern int X509_STORE_CTX_get_error_depth(X509_STORE_CTX *);
+%rename(x509_store_ctx_free) X509_STORE_CTX_free;
+extern void X509_STORE_CTX_free(X509_STORE_CTX *);
+%rename(x509_store_ctx_get1_chain) X509_STORE_CTX_get1_chain;
+extern STACK_OF(X509) *X509_STORE_CTX_get1_chain(X509_STORE_CTX *);
+
+%rename(x509_extension_get_critical) X509_EXTENSION_get_critical;
+extern int X509_EXTENSION_get_critical(X509_EXTENSION *);
+%rename(x509_extension_set_critical) X509_EXTENSION_set_critical;
+extern int X509_EXTENSION_set_critical(X509_EXTENSION *, int);
+
+
+%constant int NID_commonName = 13;
+%constant int NID_countryName = 14;
+%constant int NID_localityName = 15;
+%constant int NID_stateOrProvinceName = 16;
+%constant int NID_organizationName = 17;
+%constant int NID_organizationalUnitName = 18;
+%constant int NID_serialNumber = 105;
+%constant int NID_surname = 100;
+%constant int NID_givenName = 99;
+%constant int NID_pkcs9_emailAddress = 48;
+
+/* Cribbed from x509_vfy.h. */
+%constant int X509_V_OK = 0;
+%constant int X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT = 2;
+%constant int X509_V_ERR_UNABLE_TO_GET_CRL = 3;
+%constant int X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE = 4;
+%constant int X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE = 5;
+%constant int X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY = 6;
+%constant int X509_V_ERR_CERT_SIGNATURE_FAILURE = 7;
+%constant int X509_V_ERR_CRL_SIGNATURE_FAILURE = 8;
+%constant int X509_V_ERR_CERT_NOT_YET_VALID = 9;
+%constant int X509_V_ERR_CERT_HAS_EXPIRED = 10;
+%constant int X509_V_ERR_CRL_NOT_YET_VALID = 11;
+%constant int X509_V_ERR_CRL_HAS_EXPIRED = 12;
+%constant int X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD = 13;
+%constant int X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD = 14;
+%constant int X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD = 15;
+%constant int X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD = 16;
+%constant int X509_V_ERR_OUT_OF_MEM = 17;
+%constant int X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT = 18;
+%constant int X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN = 19;
+%constant int X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY = 20;
+%constant int X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE = 21;
+%constant int X509_V_ERR_CERT_CHAIN_TOO_LONG = 22;
+%constant int X509_V_ERR_CERT_REVOKED = 23;
+%constant int X509_V_ERR_INVALID_CA = 24;
+%constant int X509_V_ERR_PATH_LENGTH_EXCEEDED = 25;
+%constant int X509_V_ERR_INVALID_PURPOSE = 26;
+%constant int X509_V_ERR_CERT_UNTRUSTED = 27;
+%constant int X509_V_ERR_CERT_REJECTED = 28;
+%constant int X509_V_ERR_APPLICATION_VERIFICATION = 50;
+
+/* x509.h */
+%constant int XN_FLAG_COMPAT = 0;
+%constant int XN_FLAG_SEP_COMMA_PLUS = (1 << 16);
+%constant int XN_FLAG_SEP_CPLUS_SPC = (2 << 16);
+%constant int XN_FLAG_SEP_MULTILINE = (4 << 16);
+%constant int XN_FLAG_DN_REV = (1 << 20);
+%constant int XN_FLAG_FN_LN = (1 << 21);
+%constant int XN_FLAG_SPC_EQ = (1 << 23);
+%constant int XN_FLAG_DUMP_UNKNOWN_FIELDS = (1 << 24);
+%constant int XN_FLAG_FN_ALIGN = (1 << 25);
+%constant int XN_FLAG_ONELINE =(ASN1_STRFLGS_RFC2253 | \
+ ASN1_STRFLGS_ESC_QUOTE | \
+ XN_FLAG_SEP_CPLUS_SPC | \
+ XN_FLAG_SPC_EQ);
+%constant int XN_FLAG_MULTILINE = (ASN1_STRFLGS_ESC_CTRL | \
+ ASN1_STRFLGS_ESC_MSB | \
+ XN_FLAG_SEP_MULTILINE | \
+ XN_FLAG_SPC_EQ | \
+ XN_FLAG_FN_LN | \
+ XN_FLAG_FN_ALIGN);
+%constant int XN_FLAG_RFC2253 = (ASN1_STRFLGS_RFC2253 | \
+ XN_FLAG_SEP_COMMA_PLUS | \
+ XN_FLAG_DN_REV | \
+ XN_FLAG_DUMP_UNKNOWN_FIELDS);
+
+/* Cribbed from rsa.h. */
+%constant int RSA_3 = 0x3L;
+%constant int RSA_F4 = 0x10001L;
+
+%inline %{
+static PyObject *_x509_err;
+
+void x509_init(PyObject *x509_err) {
+ Py_INCREF(x509_err);
+ _x509_err = x509_err;
+}
+%}
+
+%threadallow x509_read_pem;
+%inline %{
+X509 *x509_read_pem(BIO *bio) {
+ return PEM_read_bio_X509(bio, NULL, NULL, NULL);
+}
+%}
+
+%threadallow d2i_x509;
+%inline %{
+X509 *d2i_x509(BIO *bio) {
+ return d2i_X509_bio(bio, NULL);
+}
+%}
+
+%threadallow d2i_x509_req;
+%inline %{
+X509_REQ *d2i_x509_req(BIO *bio) {
+ return d2i_X509_REQ_bio(bio, NULL);
+}
+
+PyObject *i2d_x509(X509 *x)
+{
+ int len;
+ PyObject *ret = NULL;
+ unsigned char *buf = NULL;
+ len = i2d_X509(x, &buf);
+ if (len < 0) {
+ PyErr_SetString(_x509_err, ERR_reason_error_string(ERR_get_error()));
+ }
+ else {
+ ret = PyString_FromStringAndSize((char*)buf, len);
+ OPENSSL_free(buf);
+ }
+ return ret;
+}
+%}
+
+%threadallow x509_req_read_pem;
+%inline %{
+X509_REQ *x509_req_read_pem(BIO *bio) {
+ return PEM_read_bio_X509_REQ(bio, NULL, NULL, NULL);
+}
+%}
+
+%threadallow x509_req_write_pem;
+%inline %{
+int x509_req_write_pem(BIO *bio, X509_REQ *x) {
+ return PEM_write_bio_X509_REQ(bio, x);
+}
+%}
+
+%threadallow x509_crl_read_pem;
+%inline %{
+X509_CRL *x509_crl_read_pem(BIO *bio) {
+ return PEM_read_bio_X509_CRL(bio, NULL, NULL, NULL);
+}
+
+/* X509_set_version() is a macro. */
+int x509_set_version(X509 *x, long version) {
+ return X509_set_version(x, version);
+}
+
+/* X509_get_version() is a macro. */
+long x509_get_version(X509 *x) {
+ return X509_get_version(x);
+}
+
+/* X509_set_notBefore() is a macro. */
+int x509_set_not_before(X509 *x, ASN1_UTCTIME *tm) {
+ return X509_set_notBefore(x, tm);
+}
+
+/* X509_get_notBefore() is a macro. */
+ASN1_UTCTIME *x509_get_not_before(X509 *x) {
+ return X509_get_notBefore(x);
+}
+
+/* X509_set_notAfter() is a macro. */
+int x509_set_not_after(X509 *x, ASN1_UTCTIME *tm) {
+ return X509_set_notAfter(x, tm);
+}
+
+/* X509_get_notAfter() is a macro. */
+ASN1_UTCTIME *x509_get_not_after(X509 *x) {
+ return X509_get_notAfter(x);
+}
+
+int x509_sign(X509 *x, EVP_PKEY *pkey, EVP_MD *md) {
+ return X509_sign(x, pkey, md);
+}
+
+/* XXX The first parameter is really ASN1_TIME, does it matter? */
+ASN1_TIME *x509_gmtime_adj(ASN1_UTCTIME *s, long adj) {
+ return X509_gmtime_adj(s, adj);
+}
+
+PyObject *x509_name_by_nid(X509_NAME *name, int nid) {
+ void *buf;
+ int len, xlen;
+ PyObject *ret;
+
+ if ((len = X509_NAME_get_text_by_NID(name, nid, NULL, 0)) == -1) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ len++;
+ if (!(buf = PyMem_Malloc(len))) {
+ PyErr_SetString(PyExc_MemoryError, "x509_name_by_nid");
+ return NULL;
+ }
+ xlen = X509_NAME_get_text_by_NID(name, nid, buf, len);
+ ret = PyString_FromStringAndSize(buf, xlen);
+ PyMem_Free(buf);
+ return ret;
+}
+
+int x509_name_set_by_nid(X509_NAME *name, int nid, PyObject *obj) {
+ return X509_NAME_add_entry_by_NID(name, nid, MBSTRING_ASC, (unsigned char *)PyString_AsString(obj), -1, -1, 0);
+}
+
+/* x509_name_add_entry_by_txt */
+int x509_name_add_entry_by_txt(X509_NAME *name, char *field, int type, char *bytes, int len, int loc, int set) {
+ return X509_NAME_add_entry_by_txt(name, field, type, (unsigned char *)bytes, len, loc, set);
+}
+
+PyObject *x509_name_get_der(X509_NAME *name)
+{
+ i2d_X509_NAME(name, 0);
+ return PyString_FromStringAndSize(name->bytes->data, name->bytes->length);
+}
+
+/* sk_X509_new_null() is a macro returning "STACK_OF(X509) *". */
+STACK_OF(X509) *sk_x509_new_null(void) {
+ return sk_X509_new_null();
+}
+
+/* sk_X509_free() is a macro. */
+void sk_x509_free(STACK_OF(X509) *stack) {
+ sk_X509_free(stack);
+}
+
+/* sk_X509_push() is a macro. */
+int sk_x509_push(STACK_OF(X509) *stack, X509 *x509) {
+ return sk_X509_push(stack, x509);
+}
+
+/* sk_X509_pop() is a macro. */
+X509 *sk_x509_pop(STACK_OF(X509) *stack) {
+ return sk_X509_pop(stack);
+}
+
+int x509_store_load_locations(X509_STORE *store, const char *file) {
+ return X509_STORE_load_locations(store, file, NULL);
+}
+
+int x509_type_check(X509 *x509) {
+ return 1;
+}
+
+int x509_name_type_check(X509_NAME *name) {
+ return 1;
+}
+
+X509_NAME *x509_req_get_subject_name(X509_REQ *x) {
+ return X509_REQ_get_subject_name(x);
+}
+
+long x509_req_get_version(X509_REQ *x) {
+ return X509_REQ_get_version(x);
+}
+
+int x509_req_set_version(X509_REQ *x, long version) {
+ return X509_REQ_set_version(x, version);
+}
+
+int x509_req_add_extensions(X509_REQ *req, STACK_OF(X509_EXTENSION) *exts) {
+ return X509_REQ_add_extensions(req, exts);
+}
+
+X509_NAME_ENTRY *x509_name_entry_create_by_txt(X509_NAME_ENTRY **ne, char *field, int type, char *bytes, int len) {
+ return X509_NAME_ENTRY_create_by_txt( ne, field, type, (unsigned char *)bytes, len);
+}
+
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+LHASH_OF(CONF_VALUE)
+#else
+LHASH
+#endif
+*x509v3_lhash() {
+ return lh_new(NULL, NULL); /* Should probably be lh_CONF_VALUE_new but won't compile. */
+}
+
+X509V3_CTX *
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+x509v3_set_conf_lhash(LHASH_OF(CONF_VALUE) * lhash) {
+#else
+x509v3_set_conf_lhash(LHASH * lhash) {
+#endif
+ X509V3_CTX * ctx;
+ if (!(ctx=(X509V3_CTX *)PyMem_Malloc(sizeof(X509V3_CTX)))) {
+ PyErr_SetString(PyExc_MemoryError, "x509v3_set_conf_lhash");
+ return NULL;
+ }
+ X509V3_set_conf_lhash(ctx, lhash);
+ return ctx;
+}
+
+X509_EXTENSION *
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+x509v3_ext_conf(LHASH_OF(CONF_VALUE) *conf, X509V3_CTX *ctx, char *name, char *value) {
+#else
+x509v3_ext_conf(LHASH *conf, X509V3_CTX *ctx, char *name, char *value) {
+#endif
+ X509_EXTENSION * ext = NULL;
+ ext = X509V3_EXT_conf(conf, ctx, name, value);
+ PyMem_Free(ctx);
+#if OPENSSL_VERSION_NUMBER >= 0x10000000L
+ lh_CONF_VALUE_free(conf);
+#else
+ lh_free(conf);
+#endif
+ return ext;
+}
+
+/* X509_EXTENSION_free() might be a macro, didn't find definition. */
+void x509_extension_free(X509_EXTENSION *ext) {
+ X509_EXTENSION_free(ext);
+}
+
+PyObject *x509_extension_get_name(X509_EXTENSION *ext) {
+ PyObject * ext_name;
+ const char * ext_name_str;
+ ext_name_str = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext)));
+ if (!ext_name_str) {
+ PyErr_SetString(_x509_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ ext_name = PyString_FromStringAndSize(ext_name_str, strlen(ext_name_str));
+ return ext_name;
+}
+
+/* sk_X509_EXTENSION_new_null is a macro. */
+STACK_OF(X509_EXTENSION) *sk_x509_extension_new_null(void) {
+ return sk_X509_EXTENSION_new_null();
+}
+
+/* sk_X509_EXTENSION_free() is a macro. */
+void sk_x509_extension_free(STACK_OF(X509_EXTENSION) *stack) {
+ sk_X509_EXTENSION_free(stack);
+}
+
+/* sk_X509_EXTENSION_push() is a macro. */
+int sk_x509_extension_push(STACK_OF(X509_EXTENSION) *stack, X509_EXTENSION *x509_ext) {
+ return sk_X509_EXTENSION_push(stack, x509_ext);
+}
+
+/* sk_X509_EXTENSION_pop() is a macro. */
+X509_EXTENSION *sk_x509_extension_pop(STACK_OF(X509_EXTENSION) *stack) {
+ return sk_X509_EXTENSION_pop(stack);
+}
+
+/* sk_X509_EXTENSION_num() is a macro. */
+int sk_x509_extension_num(STACK_OF(X509_EXTENSION) *stack) {
+ return sk_X509_EXTENSION_num(stack);
+}
+
+/* sk_X509_EXTENSION_value() is a macro. */
+X509_EXTENSION *sk_x509_extension_value(STACK_OF(X509_EXTENSION) *stack, int i) {
+ return sk_X509_EXTENSION_value(stack, i);
+}
+
+/* X509_STORE_CTX_get_app_data is a macro. */
+void *x509_store_ctx_get_app_data(X509_STORE_CTX *ctx) {
+ return X509_STORE_CTX_get_app_data(ctx);
+}
+
+/*#defines for i2d and d2i types, which are typed differently
+in openssl-0.9.8 than they are in openssl-0.9.7. This will
+be picked up by the C preprocessor, not the SWIG preprocessor.
+Used in the wrapping of ASN1_seq_unpack and ASN1_seq_pack functions.
+*/
+#if OPENSSL_VERSION_NUMBER >= 0x0090800fL
+#define D2ITYPE d2i_of_void *
+#define I2DTYPE i2d_of_void *
+#else
+#define D2ITYPE char *(*)()
+#define I2DTYPE int (*)()
+#endif
+
+STACK_OF(X509) *
+make_stack_from_der_sequence(PyObject * pyEncodedString){
+ STACK_OF(X509) *certs;
+ Py_ssize_t encoded_string_len;
+ char *encoded_string;
+
+ encoded_string_len = PyString_Size(pyEncodedString);
+ if (encoded_string_len > INT_MAX) {
+ PyErr_SetString(PyExc_ValueError, "object too large");
+ return NULL;
+ }
+ encoded_string = PyString_AsString(pyEncodedString);
+ if (!encoded_string) {
+ return NULL;
+ }
+
+ certs = ASN1_seq_unpack_X509((unsigned char *)encoded_string, encoded_string_len, d2i_X509, X509_free );
+ if (!certs) {
+ PyErr_SetString(_x509_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+
+ return certs;
+}
+
+PyObject *
+get_der_encoding_stack(STACK_OF(X509) *stack){
+ PyObject * encodedString;
+
+ unsigned char * encoding;
+ int len;
+
+ encoding = ASN1_seq_pack_X509(stack, i2d_X509, NULL, &len);
+ if (!encoding) {
+ PyErr_SetString(_x509_err, ERR_reason_error_string(ERR_get_error()));
+ return NULL;
+ }
+ encodedString = PyString_FromStringAndSize((const char *)encoding, len);
+ OPENSSL_free(encoding);
+ return encodedString;
+}
+
+%}
+
+/* Free malloc'ed return value for x509_name_oneline */
+%typemap(ret) char * {
+ if ($1 != NULL)
+ OPENSSL_free($1);
+}
+%inline %{
+char *x509_name_oneline(X509_NAME *x) {
+ return X509_NAME_oneline(x, NULL, 0);
+}
+%}
+%typemap(ret) char *;
diff --git a/contrib/README b/contrib/README
new file mode 100644
index 0000000..26483f1
--- /dev/null
+++ b/contrib/README
@@ -0,0 +1,6 @@
+This directory contains contributions by users of M2Crypto. Some of these
+may get folded into the main distribution in time.
+
+Thanks guys!
+
+
diff --git a/contrib/SimpleX509create.README b/contrib/SimpleX509create.README
new file mode 100644
index 0000000..a08db85
--- /dev/null
+++ b/contrib/SimpleX509create.README
@@ -0,0 +1,3 @@
+Contributed by Peter Teniz <peter.teniz@inverisa.net> as a demonstration of
+PKI functionality, also contributed by him.
+
diff --git a/contrib/SimpleX509create.py b/contrib/SimpleX509create.py
new file mode 100644
index 0000000..7f5fc67
--- /dev/null
+++ b/contrib/SimpleX509create.py
@@ -0,0 +1,162 @@
+#!/usr/bin/env python
+#
+#vim: ts=4 sw=4 nowrap
+#
+
+"""PKI demo by Peter Teniz <peter.teniz@inverisa.net>"""
+
+import M2Crypto
+
+
+MBSTRING_FLAG = 0x1000
+MBSTRING_ASC = MBSTRING_FLAG | 1
+MBSTRING_BMP = MBSTRING_FLAG | 2
+
+
+class Cert:
+ def __init__ ( self ):
+ self.RsaKey = { 'KeyLength' : 1024,
+ 'PubExponent' : 0x10001, # -> 65537
+ 'keygen_callback' : self.callback
+ }
+
+ self.KeyPair = None
+ self.PKey = None
+
+ self.X509Request = None
+ self.X509Certificate = None
+
+ def callback ( self, *args ):
+ return 'p'
+
+
+
+ def CreatePKey ( self ):
+ self.KeyPair = M2Crypto.RSA.gen_key( self.RsaKey['KeyLength'], self.RsaKey['PubExponent'], self.RsaKey['keygen_callback'] )
+ #PubKey = M2Crypto.RSA.new_pub_key( self.KeyPair.pub () )
+
+ self.KeyPair.save_key( 'KeyPair.pem', cipher='des_ede3_cbc', callback=self.callback )
+
+ self.PKey = M2Crypto.EVP.PKey ( md='sha1')
+ self.PKey.assign_rsa ( self.KeyPair )
+
+
+ def CreateX509Request ( self ):
+ #
+ # X509 REQUEST
+ #
+
+ self.X509Request = M2Crypto.X509.Request ()
+
+ #
+ # subject
+ #
+
+ X509Name = M2Crypto.X509.X509_Name ()
+
+ X509Name.add_entry_by_txt ( field='C', type=MBSTRING_ASC, entry='austria', len=-1, loc=-1, set=0 ) # country name
+ X509Name.add_entry_by_txt ( field='SP', type=MBSTRING_ASC, entry='kernten', len=-1, loc=-1, set=0 ) # state of province name
+ X509Name.add_entry_by_txt ( field='L', type=MBSTRING_ASC, entry='stgallen', len=-1, loc=-1, set=0 ) # locality name
+ X509Name.add_entry_by_txt ( field='O', type=MBSTRING_ASC, entry='labor', len=-1, loc=-1, set=0 ) # organization name
+ X509Name.add_entry_by_txt ( field='OU', type=MBSTRING_ASC, entry='it-department', len=-1, loc=-1, set=0 ) # organizational unit name
+ X509Name.add_entry_by_txt ( field='CN', type=MBSTRING_ASC, entry='Certificate client', len=-1, loc=-1, set=0 ) # common name
+ X509Name.add_entry_by_txt ( field='Email', type=MBSTRING_ASC, entry='user@localhost', len=-1, loc=-1, set=0 ) # pkcs9 email address
+ X509Name.add_entry_by_txt ( field='emailAddress', type=MBSTRING_ASC, entry='user@localhost', len=-1, loc=-1, set=0 ) # pkcs9 email address
+
+ self.X509Request.set_subject_name( X509Name )
+
+ #
+ # publickey
+ #
+
+ self.X509Request.set_pubkey ( pkey=self.PKey )
+ self.X509Request.sign ( pkey=self.PKey, md='sha1' )
+ #print X509Request.as_text ()
+
+
+
+
+
+
+ def CreateX509Certificate ( self ):
+ #
+ # X509 CERTIFICATE
+ #
+
+ self.X509Certificate = M2Crypto.X509.X509 ()
+
+ #
+ # version
+ #
+
+ self.X509Certificate.set_version ( 0 )
+
+ #
+ # time notBefore
+ #
+
+ ASN1 = M2Crypto.ASN1.ASN1_UTCTIME ()
+ ASN1.set_time ( 500 )
+ self.X509Certificate.set_not_before( ASN1 )
+
+ #
+ # time notAfter
+ #
+
+ ASN1 = M2Crypto.ASN1.ASN1_UTCTIME ()
+ ASN1.set_time ( 500 )
+ self.X509Certificate.set_not_after( ASN1 )
+
+ #
+ # public key
+ #
+
+ self.X509Certificate.set_pubkey ( pkey=self.PKey )
+
+ #
+ # subject
+ #
+
+ X509Name = self.X509Request.get_subject ()
+
+ #print X509Name.entry_count ()
+ #print X509Name.as_text ()
+
+ self.X509Certificate.set_subject_name( X509Name )
+
+ #
+ # issuer
+ #
+
+ X509Name = M2Crypto.X509.X509_Name ( M2Crypto.m2.x509_name_new () )
+
+ X509Name.add_entry_by_txt ( field='C', type=MBSTRING_ASC, entry='germany', len=-1, loc=-1, set=0 ) # country name
+ X509Name.add_entry_by_txt ( field='SP', type=MBSTRING_ASC, entry='bavaria', len=-1, loc=-1, set=0 ) # state of province name
+ X509Name.add_entry_by_txt ( field='L', type=MBSTRING_ASC, entry='munich', len=-1, loc=-1, set=0 ) # locality name
+ X509Name.add_entry_by_txt ( field='O', type=MBSTRING_ASC, entry='sbs', len=-1, loc=-1, set=0 ) # organization name
+ X509Name.add_entry_by_txt ( field='OU', type=MBSTRING_ASC, entry='it-department', len=-1, loc=-1, set=0 ) # organizational unit name
+ X509Name.add_entry_by_txt ( field='CN', type=MBSTRING_ASC, entry='Certificate Authority', len=-1, loc=-1, set=0 ) # common name
+ X509Name.add_entry_by_txt ( field='Email', type=MBSTRING_ASC, entry='admin@localhost', len=-1, loc=-1, set=0 ) # pkcs9 email address
+ X509Name.add_entry_by_txt ( field='emailAddress', type=MBSTRING_ASC, entry='admin@localhost', len=-1, loc=-1, set=0 ) # pkcs9 email address
+
+ #print X509Name.entry_count ()
+ #print X509Name.as_text ()
+
+ self.X509Certificate.set_issuer_name( X509Name )
+
+ #
+ # signing
+ #
+
+ self.X509Certificate.sign( pkey=self.PKey, md='sha1' )
+ print self.X509Certificate.as_text ()
+
+
+
+
+
+if __name__ == '__main__':
+ run = Cert ()
+ run.CreatePKey ()
+ run.CreateX509Request ()
+ run.CreateX509Certificate ()
diff --git a/contrib/dave.README b/contrib/dave.README
new file mode 100644
index 0000000..1a3339a
--- /dev/null
+++ b/contrib/dave.README
@@ -0,0 +1,64 @@
+From dave@pythonapocrypha.com Wed Dec 11 07:57:55 2002
+Date: Tue, 10 Dec 2002 15:05:26 -0800 (PST)
+From: Dave Brueck <dave@pythonapocrypha.com>
+To: ngps@netmemetic.com
+Subject: M2Crypto problem with asynch sockets
+
+Hi and thanks for M2Crypto - great stuff!
+
+I wrote an asynchronous socket layer and decided to use M2Crypto to add
+SSL support to it. Unfortunately, I've found a small problem in
+_m2crypto_wrap.c - hopefully I'm just not understanding something.
+
+The ssl_connect, ssl_read_nbio, etc. calls don't differentiate between
+SSL_ERROR_WANT_WRITE and SSL_ERROR_WANT_READ when a non-blocking call
+couldn't finish. But without this information, I don't know whether the
+socket needs to do more reading or more writing before a subsequent
+attempt will work without blocking. The demo applications (e.g.
+echod-async.py) don't seem to care about this but they get around it by
+simply trying the operation over and over again, which I can't do for
+performance reasons.
+
+Am I missing something? I thought about just calling SSL_get_error when
+the above calls return None (indicating WANT_READ or WANT_WRITE), but by
+then the error has already been removed from the SSL error queue.
+
+Any help or suggestions would be appreciated. I'd be happy to submit a
+patch fixing those calls, but by not returning None they would break
+existing code.
+
+Thanks again for M2Crypto though!
+
+-Dave
+
+
+From dave@pythonapocrypha.com Fri Dec 13 00:46:39 2002
+Date: Thu, 12 Dec 2002 09:52:08 -0800 (PST)
+From: Dave Brueck <dave@pythonapocrypha.com>
+To: ngps@netmemetic.com
+Subject: Re: M2Crypto problem with asynch sockets
+
+Hello again,
+
+Here is a patch to M2Crypto's _ssl.i that illustrates the fix I had in
+mind in my previous message. You might not want to use it as is since it
+changes the error semantics of the affected functions (they now raise an
+exception that contains the SSL_WANT_READ or SSL_WANT_WRITE flag instead
+of returning None or whatever), but if you tell me how you'd like it
+instead then I'd be happy to fix the patch and send it back to you.
+
+Just to refresh your memory, this patch fixes the problem where a
+non-blocking call to accept/connect/etc results in an SSL_NEED_READ/WRITE;
+currently there's no way for the caller to know _which_ of the two
+occurred and so it must try again once the socket has become readable OR
+writeable, instead of waiting specifically for one or the other. For many
+people this won't matter because their performance requirements are low
+enough that trying the ssl_accept/etc call again prematurely won't hurt
+too much, but for servers with lots of connections or high throughput it's
+much more critical to wait until you know the SSL call has a better chance
+of success.
+
+Thanks!
+-Dave Brueck
+
+
diff --git a/contrib/dave.patch b/contrib/dave.patch
new file mode 100644
index 0000000..6e41073
--- /dev/null
+++ b/contrib/dave.patch
@@ -0,0 +1,180 @@
+--- _ssl.i.orig Tue Sep 18 06:06:34 2001
++++ _ssl.i Thu Dec 12 08:57:39 2002
+@@ -238,9 +238,18 @@
+ return ret;
+ }
+
++void SetWantRWError(int which) {
++ PyObject *o = Py_BuildValue("(is)", which,
++
+ERR_reason_error_string(ERR_get_error()));
++ if (o != NULL) {
++ PyErr_SetObject(_ssl_err, o);
++ Py_DECREF(o);
++ }
++}
++
+ PyObject *ssl_accept(SSL *ssl) {
+ PyObject *obj;
+- int r, err;
++ int r, err, which;
+ PyThreadState *_save;
+
+ if (thread_mode) {
+@@ -252,14 +261,15 @@
+ _save = (PyThreadState *)SSL_get_app_data(ssl);
+ PyEval_RestoreThread(_save);
+ }
+- switch (SSL_get_error(ssl, r)) {
++ switch ((which = SSL_get_error(ssl, r))) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ obj = PyInt_FromLong((long)1);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+- obj = PyInt_FromLong((long)0);
++ SetWantRWError(which);
++ obj = NULL;
+ break;
+ case SSL_ERROR_SSL:
+ PyErr_SetString(_ssl_err,
+ERR_reason_error_string(ERR_get_error()));
+@@ -281,7 +291,7 @@
+
+ PyObject *ssl_connect(SSL *ssl) {
+ PyObject *obj;
+- int r, err;
++ int r, err, which;
+ PyThreadState *_save;
+
+ if (thread_mode) {
+@@ -293,14 +303,15 @@
+ _save = (PyThreadState *)SSL_get_app_data(ssl);
+ PyEval_RestoreThread(_save);
+ }
+- switch (SSL_get_error(ssl, r)) {
++ switch ((which = SSL_get_error(ssl, r))) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ obj = PyInt_FromLong((long)1);
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+- obj = PyInt_FromLong((long)0);
++ SetWantRWError(which);
++ obj = NULL;
+ break;
+ case SSL_ERROR_SSL:
+ PyErr_SetString(_ssl_err,
+ERR_reason_error_string(ERR_get_error()));
+@@ -327,7 +338,7 @@
+ PyObject *ssl_read(SSL *ssl, int num) {
+ PyObject *obj;
+ void *buf;
+- int r, err;
++ int r, err, which;
+ PyThreadState *_save;
+
+ if (!(buf = PyMem_Malloc(num))) {
+@@ -343,7 +354,7 @@
+ _save = (PyThreadState *)SSL_get_app_data(ssl);
+ PyEval_RestoreThread(_save);
+ }
+- switch (SSL_get_error(ssl, r)) {
++ switch ((which = SSL_get_error(ssl, r))) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ buf = PyMem_Realloc(buf, r);
+@@ -352,8 +363,8 @@
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
+ case SSL_ERROR_WANT_X509_LOOKUP:
+- Py_INCREF(Py_None);
+- obj = Py_None;
++ SetWantRWError(which);
++ obj = NULL;
+ break;
+ case SSL_ERROR_SSL:
+ PyErr_SetString(_ssl_err,
+ERR_reason_error_string(ERR_get_error()));
+@@ -377,14 +388,14 @@
+ PyObject *ssl_read_nbio(SSL *ssl, int num) {
+ PyObject *obj;
+ void *buf;
+- int r, err;
++ int r, err, which;
+
+ if (!(buf = PyMem_Malloc(num))) {
+ PyErr_SetString(PyExc_MemoryError, "ssl_read");
+ return NULL;
+ }
+ r = SSL_read(ssl, buf, num);
+- switch (SSL_get_error(ssl, r)) {
++ switch ((which = SSL_get_error(ssl, r))) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ buf = PyMem_Realloc(buf, r);
+@@ -392,6 +403,9 @@
+ break;
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
++ SetWantRWError(which);
++ obj = NULL;
++ break;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ Py_INCREF(Py_None);
+ obj = Py_None;
+@@ -417,7 +431,7 @@
+
+ int ssl_write(SSL *ssl, PyObject *blob) {
+ const void *buf;
+- int len, r, err;
++ int len, r, err, which;
+ PyThreadState *_save;
+
+ #if PYTHON_API_VERSION >= 1009
+@@ -440,12 +454,14 @@
+ _save = (PyThreadState *)SSL_get_app_data(ssl);
+ PyEval_RestoreThread(_save);
+ }
+- switch (SSL_get_error(ssl, r)) {
++ switch ((which = SSL_get_error(ssl, r))) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ return r;
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
++ SetWantRWError(which);
++ return -1;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ return -1;
+ case SSL_ERROR_SSL:
+@@ -466,7 +482,7 @@
+
+ int ssl_write_nbio(SSL *ssl, PyObject *blob) {
+ const void *buf;
+- int len, r, err;
++ int len, r, err, which;
+
+ #if PYTHON_API_VERSION >= 1009
+ if (PyObject_AsReadBuffer(blob, &buf, &len) == -1)
+@@ -480,12 +496,14 @@
+ buf = (const void *)PyString_AsString(blob);
+ #endif
+ r = SSL_write(ssl, buf, len);
+- switch (SSL_get_error(ssl, r)) {
++ switch ((which = SSL_get_error(ssl, r))) {
+ case SSL_ERROR_NONE:
+ case SSL_ERROR_ZERO_RETURN:
+ return r;
+ case SSL_ERROR_WANT_WRITE:
+ case SSL_ERROR_WANT_READ:
++ SetWantRWError(which);
++ return -1;
+ case SSL_ERROR_WANT_X509_LOOKUP:
+ return -1;
+ case SSL_ERROR_SSL:
+
+
+
diff --git a/contrib/dispatcher.README b/contrib/dispatcher.README
new file mode 100644
index 0000000..3049a5e
--- /dev/null
+++ b/contrib/dispatcher.README
@@ -0,0 +1,30 @@
+Date: Thu, 31 May 2001 17:11:45 +0400 (MSD)
+From: Ilya Etingof <ilya@glas.net>
+To: ngps@post1.com
+Cc: Ilya Etingof <ilya@glas.net>
+Subject: Another kind of non-blocking SSL dispatcher
+
+--1922505501-409592217-991314705=:1995
+Content-Type: TEXT/PLAIN; charset=US-ASCII
+
+
+Hi,
+
+Thanks for writing M2Crypto!
+
+I've been trying to use the ssl_dispatcher.py though I felt like the
+bundled version is not absolutely non-blocking. Precisely, it looks
+like the Connection.connect() method does not handle the case when
+socket.connect() returns the WOULDBLOCK error. Another suspicious thing
+is that there seems to be no SSL "want read" and "want write" error
+return codes of SSL read and write functions.
+
+The attached [quick and dirty] code hopefully fixes these two
+problems. Please, let me know if I'm missing some important clue about all
+this.
+
+Thanks,
+ilya
+
+--1922505501-409592217-991314705=:1995
+
diff --git a/contrib/dispatcher.py b/contrib/dispatcher.py
new file mode 100644
index 0000000..6e7302d
--- /dev/null
+++ b/contrib/dispatcher.py
@@ -0,0 +1,191 @@
+#!/usr/local/bin/python -O
+"""
+ Implements a [hopefully] non-blocking SSL dispatcher on top of
+ M2Crypto package.
+
+ Written by Ilya Etingof <ilya@glas.net>, 05/2001
+"""
+import asyncore, socket
+
+# M2Crypto
+from M2Crypto import SSL
+
+class _nb_connection (SSL.Connection):
+ """Functional equivalent of SSL.Connection class. Facilitates
+ possibly delayed socket.connect() and socket.accept()
+ termination.
+ """
+ def __init__ (self, ctx, sock):
+ SSL.Connection.__init__ (self, ctx, sock)
+
+ def connect(self, addr):
+ self._setup_ssl(addr)
+ return self._check_ssl_return(SSL.m2.ssl_connect(self.ssl))
+
+ def accept(self, addr):
+ self._setup_ssl(addr)
+ self.accept_ssl()
+
+class dispatcher(asyncore.dispatcher_with_send):
+ """A non-blocking SSL dispatcher that mimics the
+ asyncode.dispatcher API.
+ """
+ def __init__ (self, cert, key, sock=None, serving=None):
+ asyncore.dispatcher_with_send.__init__ (self)
+
+ self.__serving = serving
+
+ # XXX
+ if sock:
+ if self.__serving:
+ self.set_socket(sock)
+ else:
+ self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+ self.ctx = SSL.Context('sslv23')
+ self.ctx.set_verify(SSL.verify_none, 10)
+ self.ctx.load_cert(cert, key)
+ self.ctx.set_info_callback()
+
+ self.ssl = _nb_connection(self.ctx, self.socket)
+
+ self.__output = ''
+ self.__want_write = 1
+
+ #
+ # The following are asyncore overloaded methods
+ #
+
+ def handle_connect (self):
+ """Initiate SSL connection negotiation
+ """
+ if self.__serving:
+ self.ssl.accept (self.addr)
+
+ self.peer = self.ssl.get_peer_cert()
+
+ self.handle_ssl_accept()
+
+ else:
+ self.ssl.connect (self.addr)
+
+ self.handle_ssl_connect()
+
+ def handle_read(self):
+ """Read user and/or SSL protocol data from SSL connection
+ """
+ ret = self.ssl._read_nbio()
+
+ if ret:
+ self.handle_ssl_read(ret)
+ else:
+ # Assume write is wanted
+ self.__want_write = 1
+
+ def handle_write(self):
+ """Write pending user and/or SSL protocol data down to SSL
+ connection
+ """
+ self.__want_write = 0
+
+ ret = self.ssl._write_nbio(self.__output)
+
+ if ret < 0:
+ try:
+ err = SSL.m2.ssl_get_error(self.ssl.ssl, ret)
+
+ except SSL.SSLError:
+ return
+
+ if err == SSL.m2.ssl_error_want_write:
+ self.__want_write = 1
+ else:
+ self.__output = self.__output[ret:]
+
+ def writable (self):
+ """Indicate that write is desired if here're some
+ user and/or SSL protocol data.
+ """
+ if self.__output or self.__want_write:
+ return 1
+
+ return self.ssl_writable()
+
+ def handle_close (self):
+ """Shutdown SSL connection.
+ """
+ self.ssl = None
+
+ self.ctx = None
+ self.close ()
+
+ self.handle_ssl_close()
+
+ def handle_error (self, *info):
+ """A trap for asyncore errors
+ """
+ self.handle_ssl_error(info)
+
+ #
+ # The following are ssl.dispatcher API
+ #
+
+ def ssl_connect(self, server):
+ """Initiate SSL connection
+ """
+ self.connect(server)
+
+ def ssl_write(self, data):
+ """Write data to SSL connection
+ """
+ self.__output = self.__output + data
+
+ def ssl_close(self):
+ """Close SSL connection
+ """
+ self.handle_close()
+
+ def handle_ssl_connect(self):
+ """Invoked on SSL connection establishment (whilst
+ in client mode)
+ """
+ print 'Unhandled handle_ssl_connect()'
+
+ def handle_ssl_accept(self):
+ """Invoked on SSL connection establishment (whilst
+ in server mode)
+ """
+ print 'Unhandled handle_ssl_accept()'
+
+ def handle_ssl_read(self, data):
+ """Invoked on new data arrival to SSL connection
+ """
+ print 'Unhandled handle_ssl_read event'
+
+ def handle_ssl_close(self):
+ """Invoked on SSL connection termination
+ """
+ pass
+
+ def ssl_writable(self):
+ """Invoked prior to every select() call
+ """
+ return 0
+
+if __name__=='__main__':
+ """Give it a test run
+ """
+ class client(dispatcher):
+ """SSL client class
+ """
+ def __init__ (self, cert, key):
+ dispatcher.__init__(self, cert, key)
+
+ def handle_ssl_read(self, data):
+ print data
+ self.ssl_write('test write')
+
+ ssl = client('test.cert', 'test.key')
+ ssl.ssl_connect(('localhost', 7777))
+
+ asyncore.loop()
diff --git a/contrib/isaac.README b/contrib/isaac.README
new file mode 100644
index 0000000..b47932e
--- /dev/null
+++ b/contrib/isaac.README
@@ -0,0 +1,18 @@
+This is Isaac Salzberg's application of Mihai Ibanescu's patch
+(available on SF) that allows HTTPS tunneling through an
+authenticating proxy.
+
+This one's a double whammy: it works with IIS through the
+authenticating proxy, whereas the one on SF, which uses Python's
+built-in SSL, doesn't.
+
+This code is not folded into the main distribution because:
+
+1. Apparently Mihai is still working on it.
+2. Mihai uses Python's built-in SSL. Isaac patched it to use
+ M2Crypto.SSL. The stuff is essentially #ifdef'ed code.
+3. I don't have an authenticating proxy nor an IIS server to test
+ against, so I can't clean up the code. Volunteers welcome. ;-)
+
+Thanks Isaac.
+
diff --git a/contrib/isaac.httpslib.py b/contrib/isaac.httpslib.py
new file mode 100644
index 0000000..ae9d23d
--- /dev/null
+++ b/contrib/isaac.httpslib.py
@@ -0,0 +1,269 @@
+"""M2Crypto support for Python 1.5.2 and Python 2.x's httplib.
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+import string, sys
+from httplib import *
+import SSL
+
+if sys.version[0] == '2':
+
+ if sys.version[:3] in ['2.1', '2.2']:
+ # In 2.1 and above, httplib exports "HTTP" only.
+ from httplib import HTTPConnection, HTTPS_PORT
+ # ISS Added:
+ from httplib import HTTPResponse,FakeSocket
+
+ class HTTPSConnection(HTTPConnection):
+
+ """
+ This class allows communication via SSL using M2Crypto.
+ """
+
+ default_port = HTTPS_PORT
+
+ def __init__(self, host, port=None, **ssl):
+ keys = ssl.keys()
+ try:
+ keys.remove('key_file')
+ except ValueError:
+ pass
+ try:
+ keys.remove('cert_file')
+ except ValueError:
+ pass
+ try:
+ keys.remove('ssl_context')
+ except ValueError:
+ pass
+ if keys:
+ raise IllegalKeywordArgument()
+ try:
+ self.ssl_ctx = ssl['ssl_context']
+ assert isinstance(self.ssl_ctx, SSL.Context)
+ except KeyError:
+ self.ssl_ctx = SSL.Context('sslv23')
+ HTTPConnection.__init__(self, host, port)
+
+ def connect(self):
+ self.sock = SSL.Connection(self.ssl_ctx)
+ self.sock.connect((self.host, self.port))
+
+ def close(self):
+ # This kludges around line 545 of httplib.py,
+ # which closes the connection in this object;
+ # the connection remains open in the response
+ # object.
+ #
+ # M2Crypto doesn't close-here-keep-open-there,
+ # so, in effect, we don't close until the whole
+ # business is over and gc kicks in.
+ #
+ # Long-running callers beware leakage.
+ #
+ # 05-Jan-2002: This module works with Python 2.2,
+ # but I've not investigated if the above conditions
+ # remain.
+ pass
+
+
+ class HTTPS(HTTP):
+
+ _connection_class = HTTPSConnection
+
+ def __init__(self, host='', port=None, **ssl):
+ HTTP.__init__(self, host, port)
+ try:
+ self.ssl_ctx = ssl['ssl_context']
+ except KeyError:
+ self.ssl_ctx = SSL.Context('sslv23')
+
+
+elif sys.version[:3] == '1.5':
+
+ class HTTPS(HTTP):
+
+ def __init__(self, ssl_context, host='', port=None):
+ assert isinstance(ssl_context, SSL.Context)
+ self.debuglevel=0
+ self.file=None
+ self.ssl_ctx=ssl_context
+ if host:
+ self.connect(host, port)
+
+ def connect(self, host, port=None):
+ # Cribbed from httplib.HTTP.
+ if not port:
+ i = string.find(host, ':')
+ if i >= 0:
+ host, port = host[:i], host[i+1:]
+ try: port = string.atoi(port)
+ except string.atoi_error:
+ raise socket.error, "nonnumeric port"
+ if not port: port = HTTPS_PORT
+ self.sock = SSL.Connection(self.ssl_ctx)
+ if self.debuglevel > 0: print 'connect:', (host, port)
+ self.sock.connect((host, port))
+
+# ISS Added.
+# From here, starts the proxy patch
+class HTTPProxyConnection(HTTPConnection):
+ """
+ This class provides HTTP access through (authenticated) proxies.
+
+ Example:
+ If the HTTP proxy address is proxy.your.org:8080, an authenticated proxy
+ (one which requires a username/password combination in order to serve
+ requests), one can fetch HTTP documents from 'www.webserver.net', port 81:
+
+ conn = HTTPProxyConnection('proxy.your.org:8080', 'www.webserver.net',
+ port=81, username='username', password='password')
+ conn.connect()
+ conn.request("HEAD", "/index.html", headers={'X-Custom-Header-1' : 'Value-1'})
+ resp = conn.getresponse()
+ ...
+
+ """
+ def __init__(self, proxy, host, port=None, username=None, password=None):
+ # The connection goes through the proxy
+ HTTPConnection.__init__(self, proxy)
+ # save the proxy connection settings
+ self.__proxy, self.__proxy_port = self.host, self.port
+ # self.host and self.port will point to the real host
+ self._set_hostport(host, port)
+ # save the host and port
+ self._host, self._port = self.host, self.port
+ # Authenticated proxies support
+ self.__username = username
+ self.__password = password
+
+ def connect(self):
+ """Connect to the host and port specified in __init__ (through a
+ proxy)."""
+ # We are connecting to the proxy, so use the proxy settings
+ self._set_hostport(self.__proxy, self.__proxy_port)
+ HTTPConnection.connect(self)
+ # Restore the real host and port
+ self._set_hostport(self._host, self._port)
+
+ def putrequest(self, method, url):
+ """Send a request to the server.
+
+ `method' specifies an HTTP request method, e.g. 'GET'.
+ `url' specifies the object being requested, e.g. '/index.html'.
+ """
+ # The URL has to include the real host
+ hostname = self._host
+ if self._port != self.default_port:
+ hostname = hostname + ':' + str(self._port)
+ newurl = "http://%s%s" % (hostname, url)
+ # Piggyback on the parent class
+ HTTPConnection.putrequest(self, method, newurl)
+ # Add proxy-specific headers
+ self._add_auth_proxy_header()
+
+ def _add_auth_proxy_header(self):
+ """Adds an HTTP header for authenticated proxies
+ """
+ if not self.__username:
+ # No username, so assume not an authenticated proxy
+ return
+ # Authenticated proxy
+ import base64
+ userpass = "%s:%s" % (self.__username, self.__password)
+ enc_userpass = string.strip(base64.encodestring(userpass))
+ self.putheader("Proxy-Authorization", "Basic %s" % enc_userpass)
+
+class HTTPSProxyResponse(HTTPResponse):
+ """
+ Replacement class for HTTPResponse
+ Proxy responses (made through SSL) have to keep the connection open
+ after the initial request, since the connection is tunneled to the SSL
+ host with the CONNECT method.
+ """
+ def begin(self):
+ HTTPResponse.begin(self)
+ self.will_close = 0
+
+class HTTPSProxyConnection(HTTPProxyConnection):
+ """This class provides HTTP access through (authenticated) proxies.
+
+ Example:
+ If the HTTP proxy address is proxy.your.org:8080, an authenticated proxy
+ (one which requires a username/password combination in order to serve
+ requests), one can fetch HTTP documents from 'www.webserver.net', port 81:
+
+ conn = HTTPProxyConnection('proxy.your.org:8080', 'www.webserver.net',
+ port=81, username='username', password='password')
+ conn.connect()
+ conn.request("HEAD", "/index.html", headers={'X-Custom-Header-1' : 'Value-1'})
+ resp = conn.getresponse()
+ ...
+
+ To avoid dealing with multiple inheritance, this class only inherits from
+ HTTPProxyConnection.
+ """
+ default_port = HTTPSConnection.default_port
+
+ def __init__(self, proxy, host, port=None, username=None, password=None, **x509):
+ for key in x509.keys():
+ if key not in ['cert_file', 'key_file','ssl_context']:
+ raise IllegalKeywordArgument()
+ self.key_file = x509.get('key_file')
+ self.cert_file = x509.get('cert_file')
+ #ISS Added
+ self.ssl_ctx = x509.get('ssl_context')
+ # Piggybacking on HTTPProxyConnection
+ HTTPProxyConnection.__init__(self, proxy, host, port, username, password)
+
+ def connect(self):
+ """Connect (using SSL) to the host and port specified in __init__
+ (through a proxy)."""
+ import socket
+ # Set the connection with the proxy
+ HTTPProxyConnection.connect(self)
+ # Use the stock HTTPConnection putrequest
+ host = "%s:%s" % (self._host, self._port)
+ HTTPConnection.putrequest(self, "CONNECT", host)
+ # Add proxy-specific stuff
+ self._add_auth_proxy_header()
+ # And send the request
+ HTTPConnection.endheaders(self)
+ # Save the response class
+ response_class = self.response_class
+ # And replace the response class with our own one, which does not
+ # close the connection
+ self.response_class = HTTPSProxyResponse
+ response = HTTPConnection.getresponse(self)
+ # Restore the response class
+ self.response_class = response_class
+ # Close the response object manually
+ response.close()
+ if response.status != 200:
+ # Close the connection manually
+ self.close()
+ # XXX Find the appropriate error code
+ raise socket.error(1001, response.status, response.value)
+
+ # NgPS: I haven't read the code recently, but I think it is
+ # reasonable to assume that self.sock is a connected TCP socket at
+ # this point.
+
+ # Use the real stuff. ;-)
+ if self.ssl_ctx and isinstance(self.ssl_ctx, SSL.Context):
+ self.sock = SSL.Connection(self.ssl_ctx)
+ self.sock.connect((self.host, self.port))
+ else:
+ # Fake the socket
+ ssl = socket.ssl(self.sock, self.key_file, self.cert_file)
+ self.sock = FakeSocket(self.sock, ssl)
+ if self.debuglevel > 0: print 'socket type:', self.sock
+
+ def putrequest(self, method, url):
+ """Send a request to the server.
+
+ `method' specifies an HTTP request method, e.g. 'GET'.
+ `url' specifies the object being requested, e.g. '/index.html'.
+ """
+ # bypass the parent class's putrequest: use the grandparent's one :-)
+ return HTTPConnection.putrequest(self, method, url)
diff --git a/contrib/m2crypto.spec b/contrib/m2crypto.spec
new file mode 100644
index 0000000..f80cddb
--- /dev/null
+++ b/contrib/m2crypto.spec
@@ -0,0 +1,46 @@
+%define name m2crypto
+%define version 0.06
+%define snap snap5
+%define release %{snap}.1
+%define prefix %{_prefix}
+
+Summary: Python crypto library
+Name: %{name}
+Version: %{version}
+Release: %{release}
+Copyright: tummy.com, ltd.
+Group: Applications/Crypto
+Source: %{name}-%{version}-%{snap}.zip
+Packager: Sean Reifschneider <jafo-rpms@tummy.com>
+BuildRoot: /var/tmp/%{name}-root
+Requires: openssl >= 0.9.6a
+Patch0: m2crypto-makefile.patch
+BuildPrereq: openssl-devel >= 0.9.6a
+BuildPrereq: swig >= 1.1p5
+
+%description
+M2Crypto makes available to the Python programmer the following:
+
+ RSA, DH, DSA, HMACs, message digests, symmetric ciphers.
+ SSL functionality to implement clients and servers.
+ HTTPS extensions to Python's httplib, urllib, and the eff-bot's xmlrpclib.
+ S/MIME v2.
+
+%prep
+%setup -n %{name}-%{version}-%{snap}
+%patch0 -p1
+%build
+( cd swig; make -f Makefile.py1 )
+
+%install
+[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"
+mkdir -p "$RPM_BUILD_ROOT"/usr/lib/python1.5/site-packages
+cp -a M2Crypto "$RPM_BUILD_ROOT"/usr/lib/python1.5/site-packages
+
+%clean
+[ -n "$RPM_BUILD_ROOT" -a "$RPM_BUILD_ROOT" != / ] && rm -rf "$RPM_BUILD_ROOT"
+
+%files
+%defattr(755,root,root)
+%doc BUGS CHANGES INSTALL LICENCE README STORIES doc demo tests patches
+/usr/lib/python1.5/site-packages
diff --git a/contrib/smimeplus.README b/contrib/smimeplus.README
new file mode 100644
index 0000000..2125b9f
--- /dev/null
+++ b/contrib/smimeplus.README
@@ -0,0 +1,3 @@
+Contributed by Bernard Yue: A high level smime interface. "It works
+for Python 2.2 and above, and requires the email module from Python
+2.2.3 to work on Python 2.1.
diff --git a/contrib/smimeplus.py b/contrib/smimeplus.py
new file mode 100644
index 0000000..5a9c22d
--- /dev/null
+++ b/contrib/smimeplus.py
@@ -0,0 +1,187 @@
+import sys, os, tempfile
+import UserDict
+from email import Message
+import M2Crypto
+
+if not (sys.version_info[0] >= 2 and sys.version_info[1] >= 2):
+ class object:
+ pass
+ True=1
+ False=0
+
+
+class smimeplus(object):
+ def __init__(self, cert, privkey, passphrase, cacert, randfile=None):
+ self.cipher = 'des_ede3_cbc' # XXX make it configable??
+ self.setsender(cert, privkey, passphrase)
+ self.setcacert(cacert)
+ self.randfile = randfile
+ self.__loadrand()
+
+ def __passcallback(self, v):
+ """private key passphrase callback function"""
+ return self.passphrase
+
+ def __loadrand(self):
+ """Load random number file"""
+ if self.randfile:
+ M2Crypto.Rand.load_file(self.randfile, -1)
+
+ def __saverand(self):
+ """Save random number file"""
+ if self.randfile:
+ M2Crypto.Rand.save_file(self.randfile)
+
+ def __gettext(self, msg):
+ """Return a string representation of 'msg'"""
+ _data = ''
+ if isinstance(msg, Message.Message):
+ for _p in msg.walk():
+ _data = _data + _p.as_string()
+ else:
+ _data = str(msg)
+ return _data
+
+ def __pack(self, msg):
+ """Convert 'msg' to string and put it into an memory buffer for
+ openssl operation"""
+ return M2Crypto.BIO.MemoryBuffer(self.__gettext(msg))
+
+ def setsender(self, cert=None, privkey=None, passphrase=None):
+ if cert:
+ self.cert = cert
+ if privkey:
+ self.key = privkey
+ if passphrase:
+ self.passphrase = passphrase
+
+ def setcacert(self, cacert):
+ self.cacert = cacert
+
+ def sign(self, msg):
+ """Sign a message"""
+ _sender = M2Crypto.SMIME.SMIME()
+ _sender.load_key_bio(self.__pack(self.key), self.__pack(self.cert),
+ callback=self.__passcallback)
+
+ _signed = _sender.sign(self.__pack(msg))
+
+ _out = self.__pack(None)
+ _sender.write(_out, _signed, self.__pack(msg))
+ return _out.read()
+
+ def verify(self, smsg, scert):
+ """Verify to see if 'smsg' was signed by 'scert', and scert was
+ issued by cacert of this object. Return message signed if success,
+ None otherwise"""
+ # Load signer's cert.
+ _x509 = M2Crypto.X509.load_cert_bio(self.__pack(scert))
+ _stack = M2Crypto.X509.X509_Stack()
+ _stack.push(_x509)
+
+ # Load CA cert.
+ _tmpfile = persistdata(self.cacert)
+ _store = M2Crypto.X509.X509_Store()
+ _store.load_info(_tmpfile)
+ os.remove(_tmpfile)
+
+ # prepare SMIME object
+ _sender = M2Crypto.SMIME.SMIME()
+ _sender.set_x509_stack(_stack)
+ _sender.set_x509_store(_store)
+
+ # Load signed message, verify it, and return result
+ _p7, _data = M2Crypto.SMIME.smime_load_pkcs7_bio(self.__pack(smsg))
+ try:
+ return _sender.verify(_p7, flags=M2Crypto.SMIME.PKCS7_SIGNED)
+ except M2Crypto.SMIME.SMIME_Error, _msg:
+ return None
+
+ def encrypt(self, rcert, msg):
+ # Instantiate an SMIME object.
+ _sender = M2Crypto.SMIME.SMIME()
+
+ # Load target cert to encrypt to.
+ _x509 = M2Crypto.X509.load_cert_bio(self.__pack(rcert))
+ _stack = M2Crypto.X509.X509_Stack()
+ _stack.push(_x509)
+ _sender.set_x509_stack(_stack)
+
+ _sender.set_cipher(M2Crypto.SMIME.Cipher(self.cipher))
+
+ # Encrypt the buffer.
+ _buf = self.__pack(self.__gettext(msg))
+ _p7 = _sender.encrypt(_buf)
+
+ # Output p7 in mail-friendly format.
+ _out = self.__pack('')
+ _sender.write(_out, _p7)
+
+ # Save the PRNG's state.
+ self.__saverand()
+
+ return _out.read()
+
+ def decrypt(self, emsg):
+ """decrypt 'msg'. Return decrypt message if success, None
+ otherwise"""
+ # Load private key and cert.
+ _sender = M2Crypto.SMIME.SMIME()
+ _sender.load_key_bio(self.__pack(self.key), self.__pack(self.cert),
+ callback=self.__passcallback)
+
+ # Load the encrypted data.
+ _p7, _data = M2Crypto.SMIME.smime_load_pkcs7_bio(self.__pack(emsg))
+
+ # Decrypt p7.
+ try:
+ return _sender.decrypt(_p7)
+ except M2Crypto.SMIME.SMIME_Error, _msg:
+ return None
+
+ def addHeader(self, rcert, content, subject=''):
+ """Add To, From, Subject Header to 'content'"""
+ _scert = M2Crypto.X509.load_cert_bio(self.__pack(self.cert))
+ _scertsubj = X509_Subject(str(_scert.get_subject()))
+ _rcert = M2Crypto.X509.load_cert_bio(self.__pack(rcert))
+ _rcertsubj = X509_Subject(str(_rcert.get_subject()))
+
+ _out = 'From: "%(CN)s" <%(emailAddress)s>\n' % _scertsubj
+ _out = _out + 'To: "%(CN)s" <%(emailAddress)s>\n' % _rcertsubj
+ _out = _out + 'Subject: %s\n' % subject
+ _out = _out + content
+
+ return _out
+
+
+class X509_Subject(UserDict.UserDict):
+ # This class needed to be rewritten or merge with X509_Name
+ def __init__(self, substr):
+ UserDict.UserDict.__init__(self)
+ try:
+ _data = substr.strip().split('/')
+ except AttributeError, _msg:
+ pass
+ else:
+ for _i in _data:
+ try:
+ _k, _v = _i.split('=')
+ self[_k] = _v
+ except ValueError, _msg:
+ pass
+
+
+def persistdata(data, file=None, isbinary=False):
+ if not file:
+ file = tempfile.mktemp()
+ if isbinary:
+ _flag = 'wb'
+ else:
+ _flag = 'w'
+
+ _fh = open(file, _flag)
+ _fh.write(data)
+ _fh.close()
+ return file
+
+
diff --git a/demo/CipherSaber/CipherSaber.py b/demo/CipherSaber/CipherSaber.py
new file mode 100644
index 0000000..81bd4c6
--- /dev/null
+++ b/demo/CipherSaber/CipherSaber.py
@@ -0,0 +1,62 @@
+#!/usr/bin/env python
+
+"""CipherSaber, http://ciphersaber.gurus.com.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# XXX getopt handling has bugs.
+
+from M2Crypto import RC4, Rand
+import getopt, getpass, sys
+
+class argerr(Exception): pass
+
+cmd = -1
+inf = sys.stdin
+outf = sys.stdout
+
+optlist, optarg = getopt.getopt(sys.argv[1:], 'dei:o:')
+for opt in optlist:
+ if '-d' in opt:
+ cmd = cmd + 1
+ elif '-e' in opt:
+ cmd = cmd + 2
+ elif '-i' in opt:
+ i = opt[1]
+ if i == '-':
+ inf = sys.stdin
+ else:
+ inf = open(i, 'rb')
+ elif '-o' in opt:
+ o = opt[1]
+ if o == '-':
+ outf = sys.stdout
+ else:
+ outf = open(o, 'wb')
+if cmd < 0:
+ raise argerr, "either -d or -e"
+if cmd > 1:
+ raise argerr, "either -d or -e, not both"
+
+if cmd == 0:
+ iv = inf.read(10)
+ pp = getpass.getpass('Enter decryption passphrase: ')
+else:
+ iv = Rand.rand_bytes(10)
+ outf.write(iv)
+ pp = getpass.getpass('Enter encryption passphrase: ')
+ pp2 = getpass.getpass('Enter passphrase again: ')
+ if pp != pp2:
+ raise SystemExit, 'passphrase mismatch, I\'m outta here...'
+
+ci = RC4.RC4(pp + iv)
+del pp, iv
+
+while 1:
+ buf = inf.read()
+ if not buf:
+ break
+ outf.write(ci.update(buf))
+outf.write(ci.final())
+
+
diff --git a/demo/CipherSaber/cstest1.cs1 b/demo/CipherSaber/cstest1.cs1
new file mode 100644
index 0000000..a9794b6
--- /dev/null
+++ b/demo/CipherSaber/cstest1.cs1
@@ -0,0 +1 @@
+om «óªg0í¶wÊtàÐ縅CV»Hã|Ûïçó¨OO_³ý \ No newline at end of file
diff --git a/demo/Zope/ZServer/HTTPS_Server.py b/demo/Zope/ZServer/HTTPS_Server.py
new file mode 100644
index 0000000..06a2668
--- /dev/null
+++ b/demo/Zope/ZServer/HTTPS_Server.py
@@ -0,0 +1,187 @@
+##############################################################################
+#
+# Copyright (c) 2000-2004, Ng Pheng Siong. All Rights Reserved.
+# This file is derived from Zope's ZServer/HTTPServer.py.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+"""
+Medusa HTTPS server for Zope
+
+changes from Medusa's http_server:
+
+ Request Threads -- Requests are processed by threads from a thread
+ pool.
+
+ Output Handling -- Output is pushed directly into the producer
+ fifo by the request-handling thread. The HTTP server does not do
+ any post-processing such as chunking.
+
+ Pipelineable -- This is needed for protocols such as HTTP/1.1 in
+ which mutiple requests come in on the same channel, before
+ responses are sent back. When requests are pipelined, the client
+ doesn't wait for the response before sending another request. The
+ server must ensure that responses are sent back in the same order
+ as requests are received.
+
+
+changes from Zope's HTTP server:
+
+ Well, this is a *HTTPS* server :)
+
+ X.509 certificate-based authentication -- When this is in force,
+ zhttps_handler, a subclass of zhttp_handler, is installed. The
+ https server is configured to request an X.509 certificate from
+ the client. When the request reaches zhttps_handler, it sets
+ REMOTE_USER to the client's subject distinguished name (DN) from
+ the certificate. Zope's REMOTE_USER machinery takes care of the
+ rest, e.g., in conjunction with the RemoteUserFolder product.
+
+"""
+
+import sys, time, types
+
+from PubCore import handle
+from medusa import asyncore
+from ZServer import CONNECTION_LIMIT, ZOPE_VERSION
+from HTTPServer import zhttp_handler
+from zLOG import register_subsystem
+
+from M2Crypto import SSL, version
+from medusa.https_server import https_server, https_channel
+from medusa.asyncore import dispatcher
+
+
+ZSERVER_SSL_VERSION=version
+
+register_subsystem('ZServer HTTPS_Server')
+
+
+class zhttps0_handler(zhttp_handler):
+ "zhttps0 handler - sets SSL request headers a la mod_ssl"
+
+ def __init__ (self, module, uri_base=None, env=None):
+ zhttp_handler.__init__(self, module, uri_base, env)
+
+ def get_environment(self, request):
+ env = zhttp_handler.get_environment(self, request)
+ env['SSL_CIPHER'] = request.channel.get_cipher()
+ return env
+
+
+class zhttps_handler(zhttps0_handler):
+ "zhttps handler - sets REMOTE_USER to user's X.509 certificate Subject DN"
+
+ def __init__ (self, module, uri_base=None, env=None):
+ zhttps0_handler.__init__(self, module, uri_base, env)
+
+ def get_environment(self, request):
+ env = zhttps0_handler.get_environment(self, request)
+ peer = request.channel.get_peer_cert()
+ if peer is not None:
+ env['REMOTE_USER'] = str(peer.get_subject())
+ return env
+
+
+class zhttps_channel(https_channel):
+ "https channel"
+
+ closed=0
+ zombie_timeout=100*60 # 100 minutes
+
+ def __init__(self, server, conn, addr):
+ https_channel.__init__(self, server, conn, addr)
+ self.queue=[]
+ self.working=0
+ self.peer_found=0
+
+ def push(self, producer, send=1):
+ # this is thread-safe when send is false
+ # note, that strings are not wrapped in
+ # producers by default
+ if self.closed:
+ return
+ self.producer_fifo.push(producer)
+ if send: self.initiate_send()
+
+ push_with_producer=push
+
+ def work(self):
+ "try to handle a request"
+ if not self.working:
+ if self.queue:
+ self.working=1
+ try: module_name, request, response=self.queue.pop(0)
+ except: return
+ handle(module_name, request, response)
+
+ def close(self):
+ self.closed=1
+ while self.queue:
+ self.queue.pop()
+ if self.current_request is not None:
+ self.current_request.channel=None # break circ refs
+ self.current_request=None
+ while self.producer_fifo:
+ p=self.producer_fifo.first()
+ if p is not None and type(p) != types.StringType:
+ p.more() # free up resources held by producer
+ self.producer_fifo.pop()
+ self.del_channel()
+ #self.socket.set_shutdown(SSL.SSL_SENT_SHUTDOWN|SSL.SSL_RECEIVED_SHUTDOWN)
+ self.socket.close()
+
+ def done(self):
+ "Called when a publishing request is finished"
+ self.working=0
+ self.work()
+
+ def kill_zombies(self):
+ now = int (time.time())
+ for channel in asyncore.socket_map.values():
+ if channel.__class__ == self.__class__:
+ if (now - channel.creation_time) > channel.zombie_timeout:
+ channel.close()
+
+
+class zhttps_server(https_server):
+ "https server"
+
+ SERVER_IDENT='ZServerSSL/%s' % (ZSERVER_SSL_VERSION,)
+
+ channel_class = zhttps_channel
+ shutup = 0
+
+ def __init__(self, ip, port, ssl_ctx, resolver=None, logger_object=None):
+ self.shutup = 1
+ https_server.__init__(self, ip, port, ssl_ctx, resolver, logger_object)
+ self.ssl_ctx = ssl_ctx
+ self.shutup = 0
+ self.log_info('(%s) HTTPS server started at %s\n'
+ '\tHostname: %s\n\tPort: %d' % (
+ self.SERVER_IDENT,
+ time.ctime(time.time()),
+ self.server_name,
+ self.server_port
+ ))
+
+ def log_info(self, message, type='info'):
+ if self.shutup: return
+ dispatcher.log_info(self, message, type)
+
+ def readable(self):
+ return self.accepting and \
+ len(asyncore.socket_map) < CONNECTION_LIMIT
+
+ def listen(self, num):
+ # override asyncore limits for nt's listen queue size
+ self.accepting = 1
+ return self.socket.listen (num)
+
diff --git a/demo/Zope/ZServer/__init__.py b/demo/Zope/ZServer/__init__.py
new file mode 100644
index 0000000..69fe9b1
--- /dev/null
+++ b/demo/Zope/ZServer/__init__.py
@@ -0,0 +1,95 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+import sys, os
+
+# HACKERY to get around asyncore issues. This ought to go away! We're
+# currently using the Python 2.2 asyncore bundled with Zope to override
+# brokenness in the Python 2.1 version. We need to do some funny business
+# to make this work, as a 2.2-ism crept into the asyncore code.
+if os.name == 'posix':
+ import fcntl
+ if not hasattr(fcntl, 'F_GETFL'):
+ import FCNTL
+ fcntl.F_GETFL = FCNTL.F_GETFL
+ fcntl.F_SETFL = FCNTL.F_SETFL
+
+from medusa import asyncore
+sys.modules['asyncore'] = asyncore
+
+
+
+from medusa.test import max_sockets
+CONNECTION_LIMIT=max_sockets.max_select_sockets()
+
+ZSERVER_VERSION='1.1b1'
+import App.FindHomes
+try:
+ import App.version_txt
+ ZOPE_VERSION=App.version_txt.version_txt()
+except:
+ ZOPE_VERSION='experimental'
+
+
+# Try to poke zLOG default logging into asyncore
+# XXX We should probably should do a better job of this,
+# however that would mean that ZServer required zLOG.
+try:
+ from zLOG import LOG, register_subsystem, BLATHER, INFO, WARNING, ERROR
+ register_subsystem('ZServer')
+ severity={'info':INFO, 'warning':WARNING, 'error': ERROR}
+
+ def log_info(self, message, type='info'):
+ if message[:14]=='adding channel' or \
+ message[:15]=='closing channel' or \
+ message == 'Computing default hostname':
+ LOG('ZServer', BLATHER, message)
+ else:
+ LOG('ZServer', severity[type], message)
+
+ import asyncore
+ asyncore.dispatcher.log_info=log_info
+except:
+ pass
+
+# A routine to try to arrange for request sockets to be closed
+# on exec. This makes it easier for folks who spawn long running
+# processes from Zope code. Thanks to Dieter Maurer for this.
+try:
+ import fcntl
+ try:
+ from fcntl import F_SETFD, FD_CLOEXEC
+ except ImportError:
+ from FCNTL import F_SETFD, FD_CLOEXEC
+
+ def requestCloseOnExec(sock):
+ try: fcntl.fcntl(sock.fileno(), F_SETFD, FD_CLOEXEC)
+ except: pass
+
+except (ImportError, AttributeError):
+
+ def requestCloseOnExec(sock):
+ pass
+
+import asyncore
+from medusa import resolver, logger
+from HTTPServer import zhttp_server, zhttp_handler
+from HTTPS_Server import zhttps_server, zhttps0_handler, zhttps_handler
+from PCGIServer import PCGIServer
+from FCGIServer import FCGIServer
+from FTPServer import FTPServer
+from PubCore import setNumberOfThreads
+from medusa.monitor import secure_monitor_server
+
+# override the service name in logger.syslog_logger
+logger.syslog_logger.svc_name='ZServer'
diff --git a/demo/Zope/ZServer/medusa/ftps_server.py b/demo/Zope/ZServer/medusa/ftps_server.py
new file mode 100644
index 0000000..8fee339
--- /dev/null
+++ b/demo/Zope/ZServer/medusa/ftps_server.py
@@ -0,0 +1,438 @@
+"""An FTP/TLS server built on Medusa's ftp_server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# Python
+import socket, string, sys, time
+
+# Medusa
+from counter import counter
+import asynchat, asyncore, ftp_server, logger
+
+# M2Crypto
+from M2Crypto import SSL
+
+VERSION_STRING='0.09'
+
+class ftp_tls_channel(ftp_server.ftp_channel):
+
+ """FTP/TLS server channel for Medusa."""
+
+ def __init__(self, server, ssl_ctx, conn, addr):
+ """Initialise the channel."""
+ self.ssl_ctx = ssl_ctx
+ self.server = server
+ self.current_mode = 'a'
+ self.addr = addr
+ asynchat.async_chat.__init__(self, conn)
+ self.set_terminator('\r\n')
+ self.client_addr = (addr[0], 21)
+ self.client_dc = None
+ self.in_buffer = ''
+ self.closing = 0
+ self.passive_acceptor = None
+ self.passive_connection = None
+ self.filesystem = None
+ self.authorized = 0
+ self._ssl_accepting = 0
+ self._ssl_accepted = 0
+ self._pbsz = None
+ self._prot = None
+ resp = '220 %s M2Crypto (Medusa) FTP/TLS server v%s ready.'
+ self.respond(resp % (self.server.hostname, VERSION_STRING))
+
+ def writable(self):
+ return self._ssl_accepting or self._ssl_accepted
+
+ def handle_read(self):
+ """Handle a read event."""
+ if self._ssl_accepting:
+ self._ssl_accepted = self.socket.accept_ssl()
+ if self._ssl_accepted:
+ self._ssl_accepting = 0
+ else:
+ try:
+ ftp_server.ftp_channel.handle_read(self)
+ except SSL.SSLError, what:
+ if str(what) == 'unexpected eof':
+ self.close()
+ else:
+ raise
+
+ def handle_write(self):
+ """Handle a write event."""
+ if self._ssl_accepting:
+ self._ssl_accepted = self.socket.accept_ssl()
+ if self._ssl_accepted:
+ self._ssl_accepting = 0
+ else:
+ try:
+ ftp_server.ftp_channel.handle_write(self)
+ except SSL.SSLError, what:
+ if str(what) == 'unexpected eof':
+ self.close()
+ else:
+ raise
+
+ def send(self, data):
+ """Send data over SSL."""
+ try:
+ result = self.socket.send(data)
+ if result <= 0:
+ return 0
+ else:
+ return result
+ except SSL.SSLError, what:
+ self.close()
+ self.log_info('send: closing channel %s %s' % (repr(self), what))
+ return 0
+
+ def recv(self, buffer_size):
+ """Receive data over SSL."""
+ try:
+ result = self.socket.recv(buffer_size)
+ if not result:
+ return ''
+ else:
+ return result
+ except SSL.SSLError, what:
+ self.close()
+ self.log_info('recv: closing channel %s %s' % (repr(self), what))
+ return ''
+
+ def found_terminator(self):
+ """Dispatch the FTP command."""
+ line = self.in_buffer
+ if not len(line):
+ return
+
+ sp = string.find(line, ' ')
+ if sp != -1:
+ line = [line[:sp], line[sp+1:]]
+ else:
+ line = [line]
+
+ command = string.lower(line[0])
+ if string.find(command, 'stor') != -1:
+ while command and command[0] not in string.letters:
+ command = command[1:]
+
+ func_name = 'cmd_%s' % command
+ if command != 'pass':
+ self.log('<== %s' % repr(self.in_buffer)[1:-1])
+ else:
+ self.log('<== %s' % line[0]+' <password>')
+
+ self.in_buffer = ''
+ if not hasattr(self, func_name):
+ self.command_not_understood(line[0])
+ return
+
+ func = getattr(self, func_name)
+ if not self.check_command_authorization(command):
+ self.command_not_authorized(command)
+ else:
+ try:
+ result = apply(func, (line,))
+ except:
+ self.server.total_exceptions.increment()
+ (file, func, line), t, v, tbinfo = asyncore.compact_traceback()
+ if self.client_dc:
+ try:
+ self.client_dc_close()
+ except:
+ pass
+ resp = '451 Server error: %s, %s: file %s line: %s'
+ self.respond(resp % (t, v, file, line))
+
+ def make_xmit_channel(self):
+ """Create a connection for sending data."""
+ pa = self.passive_acceptor
+ if pa:
+ if pa.ready:
+ conn, addr = pa.ready
+ if self._prot:
+ cdc = tls_xmit_channel(self, conn, self.ssl_ctx, addr)
+ else:
+ cdc = ftp_server.xmit_channel(self, addr)
+ cdc.set_socket(conn)
+ cdc.connected = 1
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ else:
+ if self._prot:
+ cdc = tls_xmit_channel(self, None, self.ssl_ctx, None)
+ else:
+ cdc = ftp_server.xmit_channel(self)
+ else:
+ if self._prot:
+ cdc = tls_xmit_channel(self, None, self.ssl_ctx, self.client_addr)
+ else:
+ cdc = ftp_server.xmit_channel(self, self.client_addr)
+ cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ if self.bind_local_minus_one:
+ cdc.bind(('', self.server.port - 1))
+ try:
+ cdc.connect(self.client_addr)
+ except socket.error, what:
+ self.respond('425 Cannot build data connection')
+ self.client_dc = cdc
+
+ def make_recv_channel(self, fd):
+ """Create a connection for receiving data."""
+ pa = self.passive_acceptor
+ if pa:
+ if pa.ready:
+ conn, addr = pa.ready
+ if self._prot:
+ cdc = tls_recv_channel(self, conn, self.ssl_ctx, addr, fd)
+ else:
+ cdc = ftp_server.recv_channel(self, addr, fd)
+ cdc.set_socket(conn)
+ cdc.connected = 1
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ else:
+ if self._prot:
+ cdc = tls_recv_channel(self, None, self.ssl_ctx, None, fd)
+ else:
+ cdc = ftp_server.recv_channel(self, None, fd)
+ else:
+ if self._prot:
+ cdc = tls_recv_channel(self, None, self.ssl_ctx, self._prot, self.client_addr, fd)
+ else:
+ cdc = ftp_server.recv_channel(self, self.client_addr, fd)
+ cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ cdc.connect(self.client_addr)
+ except socket.error, what:
+ self.respond('425 Cannot build data connection')
+ self.client_dc = cdc
+
+ def cmd_auth(self, line):
+ """Prepare for TLS operation."""
+ # XXX Handle variations.
+ if line[1] != 'TLS':
+ self.command_not_understood (string.join(line))
+ else:
+ self.respond('234 AUTH TLS successful')
+ self._ssl_accepting = 1
+ self.socket = SSL.Connection(self.ssl_ctx, self.socket)
+ self.socket.setup_addr(self.addr)
+ self.socket.setup_ssl()
+ self.socket.set_accept_state()
+ self._ssl_accepted = self.socket.accept_ssl()
+ if self._ssl_accepted:
+ self._ssl_accepting = 0
+
+ def cmd_pbsz(self, line):
+ """Negotiate size of buffer for secure data transfer. For
+ FTP/TLS the only valid value for the parameter is '0'; any
+ other value is accepted but ignored."""
+ if not (self._ssl_accepting or self._ssl_accepted):
+ return self.respond('503 AUTH TLS must be issued prior to PBSZ')
+ self._pbsz = 1
+ self.respond('200 PBSZ=0 successful.')
+
+ def cmd_prot(self, line):
+ """Negotiate the security level of the data connection."""
+ if self._pbsz is None:
+ return self.respond('503 PBSZ must be issued prior to PROT')
+ if line[1] == 'C':
+ self.respond('200 Protection set to Clear')
+ self._pbsz = None
+ self._prot = None
+ elif line[1] == 'P':
+ self.respond('200 Protection set to Private')
+ self._prot = 1
+ elif line[1] in ('S', 'E'):
+ self.respond('536 PROT %s unsupported' % line[1])
+ else:
+ self.respond('504 PROT %s unsupported' % line[1])
+
+
+class ftp_tls_server(ftp_server.ftp_server):
+
+ """FTP/TLS server for Medusa."""
+
+ SERVER_IDENT = 'M2Crypto FTP/TLS Server (v%s)' % VERSION_STRING
+
+ ftp_channel_class = ftp_tls_channel
+
+ def __init__(self, authz, ssl_ctx, host=None, ip='', port=21, resolver=None, log_obj=None):
+ """Initialise the server."""
+ self.ssl_ctx = ssl_ctx
+ self.ip = ip
+ self.port = port
+ self.authorizer = authz
+
+ if host is None:
+ self.hostname = socket.gethostname()
+ else:
+ self.hostname = host
+
+ self.total_sessions = counter()
+ self.closed_sessions = counter()
+ self.total_files_out = counter()
+ self.total_files_in = counter()
+ self.total_bytes_out = counter()
+ self.total_bytes_in = counter()
+ self.total_exceptions = counter()
+
+ asyncore.dispatcher.__init__(self)
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.set_reuse_addr()
+ self.bind((self.ip, self.port))
+ self.listen(5)
+
+ if log_obj is None:
+ log_obj = sys.stdout
+
+ if resolver:
+ self.logger = logger.resolving_logger(resolver, log_obj)
+ else:
+ self.logger = logger.unresolving_logger(logger.file_logger(sys.stdout))
+
+ l = 'M2Crypto (Medusa) FTP/TLS server started at %s\n\tAuthz: %s\n\tHostname: %s\n\tPort: %d'
+ self.log_info(l % (time.ctime(time.time()), repr(self.authorizer), self.hostname, self.port))
+
+ def handle_accept(self):
+ """Accept a socket and dispatch a channel to handle it."""
+ conn, addr = self.accept()
+ self.total_sessions.increment()
+ self.log_info('Connection from %s:%d' % addr)
+ self.ftp_channel_class(self, self.ssl_ctx, conn, addr)
+
+
+class nbio_ftp_tls_actor:
+
+ """TLS protocol negotiation mixin for FTP/TLS."""
+
+ def tls_init(self, sock, ssl_ctx, client_addr):
+ """Perform TLS protocol negotiation."""
+ self.ssl_ctx = ssl_ctx
+ self.client_addr = client_addr
+ self._ssl_handshaking = 1
+ self._ssl_handshake_ok = 0
+ if sock:
+ self.socket = SSL.Connection(self.ssl_ctx, sock)
+ self.socket.setup_addr(self.client_addr)
+ self.socket.setup_ssl()
+ self._ssl_handshake_ok = self.socket.accept_ssl()
+ if self._ssl_handshake_ok:
+ self._ssl_handshaking = 0
+ self.add_channel()
+ # else the client hasn't connected yet; when that happens,
+ # handle_connect() will be triggered.
+
+ def tls_neg_ok(self):
+ """Return status of TLS protocol negotiation."""
+ if self._ssl_handshaking:
+ self._ssl_handshake_ok = self.socket.accept_ssl()
+ if self._ssl_handshake_ok:
+ self._ssl_handshaking = 0
+ return self._ssl_handshake_ok
+
+ def handle_connect(self):
+ """Handle a data connection that occurs after this instance came
+ into being. When this handler is triggered, self.socket has been
+ created and refers to the underlying connected socket."""
+ self.socket = SSL.Connection(self.ssl_ctx, self.socket)
+ self.socket.setup_addr(self.client_addr)
+ self.socket.setup_ssl()
+ self._ssl_handshake_ok = self.socket.accept_ssl()
+ if self._ssl_handshake_ok:
+ self._ssl_handshaking = 0
+ self.add_channel()
+
+ def send(self, data):
+ """Send data over SSL."""
+ try:
+ result = self.socket.send(data)
+ if result <= 0:
+ return 0
+ else:
+ return result
+ except SSL.SSLError, what:
+ self.close()
+ self.log_info('send: closing channel %s %s' % (repr(self), what))
+ return 0
+
+ def recv(self, buffer_size):
+ """Receive data over SSL."""
+ try:
+ result = self.socket.recv(buffer_size)
+ if not result:
+ return ''
+ else:
+ return result
+ except SSL.SSLError, what:
+ self.close()
+ self.log_info('recv: closing channel %s %s' % (repr(self), what))
+ return ''
+
+
+class tls_xmit_channel(nbio_ftp_tls_actor, ftp_server.xmit_channel):
+
+ """TLS driver for a send-only data connection."""
+
+ def __init__(self, channel, conn, ssl_ctx, client_addr=None):
+ """Initialise the driver."""
+ ftp_server.xmit_channel.__init__(self, channel, client_addr)
+ self.tls_init(conn, ssl_ctx, client_addr)
+
+ def readable(self):
+ """This channel is readable iff TLS negotiation is in progress.
+ (Which implies a connected channel, of course.)"""
+ if not self.connected:
+ return 0
+ else:
+ return self._ssl_handshaking
+
+ def writable(self):
+ """This channel is writable iff TLS negotiation is in progress
+ or the application has data to send."""
+ if self._ssl_handshaking:
+ return 1
+ else:
+ return ftp_server.xmit_channel.writable(self)
+
+ def handle_read(self):
+ """Handle a read event: either continue with TLS negotiation
+ or let the application handle this event."""
+ if self.tls_neg_ok():
+ ftp_server.xmit_channel.handle_read(self)
+
+ def handle_write(self):
+ """Handle a write event: either continue with TLS negotiation
+ or let the application handle this event."""
+ if self.tls_neg_ok():
+ ftp_server.xmit_channel.handle_write(self)
+
+
+class tls_recv_channel(nbio_ftp_tls_actor, ftp_server.recv_channel):
+
+ """TLS driver for a receive-only data connection."""
+
+ def __init__(self, channel, conn, ssl_ctx, client_addr, fd):
+ """Initialise the driver."""
+ ftp_server.recv_channel.__init__(self, channel, client_addr, fd)
+ self.tls_init(conn, ssl_ctx, client_addr)
+
+ def writable(self):
+ """This channel is writable iff TLS negotiation is in progress."""
+ return self._ssl_handshaking
+
+ def handle_read(self):
+ """Handle a read event: either continue with TLS negotiation
+ or let the application handle this event."""
+ if self.tls_neg_ok():
+ ftp_server.recv_channel.handle_read(self)
+
+ def handle_write(self):
+ """Handle a write event: either continue with TLS negotiation
+ or let the application handle this event."""
+ if self.tls_neg_ok():
+ ftp_server.recv_channel.handle_write(self)
+
+
diff --git a/demo/Zope/ZServer/medusa/https_server.py b/demo/Zope/ZServer/medusa/https_server.py
new file mode 100644
index 0000000..9b932b7
--- /dev/null
+++ b/demo/Zope/ZServer/medusa/https_server.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+
+"""A https server built on Medusa's http_server.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+import asynchat, asyncore, http_server, socket, sys
+from M2Crypto import SSL, version
+
+VERSION_STRING=version
+
+class https_channel(http_server.http_channel):
+
+ ac_in_buffer_size = 1 << 16
+
+ def __init__(self, server, conn, addr):
+ http_server.http_channel.__init__(self, server, conn, addr)
+
+ def send(self, data):
+ try:
+ result = self.socket._write_nbio(data)
+ if result <= 0:
+ return 0
+ else:
+ self.server.bytes_out.increment(result)
+ return result
+ except SSL.SSLError, why:
+ self.close()
+ self.log_info('send: closing channel %s %s' % (repr(self), why))
+ return 0
+
+ def recv(self, buffer_size):
+ try:
+ result = self.socket._read_nbio(buffer_size)
+ if result is None:
+ return ''
+ elif result == '':
+ self.close()
+ return ''
+ else:
+ self.server.bytes_in.increment(len(result))
+ return result
+ except SSL.SSLError, why:
+ self.close()
+ self.log_info('recv: closing channel %s %s' % (repr(self), why))
+ return ''
+
+
+class https_server(http_server.http_server):
+
+ SERVER_IDENT='M2Crypto HTTPS Server (v%s)' % VERSION_STRING
+
+ channel_class=https_channel
+
+ def __init__(self, ip, port, ssl_ctx, resolver=None, logger_object=None):
+ http_server.http_server.__init__(self, ip, port, resolver, logger_object)
+ self.ssl_ctx=ssl_ctx
+
+ def handle_accept(self):
+ # Cribbed from http_server.
+ self.total_clients.increment()
+ try:
+ conn, addr = self.accept()
+ except socket.error:
+ # linux: on rare occasions we get a bogus socket back from
+ # accept. socketmodule.c:makesockaddr complains that the
+ # address family is unknown. We don't want the whole server
+ # to shut down because of this.
+ sys.stderr.write ('warning: server accept() threw an exception\n')
+ return
+
+ # Turn the vanilla socket into an SSL connection.
+ try:
+ ssl_conn=SSL.Connection(self.ssl_ctx, conn)
+ ssl_conn._setup_ssl(addr)
+ ssl_conn.accept_ssl()
+ self.channel_class(self, ssl_conn, addr)
+ except SSL.SSLError:
+ pass
+
+ def writeable(self):
+ return 0
+
diff --git a/demo/Zope/ca.pem b/demo/Zope/ca.pem
new file mode 100644
index 0000000..b7c84a1
--- /dev/null
+++ b/demo/Zope/ca.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/Zope/dh1024.pem b/demo/Zope/dh1024.pem
new file mode 100644
index 0000000..81d43f6
--- /dev/null
+++ b/demo/Zope/dh1024.pem
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/Zope/lib/python/Products/GuardedFile/GuardedFile.py b/demo/Zope/lib/python/Products/GuardedFile/GuardedFile.py
new file mode 100644
index 0000000..1e3137f
--- /dev/null
+++ b/demo/Zope/lib/python/Products/GuardedFile/GuardedFile.py
@@ -0,0 +1,53 @@
+"""GuardedFile.GuardedFile
+
+Copyright (c) 2000-2003 Ng Pheng Siong. All rights reserved.
+This software is released under the ZPL. Usual disclaimers apply."""
+
+__version__ = '1.3'
+
+from AccessControl import getSecurityManager
+from Globals import HTMLFile, MessageDialog
+from OFS.Image import File, cookId
+
+manage_addForm = HTMLFile('add', globals(),Kind='GuardedFile',kind='GuardedFile')
+def manage_addGuardedFile(self, id, file, title='', precondition='', content_type='', REQUEST=None):
+ """
+ Add a new GuardedFile object.
+
+ Creates a new GuardedFile object 'id' with the content of 'file'.
+ """
+ # Object creation stuff, cribbed from OFS.Image.manage_addFile().
+ id, title = cookId(id, title, file)
+ self = self.this()
+ self._setObject(id, GuardedFile(id, title, '', content_type, precondition))
+ obj = self._getOb(id)
+ obj.manage_upload(file)
+
+ # Unset permission acquisition.
+ obj.manage_acquiredPermissions()
+
+ # Create a proxy role and set a specific permission for it.
+ proxy_role = "proxy_for_%s" % id
+ self._addRole(proxy_role)
+ obj.manage_role(proxy_role, ['View'])
+ uname = getSecurityManager().getUser().getUserName()
+ self.manage_addLocalRoles(uname, (proxy_role,), REQUEST)
+
+ # Feedback.
+ if REQUEST: return MessageDialog(
+ title ='Success!',
+ message='GuardedFile "%s" has been created.' % id,
+ action ='manage_main')
+
+
+class GuardedFile(File):
+ """A File object accessible by proxy only."""
+ meta_type = "GuardedFile"
+
+ def manage_beforeDelete(self, item, container):
+ """Delete self's proxy role."""
+ role = "proxy_for_%s" % self.__name__
+ container._delRoles([role], None)
+ self.manage_delLocalRoles(self.users_with_local_role(role))
+
+
diff --git a/demo/Zope/lib/python/Products/GuardedFile/README.txt b/demo/Zope/lib/python/Products/GuardedFile/README.txt
new file mode 100644
index 0000000..d35b9a5
--- /dev/null
+++ b/demo/Zope/lib/python/Products/GuardedFile/README.txt
@@ -0,0 +1,16 @@
+GuardedFile
+
+ A GuardedFile is a Zope File that is accessible *by proxy* only.
+
+ When a GuardedFile is created, all acquired permissions are unset.
+ A proxy role is created in its container with the sole permission
+ "View".
+
+ When the GuardedFile is deleted, its associated proxy role is also
+ removed.
+
+ In all other aspects a GuardedFile behaves exactly like a File.
+
+
+ $Id: README.txt 299 2005-06-09 17:32:28Z heikki $
+ $Revision: 1.2 $
diff --git a/demo/Zope/lib/python/Products/GuardedFile/TODO.txt b/demo/Zope/lib/python/Products/GuardedFile/TODO.txt
new file mode 100644
index 0000000..8afc8c7
--- /dev/null
+++ b/demo/Zope/lib/python/Products/GuardedFile/TODO.txt
@@ -0,0 +1 @@
+1. Icon.
diff --git a/demo/Zope/lib/python/Products/GuardedFile/__init__.py b/demo/Zope/lib/python/Products/GuardedFile/__init__.py
new file mode 100644
index 0000000..f587c75
--- /dev/null
+++ b/demo/Zope/lib/python/Products/GuardedFile/__init__.py
@@ -0,0 +1,25 @@
+"""GuardedFile.__init__
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved.
+This software is released under the ZPL."""
+
+__version__ = '1.1'
+
+import GuardedFile
+
+def initialize(context):
+ try:
+ context.registerClass(
+ GuardedFile.GuardedFile,
+ constructors=(GuardedFile.manage_addForm, GuardedFile.manage_addGuardedFile)
+ #icon='folder.gif'
+ )
+ context.registerBaseClass(GuardedFile.GuardedFile)
+
+ except:
+ import sys, traceback, string
+ type, val, tb = sys.exc_info()
+ sys.stderr.write(string.join(
+ traceback.format_exception(type, val, tb),''))
+ del type, val, tb
+
diff --git a/demo/Zope/lib/python/Products/GuardedFile/add.dtml b/demo/Zope/lib/python/Products/GuardedFile/add.dtml
new file mode 100644
index 0000000..ed78fa6
--- /dev/null
+++ b/demo/Zope/lib/python/Products/GuardedFile/add.dtml
@@ -0,0 +1,78 @@
+<dtml-var manage_page_header>
+
+<dtml-var "manage_form_title(this(), _, form_title='Add GuardedFile')">
+
+<p class="form-help">
+ A GuardedFile is a Zope File that is accessible <em>by proxy</em> only.
+</p>
+
+<p class="form-help">
+ When a GuardedFile is created, all acquired permissions are unset.
+ A proxy role is created in its container with the sole permission
+ "View".
+</p>
+
+<p class="form-help">
+ When the GuardedFile is deleted, its associated proxy role is also
+ removed.
+</p>
+
+<p class="form-help">
+ In all other aspects a GuardedFile behaves exactly like a File.
+</p>
+
+<p class="form-help">
+ You can create a new <dtml-var kind> using the form below.
+ Select a file from your local computer by clicking the
+ <em>Browse</em> button. The file you select will be uploaded
+ to Zope as a <dtml-var kind>.
+</P>
+
+<FORM ACTION="manage_add<dtml-var Kind>" METHOD="POST"
+ ENCTYPE="multipart/form-data">
+<table cellspacing="0" cellpadding="2" border="0">
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ Id
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="id" size="40" />
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-optional">
+ Title
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="text" name="title" size="40" />
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ <div class="form-label">
+ File
+ </div>
+ </td>
+ <td align="left" valign="top">
+ <input type="file" name="file:string" size="25" value="" />
+ </td>
+ </tr>
+ <tr>
+ <td align="left" valign="top">
+ </td>
+ <td align="left" valign="top">
+ <div class="form-element">
+ <input class="form-element" type="submit" name="submit"
+ value=" Add " />
+ </div>
+ </td>
+ </tr>
+</TABLE>
+</FORM>
+
+<dtml-var manage_page_footer>
+
diff --git a/demo/Zope/lib/python/Products/GuardedFile/refresh.txt b/demo/Zope/lib/python/Products/GuardedFile/refresh.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/demo/Zope/lib/python/Products/GuardedFile/refresh.txt
diff --git a/demo/Zope/lib/python/Products/GuardedFile/version.txt b/demo/Zope/lib/python/Products/GuardedFile/version.txt
new file mode 100644
index 0000000..1e5ebbf
--- /dev/null
+++ b/demo/Zope/lib/python/Products/GuardedFile/version.txt
@@ -0,0 +1 @@
+GuardedFile-1-1-0
diff --git a/demo/Zope/lib/python/Products/ZSmime/README.txt b/demo/Zope/lib/python/Products/ZSmime/README.txt
new file mode 100644
index 0000000..f43740b
--- /dev/null
+++ b/demo/Zope/lib/python/Products/ZSmime/README.txt
@@ -0,0 +1,18 @@
+ZSmime enables Zope to generate S/MIME-signed/encrypted messages.
+
+ZSmime is useful where Zope accepts confidential information over the
+web, e.g., credit card information, Swiss bank account instructions, etc.
+Such information can be protected by ZSmime and relayed off-site
+immediately. This reduces the value of the information carried on-site
+and in turn reduces the effect of a successful attack against the site.
+
+Even if the S/MIME-protected information remains on-site, it is now
+encrypted - this introduces additional cost in defeating the protection
+and may mitigate the impact of a successful site penetration.
+
+ZSmime adds a DTML tag "dtml-smime" to Zope.
+
+
+$Id: README.txt 299 2005-06-09 17:32:28Z heikki $
+$Revision: 1.1 $
+
diff --git a/demo/Zope/lib/python/Products/ZSmime/SmimeTag.py b/demo/Zope/lib/python/Products/ZSmime/SmimeTag.py
new file mode 100644
index 0000000..142bec9
--- /dev/null
+++ b/demo/Zope/lib/python/Products/ZSmime/SmimeTag.py
@@ -0,0 +1,104 @@
+"""ZSmime.SmimeTag
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved.
+This software is released under the ZPL. Usual disclaimers apply."""
+
+__version__ = '1.2'
+
+# Zope tag stuff.
+from DocumentTemplate.DT_String import String
+from DocumentTemplate.DT_Util import *
+from DocumentTemplate.DT_Var import Var, Call
+
+# M2Crypto.
+from M2Crypto import BIO, SMIME, X509
+
+SmimeError = "SmimeTag Error"
+
+class SmimeTag:
+ """<dtml-smime>"""
+
+ name = 'smime'
+ blockContinuations = ()
+
+ def __init__(self, blocks):
+ tname, args, section = blocks[0]
+ self.section = section
+
+ args = parse_params(args, signer=None, recipients=None)
+ has_key = args.has_key
+
+ if has_key('signer'):
+ self.signer = args['signer']
+ try:
+ Call(self.signer)
+ except ParseError:
+ raise SmimeError, ('Invalid parameter "signer".')
+ else:
+ raise SmimeError, ('The parameter "signer" was not specified in tag.')
+
+ if has_key('recipients'):
+ self.recipients = args['recipients']
+ try:
+ Call(self.recipients)
+ except ParseError:
+ raise SmimeError, ('Invalid parameter "recipients".')
+ else:
+ raise SmimeError, ('The parameter "recipients" was not specified in tag.')
+
+
+ def render(self, md):
+ # Render the dtml block.
+ data = render_blocks(self.section.blocks, md)
+ data_bio = BIO.MemoryBuffer(data)
+
+ # Prepare to S/MIME.
+ s = SMIME.SMIME()
+
+ # Render the signer key, load into BIO.
+ try:
+ signer = Var(self.signer).render(md)
+ except ParseError:
+ raise SmimeError, ('Invalid parameter "signer".')
+ signer_key_bio = BIO.MemoryBuffer(signer)
+ signer_cert_bio = BIO.MemoryBuffer(signer) # XXX Kludge.
+
+ # Sign the data.
+ s.load_key_bio(signer_key_bio, signer_cert_bio)
+ p7 = s.sign(data_bio, flags=SMIME.PKCS7_TEXT)
+
+ # Recreate coz sign() has consumed the MemoryBuffer.
+ # May be cheaper to seek to start.
+ data_bio = BIO.MemoryBuffer(data)
+
+ # Render recipients, load into BIO.
+ try:
+ recip = Var(self.recipients).render(md)
+ except ParseError:
+ raise SmimeError, ('Invalid parameter "recipients".')
+ recip_bio = BIO.MemoryBuffer(recip)
+
+ # Load recipient certificates.
+ sk = X509.X509_Stack()
+ sk.push(X509.load_cert_bio(recip_bio))
+ s.set_x509_stack(sk)
+
+ # Set a cipher.
+ s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+
+ # Encrypt.
+ tmp_bio = BIO.MemoryBuffer()
+ s.write(tmp_bio, p7)
+ p7 = s.encrypt(tmp_bio)
+
+ # Finally, return the now signed/encrypted PKCS7.
+ out = BIO.MemoryBuffer()
+ s.write(out, p7)
+ return out.getvalue()
+
+
+ __call__ = render
+
+
+String.commands['smime'] = SmimeTag
+
diff --git a/demo/Zope/lib/python/Products/ZSmime/__init__.py b/demo/Zope/lib/python/Products/ZSmime/__init__.py
new file mode 100644
index 0000000..ebb77f6
--- /dev/null
+++ b/demo/Zope/lib/python/Products/ZSmime/__init__.py
@@ -0,0 +1,9 @@
+"""ZSmime.__init__
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved.
+This software is released under the ZPL. Usual disclaimers apply."""
+
+__version__='1.1'
+
+import SmimeTag
+
diff --git a/demo/Zope/lib/python/Products/ZSmime/version.txt b/demo/Zope/lib/python/Products/ZSmime/version.txt
new file mode 100644
index 0000000..ac26a94
--- /dev/null
+++ b/demo/Zope/lib/python/Products/ZSmime/version.txt
@@ -0,0 +1 @@
+ZSmime-1-0-1
diff --git a/demo/Zope/server.pem b/demo/Zope/server.pem
new file mode 100644
index 0000000..1ee9282
--- /dev/null
+++ b/demo/Zope/server.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/Zope/starts b/demo/Zope/starts
new file mode 100644
index 0000000..2676243
--- /dev/null
+++ b/demo/Zope/starts
@@ -0,0 +1,17 @@
+#!/bin/sh
+umask 077
+reldir=`dirname $0`
+cwd=`cd $reldir; pwd`
+# Zope's event logger is controlled by the "EVENT_LOG_FILE" environment
+# variable. If you don't have a "EVENT_LOG_FILE" environment variable
+# (or its older alias "STUPID_LOG_FILE") set, Zope will log to the standard
+# output. For more information on EVENT_LOG_FILE, see doc/ENVIRONMENT.txt.
+ZLOGFILE=$EVENT_LOG_FILE
+if [ -z "$ZLOGFILE" ]; then
+ ZLOGFILE=$STUPID_LOG_FILE
+fi
+if [ -z "$ZLOGFILE" ]; then
+ EVENT_LOG_FILE=""
+ export EVENT_LOG_FILE
+fi
+exec /usr/local/bin/python2.1 $cwd/z2s.py -D "$@"
diff --git a/demo/Zope/starts.bat b/demo/Zope/starts.bat
new file mode 100755
index 0000000..c8bd3b6
--- /dev/null
+++ b/demo/Zope/starts.bat
@@ -0,0 +1 @@
+"C:\pkg\zope260\bin\python.exe" "C:\pkg\zope260\z2s.py" -D %1 %2 %3 %4 %5 %6 %7 %8 %9
diff --git a/demo/Zope/utilities/x509_user.py b/demo/Zope/utilities/x509_user.py
new file mode 100644
index 0000000..1d350d6
--- /dev/null
+++ b/demo/Zope/utilities/x509_user.py
@@ -0,0 +1,79 @@
+#!/usr/bin/env python
+
+"""
+This is a very simple program to manage the access_x509 database. The
+overriding goal is program portability, hence its use of 'anydbm'.
+
+Invoke it thusly:
+
+ x509_user.py
+ -u <username>
+ [ -x <X.509 subject DN> ]
+ [ -f <database> ]
+
+<username> is the Zope username; it must be present.
+
+<X.509 subject DN> is the X.509 certificate's subject distinguished name
+to associate with the user. If it is present, the association is created
+or updated. If it is absent, the association is removed.
+
+<database> defaults to 'access_x509'.
+
+(I told you this is a dumb program.)
+
+
+To read the subject distinguished name from the certificate 'client.pem',
+invoke 'openssl' thusly:
+
+ openssl x509 -subject -noout -in client.pem
+
+This produces the output:
+
+ subject=/C=SG/O=M2Crypto Client/CN=M2Crypto Client/Email=ngps@post1.com
+
+
+Next, invoke this tool:
+
+ x509_user.py -u superuser \\
+ -f "/C=SG/O=M2Crypto Client/CN=M2Crypto Client/Email=ngps@post1.com"
+
+This associates the user who owns client.pem to the Zope "superuser".
+
+
+Copyright (c) 2000 Ng Pheng Siong. This program is released under the ZPL.
+"""
+
+import anydbm, getopt, sys
+
+x509_db = 'access_x509'
+username = subject_dn = None
+
+argerr='Usage'
+
+optlist, optarg=getopt.getopt(sys.argv[1:], 'f:u:x:') # ;-)
+for opt in optlist:
+ if '-f' in opt:
+ x509_db = opt[1]
+ elif '-u' in opt:
+ username = opt[1]
+ elif '-x' in opt:
+ subject_dn = opt[1]
+
+if username is None:
+ raise argerr, '\n' + __doc__
+
+db = anydbm.open(x509_db, 'cw')
+if subject_dn is None:
+ # Remove the association...
+ try:
+ subject_dn = db[username]
+ del db[subject_dn]
+ del db[username]
+ except:
+ pass
+else:
+ # Create/update the association.
+ db[subject_dn] = username
+ db[username] = subject_dn
+db.close()
+
diff --git a/demo/Zope/z2s.py b/demo/Zope/z2s.py
new file mode 100644
index 0000000..33885ab
--- /dev/null
+++ b/demo/Zope/z2s.py
@@ -0,0 +1,1078 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+"""Zope 2 ZServer start-up file
+
+Usage: %(program)s [options] [environment settings]
+
+Options:
+
+ -h
+
+ Output this text.
+
+ -z path
+
+ The location of the Zope installation.
+ The default is the location of this script, %(here)s.
+
+ -Z 0 or 1
+
+ UNIX only! This option is ignored on Windows.
+
+ This option controls whether a management process will be created
+ that restarts Zope after a shutdown or crash.
+
+ If the argument to -Z is non-null (e.g. "-Z1" or "-Zyes"), a
+ management process will be used. If the argument to -Z is "-", or
+ "0", (e.g. "-Z-" or "-Z0"), a management process will not be used.
+ On UNIX, the default behavior is to create a separate management
+ process (e.g. -Z1) if the -Z option is not specified.
+
+ (Note: the -Z option in Zopes before Zope 2.6 used to be used to specify
+ a pidfile name for the management process. This pidfile no longer
+ exists).
+
+ -t n
+
+ The number of threads to use, if ZODB3 is used. The default is
+ %(NUMBER_OF_THREADS)s.
+
+ -i n
+
+ Set the interpreter check interval. This integer value
+ determines how often the interpreter checks for periodic things
+ such as thread switches and signal handlers. The Zope default
+ is 500, but you may want to experiment with other values that
+ may increase performance in your particular environment.
+
+ -D
+
+ Run in Zope debug mode. This causes the Zope process not to
+ detach from the controlling terminal, and is equivalent to
+ supplying the environment variable setting Z_DEBUG_MODE=1
+
+ -a ipaddress
+
+ The IP address to listen on. If this is an empty string
+ (e.g. -a ''), then all addresses on the machine are used. The
+ default is %(IP_ADDRESS)s.
+
+ -d ipaddress
+
+ IP address of your DNS server. If this is an empty string
+ (e.g. -d ''), then IP addresses will not be logged. If you have
+ DNS service on your local machine then you can set this to
+ 127.0.0.1. The default is: %(DNS_IP)s.
+
+ -u username or uid number
+
+ The username to run ZServer as. You may want to run ZServer as
+ a dedicated user. This only works under Unix, and if ZServer
+ is started as root, and is required in that case.
+
+ -P [ipaddress:]number
+
+ Set the web, ftp and monitor port numbers simultaneously
+ as offsets from the number. The web port number will be number+80.
+ The FTP port number will be number+21. The monitor port number will
+ be number+99.
+
+ The number can be preeceeded by an ip address follwed by a colon
+ to specify an address to listen on. This allows different servers
+ to listen on different addresses.
+
+ Multiple -P options can be provided to run multiple sets of servers.
+
+ -w port
+
+ The Web server (HTTP) port. This defaults to %(HTTP_PORT)s. The
+ standard port for HTTP services is 80. If this is a dash
+ (e.g. -w -), then HTTP is disabled.
+
+ The number can be preeceeded by an ip address follwed by a colon
+ to specify an address to listen on. This allows different servers
+ to listen on different addresses.
+
+ Multiple -w options can be provided to run multiple servers.
+
+ -y port
+
+ The SSL Web server (HTTPS) port. This defaults to %(HTTPS_PORT)s. The
+ standard port for HTTPS services is 443. If this is a dash
+ (e.g. -y -), then HTTPS is disabled.
+
+ The number can be preeceeded by an ip address follwed by a colon
+ to specify an address to listen on. This allows different servers
+ to listen on different addresses.
+
+ Multiple -y options can be provided to run multiple servers.
+
+ -W port
+
+ The "WebDAV source" port. If this is a dash (e.g. -W -), then
+ "WebDAV source" is disabled. The default is disabled. Note that
+ this feature is a workaround for the lack of "source-link" support
+ in standard WebDAV clients.
+
+ The port can be preeceeded by an ip address follwed by a colon
+ to specify an address to listen on. This allows different servers
+ to listen on different addresses.
+
+ Multiple -W options can be provided to run multiple servers.
+
+ -Y port
+
+ The "WebDAV source over HTTPS" port. If this is a dash (e.g. -Y -), then
+ "WebDAV source over HTTPS" is disabled. The default is disabled. Note that
+ this feature is a workaround for the lack of "source-link" support
+ in standard WebDAV clients.
+
+ The port can be preeceeded by an ip address follwed by a colon
+ to specify an address to listen on. This allows different servers
+ to listen on different addresses.
+
+ Multiple -Y options can be provided to run multiple servers.
+
+ -x
+
+ If present, this option causes Zope to run in X.509 certificate-based
+ authentication mode.
+
+ -C
+ --force-http-connection-close
+
+ If present, this option causes Zope to close all HTTP connections,
+ regardless of the 'Connection:' header (or lack of one) sent by
+ the client.
+
+ -f port
+
+ The FTP port. If this is a dash (e.g. -f -), then FTP
+ is disabled. The standard port for FTP services is 21. The
+ default is %(FTP_PORT)s.
+
+ The port can be preeceeded by an ip address follwed by a colon
+ to specify an address to listen on. This allows different servers
+ to listen on different addresses.
+
+ Multiple -f options can be provided to run multiple servers.
+
+ -p path
+
+ Path to the PCGI resource file. The default value is
+ %(PCGI_FILE)s, relative to the Zope location. If this is a dash
+ (-p -) or the file does not exist, then PCGI is disabled.
+
+ -F path_or_port
+
+ Either a port number (for inet sockets) or a path name (for unix
+ domain sockets) for the FastCGI Server. If the flag and value are
+ not specified then the FastCGI Server is disabled.
+
+ -m port
+
+ The secure monitor server port. If this is a dash
+ (-m -), then the monitor server is disabled. The monitor server
+ allows interactive Python style access to a running ZServer. To
+ access the server see medusa/monitor_client.py or
+ medusa/monitor_client_win32.py. The monitor server password is the
+ same as the Zope emergency user password set in the 'access'
+ file. The default is to not start up a monitor server.
+
+ The port can be preeceeded by an ip address follwed by a colon
+ to specify an address to listen on. This allows different servers
+ to listen on different addresses.
+
+ Multiple -m options can be provided to run multiple servers.
+
+ --icp port
+
+ The ICP port. ICP can be used to distribute load between back-end
+ zope servers, if you are using an ICP-aware front-end proxy such
+ as Squid.
+
+ The port can be preeceeded by an ip address follwed by a colon
+ to specify an address to listen on. This allows different servers
+ to listen on different addresses.
+
+ Multiple --icp options can be provided to run multiple servers.
+
+ -l path
+
+ Path to the ZServer log file. If this is a relative path then the
+ log file will be written to the 'var' directory. The default is
+ %(LOG_FILE)s.
+
+ -r
+
+ Run ZServer is read-only mode. ZServer won't write anything to disk.
+ No log files, no pid files, nothing. This means that you can't do a
+ lot of stuff like use PCGI, and zdaemon. ZServer will log hits to
+ STDOUT and zLOG will log to STDERR.
+
+ -L
+
+ Enable locale (internationalization) support. The value passed for
+ this option should be the name of the locale to be used (see your
+ operating system documentation for locale information specific to
+ your system). If an empty string is passed for this option (-L ''),
+ Zope will set the locale to the user's default setting (typically
+ specified in the $LANG environment variable). If your Python
+ installation does not support the locale module, the requested
+ locale is not supported by your system or an empty string was
+ passed but no default locale can be found, an error will be raised
+ and Zope will not start.
+
+ -X
+
+ Disable servers. This might be used to effectively disable all
+ default server settings or previous server settings in the option
+ list before providing new settings. For example to provide just a
+ web server:
+
+ %(program)s -X -w80
+
+ -M file
+
+ Save detailed logging information to the given file.
+ This log includes separate entries for:
+
+ - The start of a request,
+ - The start of processing the request in an application thread,
+ - The start of response output, and
+ - The end of the request.
+
+Environment settings are of the form: NAME=VALUE.
+
+Note: you *must* use Python 2.1 or later!
+"""
+
+
+# This is required path hackery for the win32 binary distribution
+# that ensures that the bundled python libraries are used. In a
+# win32 binary distribution, the installer will have replaced the
+# marker string with the actual software home. If that has not
+# happened, then the path munging code is skipped.
+swhome=r'INSERT_SOFTWARE_HOME'
+if swhome != 'INSERT_SOFTWARE_HOME':
+ import sys
+ sys.path.insert(0, '%s/lib/python' % swhome)
+ sys.path.insert(1, '%s/bin/lib' % swhome)
+ sys.path.insert(2, '%s/bin/lib/plat-win' % swhome)
+ sys.path.insert(3, '%s/bin/lib/win32' % swhome)
+ sys.path.insert(4, '%s/bin/lib/win32/lib' % swhome)
+ sys.path.insert(5, '%s' % swhome)
+
+
+import os, sys, getopt, codecs, string
+import socket
+
+from types import StringType, IntType
+# workaround to allow unicode encoding conversions in DTML
+dummy = codecs.lookup('iso-8859-1')
+
+sys.setcheckinterval(500)
+
+program=sys.argv[0]
+here=os.path.join(os.getcwd(), os.path.split(program)[0])
+
+########################################################################
+# Configuration section
+
+## General configuration options
+##
+
+# This is the IP address of the network interface you want your servers to
+# be visible from. This can be changed to '' to listen on all interfaces.
+IP_ADDRESS=''
+
+# IP address of your DNS server. Set to '' if you do not want to resolve
+# IP addresses. If you have DNS service on your local machine then you can
+# set this to '127.0.0.1'
+DNS_IP=''
+
+# User id to run ZServer as. Note that this only works under Unix, and if
+# ZServer is started by root. This no longer defaults to 'nobody' since
+# that can lead to a Zope file compromise.
+UID=None
+
+# Log file location. If this is a relative path, then it is joined the
+# the 'var' directory.
+LOG_FILE='Z2.log'
+
+## HTTP configuration
+##
+
+# Port for HTTP Server. The standard port for HTTP services is 80.
+HTTP_PORT=8080
+
+# Port for HTTPS Server. The standard port for HTTPS services is 443.
+HTTPS_PORT=8443
+
+# HTTP enivornment settings.
+HTTP_ENV={}
+
+# HTTPS enivornment settings.
+HTTPS_ENV={}
+
+# Should we close all HTTP connections, ignoring the (usually absent)
+# 'Connection:' header?
+FORCE_HTTP_CONNECTION_CLOSE=0
+
+# Port for the special "WebDAV source view" HTTP handler. There is no
+# standard port for this handler, which is disabled by default.
+WEBDAV_SOURCE_PORT=[]
+
+# Port for the special "WebDAV source view over SSL" HTTP handler. There is no
+# standard port for this handler, which is disabled by default.
+WEBDAV_SSL_SOURCE_PORT=[]
+
+# Should we use client X.509 certificate-based authentication?
+X509_REMOTE_USER=None
+
+## FTP configuration
+
+# Port for the FTP Server. The standard port for FTP services is 21.
+FTP_PORT=8021
+
+## PCGI configuration
+
+# You can configure the PCGI server manually, or have it read its
+# configuration information from a PCGI info file.
+PCGI_FILE='Zope.cgi'
+
+## Monitor configuration
+MONITOR_PORT=0
+
+## ICP configuration
+ICP_PORT=0
+
+# Module to be published, which must be Main or Zope
+MODULE='Zope'
+
+# The size of the thread pool, if ZODB3 is used.
+NUMBER_OF_THREADS=4
+
+# Localization support
+LOCALE_ID=None
+
+# Socket path or port for the FastCGI Server
+FCGI_PORT=None
+
+# Detailed log file
+DETAILED_LOG_FILE=''
+
+# Use a daemon process
+USE_DAEMON = 1
+
+#
+########################################################################
+
+########################################################################
+# Handle command-line arguments:
+
+def server_info(old, v, offset=0):
+ # interpret v as a port or address/port and get new value
+ if v == '-': v=''
+ l=v.find(':')
+ if l >= 0:
+ a=v[:l]
+ v=v[l+1:]
+ else:
+ a=IP_ADDRESS
+
+ if not v: return v
+
+ try:
+ v=int(v)
+ if v < 0: raise 'Invalid port', v
+ v=v+offset
+ except: raise 'Invalid port', v
+
+ if isinstance(old, IntType): old=[(a,v)]
+ else: old.append((a,v))
+
+ return old
+
+
+try:
+ python_version = sys.version.split()[0]
+ if python_version < '2.1':
+ raise 'Invalid python version', python_version
+ if python_version[:3] == '2.1':
+ if python_version[4:5] < '3':
+ import warnings
+ err = ('You are running Python version %s. This Python version '
+ 'has known bugs that may cause Zope to run improperly. '
+ 'Consider upgrading to a Python in the 2.1 series '
+ 'with at least version number 2.1.3. (Note that Zope does '
+ 'not yet run under any Python 2.2 version).' %
+ python_version)
+ warnings.warn(err)
+ if python_version[:3] == '2.2':
+ import warnings
+ err = ('You are running Python version %s. This Python version '
+ 'has not yet been tested with Zope and you may experience '
+ 'operational problems as a result. Consider using '
+ 'Python 2.1.3 instead.' % python_version)
+ warnings.warn(err)
+
+
+ opts, args = getopt.getopt(sys.argv[1:],
+ 'hz:Z:t:i:a:d:u:w:W:y:Y:x:f:p:m:Sl:2DP:rF:L:XM:C',
+ ['icp=', 'force-http-connection-close'
+ ])
+
+ DEBUG=0
+ READ_ONLY=0
+ if sys.platform == 'win32':
+ USE_DAEMON = 0
+
+
+ # Get environment variables
+ for a in args:
+ if a.find('='):
+ a=a.split('=')
+ o=a[0]
+ v='='.join(a[1:])
+ if o:
+ os.environ[o]=v
+ HTTP_ENV[o]=v
+ else:
+ raise 'Invalid argument', a
+
+ for o, v in opts:
+ if o=='-z': here=v
+ elif o=='-Z':
+ if v in ('-', '0', ''):
+ USE_DAEMON=0
+ elif sys.platform != 'win32':
+ USE_DAEMON = 1
+ elif o=='-r': READ_ONLY=1
+ elif o=='-t':
+ try: v=int(v)
+ except: raise 'Invalid number of threads', v
+ NUMBER_OF_THREADS=v
+
+ elif o=='-i':
+ try: v=int(v)
+ except: raise 'Invalid value for -i option', v
+ sys.setcheckinterval(v)
+
+ elif o=='-a': IP_ADDRESS=v
+ elif o=='-d':
+ if v=='-': v=''
+ DNS_IP=v
+ elif o=='-u': UID=v
+ elif o=='-D':
+ os.environ['Z_DEBUG_MODE']='1'
+ DEBUG=1
+ elif o=='-S': sys.ZMANAGED=1
+ elif o=='-X':
+ MONITOR_PORT=HTTP_PORT=FTP_PORT=FCGI_PORT=ICP_PORT=0
+ WEBDAV_SOURCE_PORT=0
+ PCGI_FILE=''
+ elif o=='-m':
+ MONITOR_PORT=server_info(MONITOR_PORT, v)
+ elif o=='-w':
+ HTTP_PORT=server_info(HTTP_PORT, v)
+ elif o=='-y':
+ HTTPS_PORT=server_info(HTTPS_PORT, v)
+ elif o=='-C' or o=='--force-http-connection-close':
+ FORCE_HTTP_CONNECTION_CLOSE=1
+ elif o=='-W':
+ WEBDAV_SOURCE_PORT=server_info(WEBDAV_SOURCE_PORT, v)
+ elif o=='-Y':
+ WEBDAV_SSL_SOURCE_PORT=server_info(WEBDAV_SSL_SOURCE_PORT, v)
+ elif o=='-x':
+ if v in ('-', '0', ''):
+ X509_REMOTE_USER=None
+ else:
+ X509_REMOTE_USER=1
+ elif o=='-f':
+ FTP_PORT=server_info(FTP_PORT, v)
+ elif o=='-P':
+ HTTP_PORT=server_info(HTTP_PORT, v, 80)
+ FTP_PORT=server_info(FTP_PORT, v, 21)
+ elif o=='--icp':
+ ICP_PORT=server_info(ICP_PORT, v)
+
+ elif o=='-p':
+ if v=='-': v=''
+ PCGI_FILE=v
+ elif o=='-h':
+ print __doc__ % vars()
+ sys.exit(0)
+ elif o=='-2': MODULE='Main'
+ elif o=='-l': LOG_FILE=v
+ elif o=='-L':
+ if v: LOCALE_ID=v
+ else: LOCALE_ID=''
+ elif o=='-F':
+ if v=='-': v=''
+ FCGI_PORT=v
+ elif o=='-M': DETAILED_LOG_FILE=v
+
+except SystemExit: sys.exit(0)
+except:
+ print __doc__ % vars()
+ print
+ print 'Error:'
+ print "%s: %s" % (sys.exc_type, sys.exc_value)
+ sys.exit(1)
+
+#
+########################################################################
+
+########################################################################
+# OK, let's get going!
+
+# Jigger path:
+sys.path=[os.path.join(here,'lib','python'),here
+ ]+filter(None, sys.path)
+
+
+
+# Try to set the locale if specified on the command
+# line. If the locale module is not available or the
+# requested locale is not supported by the local
+# machine, raise an error so that the user is made
+# aware of the problem.
+
+def set_locale(val):
+ try:
+ import locale
+ except:
+ raise SystemExit, (
+ 'The locale module could not be imported.\n'
+ 'To use localization options, you must ensure\n'
+ 'that the locale module is compiled into your\n'
+ 'Python installation.'
+ )
+ try:
+ locale.setlocale(locale.LC_ALL, val)
+ except:
+ raise SystemExit, (
+ 'The specified locale is not supported by your system.\n'
+ 'See your operating system documentation for more\n'
+ 'information on locale support.'
+ )
+if LOCALE_ID is not None:
+ set_locale(LOCALE_ID)
+
+import zdaemon
+# from this point forward we can use the zope logger
+# importing ZDaemon before importing ZServer causes ZServer logging
+# not to work.
+
+# Import ZServer before we open the database or get at interesting
+# application code so that ZServer's asyncore gets to be the
+# official one. Also gets SOFTWARE_HOME, INSTANCE_HOME, and CLIENT_HOME
+import ZServer
+
+# install signal handlers if on posix
+if os.name == 'posix':
+ from Signals import Signals
+ Signals.registerZopeSignals()
+
+# Location of the ZServer pid file. When Zope starts up it will write
+# its PID to this file. If Zope is run under zdaemon control, zdaemon
+# will write to this pidfile instead of Zope.
+PID_FILE=os.path.join(CLIENT_HOME, 'Z2.pid')
+
+if USE_DAEMON and not READ_ONLY:
+ import App.FindHomes
+ sys.ZMANAGED=1
+ # zdaemon.run creates a process which "manages" the actual Zope
+ # process (restarts it if it dies). The management process passes along
+ # signals that it receives to its child.
+ zdaemon.run(sys.argv, os.path.join(CLIENT_HOME, PID_FILE))
+
+os.chdir(CLIENT_HOME)
+
+def _warn_nobody():
+ zLOG.LOG("z2", zLOG.INFO, ("Running Zope as 'nobody' can compromise "
+ "your Zope files; consider using a "
+ "dedicated user account for Zope") )
+
+try:
+ # Import logging support
+ import zLOG
+ import ZLogger
+
+ if READ_ONLY:
+ if hasattr(zLOG, '_set_stupid_dest'):
+ zLOG._set_stupid_dest(sys.stderr)
+ else:
+ zLOG._stupid_dest = sys.stderr
+ else:
+ zLOG.log_write = ZLogger.ZLogger.log_write
+
+ if DETAILED_LOG_FILE:
+ from ZServer import DebugLogger
+ logfile=os.path.join(CLIENT_HOME, DETAILED_LOG_FILE)
+ zLOG.LOG('z2', zLOG.BLATHER,
+ 'Using detailed request log file %s' % logfile)
+ DL=DebugLogger.DebugLogger(logfile)
+ DebugLogger.log=DL.log
+ DebugLogger.reopen=DL.reopen
+ sys.__detailedlog=DL
+
+ # Import Zope (or Main)
+ if MODULE == 'Zope':
+ import Zope
+ Zope.startup()
+ else:
+ exec "import "+MODULE in {}
+
+ # Location of the ZServer log file. This file logs all ZServer activity.
+ # You may wish to create different logs for different servers. See
+ # medusa/logger.py for more information.
+ if not os.path.isabs(LOG_FILE):
+ LOG_PATH=os.path.join(CLIENT_HOME, LOG_FILE)
+ else:
+ LOG_PATH=LOG_FILE
+
+ # import ZServer stuff
+
+ # First, we need to increase the number of threads
+ if MODULE=='Zope':
+ from ZServer import setNumberOfThreads
+ setNumberOfThreads(NUMBER_OF_THREADS)
+
+ from ZServer import resolver, logger, asyncore
+
+ from ZServer import zhttp_server, zhttp_handler
+ from ZServer import zhttps_server, zhttps0_handler, zhttps_handler
+ from ZServer.WebDAVSrcHandler import WebDAVSrcHandler
+ from ZServer import PCGIServer,FTPServer,FCGIServer
+
+ from ZServer import secure_monitor_server
+
+ from M2Crypto import SSL, Rand
+
+ ## ZServer startup
+ ##
+
+ ## In X509_REMOTE_USER mode, we log the client cert's subject DN.
+ if X509_REMOTE_USER:
+
+ import base64, string, time
+
+ def log (self, bytes):
+ user_agent=self.get_header('user-agent')
+ if not user_agent: user_agent=''
+ referer=self.get_header('referer')
+ if not referer: referer=''
+
+ get_peer_cert = getattr(self.channel, 'get_peer_cert', None)
+ if get_peer_cert is not None:
+ name = str(get_peer_cert().get_subject())
+ else:
+ name = 'Anonymous'
+ auth=self.get_header('Authorization')
+ if auth is not None:
+ if string.lower(auth[:6]) == 'basic ':
+ try: decoded=base64.decodestring(auth[6:])
+ except base64.binascii.Error: decoded=''
+ t = string.split(decoded, ':', 1)
+ if len(t) < 2:
+ name = 'Unknown (bad auth string)'
+ else:
+ name = t[0]
+
+ self.channel.server.logger.log (
+ self.channel.addr[0],
+ ' - %s [%s] "%s" %d %d "%s" "%s"\n' % (
+ name,
+ self.log_date_string (time.time()),
+ self.request,
+ self.reply_code,
+ bytes,
+ referer,
+ user_agent
+ )
+ )
+
+ from ZServer.medusa import http_server
+ http_server.http_request.log = log
+
+ # Resolver and Logger, used by other servers
+ if DNS_IP:
+ rs = resolver.caching_resolver(DNS_IP)
+ else:
+ rs=None
+
+ if READ_ONLY:
+ lg = logger.file_logger('-') # log to stdout
+ zLOG.LOG('z2', zLOG.BLATHER, 'Logging access log to stdout')
+ elif os.environ.has_key('ZSYSLOG_ACCESS'):
+ if os.environ.has_key("ZSYSLOG_ACCESS_FACILITY"):
+ lg = logger.syslog_logger(
+ os.environ['ZSYSLOG_ACCESS'],
+ facility=os.environ['ZSYSLOG_ACCESS_FACILITY'])
+ else:
+ lg = logger.syslog_logger(os.environ['ZSYSLOG_ACCESS'])
+ zLOG.LOG('z2', zLOG.BLATHER, 'Using local syslog access log')
+ elif os.environ.has_key('ZSYSLOG_ACCESS_SERVER'):
+ (addr, port) = os.environ['ZSYSLOG_ACCESS_SERVER'].split( ':')
+ lg = logger.syslog_logger((addr, int(port)))
+ zLOG.LOG('z2', zLOG.BLATHER, 'Using remote syslog access log')
+ else:
+ lg = logger.file_logger(LOG_PATH)
+ zLOG.LOG('z2', zLOG.BLATHER, 'Using access log file %s' % LOG_PATH)
+ sys.__lg = lg
+
+ port_err=('\n\nZope wants to use %(socktype)s port %(port)s for its '
+ '%(protocol)s service, but it is already in use by another '
+ 'application on this machine. Either shut the application down '
+ 'which is using this port, or start Zope with a different '
+ '%(protocol)s port via the "%(switch)s" command-line switch.\n')
+
+ # HTTP Server
+ if HTTP_PORT:
+ if isinstance(HTTP_PORT, IntType): HTTP_PORT=((IP_ADDRESS, HTTP_PORT),)
+ for address, port in HTTP_PORT:
+ try:
+ hs = zhttp_server(
+ ip=address,
+ port=port,
+ resolver=rs,
+ logger_object=lg)
+ except socket.error, why:
+ if why[0] == 98: # address in use
+ raise port_err % {'port':port,
+ 'socktype':'TCP',
+ 'protocol':'HTTP',
+ 'switch':'-w'}
+ raise
+ # Handler for a published module. zhttp_handler takes 3 arguments:
+ # The name of the module to publish, and optionally the URI base
+ # which is basically the SCRIPT_NAME, and optionally a dictionary
+ # with CGI environment variables which override default
+ # settings. The URI base setting is useful when you want to
+ # publish more than one module with the same HTTP server. The CGI
+ # environment setting is useful when you want to proxy requests
+ # from another web server to ZServer, and would like the CGI
+ # environment to reflect the CGI environment of the other web
+ # server.
+ try:
+ del HTTP_ENV['HTTPS']
+ except KeyError:
+ pass
+ zh = zhttp_handler(MODULE, '', HTTP_ENV)
+ if FORCE_HTTP_CONNECTION_CLOSE:
+ zh._force_connection_close = 1
+ hs.install_handler(zh)
+
+ # HTTPS Server
+ if HTTPS_PORT:
+ ssl_ctx = SSL.Context('sslv23')
+ ssl_ctx.load_cert_chain('%s/server.pem' % INSTANCE_HOME)
+ ssl_ctx.load_verify_locations('%s/ca.pem' % INSTANCE_HOME)
+ ssl_ctx.load_client_CA('%s/ca.pem' % INSTANCE_HOME)
+ #ssl_ctx.set_allow_unknown_ca(1)
+ ssl_ctx.set_session_id_ctx(MODULE)
+ ssl_ctx.set_tmp_dh('%s/dh1024.pem' % INSTANCE_HOME)
+ if X509_REMOTE_USER:
+ ssl_ctx.set_verify(SSL.verify_peer, 10)
+ else:
+ ssl_ctx.set_verify(SSL.verify_none, 10)
+ if type(HTTPS_PORT) is type(0): HTTPS_PORT=((IP_ADDRESS, HTTPS_PORT),)
+
+ for address, port in HTTPS_PORT:
+ hss = zhttps_server(
+ ip=address,
+ port=port,
+ ssl_ctx=ssl_ctx,
+ resolver=rs,
+ logger_object=lg)
+
+ try:
+ del HTTPS_ENV['HTTP']
+ except KeyError:
+ pass
+ HTTPS_ENV['HTTPS']='ON'
+
+ if X509_REMOTE_USER:
+ zsh = zhttps_handler(MODULE, '', HTTPS_ENV)
+ else:
+ zsh = zhttps0_handler(MODULE, '', HTTPS_ENV)
+ hss.install_handler(zsh)
+
+ # WebDAV source Server (runs HTTP, but munges request to return
+ # 'manage_FTPget').
+ if WEBDAV_SOURCE_PORT:
+ if isinstance(WEBDAV_SOURCE_PORT, IntType):
+ WEBDAV_SOURCE_PORT=((IP_ADDRESS, WEBDAV_SOURCE_PORT),)
+ for address, port in WEBDAV_SOURCE_PORT:
+ try:
+ hs = zhttp_server(
+ ip=address,
+ port=port,
+ resolver=rs,
+ logger_object=lg)
+ except socket.error, why:
+ if why[0] == 98: # address in use
+ raise port_err % {'port':port,
+ 'socktype':'TCP',
+ 'protocol':'WebDAV source',
+ 'switch':'-W'}
+ raise
+
+ # Handler for a published module. zhttp_handler takes 3 arguments:
+ # The name of the module to publish, and optionally the URI base
+ # which is basically the SCRIPT_NAME, and optionally a dictionary
+ # with CGI environment variables which override default
+ # settings. The URI base setting is useful when you want to
+ # publish more than one module with the same HTTP server. The CGI
+ # environment setting is useful when you want to proxy requests
+ # from another web server to ZServer, and would like the CGI
+ # environment to reflect the CGI environment of the other web
+ # server.
+ zh = WebDAVSrcHandler(MODULE, '', HTTP_ENV)
+ hs.install_handler(zh)
+
+ # enable document retrieval of the document source on the
+ # standard HTTP port
+
+ clients = os.environ.get('WEBDAV_SOURCE_PORT_CLIENTS')
+ if clients:
+ import re
+ sys.WEBDAV_SOURCE_PORT_CLIENTS = re.compile(clients).search
+ else:
+ sys.WEBDAV_SOURCE_PORT_CLIENTS = None
+
+ # WebDAV-over-SSL source Server (runs HTTPS, but munges request to return
+ # 'manage_FTPget').
+ if WEBDAV_SSL_SOURCE_PORT:
+ ssl_ctx = SSL.Context('sslv23')
+ ssl_ctx.load_cert_chain('%s/server.pem' % INSTANCE_HOME)
+ ssl_ctx.load_verify_locations('%s/ca.pem' % INSTANCE_HOME)
+ ssl_ctx.load_client_CA('%s/ca.pem' % INSTANCE_HOME)
+ ssl_ctx.set_verify(SSL.verify_none, 10)
+ ssl_ctx.set_session_id_ctx(MODULE)
+ ssl_ctx.set_tmp_dh('%s/dh1024.pem' % INSTANCE_HOME)
+ if type(WEBDAV_SSL_SOURCE_PORT) is type(0):
+ WEBDAV_SSL_SOURCE_PORT=((IP_ADDRESS, WEBDAV_SSL_SOURCE_PORT),)
+ for address, port in WEBDAV_SSL_SOURCE_PORT:
+ hss = zhttps_server(
+ ip=address,
+ port=port,
+ ssl_ctx=ssl_ctx,
+ resolver=rs,
+ logger_object=lg)
+
+ try:
+ del HTTPS_ENV['HTTP']
+ except KeyError:
+ pass
+ HTTPS_ENV['HTTPS']='ON'
+
+ zsh = WebDAVSrcHandler(MODULE, '', HTTPS_ENV)
+ hss.install_handler(zsh)
+
+ # FTP Server
+ if FTP_PORT:
+ if isinstance(FTP_PORT, IntType): FTP_PORT=((IP_ADDRESS, FTP_PORT),)
+ for address, port in FTP_PORT:
+ try:
+ FTPServer(
+ module=MODULE,
+ ip=address,
+ port=port,
+ resolver=rs,
+ logger_object=lg)
+ except socket.error, why:
+ if why[0] == 98: # address in use
+ raise port_err % {'port':port,
+ 'socktype':'TCP',
+ 'protocol':'FTP',
+ 'switch':'-f'}
+ raise
+
+ # PCGI Server
+ if PCGI_FILE and not READ_ONLY:
+ PCGI_FILE=os.path.join(here, PCGI_FILE)
+ if os.path.exists(PCGI_FILE):
+ zpcgi = PCGIServer(
+ module=MODULE,
+ ip=IP_ADDRESS,
+ pcgi_file=PCGI_FILE,
+ resolver=rs,
+ logger_object=lg)
+
+
+ # FastCGI Server
+ if FCGI_PORT and not READ_ONLY:
+ fcgiPort = None
+ fcgiPath = None
+ try:
+ fcgiPort = int(FCGI_PORT)
+ except ValueError:
+ fcgiPath = FCGI_PORT
+ try:
+ zfcgi = FCGIServer(module=MODULE,
+ ip=IP_ADDRESS,
+ port=fcgiPort,
+ socket_file=fcgiPath,
+ resolver=rs,
+ logger_object=lg)
+ except socket.error, why:
+ if why[0] == 98: # address in use
+ raise port_err % {'port':fcgiPort,
+ 'socktype':'TCP',
+ 'protocol':'FastCGI',
+ 'switch':'-F'}
+ raise
+
+
+ # Monitor Server
+ if MONITOR_PORT:
+ from AccessControl.User import emergency_user
+ if not hasattr(emergency_user, '__null_user__'):
+ pw = emergency_user._getPassword()
+ else:
+ pw = None
+ zLOG.LOG("z2", zLOG.WARNING, 'Monitor server not started'
+ ' because no emergency user exists.')
+ if pw:
+ if isinstance(MONITOR_PORT, IntType):
+ MONITOR_PORT=((IP_ADDRESS, MONITOR_PORT),)
+ for address, port in MONITOR_PORT:
+ try:
+ monitor=secure_monitor_server(
+ password=pw,
+ hostname=address,
+ port=port)
+ except socket.error, why:
+ if why[0] == 98: # address in use
+ raise port_err % {'port':port,
+ 'socktype':'TCP',
+ 'protocol':'monitor server',
+ 'switch':'-m'}
+ raise
+
+ if ICP_PORT:
+ if isinstance(ICP_PORT, IntType): ICP_PORT=((IP_ADDRESS, ICP_PORT),)
+ from ZServer.ICPServer import ICPServer
+ for address, port in ICP_PORT:
+ try:
+ ICPServer(address,port)
+ except socket.error, why:
+ if why[0] == 98: # address in use
+ raise port_err % {'port':port,
+ 'socktype':'UDP',
+ 'protocol':'ICP',
+ 'switch':'--icp'}
+ raise
+
+ if not USE_DAEMON and not READ_ONLY:
+ if os.path.exists(PID_FILE): os.unlink(PID_FILE)
+ pf = open(PID_FILE, 'w')
+ pid='%s\n' % os.getpid()
+ pf.write(pid)
+ pf.close()
+
+ # Warn if we were started as nobody.
+ try:
+ import pwd
+ if os.getuid():
+ if pwd.getpwuid(os.getuid())[0] == 'nobody':
+ _warn_nobody()
+ except:
+ pass
+
+ # Drop root privileges if we have them, and do some sanity checking
+ # to make sure we're not starting with an obviously insecure setup.
+ try:
+ if os.getuid() == 0:
+ try:
+ import initgroups
+ except:
+ raise SystemExit, 'initgroups is required to safely setuid'
+ if UID == None:
+ raise SystemExit, ('A user was not specified to setuid '
+ 'to; fix this to start as root (see '
+ 'doc/SETUID.txt)')
+ import stat
+ client_home_stat = os.stat(CLIENT_HOME)
+ client_home_faults = []
+ if not (client_home_stat[stat.ST_MODE]&01000):
+ client_home_faults.append('does not have the sticky bit set')
+ if client_home_stat[stat.ST_UID] != 0:
+ client_home_faults.append('is not owned by root')
+ if client_home_faults:
+ client_home_faults.append('fix this to start as root (see '
+ 'doc/SETUID.txt)')
+ err = '%s %s' % (CLIENT_HOME, ', '.join(client_home_faults))
+ raise SystemExit, err
+
+ try:
+ try: UID = string.atoi(UID)
+ except: pass
+ gid = None
+ if isinstance(UID, StringType):
+ uid = pwd.getpwnam(UID)[2]
+ gid = pwd.getpwnam(UID)[3]
+ elif isinstance(UID, IntType):
+ uid = pwd.getpwuid(UID)[2]
+ gid = pwd.getpwuid(UID)[3]
+ UID = pwd.getpwuid(UID)[0]
+ else:
+ raise KeyError
+ if UID == 'nobody':
+ _warn_nobody()
+ try:
+ initgroups.initgroups(UID, gid)
+ if gid is not None:
+ try:
+ os.setgid(gid)
+ except OSError:
+ pass
+ os.setuid(uid)
+ except OSError:
+ pass
+ except KeyError:
+ zLOG.LOG("z2", zLOG.ERROR, ("Can't find UID %s" % UID))
+ except AttributeError:
+ pass
+ except:
+ raise
+
+ # Check umask sanity if we're on posix.
+ if os.name == 'posix' and not os.environ.get('Z_DEBUG_MODE'):
+ # umask is silly, blame POSIX. We have to set it to get its value.
+ current_umask = os.umask(0)
+ os.umask(current_umask)
+ if current_umask != 077:
+ current_umask = '%03o' % current_umask
+ zLOG.LOG("z2", zLOG.INFO, (
+ 'Your umask of %s may be too permissive; for the security of '
+ 'your Zope data, it is recommended you use 077' % current_umask
+ ))
+
+except:
+ # Log startup exception and tell zdaemon not to restart us.
+ try:
+ zLOG.LOG("z2", zLOG.PANIC, "Startup exception",
+ error=sys.exc_info())
+ except: pass
+ sys.exit(0)
+
+# Start Medusa, Ye Hass!
+Rand.load_file('%s/randpool.dat' % INSTANCE_HOME, -1)
+sys.ZServerExitCode=0
+asyncore.loop()
+Rand.save_file('%s/randpool.dat' % INSTANCE_HOME)
+sys.exit(sys.ZServerExitCode)
diff --git a/demo/Zope/z2s.py.diff b/demo/Zope/z2s.py.diff
new file mode 100644
index 0000000..f79cd94
--- /dev/null
+++ b/demo/Zope/z2s.py.diff
@@ -0,0 +1,266 @@
+--- z2s.py Sun Oct 26 17:51:00 2003
++++ /usr/local/home/ngps/pkg/zope261/z2.py Thu Jan 30 22:41:42 2003
+@@ -105,21 +105,9 @@
+
+ Multiple -w options can be provided to run multiple servers.
+
+- -y port
+-
+- The SSL Web server (HTTPS) port. This defaults to %(HTTPS_PORT)s. The
+- standard port for HTTPS services is 443. If this is a dash
+- (e.g. -y -), then HTTPS is disabled.
+-
+- The number can be preeceeded by an ip address follwed by a colon
+- to specify an address to listen on. This allows different servers
+- to listen on different addresses.
+-
+- Multiple -y options can be provided to run multiple servers.
+-
+ -W port
+
+- The "WebDAV source" port. If this is a dash (e.g. -W -), then
++ The "WebDAV source" port. If this is a dash (e.g. -w -), then
+ "WebDAV source" is disabled. The default is disabled. Note that
+ this feature is a workaround for the lack of "source-link" support
+ in standard WebDAV clients.
+@@ -130,24 +118,6 @@
+
+ Multiple -W options can be provided to run multiple servers.
+
+- -Y port
+-
+- The "WebDAV source over HTTPS" port. If this is a dash (e.g. -Y -), then
+- "WebDAV source over HTTPS" is disabled. The default is disabled. Note that
+- this feature is a workaround for the lack of "source-link" support
+- in standard WebDAV clients.
+-
+- The port can be preeceeded by an ip address follwed by a colon
+- to specify an address to listen on. This allows different servers
+- to listen on different addresses.
+-
+- Multiple -Y options can be provided to run multiple servers.
+-
+- -x
+-
+- If present, this option causes Zope to run in X.509 certificate-based
+- authentication mode.
+-
+ -C
+ --force-http-connection-close
+
+@@ -316,15 +286,9 @@
+ # Port for HTTP Server. The standard port for HTTP services is 80.
+ HTTP_PORT=8080
+
+-# Port for HTTPS Server. The standard port for HTTPS services is 443.
+-HTTPS_PORT=8443
+-
+ # HTTP enivornment settings.
+ HTTP_ENV={}
+
+-# HTTPS enivornment settings.
+-HTTPS_ENV={}
+-
+ # Should we close all HTTP connections, ignoring the (usually absent)
+ # 'Connection:' header?
+ FORCE_HTTP_CONNECTION_CLOSE=0
+@@ -333,13 +297,6 @@
+ # standard port for this handler, which is disabled by default.
+ WEBDAV_SOURCE_PORT=[]
+
+-# Port for the special "WebDAV source view over SSL" HTTP handler. There is no
+-# standard port for this handler, which is disabled by default.
+-WEBDAV_SSL_SOURCE_PORT=[]
+-
+-# Should we use client X.509 certificate-based authentication?
+-X509_REMOTE_USER=None
+-
+ ## FTP configuration
+
+ # Port for the FTP Server. The standard port for FTP services is 21.
+@@ -429,7 +386,7 @@
+
+
+ opts, args = getopt.getopt(sys.argv[1:],
+- 'hz:Z:t:i:a:d:u:w:W:y:Y:x:f:p:m:Sl:2DP:rF:L:XM:C',
++ 'hz:Z:t:i:a:d:u:w:W:f:p:m:Sl:2DP:rF:L:XM:C',
+ ['icp=', 'force-http-connection-close'
+ ])
+
+@@ -486,19 +443,10 @@
+ MONITOR_PORT=server_info(MONITOR_PORT, v)
+ elif o=='-w':
+ HTTP_PORT=server_info(HTTP_PORT, v)
+- elif o=='-y':
+- HTTPS_PORT=server_info(HTTPS_PORT, v)
+ elif o=='-C' or o=='--force-http-connection-close':
+ FORCE_HTTP_CONNECTION_CLOSE=1
+ elif o=='-W':
+ WEBDAV_SOURCE_PORT=server_info(WEBDAV_SOURCE_PORT, v)
+- elif o=='-Y':
+- WEBDAV_SSL_SOURCE_PORT=server_info(WEBDAV_SSL_SOURCE_PORT, v)
+- elif o=='-x':
+- if v in ('-', '0', ''):
+- X509_REMOTE_USER=None
+- else:
+- X509_REMOTE_USER=1
+ elif o=='-f':
+ FTP_PORT=server_info(FTP_PORT, v)
+ elif o=='-P':
+@@ -653,60 +601,14 @@
+ from ZServer import resolver, logger, asyncore
+
+ from ZServer import zhttp_server, zhttp_handler
+- from ZServer import zhttps_server, zhttps0_handler, zhttps_handler
+ from ZServer.WebDAVSrcHandler import WebDAVSrcHandler
+ from ZServer import PCGIServer,FTPServer,FCGIServer
+
+ from ZServer import secure_monitor_server
+
+- from M2Crypto import SSL, Rand
+-
+ ## ZServer startup
+ ##
+
+- ## In X509_REMOTE_USER mode, we log the client cert's subject DN.
+- if X509_REMOTE_USER:
+-
+- import base64, string, time
+-
+- def log (self, bytes):
+- user_agent=self.get_header('user-agent')
+- if not user_agent: user_agent=''
+- referer=self.get_header('referer')
+- if not referer: referer=''
+-
+- get_peer_cert = getattr(self.channel, 'get_peer_cert', None)
+- if get_peer_cert is not None:
+- name = str(get_peer_cert().get_subject())
+- else:
+- name = 'Anonymous'
+- auth=self.get_header('Authorization')
+- if auth is not None:
+- if string.lower(auth[:6]) == 'basic ':
+- try: decoded=base64.decodestring(auth[6:])
+- except base64.binascii.Error: decoded=''
+- t = string.split(decoded, ':', 1)
+- if len(t) < 2:
+- name = 'Unknown (bad auth string)'
+- else:
+- name = t[0]
+-
+- self.channel.server.logger.log (
+- self.channel.addr[0],
+- ' - %s [%s] "%s" %d %d "%s" "%s"\n' % (
+- name,
+- self.log_date_string (time.time()),
+- self.request,
+- self.reply_code,
+- bytes,
+- referer,
+- user_agent
+- )
+- )
+-
+- from ZServer.medusa import http_server
+- http_server.http_request.log = log
+-
+ # Resolver and Logger, used by other servers
+ if DNS_IP:
+ rs = resolver.caching_resolver(DNS_IP)
+@@ -766,51 +668,11 @@
+ # from another web server to ZServer, and would like the CGI
+ # environment to reflect the CGI environment of the other web
+ # server.
+- try:
+- del HTTP_ENV['HTTPS']
+- except KeyError:
+- pass
+ zh = zhttp_handler(MODULE, '', HTTP_ENV)
+ if FORCE_HTTP_CONNECTION_CLOSE:
+ zh._force_connection_close = 1
+ hs.install_handler(zh)
+
+- # HTTPS Server
+- if HTTPS_PORT:
+- ssl_ctx = SSL.Context('sslv23')
+- ssl_ctx.load_cert_chain('%s/server.pem' % INSTANCE_HOME)
+- ssl_ctx.load_verify_locations('%s/ca.pem' % INSTANCE_HOME)
+- ssl_ctx.load_client_CA('%s/ca.pem' % INSTANCE_HOME)
+- #ssl_ctx.set_allow_unknown_ca(1)
+- ssl_ctx.set_session_id_ctx(MODULE)
+- ssl_ctx.set_tmp_dh('%s/dh1024.pem' % INSTANCE_HOME)
+- if X509_REMOTE_USER:
+- ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_fail_if_no_peer_cert, 10)
+- #ssl_ctx.set_verify(SSL.verify_peer, 10)
+- else:
+- ssl_ctx.set_verify(SSL.verify_none, 10)
+- if type(HTTPS_PORT) is type(0): HTTPS_PORT=((IP_ADDRESS, HTTPS_PORT),)
+-
+- for address, port in HTTPS_PORT:
+- hss = zhttps_server(
+- ip=address,
+- port=port,
+- ssl_ctx=ssl_ctx,
+- resolver=rs,
+- logger_object=lg)
+-
+- try:
+- del HTTPS_ENV['HTTP']
+- except KeyError:
+- pass
+- HTTPS_ENV['HTTPS']='ON'
+-
+- if X509_REMOTE_USER:
+- zsh = zhttps_handler(MODULE, '', HTTPS_ENV)
+- else:
+- zsh = zhttps0_handler(MODULE, '', HTTPS_ENV)
+- hss.install_handler(zsh)
+-
+ # WebDAV source Server (runs HTTP, but munges request to return
+ # 'manage_FTPget').
+ if WEBDAV_SOURCE_PORT:
+@@ -854,34 +716,6 @@
+ else:
+ sys.WEBDAV_SOURCE_PORT_CLIENTS = None
+
+- # WebDAV-over-SSL source Server (runs HTTPS, but munges request to return
+- # 'manage_FTPget').
+- if WEBDAV_SSL_SOURCE_PORT:
+- ssl_ctx = SSL.Context('sslv23')
+- ssl_ctx.load_cert_chain('%s/server.pem' % INSTANCE_HOME)
+- ssl_ctx.load_verify_locations('%s/ca.pem' % INSTANCE_HOME)
+- ssl_ctx.load_client_CA('%s/ca.pem' % INSTANCE_HOME)
+- ssl_ctx.set_verify(SSL.verify_none, 10)
+- ssl_ctx.set_session_id_ctx(MODULE)
+- ssl_ctx.set_tmp_dh('%s/dh1024.pem' % INSTANCE_HOME)
+- if type(WEBDAV_SSL_SOURCE_PORT) is type(0):
+- WEBDAV_SSL_SOURCE_PORT=((IP_ADDRESS, WEBDAV_SSL_SOURCE_PORT),)
+- for address, port in WEBDAV_SSL_SOURCE_PORT:
+- hss = zhttps_server(
+- ip=address,
+- port=port,
+- ssl_ctx=ssl_ctx,
+- resolver=rs,
+- logger_object=lg)
+-
+- try:
+- del HTTPS_ENV['HTTP']
+- except KeyError:
+- pass
+- HTTPS_ENV['HTTPS']='ON'
+-
+- zsh = WebDAVSrcHandler(MODULE, '', HTTPS_ENV)
+- hss.install_handler(zsh)
+
+ # FTP Server
+ if FTP_PORT:
+@@ -1072,8 +906,6 @@
+ sys.exit(0)
+
+ # Start Medusa, Ye Hass!
+-Rand.load_file('%s/randpool.dat' % INSTANCE_HOME, -1)
+ sys.ZServerExitCode=0
+ asyncore.loop()
+-Rand.save_file('%s/randpool.dat' % INSTANCE_HOME)
+ sys.exit(sys.ZServerExitCode)
diff --git a/demo/Zope27/INSTALL.txt b/demo/Zope27/INSTALL.txt
new file mode 100644
index 0000000..b3728c5
--- /dev/null
+++ b/demo/Zope27/INSTALL.txt
@@ -0,0 +1,200 @@
+=========================================
+ ZServerSSL for Zope 2.7.0b2
+=========================================
+
+:Author: Ng Pheng Siong
+:Id: $Id$
+:Date: $Date$
+:Web-Site: http://sandbox.rulemaker.net/ngps/zope/zssl/
+
+.. contents::
+
+
+Directories
+-----------------------
+
+This distribution is contained in ``m2crypto-0.12/demo/Zope27/``. Its
+directory structure assumes the following:
+
+- Zope 2.7.0b2 is installed in <install-dir>.
+- An instance has been created in <instance-home>.
+
+
+<install-dir>
+-----------------------
+
+The following files are to be copied to the corresponding directories
+in your <install-dir>:
+
+- install_dir/lib/python/ZServer/HTTPS_Server.py
+- install_dir/lib/python/ZServer/medusa/https_server.py
+
+The following patch files are to be applied to the corresponding
+directories in your <install-dir>:
+
+- install_dir/lib/python/ZServer/__init__.py.patch
+- install_dir/lib/python/ZServer/component.xml.patch
+- install_dir/lib/python/ZServer/datatypes.py.patch
+
+
+<instance-home>
+-----------------------
+
+The following files are to be copied to the corresponding directories
+in your <instance-home>:
+
+- instance_home/ssl/ca.pem
+- instance_home/ssl/server.pem
+- instance_home/ssl/dh1024.pem
+
+These are example files. For more information on them, consult the
+ZServerSSL HOWTO for Zope 2.6.
+
+The following patch files are to be applied to the corresponding
+directories in your <instance-home>:
+
+- instance_home/README.txt.patch
+- instance_home/etc/zope.conf.patch
+
+(Patching README.txt is optional.)
+
+There appears to be a bug in Zope 2.7.0b2, where INSTANCE_HOME in a
+running Zope points to <install-dir>, not <instance-home>. Workaround
+this with the following:
+
+::
+
+ $ (cd <install-dir>; ln -s <instance-home>/ssl)
+
+
+Launch ZServerSSL
+-------------------
+
+::
+
+ $ <instance-home>/bin/runzope
+
+
+Testing
+---------
+
+Below, we assume your Zope server is running on ``localhost``.
+
+
+HTTPS
+~~~~~~~
+
+This testing is done with Mozilla 1.1 on FreeBSD.
+
+1. With a browser, connect to https://localhost:8443/. Browse
+ around. Check out your browser's HTTPS informational screens.
+
+2. Connect to https://localhost:8443/manage. Verify that you can
+ access Zope's management functionality.
+
+
+WebDAV-over-HTTPS
+~~~~~~~~~~~~~~~~~~~
+
+This testing is done with Cadaver 0.21.0 on FreeBSD.
+
+::
+
+ $ cadaver https://localhost:8443/
+ WARNING: Untrusted server certificate presented:
+ Issued to: M2Crypto, SG
+ Issued by: M2Crypto, SG
+ Do you wish to accept the certificate? (y/n) y
+ Authentication required for Zope on server `localhost':
+ Username: admin
+ Password:
+ dav:/> ls
+ Listing collection `/': succeeded.
+ Coll: Control_Panel 0 Sep 28 00:38
+ Coll: temp_folder 0 Sep 28 17:30
+ acl_users 0 Sep 28 00:38
+ browser_id_manager 0 Sep 28 00:38
+ error_log 0 Sep 28 00:38
+ index_html 28 Sep 28 00:39
+ session_data_manager 0 Sep 28 00:38
+ standard_error_message 1189 Sep 28 00:39
+ standard_html_footer 18 Sep 28 00:39
+ standard_html_header 82 Sep 28 00:39
+ standard_template.pt 282 Sep 28 00:39
+ dav:/> quit
+ Connection to `localhost' closed.
+ $
+
+
+Python with M2Crypto
+~~~~~~~~~~~~~~~~~~~~~~
+
+This testing is done with M2Crypto 0.12 and Python 2.2.3 on FreeBSD.
+
+HTTPS
+```````
+
+>>> from M2Crypto import Rand, SSL, m2urllib
+>>> url = m2urllib.FancyURLopener()
+>>> url.addheader('Connection', 'close')
+>>> u = url.open('https://127.0.0.1:8443/')
+send: 'GET / HTTP/1.1\r\nHost: 127.0.0.1:8443\r\nAccept-Encoding: identity\r\nUser-agent: Python-urllib/1.15\r\nConnection: close\r\n\r\n'
+reply: 'HTTP/1.1 200 OK\r\n'
+header: Server: ZServerSSL/0.12
+header: Date: Sun, 28 Sep 2003 09:40:14 GMT
+header: Content-Length: 3055
+header: Etag:
+header: Content-Type: text/html
+header: Connection: close
+>>> while 1:
+... data = u.read()
+... if not data: break
+... print data
+...
+
+::
+
+ [blah blah blah]
+
+ <p>
+ Go directly to the <a href="/manage" target="_top">
+ Zope Management Interface</a> if you'd like to start working with Zope
+ right away. <strong>NOTE: Some versions of Microsoft Internet Explorer,
+ (specifically IE 5.01 and early versions of IE 5.5) may have problems
+ displaying Zope management pages. If you cannot view the management pages,
+ try upgrading your IE installation to the latest release version, or use
+ a different browser.</strong>
+ </p>
+ </li>
+
+ <li>
+ <p>
+ Find out about <a href="http://www.zope.com/" target="_new">Zope
+ Corporation</a>, the publishers of Zope.
+ </p>
+ </li>
+
+ </ul>
+
+ </body>
+ </html>
+
+>>> u.close()
+>>>
+
+
+XMLRPC-over-HTTPS
+```````````````````
+
+>>> from M2Crypto.m2xmlrpclib import Server, SSL_Transport
+>>> zs = Server('https://127.0.0.1:8443/', SSL_Transport())
+>>> print zs.propertyMap()
+[{'type': 'string', 'id': 'title', 'mode': 'w'}]
+>>>
+
+
+Conclusion
+------------
+
+Yes, it works! ;-)
+
diff --git a/demo/Zope27/install_dir/lib/python/ZServer/HTTPS_Server.py b/demo/Zope27/install_dir/lib/python/ZServer/HTTPS_Server.py
new file mode 100644
index 0000000..49b6177
--- /dev/null
+++ b/demo/Zope27/install_dir/lib/python/ZServer/HTTPS_Server.py
@@ -0,0 +1,187 @@
+##############################################################################
+#
+# Copyright (c) 2000-2003, Ng Pheng Siong. All Rights Reserved.
+# This file is derived from Zope's ZServer/HTTPServer.py.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE
+#
+##############################################################################
+
+"""
+Medusa HTTPS server for Zope
+
+changes from Medusa's http_server:
+
+ Request Threads -- Requests are processed by threads from a thread
+ pool.
+
+ Output Handling -- Output is pushed directly into the producer
+ fifo by the request-handling thread. The HTTP server does not do
+ any post-processing such as chunking.
+
+ Pipelineable -- This is needed for protocols such as HTTP/1.1 in
+ which mutiple requests come in on the same channel, before
+ responses are sent back. When requests are pipelined, the client
+ doesn't wait for the response before sending another request. The
+ server must ensure that responses are sent back in the same order
+ as requests are received.
+
+
+changes from Zope's HTTP server:
+
+ Well, this is a *HTTPS* server :)
+
+ X.509 certificate-based authentication -- When this is in force,
+ zhttps_handler, a subclass of zhttp_handler, is installed. The
+ https server is configured to request an X.509 certificate from
+ the client. When the request reaches zhttps_handler, it sets
+ REMOTE_USER to the client's subject distinguished name (DN) from
+ the certificate. Zope's REMOTE_USER machinery takes care of the
+ rest, e.g., in conjunction with the RemoteUserFolder product.
+
+"""
+
+import sys, time, types
+
+from PubCore import handle
+import asyncore
+from ZServer import CONNECTION_LIMIT, ZOPE_VERSION
+from HTTPServer import zhttp_handler
+from zLOG import register_subsystem
+
+from M2Crypto import SSL
+from medusa.https_server import https_server, https_channel
+from asyncore import dispatcher
+
+
+ZSERVER_SSL_VERSION='0.12'
+
+register_subsystem('ZServer HTTPS_Server')
+
+
+class zhttps0_handler(zhttp_handler):
+ "zhttps0 handler - sets SSL request headers a la mod_ssl"
+
+ def __init__ (self, module, uri_base=None, env=None):
+ zhttp_handler.__init__(self, module, uri_base, env)
+
+ def get_environment(self, request):
+ env = zhttp_handler.get_environment(self, request)
+ env['SSL_CIPHER'] = request.channel.get_cipher()
+ return env
+
+
+class zhttps_handler(zhttps0_handler):
+ "zhttps handler - sets REMOTE_USER to user's X.509 certificate Subject DN"
+
+ def __init__ (self, module, uri_base=None, env=None):
+ zhttps0_handler.__init__(self, module, uri_base, env)
+
+ def get_environment(self, request):
+ env = zhttps0_handler.get_environment(self, request)
+ peer = request.channel.get_peer_cert()
+ if peer is not None:
+ env['REMOTE_USER'] = str(peer.get_subject())
+ return env
+
+
+class zhttps_channel(https_channel):
+ "https channel"
+
+ closed=0
+ zombie_timeout=100*60 # 100 minutes
+
+ def __init__(self, server, conn, addr):
+ https_channel.__init__(self, server, conn, addr)
+ self.queue=[]
+ self.working=0
+ self.peer_found=0
+
+ def push(self, producer, send=1):
+ # this is thread-safe when send is false
+ # note, that strings are not wrapped in
+ # producers by default
+ if self.closed:
+ return
+ self.producer_fifo.push(producer)
+ if send: self.initiate_send()
+
+ push_with_producer=push
+
+ def work(self):
+ "try to handle a request"
+ if not self.working:
+ if self.queue:
+ self.working=1
+ try: module_name, request, response=self.queue.pop(0)
+ except: return
+ handle(module_name, request, response)
+
+ def close(self):
+ self.closed=1
+ while self.queue:
+ self.queue.pop()
+ if self.current_request is not None:
+ self.current_request.channel=None # break circ refs
+ self.current_request=None
+ while self.producer_fifo:
+ p=self.producer_fifo.first()
+ if p is not None and type(p) != types.StringType:
+ p.more() # free up resources held by producer
+ self.producer_fifo.pop()
+ self.del_channel()
+ #self.socket.set_shutdown(SSL.SSL_SENT_SHUTDOWN|SSL.SSL_RECEIVED_SHUTDOWN)
+ self.socket.close()
+
+ def done(self):
+ "Called when a publishing request is finished"
+ self.working=0
+ self.work()
+
+ def kill_zombies(self):
+ now = int (time.time())
+ for channel in asyncore.socket_map.values():
+ if channel.__class__ == self.__class__:
+ if (now - channel.creation_time) > channel.zombie_timeout:
+ channel.close()
+
+
+class zhttps_server(https_server):
+ "https server"
+
+ SERVER_IDENT='ZServerSSL/%s' % (ZSERVER_SSL_VERSION,)
+
+ channel_class = zhttps_channel
+ shutup = 0
+
+ def __init__(self, ip, port, ssl_ctx, resolver=None, logger_object=None):
+ self.shutup = 1
+ https_server.__init__(self, ip, port, ssl_ctx, resolver, logger_object)
+ self.ssl_ctx = ssl_ctx
+ self.shutup = 0
+ self.log_info('(%s) HTTPS server started at %s\n'
+ '\tHostname: %s\n\tPort: %d' % (
+ self.SERVER_IDENT,
+ time.ctime(time.time()),
+ self.server_name,
+ self.server_port
+ ))
+
+ def log_info(self, message, type='info'):
+ if self.shutup: return
+ dispatcher.log_info(self, message, type)
+
+ def readable(self):
+ return self.accepting and \
+ len(asyncore.socket_map) < CONNECTION_LIMIT
+
+ def listen(self, num):
+ # override asyncore limits for nt's listen queue size
+ self.accepting = 1
+ return self.socket.listen (num)
+
diff --git a/demo/Zope27/install_dir/lib/python/ZServer/__init__.py.patch b/demo/Zope27/install_dir/lib/python/ZServer/__init__.py.patch
new file mode 100644
index 0000000..0ba84b7
--- /dev/null
+++ b/demo/Zope27/install_dir/lib/python/ZServer/__init__.py.patch
@@ -0,0 +1,10 @@
+--- __init__.py.org Sat Sep 27 20:23:00 2003
++++ __init__.py Sun Oct 26 18:01:27 2003
+@@ -68,6 +68,7 @@
+ import asyncore
+ from medusa import resolver, logger
+ from HTTPServer import zhttp_server, zhttp_handler
++from HTTPS_Server import zhttps_server, zhttps0_handler, zhttps_handler
+ from PCGIServer import PCGIServer
+ from FCGIServer import FCGIServer
+ from FTPServer import FTPServer
diff --git a/demo/Zope27/install_dir/lib/python/ZServer/component.xml.patch b/demo/Zope27/install_dir/lib/python/ZServer/component.xml.patch
new file mode 100644
index 0000000..364d91f
--- /dev/null
+++ b/demo/Zope27/install_dir/lib/python/ZServer/component.xml.patch
@@ -0,0 +1,28 @@
+--- component.xml.org Sat Sep 27 20:21:22 2003
++++ component.xml Sat Sep 27 21:11:26 2003
+@@ -21,6 +21,25 @@
+ </key>
+ </sectiontype>
+
++ <sectiontype name="https-server"
++ datatype=".HTTPS_ServerFactory"
++ implements="ZServer.server">
++ <key name="address" datatype="inet-address"/>
++ <key name="force-connection-close" datatype="boolean" default="off"/>
++ <key name="webdav-source-clients">
++ <description>
++ Regular expression used to identify clients who should
++ receive WebDAV source responses to GET requests.
++ </description>
++ </key>
++ <key name="x509-remote-user" datatype="boolean" default="on">
++ <description>
++ If "on", request client X.509 certificate and set REMOTE_USER to
++ said certificate's Subject Distinguished Name.
++ </description>
++ </key>
++ </sectiontype>
++
+ <sectiontype name="webdav-source-server"
+ datatype=".WebDAVSourceServerFactory"
+ implements="ZServer.server">
diff --git a/demo/Zope27/install_dir/lib/python/ZServer/datatypes.py.patch b/demo/Zope27/install_dir/lib/python/ZServer/datatypes.py.patch
new file mode 100644
index 0000000..b2ad9c8
--- /dev/null
+++ b/demo/Zope27/install_dir/lib/python/ZServer/datatypes.py.patch
@@ -0,0 +1,59 @@
+--- datatypes.py.org Sat Sep 27 20:21:15 2003
++++ datatypes.py Sun Oct 26 21:19:58 2003
+@@ -72,7 +72,56 @@
+
+ def createHandler(self):
+ from ZServer import HTTPServer
++ try:
++ del self.cgienv['HTTPS']
++ except KeyError:
++ pass
+ return HTTPServer.zhttp_handler(self.module, '', self.cgienv)
++
++
++class HTTPS_ServerFactory(HTTPServerFactory):
++ def __init__(self, section):
++ HTTPServerFactory.__init__(self, section)
++ self.x509_remote_user = section.x509_remote_user
++ from M2Crypto import Rand, SSL
++ Rand.load_file('%s/randpool.dat' % INSTANCE_HOME, -1)
++ ssl_ctx = SSL.Context('sslv23')
++ ssl_ctx.load_cert_chain('%s/ssl/server.pem' % INSTANCE_HOME)
++ ssl_ctx.load_verify_locations('%s/ssl/ca.pem' % INSTANCE_HOME,'')
++ ssl_ctx.load_client_CA('%s/ssl/ca.pem' % INSTANCE_HOME)
++ ssl_ctx.set_session_id_ctx('Zope 2.7.0b2')
++ ssl_ctx.set_tmp_dh('%s/ssl/dh1024.pem' % INSTANCE_HOME)
++ if self.x509_remote_user:
++ ssl_ctx.set_verify(SSL.verify_peer, 10)
++ else:
++ ssl_ctx.set_verify(SSL.verify_none, 10)
++ self.ssl_ctx = ssl_ctx
++
++ def create(self):
++ from ZServer import HTTPS_Server
++ from ZServer.AccessLogger import access_logger
++ handler = self.createHandler()
++ handler._force_connection_close = self.force_connection_close
++ if self.webdav_source_clients:
++ handler.set_webdav_source_clients(self.webdav_source_clients)
++ server = HTTPS_Server.zhttps_server(ip=self.host, port=self.port,
++ ssl_ctx=self.ssl_ctx,
++ resolver=self.dnsresolver,
++ logger_object=access_logger)
++ server.install_handler(handler)
++ return server
++
++ def createHandler(self):
++ from ZServer import HTTPS_Server
++ try:
++ del self.cgienv['HTTP']
++ except KeyError:
++ pass
++ self.cgienv['HTTPS'] = 'ON'
++ if self.x509_remote_user:
++ return HTTPS_Server.zhttps_handler(self.module, '', self.cgienv)
++ else:
++ return HTTPS_Server.zhttps0_handler(self.module, '', self.cgienv)
+
+
+ class WebDAVSourceServerFactory(HTTPServerFactory):
diff --git a/demo/Zope27/install_dir/lib/python/ZServer/medusa/https_server.py b/demo/Zope27/install_dir/lib/python/ZServer/medusa/https_server.py
new file mode 100644
index 0000000..84a0197
--- /dev/null
+++ b/demo/Zope27/install_dir/lib/python/ZServer/medusa/https_server.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+
+"""A https server built on Medusa's http_server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import asynchat, asyncore, http_server, socket, sys
+from M2Crypto import SSL, version
+
+VERSION_STRING=version
+
+class https_channel(http_server.http_channel):
+
+ ac_in_buffer_size = 1 << 16
+
+ def __init__(self, server, conn, addr):
+ http_server.http_channel.__init__(self, server, conn, addr)
+
+ def send(self, data):
+ try:
+ result = self.socket._write_nbio(data)
+ if result <= 0:
+ return 0
+ else:
+ self.server.bytes_out.increment(result)
+ return result
+ except SSL.SSLError, why:
+ self.close()
+ self.log_info('send: closing channel %s %s' % (repr(self), why))
+ return 0
+
+ def recv(self, buffer_size):
+ try:
+ result = self.socket._read_nbio(buffer_size)
+ if result is None:
+ return ''
+ elif result == '':
+ self.close()
+ return ''
+ else:
+ self.server.bytes_in.increment(len(result))
+ return result
+ except SSL.SSLError, why:
+ self.close()
+ self.log_info('recv: closing channel %s %s' % (repr(self), why))
+ return ''
+
+
+class https_server(http_server.http_server):
+
+ SERVER_IDENT='M2Crypto HTTPS Server (v%s)' % VERSION_STRING
+
+ channel_class=https_channel
+
+ def __init__(self, ip, port, ssl_ctx, resolver=None, logger_object=None):
+ http_server.http_server.__init__(self, ip, port, resolver, logger_object)
+ self.ssl_ctx=ssl_ctx
+
+ def handle_accept(self):
+ # Cribbed from http_server.
+ self.total_clients.increment()
+ try:
+ conn, addr = self.accept()
+ except socket.error:
+ # linux: on rare occasions we get a bogus socket back from
+ # accept. socketmodule.c:makesockaddr complains that the
+ # address family is unknown. We don't want the whole server
+ # to shut down because of this.
+ sys.stderr.write ('warning: server accept() threw an exception\n')
+ return
+
+ # Turn the vanilla socket into an SSL connection.
+ try:
+ ssl_conn=SSL.Connection(self.ssl_ctx, conn)
+ ssl_conn._setup_ssl(addr)
+ ssl_conn.accept_ssl()
+ self.channel_class(self, ssl_conn, addr)
+ except SSL.SSLError:
+ pass
+
+ def writeable(self):
+ return 0
+
diff --git a/demo/Zope27/instance_home/README.txt.patch b/demo/Zope27/instance_home/README.txt.patch
new file mode 100644
index 0000000..0d39005
--- /dev/null
+++ b/demo/Zope27/instance_home/README.txt.patch
@@ -0,0 +1,7 @@
+--- README.txt.org Sat Sep 27 20:56:11 2003
++++ README.txt Sat Sep 27 20:56:44 2003
+@@ -7,3 +7,4 @@
+ log/ Log files
+ Products/ Installed products specific to the instance
+ var/ Run-time data files, including the object database
++ ssl/ ZServerSSL data files
diff --git a/demo/Zope27/instance_home/etc/zope.conf.patch b/demo/Zope27/instance_home/etc/zope.conf.patch
new file mode 100644
index 0000000..cc9b093
--- /dev/null
+++ b/demo/Zope27/instance_home/etc/zope.conf.patch
@@ -0,0 +1,16 @@
+--- zope.conf.org Sat Sep 27 21:03:22 2003
++++ zope.conf Sat Sep 27 21:05:08 2003
+@@ -650,6 +650,13 @@
+ # force-connection-close on
+ </http-server>
+
++<https-server>
++ # valid keys are "address", "force-connection-close" and "x509-remote-user"
++ address 8443
++ # force-connection-close on
++ x509-remote-user on
++</https-server>
++
+ <ftp-server>
+ # valid key is "address"
+ address 8021
diff --git a/demo/Zope27/instance_home/ssl/ca.pem b/demo/Zope27/instance_home/ssl/ca.pem
new file mode 100644
index 0000000..b7c84a1
--- /dev/null
+++ b/demo/Zope27/instance_home/ssl/ca.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/Zope27/instance_home/ssl/dh1024.pem b/demo/Zope27/instance_home/ssl/dh1024.pem
new file mode 100644
index 0000000..81d43f6
--- /dev/null
+++ b/demo/Zope27/instance_home/ssl/dh1024.pem
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/Zope27/instance_home/ssl/server.pem b/demo/Zope27/instance_home/ssl/server.pem
new file mode 100644
index 0000000..1ee9282
--- /dev/null
+++ b/demo/Zope27/instance_home/ssl/server.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/ZopeX3/INSTALL.txt b/demo/ZopeX3/INSTALL.txt
new file mode 100644
index 0000000..67adbc8
--- /dev/null
+++ b/demo/ZopeX3/INSTALL.txt
@@ -0,0 +1,73 @@
+=========================================
+ ZServerSSL for ZopeX3.0.0
+=========================================
+
+:Author: Ng Pheng Siong
+:Id: $Id: INSTALL.txt 354 2006-02-25 00:13:15Z heikki $
+:Date: $Date$
+:Web-Site: http://sandbox.rulemaker.net/ngps/zope/zssl/
+
+.. contents::
+
+
+Directories
+-----------------------
+
+Directory structure assumes the following:
+
+- ZopeX3.0.0 is installed in <install-dir>.
+- An instance has been created in <instance-home>.
+
+
+<install-dir>
+-----------------------
+
+The following files are to be copied to the corresponding directories
+in your <install-dir>:
+
+- install_dir/lib/python/zope/server/http/https_server.py
+- install_dir/lib/python/zope/server/http/https_serverchannel.py
+- install_dir/lib/python/zope/server/http/publisherhttps_server.py
+- install_dir/lib/python/zope/app/server/https.py
+
+
+<instance-home>
+-----------------------
+
+The following files are to be copied to the corresponding directories
+in your <instance-home>:
+
+- instance_home/ssl/ca.pem
+- instance_home/ssl/server.pem
+- instance_home/ssl/dh1024.pem
+
+These are example files. For more information on them, consult the
+ZServerSSL HOWTO for Zope 2.6.
+
+The following patch files are to be applied to the corresponding
+directories:
+
+- install_dir/lib/python/zope/app/server/configure.zcml.patch
+- instance_home/etc/zope.conf.patch
+
+
+Launch ZServerSSL
+-------------------
+
+::
+
+ $ <instance-home>/bin/runzope
+
+
+Testing
+---------
+
+This section TDB. I have tested ZServerSSL for Zope 3 with 'openssl
+s_client' and 'openssl s_client -nbio' successfully.
+
+
+Conclusion
+------------
+
+Yes, it works! ;-)
+
diff --git a/demo/ZopeX3/install_dir/lib/python/zope/app/server/configure.zcml.patch b/demo/ZopeX3/install_dir/lib/python/zope/app/server/configure.zcml.patch
new file mode 100644
index 0000000..8d8f031
--- /dev/null
+++ b/demo/ZopeX3/install_dir/lib/python/zope/app/server/configure.zcml.patch
@@ -0,0 +1,28 @@
+--- configure.zcml.org Mon Sep 27 16:10:47 2004
++++ configure.zcml Mon Sep 27 16:11:42 2004
+@@ -5,6 +5,12 @@
+ provides="zope.app.applicationcontrol.interfaces.IServerControl" />
+
+ <utility
++ name="HTTPS"
++ component=".https.https"
++ provides=".servertype.IServerType"
++ />
++
++ <utility
+ name="HTTP"
+ component=".http.http"
+ provides=".servertype.IServerType"
+@@ -13,6 +19,12 @@
+ <utility
+ name="PostmortemDebuggingHTTP"
+ component=".http.pmhttp"
++ provides=".servertype.IServerType"
++ />
++
++ <utility
++ name="PostmortemDebuggingHTTPS"
++ component=".https.pmhttps"
+ provides=".servertype.IServerType"
+ />
+
diff --git a/demo/ZopeX3/install_dir/lib/python/zope/app/server/https.py b/demo/ZopeX3/install_dir/lib/python/zope/app/server/https.py
new file mode 100644
index 0000000..2278f3e
--- /dev/null
+++ b/demo/ZopeX3/install_dir/lib/python/zope/app/server/https.py
@@ -0,0 +1,28 @@
+##############################################################################
+#
+# Copyright (c) 2004, Ng Pheng Siong.
+# All Rights Reserved.
+#
+# XXX license TBD; should be Zope 3's ZPL, I just haven't read thru that.
+#
+##############################################################################
+"""HTTPS server factories
+
+$Id: https.py 240 2004-10-02 12:40:14Z ngps $
+"""
+
+from zope.app.publication.httpfactory import HTTPPublicationRequestFactory
+from zope.app.server.servertype import ServerType
+from zope.server.http.commonaccesslogger import CommonAccessLogger
+from zope.server.http.publisherhttps_server import PMDBHTTPS_Server
+from zope.server.http.publisherhttps_server import PublisherHTTPS_Server
+
+https = ServerType(PublisherHTTPS_Server,
+ HTTPPublicationRequestFactory,
+ CommonAccessLogger,
+ 8443, True)
+
+pmhttps = ServerType(PMDBHTTPS_Server,
+ HTTPPublicationRequestFactory,
+ CommonAccessLogger,
+ 8376, True)
diff --git a/demo/ZopeX3/install_dir/lib/python/zope/server/http/https_server.py b/demo/ZopeX3/install_dir/lib/python/zope/server/http/https_server.py
new file mode 100644
index 0000000..9e1eb3e
--- /dev/null
+++ b/demo/ZopeX3/install_dir/lib/python/zope/server/http/https_server.py
@@ -0,0 +1,104 @@
+##############################################################################
+#
+# Copyright (c) 2004, Ng Pheng Siong.
+# All Rights Reserved.
+#
+# XXX license TBD; should be Zope 3's ZPL, I just haven't read thru that.
+#
+##############################################################################
+"""HTTPS Server
+
+This is a HTTPS version of HTTPServer.
+
+$Id: https_server.py 240 2004-10-02 12:40:14Z ngps $
+"""
+
+import asyncore, logging, os.path
+
+from zope.server.http.httpserver import HTTPServer
+from zope.server.http.https_serverchannel import HTTPS_ServerChannel
+from M2Crypto import SSL, version
+
+
+# 2004-09-27, ngps:
+# 'sslv2' or 'sslv23' interoperates with Firefox and IE.
+# 'sslv3' or 'tlsv1' doesn't.
+def make_ssl_context(dir, ssl_proto='sslv23'):
+ sslctx = SSL.Context(ssl_proto)
+ sslctx.load_cert(os.path.join(dir, 'server.pem'))
+ sslctx.load_verify_locations(os.path.join(dir, 'ca.pem'))
+ sslctx.load_client_CA(os.path.join(dir, 'ca.pem'))
+ sslctx.set_verify(SSL.verify_none, 10)
+ sslctx.set_session_id_ctx('someblahblahthing')
+ sslctx.set_tmp_dh(os.path.join(dir, 'dh1024.pem'))
+ #sslctx.set_info_callback() # debugging only; not thread-safe
+ return sslctx
+
+
+class HTTPS_Server(HTTPServer):
+ """This is a generic HTTPS Server."""
+
+ channel_class = HTTPS_ServerChannel
+ SERVER_IDENT = 'zope.server.zserverssl_https'
+
+ def __init__(self, ip, port, ssl_ctx=None, task_dispatcher=None, adj=None, start=1,
+ hit_log=None, verbose=0):
+ HTTPServer.__init__(self, ip, port, task_dispatcher, adj, start, hit_log, verbose)
+ if ssl_ctx is None:
+ self.ssl_ctx = make_ssl_context(os.path.realpath(__file__))
+ else:
+ self.ssl_ctx = ssl_ctx
+
+ def executeRequest(self, task):
+ """Execute an HTTP request."""
+ # This is a default implementation, meant to be overridden.
+ body = "The HTTPS server is running!\r\n" * 10
+ task.response_headers['Content-Type'] = 'text/plain'
+ task.response_headers['Content-Length'] = str(len(body))
+ task.write(body)
+
+ def handle_accept(self):
+ """See zope.server.interfaces.IDispatcherEventHandler"""
+ try:
+ v = self.accept()
+ if v is None:
+ return
+ conn, addr = v
+ except socket.error:
+ # Linux: On rare occasions we get a bogus socket back from
+ # accept. socketmodule.c:makesockaddr complains that the
+ # address family is unknown. We don't want the whole server
+ # to shut down because of this.
+ if self.adj.log_socket_errors:
+ self.log_info ('warning: server accept() threw an exception',
+ 'warning')
+ return
+ for (level, optname, value) in self.adj.socket_options:
+ conn.setsockopt(level, optname, value)
+ # Turn the vanilla socket into an SSL connection.
+ try:
+ ssl_conn = SSL.Connection(self.ssl_ctx, conn)
+ ssl_conn._setup_ssl(addr)
+ ssl_conn.accept_ssl()
+ self.channel_class(self, ssl_conn, addr, self.adj)
+ except SSL.SSLError, why:
+ self.log_info('accept: cannot make SSL connection %s' % (why,), 'warning')
+ pass
+
+
+
+if __name__ == '__main__':
+
+ from zope.server.taskthreads import ThreadedTaskDispatcher
+ td = ThreadedTaskDispatcher()
+ td.setThreadCount(4)
+ HTTPS_Server('', 8443, ssl_ctx=None, task_dispatcher=td, verbose=1)
+
+ try:
+ import asyncore
+ while 1:
+ asyncore.poll(5)
+
+ except KeyboardInterrupt:
+ print 'shutting down...'
+ td.shutdown()
diff --git a/demo/ZopeX3/install_dir/lib/python/zope/server/http/https_serverchannel.py b/demo/ZopeX3/install_dir/lib/python/zope/server/http/https_serverchannel.py
new file mode 100644
index 0000000..c33a84d
--- /dev/null
+++ b/demo/ZopeX3/install_dir/lib/python/zope/server/http/https_serverchannel.py
@@ -0,0 +1,55 @@
+##############################################################################
+#
+# Copyright (c) 2004, Ng Pheng Siong.
+# All Rights Reserved.
+#
+# XXX license TBD; should be Zope 3's ZPL, I just haven't read thru that.
+#
+##############################################################################
+"""HTTPS Server Channel
+
+$Id: https_serverchannel.py 240 2004-10-02 12:40:14Z ngps $
+"""
+from zope.server.serverchannelbase import ServerChannelBase
+from zope.server.http.httptask import HTTPTask
+from zope.server.http.httprequestparser import HTTPRequestParser
+from zope.server.http.httpserverchannel import HTTPServerChannel
+from M2Crypto import SSL
+
+
+class HTTPS_ServerChannel(HTTPServerChannel):
+ """HTTPS-specific Server Channel"""
+
+ task_class = HTTPTask
+ parser_class = HTTPRequestParser
+
+ def send(self, data):
+ try:
+ result = self.socket._write_nbio(data)
+ if result <= 0:
+ return 0
+ else:
+ #self.server.bytes_out.increment(result)
+ return result
+ except SSL.SSLError, why:
+ self.close()
+ self.log_info('send: closing channel %s %s' % (repr(self), why), 'warning')
+ return 0
+
+ def recv(self, buffer_size):
+ try:
+ result = self.socket._read_nbio(buffer_size)
+ if result is None:
+ return ''
+ elif result == '':
+ self.close()
+ return ''
+ else:
+ #self.server.bytes_in.increment(len(result))
+ return result
+ except SSL.SSLError, why:
+ self.close()
+ self.log_info('recv: closing channel %s %s' % (repr(self), why), 'warning')
+ return ''
+
+
diff --git a/demo/ZopeX3/install_dir/lib/python/zope/server/http/publisherhttps_server.py b/demo/ZopeX3/install_dir/lib/python/zope/server/http/publisherhttps_server.py
new file mode 100644
index 0000000..aa8738d
--- /dev/null
+++ b/demo/ZopeX3/install_dir/lib/python/zope/server/http/publisherhttps_server.py
@@ -0,0 +1,83 @@
+##############################################################################
+#
+# Copyright (c) 2004, Ng Pheng Siong.
+# All Rights Reserved.
+#
+# XXX license TBD; should be Zope 3's ZPL, I just haven't read thru that.
+#
+##############################################################################
+"""HTTPS Server that uses the Zope Publisher for executing a task.
+
+$Id: publisherhttps_server.py 240 2004-10-02 12:40:14Z ngps $
+"""
+import os.path, sys
+from zope.server.http.https_server import HTTPS_Server, make_ssl_context
+from zope.publisher.publish import publish
+
+
+def get_instance_ssldir():
+ # This is real cheesy: It seems Zope3 doesn't have convenient
+ # programmatic access to INSTANCE_HOME. This code relies on zopectl
+ # setting the first entry of PYTHONPATH to $INSTANCE_HOME/lib/python.
+ return os.path.join(os.path.dirname(os.path.dirname(sys.path[0])), 'ssl')
+
+
+class PublisherHTTPS_Server(HTTPS_Server):
+ """Zope Publisher-specific HTTPS Server"""
+
+ def __init__(self, request_factory, sub_protocol=None, *args, **kw):
+
+ # The common HTTP
+ self.request_factory = request_factory
+
+ # An HTTP server is not limited to serving up HTML; it can be
+ # used for other protocols, like XML-RPC, SOAP and so as well
+ # Here we just allow the logger to output the sub-protocol type.
+ if sub_protocol:
+ self.SERVER_IDENT += ' (%s)' %str(sub_protocol)
+
+ kw['ssl_ctx'] = make_ssl_context(get_instance_ssldir())
+ HTTPS_Server.__init__(self, *args, **kw)
+
+ def executeRequest(self, task):
+ """Overrides HTTPServer.executeRequest()."""
+ env = task.getCGIEnvironment()
+ env['HTTPS'] = 'ON'
+ try:
+ del env['HTTP']
+ except KeyError:
+ pass
+ instream = task.request_data.getBodyStream()
+
+ request = self.request_factory(instream, task, env)
+ response = request.response
+ response.setHeaderOutput(task)
+ response.setHTTPTransaction(task)
+ publish(request)
+
+
+class PMDBHTTPS_Server(PublisherHTTPS_Server):
+ """Enter the post-mortem debugger when there's an error"""
+
+ def executeRequest(self, task):
+ """Overrides HTTPServer.executeRequest()."""
+ env = task.getCGIEnvironment()
+ env['HTTPS'] = 'ON'
+ try:
+ del env['HTTP']
+ except KeyError:
+ pass
+ instream = task.request_data.getBodyStream()
+
+ request = self.request_factory(instream, task, env)
+ response = request.response
+ response.setHeaderOutput(task)
+ try:
+ publish(request, handle_errors=False)
+ except:
+ import sys, pdb
+ print "%s:" % sys.exc_info()[0]
+ print sys.exc_info()[1]
+ pdb.post_mortem(sys.exc_info()[2])
+ raise
+
diff --git a/demo/ZopeX3/instance_home/etc/zope.conf.patch b/demo/ZopeX3/instance_home/etc/zope.conf.patch
new file mode 100644
index 0000000..f281246
--- /dev/null
+++ b/demo/ZopeX3/instance_home/etc/zope.conf.patch
@@ -0,0 +1,26 @@
+--- zope.conf.org Tue Sep 28 09:49:02 2004
++++ zope.conf Tue Sep 28 09:49:27 2004
+@@ -20,6 +20,11 @@
+ address 8080
+ </server>
+
++<server>
++ type HTTPS
++ address 8443
++</server>
++
+ # For debugging purposes, you can use this publisher instead/as well
+ # (obviously if it's as well, use a different port number). If there's
+ # an exception, Zope will drop into pdb at the point of the exception.
+@@ -27,6 +32,11 @@
+ #<server>
+ # type PostmortemDebuggingHTTP
+ # address 8080
++#</server>
++#
++#<server>
++# type PostmortemDebuggingHTTPS
++# address 8443
+ #</server>
+
+ <server>
diff --git a/demo/ZopeX3/instance_home/ssl/ca.pem b/demo/ZopeX3/instance_home/ssl/ca.pem
new file mode 100644
index 0000000..b7c84a1
--- /dev/null
+++ b/demo/ZopeX3/instance_home/ssl/ca.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/ZopeX3/instance_home/ssl/dh1024.pem b/demo/ZopeX3/instance_home/ssl/dh1024.pem
new file mode 100644
index 0000000..81d43f6
--- /dev/null
+++ b/demo/ZopeX3/instance_home/ssl/dh1024.pem
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/ZopeX3/instance_home/ssl/server.pem b/demo/ZopeX3/instance_home/ssl/server.pem
new file mode 100644
index 0000000..1ee9282
--- /dev/null
+++ b/demo/ZopeX3/instance_home/ssl/server.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/bio_mem_rw.py b/demo/bio_mem_rw.py
new file mode 100644
index 0000000..bcb78b8
--- /dev/null
+++ b/demo/bio_mem_rw.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python2.0
+
+"""Demonstrates the use of m2.bio_set_mem_eof_return().
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import m2
+m2.lib_init()
+
+use_mem = 1
+
+if use_mem:
+ bio = m2.bio_new(m2.bio_s_mem())
+else:
+ bio = m2.bio_new_file('XXX', 'wb')
+ciph = m2.bf_cbc()
+filt = m2.bio_new(m2.bio_f_cipher())
+m2.bio_set_cipher(filt, ciph, 'key', 'iv', 1)
+m2.bio_push(filt, bio)
+m2.bio_write(filt, '12345678901234567890')
+m2.bio_flush(filt)
+m2.bio_pop(filt)
+m2.bio_free(filt)
+if use_mem:
+ m2.bio_set_mem_eof_return(bio, 0)
+ xxx = m2.bio_read(bio, 100)
+ print `xxx`, len(xxx)
+m2.bio_free(bio)
+
+if use_mem:
+ bio = m2.bio_new(m2.bio_s_mem())
+ m2.bio_write(bio, xxx)
+ m2.bio_set_mem_eof_return(bio, 0)
+else:
+ bio = m2.bio_new_file('XXX', 'rb')
+ciph = m2.bf_cbc()
+filt = m2.bio_new(m2.bio_f_cipher())
+m2.bio_set_cipher(filt, ciph, 'key', 'iv', 0)
+m2.bio_push(filt, bio)
+yyy = m2.bio_read(filt, 100)
+print `yyy`
+m2.bio_pop(filt)
+m2.bio_free(filt)
+m2.bio_free(bio)
+
diff --git a/demo/dhtest.py b/demo/dhtest.py
new file mode 100644
index 0000000..9fd87c6
--- /dev/null
+++ b/demo/dhtest.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+"""DH demonstration.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import DH, Rand
+
+def test():
+ print 'generating dh params:'
+ a = DH.gen_params(128, 2)
+ b = DH.set_params(a.p, a.g)
+ a.gen_key()
+ b.gen_key()
+ print 'p = ', `a.p`
+ print 'g = ', `a.g`
+ print 'a.pub =', `a.pub`
+ print 'a.priv =', `a.priv`
+ print 'b.pub =', `b.pub`
+ print 'b.priv =', `b.priv`
+ print 'a.key = ', `a.compute_key(b.pub)`
+ print 'b.key = ', `b.compute_key(a.pub)`
+
+if __name__=='__main__':
+ Rand.load_file('randpool.dat', -1)
+ test()
+ Rand.save_file('randpool.dat')
diff --git a/demo/dsa1024pvtkey.pem b/demo/dsa1024pvtkey.pem
new file mode 100644
index 0000000..8379ed1
--- /dev/null
+++ b/demo/dsa1024pvtkey.pem
@@ -0,0 +1,12 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIIBvQIBAAKBgQD08iE3LXjSB/lA6Gq19XMzfmTvRBgFn+t9qNN6awFhrkowgyrI
+HaR6oCCHSvUcjdC0JcJdz8lSqofZkPknX6EEDkmlzZiUhtTZf0XiooeKigAiSlE2
+PpoS4RFcOOqOwVwRJI3mC2lzypI46/OPS0IOZFsxhXQpn1xnkmpEt83ZwwIVAPNw
+e6agkM25mv12Il7IuBBNl3cPAoGBAKOES1BV2E3zWj6gXOXFP02dk5A+zd7z4Qj+
+cx5euat07cAHYU0BZGJMTXlHSGf7YQOePuaxs9vTtSeUb1TeMFEr63Jispc9Kzce
+rd+E/IjiX7KCMbeGHnhtzC0FU/squZ76vp1TAXSozpfBvn73zAwAFPo/rHO4k6kH
+lXAez1QqAoGBAN6iYzbOnMckBtouHGBrdF4ea750DYnH5O2cij+yjgLMttuaxmZe
+0iFtJpXp6m4IHKAzIGgKhUGabAz+4O2/ZnmNu0oZzXkpBLL84pksDd0nObtgueL6
+sdTbhGl1kqpWRiK9T16gwqYxdcZiG5M5qbWtJIWWdv3mI9ql0XfUPWfpAhUA8e81
+iGFXunNE3ecKjCOKUL2EnEA=
+-----END DSA PRIVATE KEY-----
diff --git a/demo/dsa_bench.py b/demo/dsa_bench.py
new file mode 100644
index 0000000..cd4b1f9
--- /dev/null
+++ b/demo/dsa_bench.py
@@ -0,0 +1,176 @@
+#!/usr/bin/env python
+
+"""
+ DSA demo and benchmark.
+
+ Usage: python -O dsa_bench.py [option option option ...]
+ where options may include:
+ makenewkey showpubkey showdigest showprofile
+ md5 sha1 sha256 sha512
+ <key length>
+
+ NB:
+ DSA is formally defined with SHA-1 and key length 1024.
+ The OpenSSL implementation actually supports most any
+ hashing algorithm and key length, as long as the key
+ length is longer than the digest length. If not SHA-1
+ and 1024, you should be very clear. The use of "DSA"
+ without any qualifiers implies SHA-1 and 1024.
+
+ Larry Bugbee
+ November 2006
+
+
+ Some portions are Copyright (c) 1999-2003 Ng Pheng Siong.
+ All rights reserved.
+
+ Portions created by Open Source Applications Foundation
+ (OSAF) are Copyright (C) 2004 OSAF. All Rights Reserved.
+
+"""
+
+from M2Crypto import DSA, EVP, Rand
+from M2Crypto.EVP import MessageDigest
+import sys, base64
+
+# --------------------------------------------------------------
+# program parameters
+
+makenewkey = 0 # 1 = make/save new key, 0 = use existing
+showpubkey = 0 # 1 = show the public key value
+showdigest = 0 # 1 = show the digest value
+showprofile = 0 # 1 = use the python profiler
+
+hashalgs = ['md5', 'ripemd160', 'sha1',
+ 'sha224', 'sha256', 'sha384', 'sha512']
+
+# default hashing algorithm
+hashalg = 'sha1'
+
+# default key length
+keylen = 1024
+
+# number of speed test loops
+N1 = N2 = 100
+
+# --------------------------------------------------------------
+# functions
+
+def test(dsa, dgst):
+ print ' testing signing and verification...',
+ try:
+ r,s = dsa.sign(dgst)
+ except Exception, e:
+ print '\n\n *** %s *** \n' % e
+ sys.exit()
+ if not dsa.verify(dgst, r, s):
+ print 'not ok'
+ else:
+ print 'ok'
+
+def test_asn1(dsa, dgst):
+ # XXX Randomly fails: bug in there somewhere... (0.9.4)
+ print ' testing asn1 signing and verification...',
+ blob = dsa.sign_asn1(dgst)
+ if not dsa.verify_asn1(dgst, blob):
+ print 'not ok'
+ else:
+ print 'ok'
+
+def speed():
+ from time import time
+ t1 = time()
+ for i in range(N1):
+ r,s = dsa.sign(dgst)
+ print ' %d signings: %8.2fs' % (N1, (time() - t1))
+ t1 = time()
+ for i in range(N2):
+ dsa.verify(dgst, r, s)
+ print ' %d verifications: %8.2fs' % (N2, (time() - t1))
+
+def test_speed(dsa, dgst):
+ print ' measuring speed...'
+ if showprofile:
+ import profile
+ profile.run('speed()')
+ else:
+ speed()
+ print
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+def main(keylen, hashalg):
+ global dsa, dgst # this exists ONLY for speed testing
+
+ Rand.load_file('randpool.dat', -1)
+
+ pvtkeyfilename = 'DSA%dpvtkey.pem' % (keylen)
+ pubkeyfilename = 'DSA%dpubkey.pem' % (keylen)
+
+ if makenewkey:
+ print ' making and saving a new key'
+ dsa = DSA.gen_params(keylen)
+ dsa.gen_key()
+ dsa.save_key(pvtkeyfilename, None ) # no pswd callback
+ dsa.save_pub_key(pubkeyfilename)
+ else:
+ print ' loading an existing key'
+ dsa = DSA.load_key(pvtkeyfilename)
+ print ' dsa key length:', len(dsa)
+
+ if not dsa.check_key():
+ raise 'key is not initialised'
+
+ if showpubkey:
+ dsa_pub = dsa.pub
+ pub_pem = base64.encodestring(dsa_pub)
+ print ' PEM public key is: \n',pub_pem
+
+ # since we are testing signing and verification, let's not
+ # be fussy about the digest. Just make one.
+ md = EVP.MessageDigest(hashalg)
+ md.update('can you spell subliminal channel?')
+ dgst = md.digest()
+ print ' hash algorithm: %s' % hashalg
+ if showdigest:
+ print ' %s digest: \n%s' % (hashalg, base64.encodestring(dgst))
+
+ test(dsa, dgst)
+# test_asn1(dsa, dgst)
+ test_speed(dsa, dgst)
+ Rand.save_file('randpool.dat')
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+def print_usage():
+ print """
+ Usage: python -O %s [option option option ...]
+ where options may include:
+ makenewkey showpubkey showdigest showprofile
+ md5 sha1 sha256 sha512
+ <key length>
+""" % sys.argv[0]
+ sys.exit()
+
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+
+if __name__=='__main__':
+ for arg in sys.argv[1:]:
+ if arg in hashalgs: hashalg = arg; continue
+ if arg == 'makenewkey': makenewkey = 1; continue
+ if arg == 'showpubkey': showpubkey = 1; continue
+ if arg == 'showdigest': showdigest = 1; continue
+ if arg == 'showprofile': showprofile = 1; continue
+ try:
+ keylen = int(arg)
+ except:
+ print '\n *** argument "%s" not understood ***' % arg
+ print_usage()
+
+ main(keylen, hashalg)
+
+
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+# --------------------------------------------------------------
diff --git a/demo/dsatest.pem b/demo/dsatest.pem
new file mode 100644
index 0000000..8d5d97f
--- /dev/null
+++ b/demo/dsatest.pem
@@ -0,0 +1,8 @@
+-----BEGIN DSA PRIVATE KEY-----
+MIH4AgEAAkEA0NGZ0GRXdPLh/0c980Ot8ZbfV/DvJ19ZzsDhKXRxNNw36Ms4lb9Y
+ZMnJ1CliIDkpHx8sXEak0vkdeB2efGGBPQIVAJY7PF7CiA+jj+t3EyHf/sgVagPP
+AkEApkvDehftx8Kt+3GRsYkEgcKqsU6tue+QQOFOFYsCbMq/3rxIEKk0q1PqHfid
++BsMiEY4FFmF5BqmgGAf6+V9twJATbbgPKi/EboVrtBdkTM52LSCQHPa/CEcj322
+0s5Ix1dwojdQaNpq6HhCm6+g9SXPENy9I/PK85YnawI4A6w1pQIULRB2HSm1X14c
++guvmhIobv6wE50=
+-----END DSA PRIVATE KEY-----
diff --git a/demo/dsatest.py b/demo/dsatest.py
new file mode 100644
index 0000000..854b8b9
--- /dev/null
+++ b/demo/dsatest.py
@@ -0,0 +1,57 @@
+#!/usr/bin/env python
+
+"""DSA demonstration.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import DSA, EVP, Rand
+
+md=EVP.MessageDigest('sha1')
+md.update('can you spell subliminal channel?')
+dgst=md.digest()
+
+d=DSA.load_key('dsatest.pem')
+
+def test():
+ print 'testing signing...',
+ r,s=d.sign(dgst)
+ if not d.verify(dgst, r, s):
+ print 'not ok'
+ else:
+ print 'ok'
+
+def test_asn1():
+ # XXX Randomly fails: bug in there somewhere... (0.9.4)
+ print 'testing asn1 signing...',
+ blob=d.sign_asn1(dgst)
+ if not d.verify_asn1(dgst, blob):
+ print 'not ok'
+ else:
+ print 'ok'
+
+def speed():
+ from time import time
+ N1 = 5242
+ N2 = 2621
+ t1 = time()
+ for i in range(N1):
+ r,s = d.sign(dgst)
+ print '%d signings: %8.2fs' % (N1, (time() - t1))
+ t1 = time()
+ for i in range(N2):
+ d.verify(dgst, r, s)
+ print '%d verifications: %8.2fs' % (N2, (time() - t1))
+
+def test_speed():
+ print 'measuring speed...'
+ import profile
+ profile.run('speed()')
+
+
+if __name__=='__main__':
+ Rand.load_file('randpool.dat', -1)
+ test()
+ test_asn1()
+ #test_speed()
+ Rand.save_file('randpool.dat')
+
diff --git a/demo/ec/ecdhtest.py b/demo/ec/ecdhtest.py
new file mode 100644
index 0000000..6d407b6
--- /dev/null
+++ b/demo/ec/ecdhtest.py
@@ -0,0 +1,31 @@
+#!/usr/bin/env python
+
+"""ECDH demonstration.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+
+Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam.
+All rights reserved."""
+
+from M2Crypto import EC,Rand
+
+def test():
+ print 'generating ec keys:'
+ a=EC.gen_params(EC.NID_sect233k1)
+ a.gen_key()
+ b=EC.gen_params(EC.NID_sect233k1)
+ b.gen_key()
+ a_shared_key = a.compute_dh_key(b.pub())
+ b_shared_key = b.compute_dh_key(a.pub())
+ print 'shared key according to a = ', `a_shared_key`
+ print 'shared key according to b = ', `b_shared_key`
+ if a_shared_key == b_shared_key:
+ print 'ok'
+ else:
+ print 'not ok'
+
+
+if __name__=='__main__':
+ Rand.load_file('randpool.dat', -1)
+ test()
+ Rand.save_file('randpool.dat')
diff --git a/demo/ec/ecdsa_bench.py b/demo/ec/ecdsa_bench.py
new file mode 100644
index 0000000..bafceb1
--- /dev/null
+++ b/demo/ec/ecdsa_bench.py
@@ -0,0 +1,368 @@
+#!/usr/bin/env python
+
+"""
+ ECDSA demo and benchmark.
+
+ Usage: python -O ecdsa_bench.py [option option option ...]
+ where options may include:
+ makenewkey showpubkey showdigest showprofile
+ md5 sha1 sha256 sha512
+ secp160r1 secp224r1 secp192k1 sect283r1
+ sect283k1 secp256k1 secp384r1 secp521r1
+ (other curves and hashes are supported, see below)
+
+ Larry Bugbee, June 2006
+
+ Portions:
+ Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+ Copyright (c) 2005 Vrije Universiteit Amsterdam. All rights reserved.
+
+"""
+
+from M2Crypto import EC, EVP, Rand
+from M2Crypto.EVP import MessageDigest
+import sys, base64
+
+# --------------------------------------------------------------
+# program parameters
+
+makenewkey = 0 # 1 = make/save new key, 0 = use existing
+showpubkey = 0 # 1 = show the public key value
+showdigest = 0 # 1 = show the digest value
+showprofile = 0 # 1 = use the python profiler
+
+hashalgs = ['md5', 'ripemd160', 'sha1',
+ 'sha224', 'sha256', 'sha384', 'sha512']
+
+curves = ['secp112r1',
+ 'secp112r2',
+ 'secp128r1',
+ 'secp128r2',
+ 'secp160k1',
+ 'secp160r1',
+ 'secp160r2',
+ 'secp192k1',
+ 'secp224k1',
+ 'secp224r1',
+ 'secp256k1',
+ 'secp384r1',
+ 'secp521r1',
+ 'sect113r1',
+ 'sect113r2',
+ 'sect131r1',
+ 'sect131r2',
+ 'sect163k1',
+ 'sect163r1',
+ 'sect163r2',
+ 'sect193r1',
+ 'sect193r2',
+ 'sect233k1',
+ 'sect233r1',
+ 'sect239k1',
+ 'sect283k1',
+ 'sect283r1',
+ 'sect409k1',
+ 'sect409r1',
+ 'sect571k1',
+ 'sect571r1',
+ 'X9_62_prime192v1',
+ 'X9_62_prime192v2',
+ 'X9_62_prime192v3',
+ 'X9_62_prime239v1',
+ 'X9_62_prime239v2',
+ 'X9_62_prime239v3',
+ 'X9_62_prime256v1',
+ 'X9_62_c2pnb163v1',
+ 'X9_62_c2pnb163v2',
+ 'X9_62_c2pnb163v3',
+ 'X9_62_c2pnb176v1',
+ 'X9_62_c2tnb191v1',
+ 'X9_62_c2tnb191v2',
+ 'X9_62_c2tnb191v3',
+ 'X9_62_c2pnb208w1',
+ 'X9_62_c2tnb239v1',
+ 'X9_62_c2tnb239v2',
+ 'X9_62_c2tnb239v3',
+ 'X9_62_c2pnb272w1',
+ 'X9_62_c2pnb304w1',
+ 'X9_62_c2tnb359v1',
+ 'X9_62_c2pnb368w1',
+ 'X9_62_c2tnb431r1',
+ 'wap_wsg_idm_ecid_wtls1',
+ 'wap_wsg_idm_ecid_wtls3',
+ 'wap_wsg_idm_ecid_wtls4',
+ 'wap_wsg_idm_ecid_wtls5',
+ 'wap_wsg_idm_ecid_wtls6',
+ 'wap_wsg_idm_ecid_wtls7',
+ 'wap_wsg_idm_ecid_wtls8',
+ 'wap_wsg_idm_ecid_wtls9',
+ 'wap_wsg_idm_ecid_wtls10',
+ 'wap_wsg_idm_ecid_wtls11',
+ 'wap_wsg_idm_ecid_wtls12',
+ ]
+
+# The following two curves, according to OpenSSL, have a
+# "Questionable extension field!" and are not supported by
+# the OpenSSL inverse function. ECError: no inverse.
+# As such they cannot be used for signing. They might,
+# however, be usable for encryption but that has not
+# been tested. Until thir usefulness can be established,
+# they are not supported at this time.
+#
+# Oakley-EC2N-3:
+# IPSec/IKE/Oakley curve #3 over a 155 bit binary field.
+# Oakley-EC2N-4:
+# IPSec/IKE/Oakley curve #4 over a 185 bit binary field.
+#
+# aka 'ipsec3' and 'ipsec4'
+
+# curves2 is a shorthand convenience so as to not require the
+# entering the "X9_62_" prefix
+curves2 = ['prime192v1',
+ 'prime192v2',
+ 'prime192v3',
+ 'prime239v1',
+ 'prime239v2',
+ 'prime239v3',
+ 'prime256v1',
+ 'c2pnb163v1',
+ 'c2pnb163v2',
+ 'c2pnb163v3',
+ 'c2pnb176v1',
+ 'c2tnb191v1',
+ 'c2tnb191v2',
+ 'c2tnb191v3',
+ 'c2pnb208w1',
+ 'c2tnb239v1',
+ 'c2tnb239v2',
+ 'c2tnb239v3',
+ 'c2pnb272w1',
+ 'c2pnb304w1',
+ 'c2tnb359v1',
+ 'c2pnb368w1',
+ 'c2tnb431r1',
+ ]
+
+# default hashing algorithm
+hashalg = 'sha1'
+
+# default elliptical curve
+curve = 'secp160r1'
+
+# for a complete list of supported algorithms and curves, see
+# the bottom of this file
+
+# number of speed test loops
+N1 = N2 = 100
+
+# --------------------------------------------------------------
+# functions
+
+def test(ec, dgst):
+ print ' testing signing and verification...',
+ try:
+# ec = EC.gen_params(EC.NID_secp160r1)
+# ec.gen_key()
+ r,s = ec.sign_dsa(dgst)
+ except Exception, e:
+ print '\n\n *** %s *** \n' % e
+ sys.exit()
+ if not ec.verify_dsa(dgst, r, s):
+ print 'not ok'
+ else:
+ print 'ok'
+
+def test_asn1(ec, dgst):
+ print ' testing asn1 signing and verification...',
+ blob = ec.sign_dsa_asn1(dgst)
+ if not ec.verify_dsa_asn1(dgst, blob):
+ print 'not ok'
+ else:
+ print 'ok'
+
+def speed():
+ from time import time
+ t1 = time()
+ for i in range(N1):
+ r,s = ec.sign_dsa(dgst)
+ print ' %d signings: %8.2fs' % (N1, (time() - t1))
+ t1 = time()
+ for i in range(N2):
+ ec.verify_dsa(dgst, r, s)
+ print ' %d verifications: %8.2fs' % (N2, (time() - t1))
+
+def test_speed(ec, dgst):
+ print ' measuring speed...'
+ if showprofile:
+ import profile
+ profile.run('speed()')
+ else:
+ speed()
+ print
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+def main(curve, hashalg):
+ global ec, dgst # this exists ONLY for speed testing
+
+ Rand.load_file('randpool.dat', -1)
+
+ if curve in curves2:
+ curve = 'X9_62_' + curve
+ ec_curve = eval('EC.NID_%s' % curve)
+
+ pvtkeyfilename = '%spvtkey.pem' % (curve)
+ pubkeyfilename = '%spubkey.pem' % (curve)
+
+ if makenewkey:
+ print ' making and saving a new key'
+ ec = EC.gen_params(ec_curve)
+ ec.gen_key()
+ ec.save_key(pvtkeyfilename, None )
+ ec.save_pub_key(pubkeyfilename)
+ else:
+ print ' loading an existing key'
+ ec=EC.load_key(pvtkeyfilename)
+ print ' ecdsa key length:', len(ec)
+ print ' curve: %s' % curve
+
+ if not ec.check_key():
+ raise 'key is not initialised'
+
+ if showpubkey:
+ ec_pub = ec.pub()
+ pub_der = ec_pub.get_der()
+ pub_pem = base64.encodestring(pub_der)
+ print ' PEM public key is: \n',pub_pem
+
+ # since we are testing signing and verification, let's not
+ # be fussy about the digest. Just make one.
+ md = EVP.MessageDigest(hashalg)
+ md.update('can you spell subliminal channel?')
+ dgst = md.digest()
+ print ' hash algorithm: %s' % hashalg
+ if showdigest:
+ print ' %s digest: \n%s' % (base64.encodestring(dgst))
+
+ test(ec, dgst)
+# test_asn1(ec, dgst)
+ test_speed(ec, dgst)
+ Rand.save_file('randpool.dat')
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+def print_usage():
+ print """
+ Usage: python -O %s [option option option ...]
+ where options may include:
+ makenewkey showpubkey showdigest showprofile
+ md5 sha1 sha256 sha512
+ secp160r1 secp224r1 secp192k1 sect283r1
+ sect283k1 secp256k1 secp384r1 secp521r1
+ (other curves and hashes are supported, check pgm src)
+""" % sys.argv[0]
+ sys.exit()
+
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+
+if __name__=='__main__':
+ for arg in sys.argv[1:]:
+ if arg in hashalgs: hashalg = arg; continue
+ if arg in curves + curves2: curve = arg; continue
+ if arg == 'makenewkey': makenewkey = 1; continue
+ if arg == 'showpubkey': showpubkey = 1; continue
+ if arg == 'showdigest': showdigest = 1; continue
+ if arg == 'showprofile': showprofile = 1; continue
+
+ print '\n *** argument "%s" not understood ***' % arg
+ print_usage()
+
+ main(curve, hashalg)
+
+
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+
+
+"""
+ Elliptical curves supported by OpenSSL
+ ======================================
+
+$ openssl ecparam -list_curves
+ secp112r1 : SECG/WTLS curve over a 112 bit prime field
+ secp112r2 : SECG curve over a 112 bit prime field
+ secp128r1 : SECG curve over a 128 bit prime field
+ secp128r2 : SECG curve over a 128 bit prime field
+ secp160k1 : SECG curve over a 160 bit prime field
+ secp160r1 : SECG curve over a 160 bit prime field
+ secp160r2 : SECG/WTLS curve over a 160 bit prime field
+ secp192k1 : SECG curve over a 192 bit prime field
+ secp224k1 : SECG curve over a 224 bit prime field
+ secp224r1 : NIST/SECG curve over a 224 bit prime field
+ secp256k1 : SECG curve over a 256 bit prime field
+ secp384r1 : NIST/SECG curve over a 384 bit prime field
+ secp521r1 : NIST/SECG curve over a 521 bit prime field
+ prime192v1: NIST/X9.62/SECG curve over a 192 bit prime field
+ prime192v2: X9.62 curve over a 192 bit prime field
+ prime192v3: X9.62 curve over a 192 bit prime field
+ prime239v1: X9.62 curve over a 239 bit prime field
+ prime239v2: X9.62 curve over a 239 bit prime field
+ prime239v3: X9.62 curve over a 239 bit prime field
+ prime256v1: X9.62/SECG curve over a 256 bit prime field
+ sect113r1 : SECG curve over a 113 bit binary field
+ sect113r2 : SECG curve over a 113 bit binary field
+ sect131r1 : SECG/WTLS curve over a 131 bit binary field
+ sect131r2 : SECG curve over a 131 bit binary field
+ sect163k1 : NIST/SECG/WTLS curve over a 163 bit binary field
+ sect163r1 : SECG curve over a 163 bit binary field
+ sect163r2 : NIST/SECG curve over a 163 bit binary field
+ sect193r1 : SECG curve over a 193 bit binary field
+ sect193r2 : SECG curve over a 193 bit binary field
+ sect233k1 : NIST/SECG/WTLS curve over a 233 bit binary field
+ sect233r1 : NIST/SECG/WTLS curve over a 233 bit binary field
+ sect239k1 : SECG curve over a 239 bit binary field
+ sect283k1 : NIST/SECG curve over a 283 bit binary field
+ sect283r1 : NIST/SECG curve over a 283 bit binary field
+ sect409k1 : NIST/SECG curve over a 409 bit binary field
+ sect409r1 : NIST/SECG curve over a 409 bit binary field
+ sect571k1 : NIST/SECG curve over a 571 bit binary field
+ sect571r1 : NIST/SECG curve over a 571 bit binary field
+ c2pnb163v1: X9.62 curve over a 163 bit binary field
+ c2pnb163v2: X9.62 curve over a 163 bit binary field
+ c2pnb163v3: X9.62 curve over a 163 bit binary field
+ c2pnb176v1: X9.62 curve over a 176 bit binary field
+ c2tnb191v1: X9.62 curve over a 191 bit binary field
+ c2tnb191v2: X9.62 curve over a 191 bit binary field
+ c2tnb191v3: X9.62 curve over a 191 bit binary field
+ c2pnb208w1: X9.62 curve over a 208 bit binary field
+ c2tnb239v1: X9.62 curve over a 239 bit binary field
+ c2tnb239v2: X9.62 curve over a 239 bit binary field
+ c2tnb239v3: X9.62 curve over a 239 bit binary field
+ c2pnb272w1: X9.62 curve over a 272 bit binary field
+ c2pnb304w1: X9.62 curve over a 304 bit binary field
+ c2tnb359v1: X9.62 curve over a 359 bit binary field
+ c2pnb368w1: X9.62 curve over a 368 bit binary field
+ c2tnb431r1: X9.62 curve over a 431 bit binary field
+ wap-wsg-idm-ecid-wtls1: WTLS curve over a 113 bit binary field
+ wap-wsg-idm-ecid-wtls3: NIST/SECG/WTLS curve over a 163 bit binary field
+ wap-wsg-idm-ecid-wtls4: SECG curve over a 113 bit binary field
+ wap-wsg-idm-ecid-wtls5: X9.62 curve over a 163 bit binary field
+ wap-wsg-idm-ecid-wtls6: SECG/WTLS curve over a 112 bit prime field
+ wap-wsg-idm-ecid-wtls7: SECG/WTLS curve over a 160 bit prime field
+ wap-wsg-idm-ecid-wtls8: WTLS curve over a 112 bit prime field
+ wap-wsg-idm-ecid-wtls9: WTLS curve over a 160 bit prime field
+ wap-wsg-idm-ecid-wtls10: NIST/SECG/WTLS curve over a 233 bit binary field
+ wap-wsg-idm-ecid-wtls11: NIST/SECG/WTLS curve over a 233 bit binary field
+ wap-wsg-idm-ecid-wtls12: WTLS curvs over a 224 bit prime field
+ Oakley-EC2N-3:
+ IPSec/IKE/Oakley curve #3 over a 155 bit binary field.
+ Not suitable for ECDSA.
+ Questionable extension field!
+ Oakley-EC2N-4:
+ IPSec/IKE/Oakley curve #4 over a 185 bit binary field.
+ Not suitable for ECDSA.
+ Questionable extension field!
+
+"""
diff --git a/demo/ec/ecdsatest.pem b/demo/ec/ecdsatest.pem
new file mode 100644
index 0000000..cc37a65
--- /dev/null
+++ b/demo/ec/ecdsatest.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MG0CAQEEHVkQ54w5gN39TScPaC+TTKmJunupCfuNqEcWOZeXoAcGBSuBBAAaoUAD
+PgAEAIcWBNwIi1fP2Sd33wpayKBuw2oqBVvgvfYiipMcASzxCf6IFUC03IOob/Lu
+Y3mPHRZKwzSKBlD1ZkXh
+-----END EC PRIVATE KEY-----
diff --git a/demo/ec/ecdsatest.py b/demo/ec/ecdsatest.py
new file mode 100644
index 0000000..61ad625
--- /dev/null
+++ b/demo/ec/ecdsatest.py
@@ -0,0 +1,68 @@
+#!/usr/bin/env python
+
+"""ECDSA demonstration.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam. All rights reserved.
+"""
+
+from M2Crypto import EC, EVP, Rand
+import base64
+
+md=EVP.MessageDigest('sha1')
+md.update('can you spell subliminal channel?')
+dgst=md.digest()
+
+ec=EC.load_key('ecdsatest.pem')
+#ec=EC.gen_params(EC.NID_sect233k1)
+#ec.gen_key()
+ec_pub = ec.pub()
+pub_der = ec_pub.get_der()
+pub_pem = base64.encodestring(pub_der)
+print 'PEM public key is',pub_pem
+ec.save_key( 'ecdsatest.pem', None )
+
+
+def test():
+ print 'testing signing...',
+ r,s=ec.sign_dsa(dgst)
+ if not ec.verify_dsa(dgst, r, s):
+ print 'not ok'
+ else:
+ print 'ok'
+
+def test_asn1():
+ # XXX Randomly fails: bug in there somewhere... (0.9.4)
+ print 'testing asn1 signing...',
+ blob=ec.sign_dsa_asn1(dgst)
+ if not ec.verify_dsa_asn1(dgst, blob):
+ print 'not ok'
+ else:
+ print 'ok'
+
+def speed():
+ from time import time
+ N1 = 5242
+ N2 = 2621
+ t1 = time()
+ for i in range(N1):
+ r,s = ec.sign(dgst)
+ print '%d signings: %8.2fs' % (N1, (time() - t1))
+ t1 = time()
+ for i in range(N2):
+ ec.verify(dgst, r, s)
+ print '%d verifications: %8.2fs' % (N2, (time() - t1))
+
+def test_speed():
+ print 'measuring speed...'
+ import profile
+ profile.run('speed()')
+
+
+if __name__=='__main__':
+ Rand.load_file('randpool.dat', -1)
+ test()
+ test_asn1()
+ #test_speed()
+ Rand.save_file('randpool.dat')
+
diff --git a/demo/ec/secp160r1pvtkey.pem b/demo/ec/secp160r1pvtkey.pem
new file mode 100644
index 0000000..6eac827
--- /dev/null
+++ b/demo/ec/secp160r1pvtkey.pem
@@ -0,0 +1,4 @@
+-----BEGIN EC PRIVATE KEY-----
+MFACAQEEFN1C7AYNKDl9dBLfm0QW1nB7WGs7oAcGBSuBBAAIoSwDKgEErnCVbfXH
+11Ax4AZq4eh8c3gWIBeaRu6tGRpITFCjtHot78c/oVqBrg==
+-----END EC PRIVATE KEY-----
diff --git a/demo/https.howto/ca.pem b/demo/https.howto/ca.pem
new file mode 100644
index 0000000..d8ba0d3
--- /dev/null
+++ b/demo/https.howto/ca.pem
@@ -0,0 +1,59 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 0 (0x0)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=CA, O=M2Crypto CA, CN=localhost
+ Validity
+ Not Before: Apr 22 04:35:56 2006 GMT
+ Not After : Apr 21 04:35:56 2009 GMT
+ Subject: C=US, ST=CA, O=M2Crypto CA, CN=localhost
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:d8:80:02:8f:b5:7d:9f:9b:79:76:67:a5:66:64:
+ c0:30:0c:71:65:f1:c6:78:01:a0:29:d4:3a:2c:e5:
+ ee:58:4d:db:53:c5:74:6e:4e:f7:b6:a5:8e:ef:ab:
+ e8:7c:f5:5d:2d:18:ba:95:b8:15:43:6e:5a:78:c2:
+ 91:05:08:b2:7e:cf:c4:d3:bb:ac:c7:43:27:fb:8f:
+ 43:0d:7b:d0:d1:32:51:86:11:6e:3e:aa:68:19:88:
+ b9:cf:d5:72:f0:a4:73:d1:69:c4:65:14:0e:12:64:
+ 7e:1f:df:18:09:0b:6a:4b:cd:bf:ae:59:82:15:1c:
+ 90:0f:c3:e5:cb:b3:ed:86:4d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:TRUE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ 5C:F9:C5:9B:B0:02:37:3C:66:73:4D:0E:CB:5A:3D:BB:3A:46:22:DD
+ X509v3 Authority Key Identifier:
+ keyid:5C:F9:C5:9B:B0:02:37:3C:66:73:4D:0E:CB:5A:3D:BB:3A:46:22:DD
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 6b:9e:71:4a:ad:d2:1c:b7:58:1a:6e:8b:89:92:8d:4e:62:61:
+ 06:2e:e8:11:f8:9c:a0:e2:11:7c:b6:e2:be:ef:b9:b1:35:20:
+ d1:81:62:c5:ca:3c:4f:c9:88:72:f7:50:d8:e8:e0:06:43:ee:
+ c5:5c:38:9b:e7:24:46:a6:ee:8d:b0:70:4e:75:96:00:db:d6:
+ 59:f9:58:74:67:9f:ca:9c:12:fc:77:a7:0e:5a:38:22:5b:de:
+ c9:33:35:bd:d0:4c:9f:6a:0f:71:7b:db:cb:fd:da:bc:39:4f:
+ 23:1e:74:5b:ff:8d:73:72:16:a9:9f:57:54:96:3e:2c:f0:65:
+ af:df
+-----BEGIN CERTIFICATE-----
+MIICfDCCAeWgAwIBAgIBADANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEL
+MAkGA1UECBMCQ0ExFDASBgNVBAoTC00yQ3J5cHRvIENBMRIwEAYDVQQDEwlsb2Nh
+bGhvc3QwHhcNMDYwNDIyMDQzNTU2WhcNMDkwNDIxMDQzNTU2WjBEMQswCQYDVQQG
+EwJVUzELMAkGA1UECBMCQ0ExFDASBgNVBAoTC00yQ3J5cHRvIENBMRIwEAYDVQQD
+Ewlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBANiAAo+1fZ+b
+eXZnpWZkwDAMcWXxxngBoCnUOizl7lhN21PFdG5O97alju+r6Hz1XS0YupW4FUNu
+WnjCkQUIsn7PxNO7rMdDJ/uPQw170NEyUYYRbj6qaBmIuc/VcvCkc9FpxGUUDhJk
+fh/fGAkLakvNv65ZghUckA/D5cuz7YZNAgMBAAGjfjB8MAwGA1UdEwQFMAMBAf8w
+LAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0G
+A1UdDgQWBBRc+cWbsAI3PGZzTQ7LWj27OkYi3TAfBgNVHSMEGDAWgBRc+cWbsAI3
+PGZzTQ7LWj27OkYi3TANBgkqhkiG9w0BAQUFAAOBgQBrnnFKrdIct1gabouJko1O
+YmEGLugR+Jyg4hF8tuK+77mxNSDRgWLFyjxPyYhy91DY6OAGQ+7FXDib5yRGpu6N
+sHBOdZYA29ZZ+Vh0Z5/KnBL8d6cOWjgiW97JMzW90Eyfag9xe9vL/dq8OU8jHnRb
+/41zchapn1dUlj4s8GWv3w==
+-----END CERTIFICATE-----
diff --git a/demo/https.howto/dh1024.pem b/demo/https.howto/dh1024.pem
new file mode 100644
index 0000000..81d43f6
--- /dev/null
+++ b/demo/https.howto/dh1024.pem
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/https.howto/get_https.py b/demo/https.howto/get_https.py
new file mode 100755
index 0000000..9728fab
--- /dev/null
+++ b/demo/https.howto/get_https.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+"""Demonstrations of M2Crypto.httpslib.
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2006 OSAF. All Rights Reserved.
+"""
+
+from M2Crypto import Rand, SSL, httpslib
+
+def get_https():
+ ctx = SSL.Context()
+ if ctx.load_verify_locations('ca.pem') != 1:
+ raise Exception('CA certificates not loaded')
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ h = httpslib.HTTPSConnection('localhost', 9443, ssl_context=ctx)
+ h.set_debuglevel(1)
+ h.putrequest('GET', '/')
+ h.endheaders()
+ resp = h.getresponse()
+ while 1:
+ data = resp.read()
+ if not data:
+ break
+ print data
+ h.close()
+
+Rand.load_file('../randpool.dat', -1)
+get_https()
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/https.howto/https_cli.py b/demo/https.howto/https_cli.py
new file mode 100644
index 0000000..bb34625
--- /dev/null
+++ b/demo/https.howto/https_cli.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""Demonstrations of M2Crypto.httpslib.
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2006 OSAF. All Rights Reserved.
+"""
+
+import sys
+from M2Crypto import Rand, SSL, httpslib, threading
+
+
+def test_httpslib():
+ ctx = SSL.Context()
+ if ctx.load_verify_locations('ca.pem') != 1:
+ raise Exception('CA certificates not loaded')
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.set_info_callback()
+ h = httpslib.HTTPSConnection('localhost', 9443, ssl_context=ctx)
+ h.set_debuglevel(1)
+ h.putrequest('GET', '/')
+ h.putheader('Accept', 'text/html')
+ h.putheader('Accept', 'text/plain')
+ h.putheader('Connection', 'close')
+ h.endheaders()
+ resp = h.getresponse()
+ f = resp.fp
+ c = 0
+ while 1:
+ # Either of following two works.
+ #data = f.readline()
+ data = resp.read()
+ if not data: break
+ c = c + len(data)
+ sys.stdout.write(data)
+ sys.stdout.flush()
+ f.close()
+ h.close()
+
+
+if __name__=='__main__':
+ Rand.load_file('../randpool.dat', -1)
+ #threading.init()
+ test_httpslib()
+ #threading.cleanup()
+ Rand.save_file('../randpool.dat')
+
diff --git a/demo/https.howto/orig_https_srv.py b/demo/https.howto/orig_https_srv.py
new file mode 100644
index 0000000..83be0fb
--- /dev/null
+++ b/demo/https.howto/orig_https_srv.py
@@ -0,0 +1,153 @@
+"""This server extends BaseHTTPServer and SimpleHTTPServer thusly:
+1. One thread per connection.
+2. Generates directory listings.
+
+In addition, it has the following properties:
+1. Works over HTTPS only.
+2. Displays SSL handshaking and SSL session info.
+3. Performs SSL renegotiation when a magic url is requested.
+
+TODO:
+1. Cache stat() of directory entries.
+2. Fancy directory indexing.
+3. Interface ZPublisher.
+
+Copyright (c) 1999-2000 Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2006 OSAF. All Rights Reserved.
+"""
+
+import os, sys
+from SimpleHTTPServer import SimpleHTTPRequestHandler
+
+from M2Crypto import Rand, SSL, threading
+from M2Crypto.SSL.SSLServer import ThreadingSSLServer
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+
+def mkdirlist(path, url):
+ dirlist = os.listdir(path)
+ dirlist.sort()
+ f = StringIO()
+ f.write('<title>Index listing for %s</title>\r\n' % (url,))
+ f.write('<h1>Index listing for %s</h1>\r\n' % (url,))
+ f.write('<pre>\r\n')
+ for d in dirlist:
+ if os.path.isdir(os.path.join(path, d)):
+ d2 = d + '/'
+ else:
+ d2 = d
+ if url == '/':
+ f.write('<a href="/%s">%s</a><br>\r\n' % (d, d2))
+ else:
+ f.write('<a href="%s/%s">%s</a><br>\r\n' % (url, d, d2))
+ f.write('</pre>\r\n\r\n')
+ f.reset()
+ return f
+
+
+class HTTP_Handler(SimpleHTTPRequestHandler):
+
+ server_version = "https_srv/0.1"
+ reneg = 0
+
+ # Cribbed from SimpleHTTPRequestHander to add the ".der" entry,
+ # which facilitates installing your own certificates into browsers.
+ extensions_map = {
+ '': 'text/plain', # Default, *must* be present
+ '.html': 'text/html',
+ '.htm': 'text/html',
+ '.gif': 'image/gif',
+ '.jpg': 'image/jpeg',
+ '.jpeg': 'image/jpeg',
+ '.der': 'application/x-x509-ca-cert'
+ }
+
+ def send_head(self):
+ if self.path[1:8] == '_reneg_':
+ self.reneg = 1
+ self.path = self.path[8:]
+ path = self.translate_path(self.path)
+ if os.path.isdir(path):
+ f = mkdirlist(path, self.path)
+ filetype = 'text/html'
+ else:
+ try:
+ f = open(path, 'rb')
+ filetype = self.guess_type(path)
+ except IOError:
+ self.send_error(404, "File not found")
+ return None
+ self.send_response(200)
+ self.send_header("Content-type", filetype)
+ self.end_headers()
+ return f
+
+ def do_GET(self):
+ #sess = self.request.get_session()
+ #self.log_message('\n%s', sess.as_text())
+ f = self.send_head()
+ if self.reneg:
+ self.reneg = 0
+ self.request.renegotiate()
+ sess = self.request.get_session()
+ self.log_message('\n%s', sess.as_text())
+ if f:
+ self.copyfile(f, self.wfile)
+ f.close()
+
+ def do_HEAD(self):
+ #sess = self.request.get_session()
+ #self.log_message('\n%s', sess.as_text())
+ f = self.send_head()
+ if f:
+ f.close()
+
+
+class HTTPS_Server(ThreadingSSLServer):
+ def __init__(self, server_addr, handler, ssl_ctx):
+ ThreadingSSLServer.__init__(self, server_addr, handler, ssl_ctx)
+ self.server_name = server_addr[0]
+ self.server_port = server_addr[1]
+
+ def finish(self):
+ self.request.set_shutdown(SSL.SSL_RECEIVED_SHUTDOWN | SSL.SSL_SENT_SHUTDOWN)
+ self.request.close()
+
+
+def init_context(protocol, certfile, cafile, verify, verify_depth=10):
+ ctx=SSL.Context(protocol)
+ ctx.load_cert(certfile)
+ ctx.load_client_ca(cafile)
+ if ctx.load_verify_locations(cafile) != 1:
+ raise Exception('CA certificates not loaded')
+ ctx.set_verify(verify, verify_depth)
+ ctx.set_allow_unknown_ca(1)
+ ctx.set_session_id_ctx('https_srv')
+ ctx.set_info_callback()
+ return ctx
+
+
+if __name__ == '__main__':
+ if len(sys.argv) < 2:
+ wdir = '.'
+ else:
+ wdir = sys.argv[1]
+ Rand.load_file('../randpool.dat', -1)
+ threading.init()
+ ctx = init_context('sslv23', 'server.pem', 'ca.pem', \
+ SSL.verify_none)
+ #SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+ ctx.set_tmp_dh('dh1024.pem')
+ os.chdir(wdir)
+ httpsd = HTTPS_Server(('', 9443), HTTP_Handler, ctx)
+ httpsd.serve_forever()
+ threading.cleanup()
+ Rand.save_file('../randpool.dat')
+
+
diff --git a/demo/https.howto/server.pem b/demo/https.howto/server.pem
new file mode 100644
index 0000000..bcd826c
--- /dev/null
+++ b/demo/https.howto/server.pem
@@ -0,0 +1,74 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=CA, O=M2Crypto CA, CN=localhost
+ Validity
+ Not Before: Apr 22 04:36:56 2006 GMT
+ Not After : Apr 22 04:36:56 2007 GMT
+ Subject: C=US, ST=CA, O=M2Crypto Server, CN=localhost
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:a5:cc:39:ad:ba:81:3d:bd:05:f4:61:50:9f:9c:
+ f6:ad:ec:29:d9:78:1e:24:61:f7:1b:36:bf:69:d8:
+ b3:45:ae:6f:3a:4c:4f:d6:13:6d:60:8d:f2:bb:2a:
+ c4:1b:79:fd:e2:f8:d6:3c:56:53:3b:27:f7:3f:70:
+ a4:64:99:63:46:2e:3f:ef:52:da:a9:04:5b:6e:d4:
+ 40:57:c5:59:61:d3:3f:7d:b8:03:c1:9b:65:46:2a:
+ c5:9d:70:b7:ca:79:6e:dd:e4:3f:c2:f4:2f:2e:81:
+ 32:c8:e9:a6:b6:a8:c8:1f:48:be:7a:66:56:98:fc:
+ 3c:25:fc:d9:3d:73:07:30:71
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ F0:48:3A:32:88:8C:80:D7:22:AB:56:F8:86:B3:04:47:10:76:37:BE
+ X509v3 Authority Key Identifier:
+ keyid:5C:F9:C5:9B:B0:02:37:3C:66:73:4D:0E:CB:5A:3D:BB:3A:46:22:DD
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 39:47:95:5c:ea:7e:db:b8:e0:80:f6:e5:d4:9f:83:bc:41:89:
+ 31:97:c8:a4:95:0d:5d:6d:cc:64:8d:19:71:17:75:4b:7f:fb:
+ 35:88:bf:68:e2:a2:be:c5:71:71:56:2a:92:31:25:2a:4b:98:
+ 4e:77:42:45:78:45:21:a5:76:99:92:39:32:7d:a2:4c:38:b0:
+ f1:db:7f:d1:4d:23:99:35:1e:0e:a1:59:a3:ff:9c:51:ef:4c:
+ 11:c9:32:61:38:11:7d:57:2a:81:9a:96:1f:b3:88:f7:ab:5b:
+ 58:f7:79:9b:a8:e3:b7:09:90:8e:c9:7d:44:4f:af:85:dc:c8:
+ 29:4d
+-----BEGIN CERTIFICATE-----
+MIICfTCCAeagAwIBAgIBATANBgkqhkiG9w0BAQUFADBEMQswCQYDVQQGEwJVUzEL
+MAkGA1UECBMCQ0ExFDASBgNVBAoTC00yQ3J5cHRvIENBMRIwEAYDVQQDEwlsb2Nh
+bGhvc3QwHhcNMDYwNDIyMDQzNjU2WhcNMDcwNDIyMDQzNjU2WjBIMQswCQYDVQQG
+EwJVUzELMAkGA1UECBMCQ0ExGDAWBgNVBAoTD00yQ3J5cHRvIFNlcnZlcjESMBAG
+A1UEAxMJbG9jYWxob3N0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQClzDmt
+uoE9vQX0YVCfnPat7CnZeB4kYfcbNr9p2LNFrm86TE/WE21gjfK7KsQbef3i+NY8
+VlM7J/c/cKRkmWNGLj/vUtqpBFtu1EBXxVlh0z99uAPBm2VGKsWdcLfKeW7d5D/C
+9C8ugTLI6aa2qMgfSL56ZlaY/Dwl/Nk9cwcwcQIDAQABo3sweTAJBgNVHRMEAjAA
+MCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0ZTAd
+BgNVHQ4EFgQU8Eg6MoiMgNciq1b4hrMERxB2N74wHwYDVR0jBBgwFoAUXPnFm7AC
+Nzxmc00Oy1o9uzpGIt0wDQYJKoZIhvcNAQEFBQADgYEAOUeVXOp+27jggPbl1J+D
+vEGJMZfIpJUNXW3MZI0ZcRd1S3/7NYi/aOKivsVxcVYqkjElKkuYTndCRXhFIaV2
+mZI5Mn2iTDiw8dt/0U0jmTUeDqFZo/+cUe9MEckyYTgRfVcqgZqWH7OI96tbWPd5
+m6jjtwmQjsl9RE+vhdzIKU0=
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQClzDmtuoE9vQX0YVCfnPat7CnZeB4kYfcbNr9p2LNFrm86TE/W
+E21gjfK7KsQbef3i+NY8VlM7J/c/cKRkmWNGLj/vUtqpBFtu1EBXxVlh0z99uAPB
+m2VGKsWdcLfKeW7d5D/C9C8ugTLI6aa2qMgfSL56ZlaY/Dwl/Nk9cwcwcQIDAQAB
+AoGBAIwzbZbePsnxXOaxoBbJCcQ7D4yJSZvkh6womKauC7Lh9carn1tc5EIg5uCl
+Il5Fw466c5dkPE+q1SZ9X1Z+avYh4ypHjpcr1Lfvwo0wfOBx5MBj6ppMdwgrMXuo
+jRChK3gBZXmKaIH19h/sGp/lZRW2HiX63aN11KDmtfwo6Bq9AkEA2OftYJUqceK3
+/E8q6OE1oQLm+oK6CPZ29A5TRNOadRu1opDR/y59GcUQ5ebJNH8DXyF82lSi3DKt
+SNoSOF32cwJBAMOuKGUAwnq1yH3q+MAF7CZjGou7Ar6VRseyLnD5nttynT85QRW8
+N/WCosKLhV7wi9kKJmHGUJfRAqdo14D8IIsCQQCVjrU6FyABDpZVvjCUClT0BBBH
+QsQLUgWLGiWIG28wuD5xLPHexas0jZCtNIgfTkSA35I66Iiy065vwQ03GHLJAkAG
+eGC/jjngAtjBSR62grufPVGoYyOhF6CCg+LDO43EJdMLPyJmzJVxGcO1+RUM4ZlO
+MOa5/uu1SWT0EiRmEHAnAkBusVcHcd6d4uaoiCybIhF4hL4GsbKoImIciakNlteA
+c1RZZHc2jzO/Ihoz50H1njXwY86YbjncOXw8shtayd8j
+-----END RSA PRIVATE KEY-----
diff --git a/demo/medusa/00_README b/demo/medusa/00_README
new file mode 100644
index 0000000..1f0ce36
--- /dev/null
+++ b/demo/medusa/00_README
@@ -0,0 +1,32 @@
+
+ 19 Sep 2001
+-------------
+
+M2Crypto HTTPS and FTP/TLS servers
+
+All the files in this directory are from the Apr 2001 release
+of Medusa, except for the following:
+
+- 00_README (this file)
+- server.pem, the server's certificate
+- ca.pem, my CA certificate
+- https_server.py
+- ftps_server.py
+- START.py
+- START_xmlrpc.py
+- index.html, a sample HTML file
+- poison_handler.py, a webpoison clone
+
+By default, http_server listens on port 9080 and https_server port 9443.
+Document root is current directory, and serves up index.html.
+
+The xmlrpc server is accessible below '/RPC2'. It requires Fredrik Lundh's
+xmlrpc_handler on PYTHONPATH.
+
+The FTP/TLS server listens on port 9021 by default. I've only tested it with
+the 'anonymous' authentication handler.
+
+Medusa files are copyright Sam Rushing. My files are copyright me.
+
+
+
diff --git a/demo/medusa/START.py b/demo/medusa/START.py
new file mode 100644
index 0000000..62021f7
--- /dev/null
+++ b/demo/medusa/START.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+# Standard Python library
+import os
+import os.path
+import sys
+
+# Medusa
+import asyncore
+import default_handler
+import filesys
+import ftp_server
+import http_server
+import status_handler
+
+# M2Crypto
+import https_server
+import poison_handler
+import ftps_server
+from M2Crypto import Rand, SSL, threading
+
+HTTP_PORT=9080
+HTTPS_PORT=9443
+FTP_PORT = 9021
+
+hs=http_server.http_server('', HTTP_PORT)
+
+Rand.load_file('../randpool.dat', -1)
+ssl_ctx=SSL.Context('sslv23')
+ssl_ctx.load_cert('server.pem')
+ssl_ctx.load_verify_location('ca.pem')
+ssl_ctx.load_client_CA('ca.pem')
+#ssl_ctx.set_verify(SSL.verify_peer, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_fail_if_no_peer_cert, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_client_once, 10)
+ssl_ctx.set_verify(SSL.verify_none, 10)
+ssl_ctx.set_session_id_ctx('127.0.0.1:9443')
+ssl_ctx.set_tmp_dh('dh1024.pem')
+ssl_ctx.set_info_callback()
+
+hss=https_server.https_server('', HTTPS_PORT, ssl_ctx)
+
+#fs=filesys.os_filesystem(os.path.abspath(os.curdir))
+fs=filesys.os_filesystem('/usr/local/pkg/apache/htdocs')
+#fs=filesys.os_filesystem('c:/pkg/jdk130/docs')
+dh=default_handler.default_handler(fs)
+hs.install_handler(dh)
+hss.install_handler(dh)
+
+#class rpc_demo (xmlrpc_handler.xmlrpc_handler):
+# def call (self, method, params):
+# print 'method="%s" params=%s' % (method, params)
+# return "Sure, that works"
+#rpch = rpc_demo()
+#hs.install_handler(rpch)
+#hss.install_handler(rpch)
+
+ph=poison_handler.poison_handler(10)
+hs.install_handler(ph)
+hss.install_handler(ph)
+
+fauthz = ftp_server.anon_authorizer('/usr/local/pkg/apache/htdocs')
+ftps = ftps_server.ftp_tls_server(fauthz, ssl_ctx, port=FTP_PORT)
+
+sh=status_handler.status_extension([hs, hss, ftps])
+hs.install_handler(sh)
+hss.install_handler(sh)
+
+asyncore.loop()
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/medusa/START_xmlrpc.py b/demo/medusa/START_xmlrpc.py
new file mode 100644
index 0000000..193e8bf
--- /dev/null
+++ b/demo/medusa/START_xmlrpc.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+
+# Standard Python library
+import os
+import os.path
+import sys
+
+# Medusa
+import asyncore
+import default_handler
+import filesys
+import http_server
+import status_handler
+
+# M2Crypto
+import https_server
+import poison_handler
+from M2Crypto import Rand, SSL
+
+# XMLrpc
+import xmlrpc_handler
+
+
+HTTP_PORT=9080
+HTTPS_PORT=9443
+
+hs=http_server.http_server('', HTTP_PORT)
+
+Rand.load_file('../randpool.dat', -1)
+ssl_ctx=SSL.Context('sslv23')
+ssl_ctx.load_cert('server.pem')
+#ssl_ctx.load_verify_location('ca.pem')
+#ssl_ctx.load_client_CA('ca.pem')
+#ssl_ctx.set_verify(SSL.verify_peer, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_fail_if_no_peer_cert, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_client_once, 10)
+ssl_ctx.set_verify(SSL.verify_none, 10)
+ssl_ctx.set_session_id_ctx('127.0.0.1:9443')
+ssl_ctx.set_tmp_dh('dh1024.pem')
+#ssl_ctx.set_info_callback()
+
+hss=https_server.https_server('', HTTPS_PORT, ssl_ctx)
+
+#fs=filesys.os_filesystem(os.path.abspath(os.curdir))
+fs=filesys.os_filesystem('/usr/local/pkg/apache/htdocs')
+#fs=filesys.os_filesystem('c:/pkg/jdk118/docs')
+dh=default_handler.default_handler(fs)
+hs.install_handler(dh)
+hss.install_handler(dh)
+
+# Cribbed from xmlrpc_handler.py.
+# This is where you implement your RPC functionality.
+class rpc_demo (xmlrpc_handler.xmlrpc_handler):
+ def call (self, method, params):
+ print 'method="%s" params=%s' % (method, params)
+ return "Sure, that works"
+
+rpch = rpc_demo()
+hs.install_handler(rpch)
+hss.install_handler(rpch)
+
+ph=poison_handler.poison_handler(10)
+hs.install_handler(ph)
+hss.install_handler(ph)
+
+sh=status_handler.status_extension([hss])
+hs.install_handler(sh)
+hss.install_handler(sh)
+
+asyncore.loop()
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/medusa/asynchat.py b/demo/medusa/asynchat.py
new file mode 100644
index 0000000..2e51f1a
--- /dev/null
+++ b/demo/medusa/asynchat.py
@@ -0,0 +1,292 @@
+# -*- Mode: Python; tab-width: 4 -*-
+# $Id: asynchat.py 299 2005-06-09 17:32:28Z heikki $
+# Author: Sam Rushing <rushing@nightmare.com>
+
+# ======================================================================
+# Copyright 1996 by Sam Rushing
+#
+# All Rights Reserved
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose and without fee is hereby
+# granted, provided that the above copyright notice appear in all
+# copies and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of Sam
+# Rushing not be used in advertising or publicity pertaining to
+# distribution of the software without specific, written prior
+# permission.
+#
+# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+# ======================================================================
+
+"""A class supporting chat-style (command/response) protocols.
+
+This class adds support for 'chat' style protocols - where one side
+sends a 'command', and the other sends a response (examples would be
+the common internet protocols - smtp, nntp, ftp, etc..).
+
+The handle_read() method looks at the input stream for the current
+'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
+for multi-line output), calling self.found_terminator() on its
+receipt.
+
+for example:
+Say you build an async nntp client using this class. At the start
+of the connection, you'll have self.terminator set to '\r\n', in
+order to process the single-line greeting. Just before issuing a
+'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST
+command will be accumulated (using your own 'collect_incoming_data'
+method) up to the terminator, and then control will be returned to
+you - by calling your self.found_terminator() method.
+"""
+
+import socket
+import asyncore
+import string
+
+class async_chat (asyncore.dispatcher):
+ """This is an abstract class. You must derive from this class, and add
+ the two methods collect_incoming_data() and found_terminator()"""
+
+ # these are overridable defaults
+
+ ac_in_buffer_size = 4096
+ ac_out_buffer_size = 4096
+
+ def __init__ (self, conn=None):
+ self.ac_in_buffer = ''
+ self.ac_out_buffer = ''
+ self.producer_fifo = fifo()
+ asyncore.dispatcher.__init__ (self, conn)
+
+ def set_terminator (self, term):
+ "Set the input delimiter. Can be a fixed string of any length, an integer, or None"
+ self.terminator = term
+
+ def get_terminator (self):
+ return self.terminator
+
+ # grab some more data from the socket,
+ # throw it to the collector method,
+ # check for the terminator,
+ # if found, transition to the next state.
+
+ def handle_read (self):
+
+ try:
+ data = self.recv (self.ac_in_buffer_size)
+ except socket.error, why:
+ self.handle_error()
+ return
+
+ self.ac_in_buffer = self.ac_in_buffer + data
+
+ # Continue to search for self.terminator in self.ac_in_buffer,
+ # while calling self.collect_incoming_data. The while loop
+ # is necessary because we might read several data+terminator
+ # combos with a single recv(1024).
+
+ while self.ac_in_buffer:
+ lb = len(self.ac_in_buffer)
+ terminator = self.get_terminator()
+ if terminator is None:
+ # no terminator, collect it all
+ self.collect_incoming_data (self.ac_in_buffer)
+ self.ac_in_buffer = ''
+ elif type(terminator) == type(0):
+ # numeric terminator
+ n = terminator
+ if lb < n:
+ self.collect_incoming_data (self.ac_in_buffer)
+ self.ac_in_buffer = ''
+ self.terminator = self.terminator - lb
+ else:
+ self.collect_incoming_data (self.ac_in_buffer[:n])
+ self.ac_in_buffer = self.ac_in_buffer[n:]
+ self.terminator = 0
+ self.found_terminator()
+ else:
+ # 3 cases:
+ # 1) end of buffer matches terminator exactly:
+ # collect data, transition
+ # 2) end of buffer matches some prefix:
+ # collect data to the prefix
+ # 3) end of buffer does not match any prefix:
+ # collect data
+ terminator_len = len(terminator)
+ index = string.find (self.ac_in_buffer, terminator)
+ if index != -1:
+ # we found the terminator
+ if index > 0:
+ # don't bother reporting the empty string (source of subtle bugs)
+ self.collect_incoming_data (self.ac_in_buffer[:index])
+ self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
+ # This does the Right Thing if the terminator is changed here.
+ self.found_terminator()
+ else:
+ # check for a prefix of the terminator
+ index = find_prefix_at_end (self.ac_in_buffer, terminator)
+ if index:
+ if index != lb:
+ # we found a prefix, collect up to the prefix
+ self.collect_incoming_data (self.ac_in_buffer[:-index])
+ self.ac_in_buffer = self.ac_in_buffer[-index:]
+ break
+ else:
+ # no prefix, collect it all
+ self.collect_incoming_data (self.ac_in_buffer)
+ self.ac_in_buffer = ''
+
+ def handle_write (self):
+ self.initiate_send ()
+
+ def handle_close (self):
+ self.close()
+
+ def push (self, data):
+ self.producer_fifo.push (simple_producer (data))
+ self.initiate_send()
+
+ def push_with_producer (self, producer):
+ self.producer_fifo.push (producer)
+ self.initiate_send()
+
+ def readable (self):
+ "predicate for inclusion in the readable for select()"
+ return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
+
+ def writable (self):
+ "predicate for inclusion in the writable for select()"
+ # return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
+ # this is about twice as fast, though not as clear.
+ return not (
+ (self.ac_out_buffer is '') and
+ self.producer_fifo.is_empty() and
+ self.connected
+ )
+
+ def close_when_done (self):
+ "automatically close this channel once the outgoing queue is empty"
+ self.producer_fifo.push (None)
+
+ # refill the outgoing buffer by calling the more() method
+ # of the first producer in the queue
+ def refill_buffer (self):
+ _string_type = type('')
+ while 1:
+ if len(self.producer_fifo):
+ p = self.producer_fifo.first()
+ # a 'None' in the producer fifo is a sentinel,
+ # telling us to close the channel.
+ if p is None:
+ if not self.ac_out_buffer:
+ self.producer_fifo.pop()
+ self.close()
+ return
+ elif type(p) is _string_type:
+ self.producer_fifo.pop()
+ self.ac_out_buffer = self.ac_out_buffer + p
+ return
+ data = p.more()
+ if data:
+ self.ac_out_buffer = self.ac_out_buffer + data
+ return
+ else:
+ self.producer_fifo.pop()
+ else:
+ return
+
+ def initiate_send (self):
+ obs = self.ac_out_buffer_size
+ # try to refill the buffer
+ if (len (self.ac_out_buffer) < obs):
+ self.refill_buffer()
+
+ if self.ac_out_buffer and self.connected:
+ # try to send the buffer
+ try:
+ num_sent = self.send (self.ac_out_buffer[:obs])
+ if num_sent:
+ self.ac_out_buffer = self.ac_out_buffer[num_sent:]
+
+ except socket.error, why:
+ self.handle_error()
+ return
+
+ def discard_buffers (self):
+ # Emergencies only!
+ self.ac_in_buffer = ''
+ self.ac_out_buffer = ''
+ while self.producer_fifo:
+ self.producer_fifo.pop()
+
+
+class simple_producer:
+
+ def __init__ (self, data, buffer_size=512):
+ self.data = data
+ self.buffer_size = buffer_size
+
+ def more (self):
+ if len (self.data) > self.buffer_size:
+ result = self.data[:self.buffer_size]
+ self.data = self.data[self.buffer_size:]
+ return result
+ else:
+ result = self.data
+ self.data = ''
+ return result
+
+class fifo:
+ def __init__ (self, list=None):
+ if not list:
+ self.list = []
+ else:
+ self.list = list
+
+ def __len__ (self):
+ return len(self.list)
+
+ def is_empty (self):
+ return self.list == []
+
+ def first (self):
+ return self.list[0]
+
+ def push (self, data):
+ self.list.append (data)
+
+ def pop (self):
+ if self.list:
+ result = self.list[0]
+ del self.list[0]
+ return (1, result)
+ else:
+ return (0, None)
+
+# Given 'haystack', see if any prefix of 'needle' is at its end. This
+# assumes an exact match has already been checked. Return the number of
+# characters matched.
+# for example:
+# f_p_a_e ("qwerty\r", "\r\n") => 1
+# f_p_a_e ("qwertydkjf", "\r\n") => 0
+# f_p_a_e ("qwerty\r\n", "\r\n") => <undefined>
+
+# this could maybe be made faster with a computed regex?
+# [answer: no; circa Python-2.0, Jan 2001]
+# new python: 28961/s
+# old python: 18307/s
+# re: 12820/s
+# regex: 14035/s
+
+def find_prefix_at_end (haystack, needle):
+ l = len(needle) - 1
+ while l and not haystack.endswith(needle[:l]):
+ l -= 1
+ return l
diff --git a/demo/medusa/asyncore.py b/demo/medusa/asyncore.py
new file mode 100644
index 0000000..7f751aa
--- /dev/null
+++ b/demo/medusa/asyncore.py
@@ -0,0 +1,552 @@
+# -*- Mode: Python; tab-width: 4 -*-
+# $Id: asyncore.py 299 2005-06-09 17:32:28Z heikki $
+# Author: Sam Rushing <rushing@nightmare.com>
+
+# ======================================================================
+# Copyright 1996 by Sam Rushing
+#
+# All Rights Reserved
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose and without fee is hereby
+# granted, provided that the above copyright notice appear in all
+# copies and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of Sam
+# Rushing not be used in advertising or publicity pertaining to
+# distribution of the software without specific, written prior
+# permission.
+#
+# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+# ======================================================================
+
+"""Basic infrastructure for asynchronous socket service clients and servers.
+
+There are only two ways to have a program on a single processor do "more
+than one thing at a time". Multi-threaded programming is the simplest and
+most popular way to do it, but there is another very different technique,
+that lets you have nearly all the advantages of multi-threading, without
+actually using multiple threads. it's really only practical if your program
+is largely I/O bound. If your program is CPU bound, then pre-emptive
+scheduled threads are probably what you really need. Network servers are
+rarely CPU-bound, however.
+
+If your operating system supports the select() system call in its I/O
+library (and nearly all do), then you can use it to juggle multiple
+communication channels at once; doing other work while your I/O is taking
+place in the "background." Although this strategy can seem strange and
+complex, especially at first, it is in many ways easier to understand and
+control than multi-threaded programming. The module documented here solves
+many of the difficult problems for you, making the task of building
+sophisticated high-performance network servers and clients a snap.
+"""
+
+import exceptions
+import select
+import socket
+import string
+import sys
+
+import os
+if os.name == 'nt':
+ EWOULDBLOCK = 10035
+ EINPROGRESS = 10036
+ EALREADY = 10037
+ ECONNRESET = 10054
+ ENOTCONN = 10057
+ ESHUTDOWN = 10058
+else:
+ from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, ENOTCONN, ESHUTDOWN
+
+try:
+ socket_map
+except NameError:
+ socket_map = {}
+
+class ExitNow (exceptions.Exception):
+ pass
+
+DEBUG = 0
+
+def poll (timeout=0.0, map=None):
+ global DEBUG
+ if map is None:
+ map = socket_map
+ if map:
+ r = []; w = []; e = []
+ for fd, obj in map.items():
+ if obj.readable():
+ r.append (fd)
+ if obj.writable():
+ w.append (fd)
+ r,w,e = select.select (r,w,e, timeout)
+
+ if DEBUG:
+ print r,w,e
+
+ for fd in r:
+ try:
+ obj = map[fd]
+ try:
+ obj.handle_read_event()
+ except ExitNow:
+ raise ExitNow
+ except:
+ obj.handle_error()
+ except KeyError:
+ pass
+
+ for fd in w:
+ try:
+ obj = map[fd]
+ try:
+ obj.handle_write_event()
+ except ExitNow:
+ raise ExitNow
+ except:
+ obj.handle_error()
+ except KeyError:
+ pass
+
+def poll2 (timeout=0.0, map=None):
+ import poll
+ if map is None:
+ map=socket_map
+ # timeout is in milliseconds
+ timeout = int(timeout*1000)
+ if map:
+ l = []
+ for fd, obj in map.items():
+ flags = 0
+ if obj.readable():
+ flags = poll.POLLIN
+ if obj.writable():
+ flags = flags | poll.POLLOUT
+ if flags:
+ l.append ((fd, flags))
+ r = poll.poll (l, timeout)
+ for fd, flags in r:
+ try:
+ obj = map[fd]
+ try:
+ if (flags & poll.POLLIN):
+ obj.handle_read_event()
+ if (flags & poll.POLLOUT):
+ obj.handle_write_event()
+ except ExitNow:
+ raise ExitNow
+ except:
+ obj.handle_error()
+ except KeyError:
+ pass
+
+def poll3 (timeout=0.0, map=None):
+ # Use the poll() support added to the select module in Python 2.0
+ if map is None:
+ map=socket_map
+ # timeout is in milliseconds
+ timeout = int(timeout*1000)
+ pollster = select.poll()
+ if map:
+ l = []
+ for fd, obj in map.items():
+ flags = 0
+ if obj.readable():
+ flags = select.POLLIN
+ if obj.writable():
+ flags = flags | select.POLLOUT
+ if flags:
+ pollster.register(fd, flags)
+ r = pollster.poll (timeout)
+ for fd, flags in r:
+ try:
+ obj = map[fd]
+ try:
+ if (flags & select.POLLIN):
+ obj.handle_read_event()
+ if (flags & select.POLLOUT):
+ obj.handle_write_event()
+ except ExitNow:
+ raise ExitNow
+ except:
+ obj.handle_error()
+ except KeyError:
+ pass
+
+def loop (timeout=30.0, use_poll=0, map=None):
+
+ if use_poll:
+ if hasattr (select, 'poll'):
+ poll_fun = poll3
+ else:
+ poll_fun = poll2
+ else:
+ poll_fun = poll
+
+ if map is None:
+ map=socket_map
+
+ while map:
+ poll_fun (timeout, map)
+
+class dispatcher:
+ debug = 0
+ connected = 0
+ accepting = 0
+ closing = 0
+ addr = None
+
+ def __init__ (self, sock=None, map=None):
+ if sock:
+ self.set_socket (sock, map)
+ # I think it should inherit this anyway
+ self.socket.setblocking (0)
+ self.connected = 1
+
+ def __repr__ (self):
+ try:
+ status = []
+ if self.accepting and self.addr:
+ status.append ('listening')
+ elif self.connected:
+ status.append ('connected')
+ if self.addr:
+ status.append ('%s:%d' % self.addr)
+ return '<%s %s at %x>' % (
+ self.__class__.__name__,
+ string.join (status, ' '),
+ id(self)
+ )
+ except:
+ try:
+ ar = repr(self.addr)
+ except:
+ ar = 'no self.addr!'
+
+ return '<__repr__ (self) failed for object at %x (addr=%s)>' % (id(self),ar)
+
+ def add_channel (self, map=None):
+ #self.log_info ('adding channel %s' % self)
+ if map is None:
+ map=socket_map
+ map [self._fileno] = self
+
+ def del_channel (self, map=None):
+ fd = self._fileno
+ if map is None:
+ map=socket_map
+ if map.has_key (fd):
+ #self.log_info ('closing channel %d:%s' % (fd, self))
+ del map [fd]
+
+ def create_socket (self, family, type):
+ self.family_and_type = family, type
+ self.socket = socket.socket (family, type)
+ self.socket.setblocking(0)
+ self._fileno = self.socket.fileno()
+ self.add_channel()
+
+ def set_socket (self, sock, map=None):
+ self.__dict__['socket'] = sock
+ self._fileno = sock.fileno()
+ self.add_channel (map)
+
+ def set_reuse_addr (self):
+ # try to re-use a server port if possible
+ try:
+ self.socket.setsockopt (
+ socket.SOL_SOCKET, socket.SO_REUSEADDR,
+ self.socket.getsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR) | 1
+ )
+ except:
+ pass
+
+ # ==================================================
+ # predicates for select()
+ # these are used as filters for the lists of sockets
+ # to pass to select().
+ # ==================================================
+
+ def readable (self):
+ return 1
+
+ if os.name == 'mac':
+ # The macintosh will select a listening socket for
+ # write if you let it. What might this mean?
+ def writable (self):
+ return not self.accepting
+ else:
+ def writable (self):
+ return 1
+
+ # ==================================================
+ # socket object methods.
+ # ==================================================
+
+ def listen (self, num):
+ self.accepting = 1
+ if os.name == 'nt' and num > 5:
+ num = 1
+ return self.socket.listen (num)
+
+ def bind (self, addr):
+ self.addr = addr
+ return self.socket.bind (addr)
+
+ def connect (self, address):
+ self.connected = 0
+ try:
+ self.socket.connect (address)
+ except socket.error, why:
+ if why[0] in (EINPROGRESS, EALREADY, EWOULDBLOCK):
+ return
+ else:
+ raise socket.error, why
+ self.connected = 1
+ self.handle_connect()
+
+ def accept (self):
+ try:
+ conn, addr = self.socket.accept()
+ return conn, addr
+ except socket.error, why:
+ if why[0] == EWOULDBLOCK:
+ pass
+ else:
+ raise socket.error, why
+
+ def send (self, data):
+ try:
+ result = self.socket.send (data)
+ return result
+ except socket.error, why:
+ if why[0] == EWOULDBLOCK:
+ return 0
+ else:
+ raise socket.error, why
+ return 0
+
+ def recv (self, buffer_size):
+ try:
+ data = self.socket.recv (buffer_size)
+ if not data:
+ # a closed connection is indicated by signaling
+ # a read condition, and having recv() return 0.
+ self.handle_close()
+ return ''
+ else:
+ return data
+ except socket.error, why:
+ # winsock sometimes throws ENOTCONN
+ if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]:
+ self.handle_close()
+ return ''
+ else:
+ raise socket.error, why
+
+ def close (self):
+ self.del_channel()
+ self.socket.close()
+
+ # cheap inheritance, used to pass all other attribute
+ # references to the underlying socket object.
+ def __getattr__ (self, attr):
+ return getattr (self.socket, attr)
+
+ # log and log_info maybe overriden to provide more sophisitcated
+ # logging and warning methods. In general, log is for 'hit' logging
+ # and 'log_info' is for informational, warning and error logging.
+
+ def log (self, message):
+ sys.stderr.write ('log: %s\n' % str(message))
+
+ def log_info (self, message, type='info'):
+ if __debug__ or type != 'info':
+ print '%s: %s' % (type, message)
+
+ def handle_read_event (self):
+ if self.accepting:
+ # for an accepting socket, getting a read implies
+ # that we are connected
+ if not self.connected:
+ self.connected = 1
+ self.handle_accept()
+ elif not self.connected:
+ self.handle_connect()
+ self.connected = 1
+ self.handle_read()
+ else:
+ self.handle_read()
+
+ def handle_write_event (self):
+ # getting a write implies that we are connected
+ if not self.connected:
+ self.handle_connect()
+ self.connected = 1
+ self.handle_write()
+
+ def handle_expt_event (self):
+ self.handle_expt()
+
+ def handle_error (self):
+ (file,fun,line), t, v, tbinfo = compact_traceback()
+
+ # sometimes a user repr method will crash.
+ try:
+ self_repr = repr (self)
+ except:
+ self_repr = '<__repr__ (self) failed for object at %0x>' % id(self)
+
+ self.log_info (
+ 'uncaptured python exception, closing channel %s (%s:%s %s)' % (
+ self_repr,
+ t,
+ v,
+ tbinfo
+ ),
+ 'error'
+ )
+ self.close()
+
+ def handle_expt (self):
+ self.log_info ('unhandled exception', 'warning')
+
+ def handle_read (self):
+ self.log_info ('unhandled read event', 'warning')
+
+ def handle_write (self):
+ self.log_info ('unhandled write event', 'warning')
+
+ def handle_connect (self):
+ self.log_info ('unhandled connect event', 'warning')
+
+ def handle_accept (self):
+ self.log_info ('unhandled accept event', 'warning')
+
+ def handle_close (self):
+ self.log_info ('unhandled close event', 'warning')
+ self.close()
+
+# ---------------------------------------------------------------------------
+# adds simple buffered output capability, useful for simple clients.
+# [for more sophisticated usage use asynchat.async_chat]
+# ---------------------------------------------------------------------------
+
+class dispatcher_with_send (dispatcher):
+ def __init__ (self, sock=None):
+ dispatcher.__init__ (self, sock)
+ self.out_buffer = ''
+
+ def initiate_send (self):
+ num_sent = 0
+ num_sent = dispatcher.send (self, self.out_buffer[:512])
+ self.out_buffer = self.out_buffer[num_sent:]
+
+ def handle_write (self):
+ self.initiate_send()
+
+ def writable (self):
+ return (not self.connected) or len(self.out_buffer)
+
+ def send (self, data):
+ if self.debug:
+ self.log_info ('sending %s' % repr(data))
+ self.out_buffer = self.out_buffer + data
+ self.initiate_send()
+
+# ---------------------------------------------------------------------------
+# used for debugging.
+# ---------------------------------------------------------------------------
+
+def compact_traceback ():
+ t,v,tb = sys.exc_info()
+ tbinfo = []
+ while 1:
+ tbinfo.append ((
+ tb.tb_frame.f_code.co_filename,
+ tb.tb_frame.f_code.co_name,
+ str(tb.tb_lineno)
+ ))
+ tb = tb.tb_next
+ if not tb:
+ break
+
+ # just to be safe
+ del tb
+
+ file, function, line = tbinfo[-1]
+ info = '[' + string.join (
+ map (
+ lambda x: string.join (x, '|'),
+ tbinfo
+ ),
+ '] ['
+ ) + ']'
+ return (file, function, line), t, v, info
+
+def close_all (map=None):
+ if map is None:
+ map=socket_map
+ for x in map.values():
+ x.socket.close()
+ map.clear()
+
+# Asynchronous File I/O:
+#
+# After a little research (reading man pages on various unixen, and
+# digging through the linux kernel), I've determined that select()
+# isn't meant for doing doing asynchronous file i/o.
+# Heartening, though - reading linux/mm/filemap.c shows that linux
+# supports asynchronous read-ahead. So _MOST_ of the time, the data
+# will be sitting in memory for us already when we go to read it.
+#
+# What other OS's (besides NT) support async file i/o? [VMS?]
+#
+# Regardless, this is useful for pipes, and stdin/stdout...
+
+import os
+if os.name == 'posix':
+ import fcntl
+ import FCNTL
+
+ class file_wrapper:
+ # here we override just enough to make a file
+ # look like a socket for the purposes of asyncore.
+ def __init__ (self, fd):
+ self.fd = fd
+
+ def recv (self, *args):
+ return apply (os.read, (self.fd,)+args)
+
+ def send (self, *args):
+ return apply (os.write, (self.fd,)+args)
+
+ read = recv
+ write = send
+
+ def close (self):
+ return os.close (self.fd)
+
+ def fileno (self):
+ return self.fd
+
+ class file_dispatcher (dispatcher):
+ def __init__ (self, fd):
+ dispatcher.__init__ (self)
+ self.connected = 1
+ # set it to non-blocking mode
+ flags = fcntl.fcntl (fd, FCNTL.F_GETFL, 0)
+ flags = flags | FCNTL.O_NONBLOCK
+ fcntl.fcntl (fd, FCNTL.F_SETFL, flags)
+ self.set_file (fd)
+
+ def set_file (self, fd):
+ self._fileno = fd
+ self.socket = file_wrapper (fd)
+ self.add_channel()
+
diff --git a/demo/medusa/auth_handler.py b/demo/medusa/auth_handler.py
new file mode 100644
index 0000000..70eaf3c
--- /dev/null
+++ b/demo/medusa/auth_handler.py
@@ -0,0 +1,135 @@
+# -*- Mode: Python; tab-width: 4 -*-
+#
+# Author: Sam Rushing <rushing@nightmare.com>
+# Copyright 1996-2000 by Sam Rushing
+# All Rights Reserved.
+#
+
+# support for 'basic' authenticaion.
+
+import base64
+import md5
+import re
+import string
+import time
+import counter
+
+import default_handler
+
+get_header = default_handler.get_header
+
+import http_server
+import producers
+
+# This is a 'handler' that wraps an authorization method
+# around access to the resources normally served up by
+# another handler.
+
+# does anyone support digest authentication? (rfc2069)
+
+class auth_handler:
+ def __init__ (self, dict, handler, realm='default'):
+ self.authorizer = dictionary_authorizer (dict)
+ self.handler = handler
+ self.realm = realm
+ self.pass_count = counter.counter()
+ self.fail_count = counter.counter()
+
+ def match (self, request):
+ # by default, use the given handler's matcher
+ return self.handler.match (request)
+
+ def handle_request (self, request):
+ # authorize a request before handling it...
+ scheme = get_header (AUTHORIZATION, request.header)
+
+ if scheme:
+ scheme = string.lower (scheme)
+ if scheme == 'basic':
+ cookie = AUTHORIZATION.group(2)
+ try:
+ decoded = base64.decodestring (cookie)
+ except:
+ print 'malformed authorization info <%s>' % cookie
+ request.error (400)
+ return
+ auth_info = string.split (decoded, ':')
+ if self.authorizer.authorize (auth_info):
+ self.pass_count.increment()
+ request.auth_info = auth_info
+ self.handler.handle_request (request)
+ else:
+ self.handle_unauthorized (request)
+ #elif scheme == 'digest':
+ # print 'digest: ',AUTHORIZATION.group(2)
+ else:
+ print 'unknown/unsupported auth method: %s' % scheme
+ self.handle_unauthorized()
+ else:
+ # list both? prefer one or the other?
+ # you could also use a 'nonce' here. [see below]
+ #auth = 'Basic realm="%s" Digest realm="%s"' % (self.realm, self.realm)
+ #nonce = self.make_nonce (request)
+ #auth = 'Digest realm="%s" nonce="%s"' % (self.realm, nonce)
+ #request['WWW-Authenticate'] = auth
+ #print 'sending header: %s' % request['WWW-Authenticate']
+ self.handle_unauthorized (request)
+
+ def handle_unauthorized (self, request):
+ # We are now going to receive data that we want to ignore.
+ # to ignore the file data we're not interested in.
+ self.fail_count.increment()
+ request.channel.set_terminator (None)
+ request['Connection'] = 'close'
+ request['WWW-Authenticate'] = 'Basic realm="%s"' % self.realm
+ request.error (401)
+
+ def make_nonce (self, request):
+ "A digest-authentication <nonce>, constructed as suggested in RFC 2069"
+ ip = request.channel.server.ip
+ now = str (long (time.time()))[:-1]
+ private_key = str (id (self))
+ nonce = string.join ([ip, now, private_key], ':')
+ return self.apply_hash (nonce)
+
+ def apply_hash (self, s):
+ "Apply MD5 to a string <s>, then wrap it in base64 encoding."
+ m = md5.new()
+ m.update (s)
+ d = m.digest()
+ # base64.encodestring tacks on an extra linefeed.
+ return base64.encodestring (d)[:-1]
+
+ def status (self):
+ # Thanks to mwm@contessa.phone.net (Mike Meyer)
+ r = [
+ producers.simple_producer (
+ '<li>Authorization Extension : '
+ '<b>Unauthorized requests:</b> %s<ul>' % self.fail_count
+ )
+ ]
+ if hasattr (self.handler, 'status'):
+ r.append (self.handler.status())
+ r.append (
+ producers.simple_producer ('</ul>')
+ )
+ return producers.composite_producer (
+ http_server.fifo (r)
+ )
+
+class dictionary_authorizer:
+ def __init__ (self, dict):
+ self.dict = dict
+
+ def authorize (self, auth_info):
+ [username, password] = auth_info
+ if (self.dict.has_key (username)) and (self.dict[username] == password):
+ return 1
+ else:
+ return 0
+
+AUTHORIZATION = re.compile (
+ # scheme challenge
+ 'Authorization: ([^ ]+) (.*)',
+ re.IGNORECASE
+ )
diff --git a/demo/medusa/ca.pem b/demo/medusa/ca.pem
new file mode 100644
index 0000000..b7c84a1
--- /dev/null
+++ b/demo/medusa/ca.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/medusa/counter.py b/demo/medusa/counter.py
new file mode 100644
index 0000000..997d687
--- /dev/null
+++ b/demo/medusa/counter.py
@@ -0,0 +1,48 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+# It is tempting to add an __int__ method to this class, but it's not
+# a good idea. This class tries to gracefully handle integer
+# overflow, and to hide this detail from both the programmer and the
+# user. Note that the __str__ method can be relied on for printing out
+# the value of a counter:
+#
+# >>> print 'Total Client: %s' % self.total_clients
+#
+# If you need to do arithmetic with the value, then use the 'as_long'
+# method, the use of long arithmetic is a reminder that the counter
+# will overflow.
+
+class counter:
+ "general-purpose counter"
+
+ def __init__ (self, initial_value=0):
+ self.value = initial_value
+
+ def increment (self, delta=1):
+ result = self.value
+ try:
+ self.value = self.value + delta
+ except OverflowError:
+ self.value = long(self.value) + delta
+ return result
+
+ def decrement (self, delta=1):
+ result = self.value
+ try:
+ self.value = self.value - delta
+ except OverflowError:
+ self.value = long(self.value) - delta
+ return result
+
+ def as_long (self):
+ return long(self.value)
+
+ def __nonzero__ (self):
+ return self.value != 0
+
+ def __repr__ (self):
+ return '<counter value=%s at %x>' % (self.value, id(self))
+
+ def __str__ (self):
+ return str(long(self.value))
+ #return str(long(self.value))[:-1]
diff --git a/demo/medusa/default_handler.py b/demo/medusa/default_handler.py
new file mode 100644
index 0000000..4585b0f
--- /dev/null
+++ b/demo/medusa/default_handler.py
@@ -0,0 +1,215 @@
+# -*- Mode: Python; tab-width: 4 -*-
+#
+# Author: Sam Rushing <rushing@nightmare.com>
+# Copyright 1997 by Sam Rushing
+# All Rights Reserved.
+#
+
+# standard python modules
+import os
+import re
+import posixpath
+import stat
+import string
+import time
+
+# medusa modules
+import http_date
+import http_server
+import mime_type_table
+import status_handler
+import producers
+
+unquote = http_server.unquote
+
+# This is the 'default' handler. it implements the base set of
+# features expected of a simple file-delivering HTTP server. file
+# services are provided through a 'filesystem' object, the very same
+# one used by the FTP server.
+#
+# You can replace or modify this handler if you want a non-standard
+# HTTP server. You can also derive your own handler classes from
+# it.
+#
+# support for handling POST requests is available in the derived
+# class <default_with_post_handler>, defined below.
+#
+
+from counter import counter
+
+class default_handler:
+
+ valid_commands = ['get', 'head']
+
+ IDENT = 'Default HTTP Request Handler'
+
+ # Pathnames that are tried when a URI resolves to a directory name
+ directory_defaults = [
+ 'index.html',
+ 'default.html'
+ ]
+
+ default_file_producer = producers.file_producer
+
+ def __init__ (self, filesystem):
+ self.filesystem = filesystem
+ # count total hits
+ self.hit_counter = counter()
+ # count file deliveries
+ self.file_counter = counter()
+ # count cache hits
+ self.cache_counter = counter()
+
+ hit_counter = 0
+
+ def __repr__ (self):
+ return '<%s (%s hits) at %x>' % (
+ self.IDENT,
+ self.hit_counter,
+ id (self)
+ )
+
+ # always match, since this is a default
+ def match (self, request):
+ return 1
+
+ # handle a file request, with caching.
+
+ def handle_request (self, request):
+
+ if request.command not in self.valid_commands:
+ request.error (400) # bad request
+ return
+
+ self.hit_counter.increment()
+
+ path, params, query, fragment = request.split_uri()
+
+ if '%' in path:
+ path = unquote (path)
+
+ # strip off all leading slashes
+ while path and path[0] == '/':
+ path = path[1:]
+
+ if self.filesystem.isdir (path):
+ if path and path[-1] != '/':
+ request['Location'] = 'http://%s/%s/' % (
+ request.channel.server.server_name,
+ path
+ )
+ request.error (301)
+ return
+
+ # we could also generate a directory listing here,
+ # may want to move this into another method for that
+ # purpose
+ found = 0
+ if path and path[-1] != '/':
+ path = path + '/'
+ for default in self.directory_defaults:
+ p = path + default
+ if self.filesystem.isfile (p):
+ path = p
+ found = 1
+ break
+ if not found:
+ request.error (404) # Not Found
+ return
+
+ elif not self.filesystem.isfile (path):
+ request.error (404) # Not Found
+ return
+
+ file_length = self.filesystem.stat (path)[stat.ST_SIZE]
+
+ ims = get_header_match (IF_MODIFIED_SINCE, request.header)
+
+ length_match = 1
+ if ims:
+ length = ims.group (4)
+ if length:
+ try:
+ length = string.atoi (length)
+ if length != file_length:
+ length_match = 0
+ except:
+ pass
+
+ ims_date = 0
+
+ if ims:
+ ims_date = http_date.parse_http_date (ims.group (1))
+
+ try:
+ mtime = self.filesystem.stat (path)[stat.ST_MTIME]
+ except:
+ request.error (404)
+ return
+
+ if length_match and ims_date:
+ if mtime <= ims_date:
+ request.reply_code = 304
+ request.done()
+ self.cache_counter.increment()
+ return
+ try:
+ file = self.filesystem.open (path, 'rb')
+ except IOError:
+ request.error (404)
+ return
+
+ request['Last-Modified'] = http_date.build_http_date (mtime)
+ request['Content-Length'] = file_length
+ self.set_content_type (path, request)
+
+ if request.command == 'get':
+ request.push (self.default_file_producer (file))
+
+ self.file_counter.increment()
+ request.done()
+
+ def set_content_type (self, path, request):
+ ext = string.lower (get_extension (path))
+ if mime_type_table.content_type_map.has_key (ext):
+ request['Content-Type'] = mime_type_table.content_type_map[ext]
+ else:
+ # TODO: test a chunk off the front of the file for 8-bit
+ # characters, and use application/octet-stream instead.
+ request['Content-Type'] = 'text/plain'
+
+ def status (self):
+ return producers.simple_producer (
+ '<li>%s' % status_handler.html_repr (self)
+ + '<ul>'
+ + ' <li><b>Total Hits:</b> %s' % self.hit_counter
+ + ' <li><b>Files Delivered:</b> %s' % self.file_counter
+ + ' <li><b>Cache Hits:</b> %s' % self.cache_counter
+ + '</ul>'
+ )
+
+# HTTP/1.0 doesn't say anything about the "; length=nnnn" addition
+# to this header. I suppose it's purpose is to avoid the overhead
+# of parsing dates...
+IF_MODIFIED_SINCE = re.compile (
+ 'If-Modified-Since: ([^;]+)((; length=([0-9]+)$)|$)',
+ re.IGNORECASE
+ )
+
+USER_AGENT = re.compile ('User-Agent: (.*)', re.IGNORECASE)
+
+CONTENT_TYPE = re.compile (
+ r'Content-Type: ([^;]+)((; boundary=([A-Za-z0-9\'\(\)+_,./:=?-]+)$)|$)',
+ re.IGNORECASE
+ )
+
+get_header = http_server.get_header
+get_header_match = http_server.get_header_match
+
+def get_extension (path):
+ dirsep = string.rfind (path, '/')
+ dotsep = string.rfind (path, '.')
+ if dotsep > dirsep:
+ return path[dotsep+1:]
+ else:
+ return ''
diff --git a/demo/medusa/dh1024.pem b/demo/medusa/dh1024.pem
new file mode 100644
index 0000000..81d43f6
--- /dev/null
+++ b/demo/medusa/dh1024.pem
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/medusa/filesys.py b/demo/medusa/filesys.py
new file mode 100644
index 0000000..003af2e
--- /dev/null
+++ b/demo/medusa/filesys.py
@@ -0,0 +1,466 @@
+# -*- Mode: Python; tab-width: 4 -*-
+# $Id: filesys.py 299 2005-06-09 17:32:28Z heikki $
+# Author: Sam Rushing <rushing@nightmare.com>
+#
+# Generic filesystem interface.
+#
+
+# We want to provide a complete wrapper around any and all
+# filesystem operations.
+
+# this class is really just for documentation,
+# identifying the API for a filesystem object.
+
+# opening files for reading, and listing directories, should
+# return a producer.
+
+class abstract_filesystem:
+ def __init__ (self):
+ pass
+
+ def current_directory (self):
+ "Return a string representing the current directory."
+ pass
+
+ def listdir (self, path, long=0):
+ """Return a listing of the directory at 'path' The empty string
+ indicates the current directory. If 'long' is set, instead
+ return a list of (name, stat_info) tuples
+ """
+ pass
+
+ def open (self, path, mode):
+ "Return an open file object"
+ pass
+
+ def stat (self, path):
+ "Return the equivalent of os.stat() on the given path."
+ pass
+
+ def isdir (self, path):
+ "Does the path represent a directory?"
+ pass
+
+ def isfile (self, path):
+ "Does the path represent a plain file?"
+ pass
+
+ def cwd (self, path):
+ "Change the working directory."
+ pass
+
+ def cdup (self):
+ "Change to the parent of the current directory."
+ pass
+
+
+ def longify (self, path):
+ """Return a 'long' representation of the filename
+ [for the output of the LIST command]"""
+ pass
+
+# standard wrapper around a unix-like filesystem, with a 'false root'
+# capability.
+
+# security considerations: can symbolic links be used to 'escape' the
+# root? should we allow it? if not, then we could scan the
+# filesystem on startup, but that would not help if they were added
+# later. We will probably need to check for symlinks in the cwd method.
+
+# what to do if wd is an invalid directory?
+
+import os
+import stat
+
+import string
+
+def safe_stat (path):
+ try:
+ return (path, os.stat (path))
+ except:
+ return None
+
+import regsub
+import glob
+
+class os_filesystem:
+ path_module = os.path
+
+ # set this to zero if you want to disable pathname globbing.
+ # [we currently don't glob, anyway]
+ do_globbing = 1
+
+ def __init__ (self, root, wd='/'):
+ self.root = root
+ self.wd = wd
+
+ def current_directory (self):
+ return self.wd
+
+ def isfile (self, path):
+ p = self.normalize (self.path_module.join (self.wd, path))
+ return self.path_module.isfile (self.translate(p))
+
+ def isdir (self, path):
+ p = self.normalize (self.path_module.join (self.wd, path))
+ return self.path_module.isdir (self.translate(p))
+
+ def cwd (self, path):
+ p = self.normalize (self.path_module.join (self.wd, path))
+ translated_path = self.translate(p)
+ if not self.path_module.isdir (translated_path):
+ return 0
+ else:
+ old_dir = os.getcwd()
+ # temporarily change to that directory, in order
+ # to see if we have permission to do so.
+ try:
+ can = 0
+ try:
+ os.chdir (translated_path)
+ can = 1
+ self.wd = p
+ except:
+ pass
+ finally:
+ if can:
+ os.chdir (old_dir)
+ return can
+
+ def cdup (self):
+ return self.cwd ('..')
+
+ def listdir (self, path, long=0):
+ p = self.translate (path)
+ # I think we should glob, but limit it to the current
+ # directory only.
+ ld = os.listdir (p)
+ if not long:
+ return list_producer (ld, 0, None)
+ else:
+ old_dir = os.getcwd()
+ try:
+ os.chdir (p)
+ # if os.stat fails we ignore that file.
+ result = filter (None, map (safe_stat, ld))
+ finally:
+ os.chdir (old_dir)
+ return list_producer (result, 1, self.longify)
+
+ # TODO: implement a cache w/timeout for stat()
+ def stat (self, path):
+ p = self.translate (path)
+ return os.stat (p)
+
+ def open (self, path, mode):
+ p = self.translate (path)
+ return open (p, mode)
+
+ def unlink (self, path):
+ p = self.translate (path)
+ return os.unlink (p)
+
+ def mkdir (self, path):
+ p = self.translate (path)
+ return os.mkdir (p)
+
+ def rmdir (self, path):
+ p = self.translate (path)
+ return os.rmdir (p)
+
+ # utility methods
+ def normalize (self, path):
+ # watch for the ever-sneaky '/+' path element
+ path = regsub.gsub ('/+', '/', path)
+ p = self.path_module.normpath (path)
+ # remove 'dangling' cdup's.
+ if len(p) > 2 and p[:3] == '/..':
+ p = '/'
+ return p
+
+ def translate (self, path):
+ # we need to join together three separate
+ # path components, and do it safely.
+ # <real_root>/<current_directory>/<path>
+ # use the operating system's path separator.
+ path = string.join (string.split (path, '/'), os.sep)
+ p = self.normalize (self.path_module.join (self.wd, path))
+ p = self.normalize (self.path_module.join (self.root, p[1:]))
+ return p
+
+ def longify (self, (path, stat_info)):
+ return unix_longify (path, stat_info)
+
+ def __repr__ (self):
+ return '<unix-style fs root:%s wd:%s>' % (
+ self.root,
+ self.wd
+ )
+
+if os.name == 'posix':
+
+ class unix_filesystem (os_filesystem):
+ pass
+
+ class schizophrenic_unix_filesystem (os_filesystem):
+ PROCESS_UID = os.getuid()
+ PROCESS_EUID = os.geteuid()
+ PROCESS_GID = os.getgid()
+ PROCESS_EGID = os.getegid()
+
+ def __init__ (self, root, wd='/', persona=(None, None)):
+ os_filesystem.__init__ (self, root, wd)
+ self.persona = persona
+
+ def become_persona (self):
+ if self.persona is not (None, None):
+ uid, gid = self.persona
+ # the order of these is important!
+ os.setegid (gid)
+ os.seteuid (uid)
+
+ def become_nobody (self):
+ if self.persona is not (None, None):
+ os.seteuid (self.PROCESS_UID)
+ os.setegid (self.PROCESS_GID)
+
+ # cwd, cdup, open, listdir
+ def cwd (self, path):
+ try:
+ self.become_persona()
+ return os_filesystem.cwd (self, path)
+ finally:
+ self.become_nobody()
+
+ def cdup (self, path):
+ try:
+ self.become_persona()
+ return os_filesystem.cdup (self)
+ finally:
+ self.become_nobody()
+
+ def open (self, filename, mode):
+ try:
+ self.become_persona()
+ return os_filesystem.open (self, filename, mode)
+ finally:
+ self.become_nobody()
+
+ def listdir (self, path, long=0):
+ try:
+ self.become_persona()
+ return os_filesystem.listdir (self, path, long)
+ finally:
+ self.become_nobody()
+
+# This hasn't been very reliable across different platforms.
+# maybe think about a separate 'directory server'.
+#
+# import posixpath
+# import fcntl
+# import FCNTL
+# import select
+# import asyncore
+#
+# # pipes /bin/ls for directory listings.
+# class unix_filesystem (os_filesystem):
+# pass
+# path_module = posixpath
+#
+# def listdir (self, path, long=0):
+# p = self.translate (path)
+# if not long:
+# return list_producer (os.listdir (p), 0, None)
+# else:
+# command = '/bin/ls -l %s' % p
+# print 'opening pipe to "%s"' % command
+# fd = os.popen (command, 'rt')
+# return pipe_channel (fd)
+#
+# # this is both a dispatcher, _and_ a producer
+# class pipe_channel (asyncore.file_dispatcher):
+# buffer_size = 4096
+#
+# def __init__ (self, fd):
+# asyncore.file_dispatcher.__init__ (self, fd)
+# self.fd = fd
+# self.done = 0
+# self.data = ''
+#
+# def handle_read (self):
+# if len (self.data) < self.buffer_size:
+# self.data = self.data + self.fd.read (self.buffer_size)
+# #print '%s.handle_read() => len(self.data) == %d' % (self, len(self.data))
+#
+# def handle_expt (self):
+# #print '%s.handle_expt()' % self
+# self.done = 1
+#
+# def ready (self):
+# #print '%s.ready() => %d' % (self, len(self.data))
+# return ((len (self.data) > 0) or self.done)
+#
+# def more (self):
+# if self.data:
+# r = self.data
+# self.data = ''
+# elif self.done:
+# self.close()
+# self.downstream.finished()
+# r = ''
+# else:
+# r = None
+# #print '%s.more() => %s' % (self, (r and len(r)))
+# return r
+
+# For the 'real' root, we could obtain a list of drives, and then
+# use that. Doesn't win32 provide such a 'real' filesystem?
+# [yes, I think something like this "\\.\c\windows"]
+
+class msdos_filesystem (os_filesystem):
+ def longify (self, (path, stat_info)):
+ return msdos_longify (path, stat_info)
+
+# A merged filesystem will let you plug other filesystems together.
+# We really need the equivalent of a 'mount' capability - this seems
+# to be the most general idea. So you'd use a 'mount' method to place
+# another filesystem somewhere in the hierarchy.
+
+# Note: this is most likely how I will handle ~user directories
+# with the http server.
+
+class merged_filesystem:
+ def __init__ (self, *fsys):
+ pass
+
+# this matches the output of NT's ftp server (when in
+# MSDOS mode) exactly.
+
+def msdos_longify (file, stat_info):
+ if stat.S_ISDIR (stat_info[stat.ST_MODE]):
+ dir = '<DIR>'
+ else:
+ dir = ' '
+ date = msdos_date (stat_info[stat.ST_MTIME])
+ return '%s %s %8d %s' % (
+ date,
+ dir,
+ stat_info[stat.ST_SIZE],
+ file
+ )
+
+def msdos_date (t):
+ try:
+ info = time.gmtime (t)
+ except:
+ info = time.gmtime (0)
+ # year, month, day, hour, minute, second, ...
+ if info[3] > 11:
+ merid = 'PM'
+ info[3] = info[3] - 12
+ else:
+ merid = 'AM'
+ return '%02d-%02d-%02d %02d:%02d%s' % (
+ info[1],
+ info[2],
+ info[0]%100,
+ info[3],
+ info[4],
+ merid
+ )
+
+months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+mode_table = {
+ '0':'---',
+ '1':'--x',
+ '2':'-w-',
+ '3':'-wx',
+ '4':'r--',
+ '5':'r-x',
+ '6':'rw-',
+ '7':'rwx'
+ }
+
+import time
+
+def unix_longify (file, stat_info):
+ # for now, only pay attention to the lower bits
+ mode = ('%o' % stat_info[stat.ST_MODE])[-3:]
+ mode = string.join (map (lambda x: mode_table[x], mode), '')
+ if stat.S_ISDIR (stat_info[stat.ST_MODE]):
+ dirchar = 'd'
+ else:
+ dirchar = '-'
+ date = ls_date (long(time.time()), stat_info[stat.ST_MTIME])
+ return '%s%s %3d %-8d %-8d %8d %s %s' % (
+ dirchar,
+ mode,
+ stat_info[stat.ST_NLINK],
+ stat_info[stat.ST_UID],
+ stat_info[stat.ST_GID],
+ stat_info[stat.ST_SIZE],
+ date,
+ file
+ )
+
+# Emulate the unix 'ls' command's date field.
+# it has two formats - if the date is more than 180
+# days in the past, then it's like this:
+# Oct 19 1995
+# otherwise, it looks like this:
+# Oct 19 17:33
+
+def ls_date (now, t):
+ try:
+ info = time.gmtime (t)
+ except:
+ info = time.gmtime (0)
+ # 15,600,000 == 86,400 * 180
+ if (now - t) > 15600000:
+ return '%s %2d %d' % (
+ months[info[1]-1],
+ info[2],
+ info[0]
+ )
+ else:
+ return '%s %2d %02d:%02d' % (
+ months[info[1]-1],
+ info[2],
+ info[3],
+ info[4]
+ )
+
+# ===========================================================================
+# Producers
+# ===========================================================================
+
+class list_producer:
+ def __init__ (self, file_list, long, longify):
+ self.file_list = file_list
+ self.long = long
+ self.longify = longify
+ self.done = 0
+
+ def ready (self):
+ if len(self.file_list):
+ return 1
+ else:
+ if not self.done:
+ self.done = 1
+ return 0
+ return (len(self.file_list) > 0)
+
+ # this should do a pushd/popd
+ def more (self):
+ if not self.file_list:
+ return ''
+ else:
+ # do a few at a time
+ bunch = self.file_list[:50]
+ if self.long:
+ bunch = map (self.longify, bunch)
+ self.file_list = self.file_list[50:]
+ return string.joinfields (bunch, '\r\n') + '\r\n'
+
diff --git a/demo/medusa/ftp_server.py b/demo/medusa/ftp_server.py
new file mode 100644
index 0000000..78363cf
--- /dev/null
+++ b/demo/medusa/ftp_server.py
@@ -0,0 +1,1127 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+# Author: Sam Rushing <rushing@nightmare.com>
+# Copyright 1996-2000 by Sam Rushing
+# All Rights Reserved.
+#
+
+# An extensible, configurable, asynchronous FTP server.
+#
+# All socket I/O is non-blocking, however file I/O is currently
+# blocking. Eventually file I/O may be made non-blocking, too, if it
+# seems necessary. Currently the only CPU-intensive operation is
+# getting and formatting a directory listing. [this could be moved
+# into another process/directory server, or another thread?]
+#
+# Only a subset of RFC 959 is implemented, but much of that RFC is
+# vestigial anyway. I've attempted to include the most commonly-used
+# commands, using the feature set of wu-ftpd as a guide.
+
+import asyncore
+import asynchat
+
+import os
+import regsub
+import socket
+import stat
+import string
+import sys
+import time
+
+# TODO: implement a directory listing cache. On very-high-load
+# servers this could save a lot of disk abuse, and possibly the
+# work of computing emulated unix ls output.
+
+# Potential security problem with the FTP protocol? I don't think
+# there's any verification of the origin of a data connection. Not
+# really a problem for the server (since it doesn't send the port
+# command, except when in PASV mode) But I think a data connection
+# could be spoofed by a program with access to a sniffer - it could
+# watch for a PORT command to go over a command channel, and then
+# connect to that port before the server does.
+
+# Unix user id's:
+# In order to support assuming the id of a particular user,
+# it seems there are two options:
+# 1) fork, and seteuid in the child
+# 2) carefully control the effective uid around filesystem accessing
+# methods, using try/finally. [this seems to work]
+
+VERSION = '1.1'
+
+from counter import counter
+import producers
+import status_handler
+import logger
+import string
+
+class ftp_channel (asynchat.async_chat):
+
+ # defaults for a reliable __repr__
+ addr = ('unknown','0')
+
+ # unset this in a derived class in order
+ # to enable the commands in 'self.write_commands'
+ read_only = 1
+ write_commands = ['appe','dele','mkd','rmd','rnfr','rnto','stor','stou']
+
+ restart_position = 0
+
+ # comply with (possibly troublesome) RFC959 requirements
+ # This is necessary to correctly run an active data connection
+ # through a firewall that triggers on the source port (expected
+ # to be 'L-1', or 20 in the normal case).
+ bind_local_minus_one = 0
+
+ def __init__ (self, server, conn, addr):
+ self.server = server
+ self.current_mode = 'a'
+ self.addr = addr
+ asynchat.async_chat.__init__ (self, conn)
+ self.set_terminator ('\r\n')
+
+ # client data port. Defaults to 'the same as the control connection'.
+ self.client_addr = (addr[0], 21)
+
+ self.client_dc = None
+ self.in_buffer = ''
+ self.closing = 0
+ self.passive_acceptor = None
+ self.passive_connection = None
+ self.filesystem = None
+ self.authorized = 0
+ # send the greeting
+ self.respond (
+ '220 %s FTP server (Medusa Async V%s [experimental]) ready.' % (
+ self.server.hostname,
+ VERSION
+ )
+ )
+
+# def __del__ (self):
+# print 'ftp_channel.__del__()'
+
+ # --------------------------------------------------
+ # async-library methods
+ # --------------------------------------------------
+
+ def handle_expt (self):
+ # this is handled below. not sure what I could
+ # do here to make that code less kludgish.
+ pass
+
+ def collect_incoming_data (self, data):
+ self.in_buffer = self.in_buffer + data
+ if len(self.in_buffer) > 4096:
+ # silently truncate really long lines
+ # (possible denial-of-service attack)
+ self.in_buffer = ''
+
+ def found_terminator (self):
+
+ line = self.in_buffer
+
+ if not len(line):
+ return
+
+ sp = string.find (line, ' ')
+ if sp != -1:
+ line = [line[:sp], line[sp+1:]]
+ else:
+ line = [line]
+
+ command = string.lower (line[0])
+ # watch especially for 'urgent' abort commands.
+ if string.find (command, 'abor') != -1:
+ # strip off telnet sync chars and the like...
+ while command and command[0] not in string.letters:
+ command = command[1:]
+ fun_name = 'cmd_%s' % command
+ if command != 'pass':
+ self.log ('<== %s' % repr(self.in_buffer)[1:-1])
+ else:
+ self.log ('<== %s' % line[0]+' <password>')
+ self.in_buffer = ''
+ if not hasattr (self, fun_name):
+ self.command_not_understood (line[0])
+ return
+ fun = getattr (self, fun_name)
+ if (not self.authorized) and (command not in ('user', 'pass', 'help', 'quit')):
+ self.respond ('530 Please log in with USER and PASS')
+ elif (not self.check_command_authorization (command)):
+ self.command_not_authorized (command)
+ else:
+ try:
+ result = apply (fun, (line,))
+ except:
+ self.server.total_exceptions.increment()
+ (file, fun, line), t,v, tbinfo = asyncore.compact_traceback()
+ if self.client_dc:
+ try:
+ self.client_dc.close()
+ except:
+ pass
+ self.respond (
+ '451 Server Error: %s, %s: file: %s line: %s' % (
+ t,v,file,line,
+ )
+ )
+
+ closed = 0
+ def close (self):
+ if not self.closed:
+ self.closed = 1
+ if self.passive_acceptor:
+ self.passive_acceptor.close()
+ if self.client_dc:
+ self.client_dc.close()
+ self.server.closed_sessions.increment()
+ asynchat.async_chat.close (self)
+
+ # --------------------------------------------------
+ # filesystem interface functions.
+ # override these to provide access control or perform
+ # other functions.
+ # --------------------------------------------------
+
+ def cwd (self, line):
+ return self.filesystem.cwd (line[1])
+
+ def cdup (self, line):
+ return self.filesystem.cdup()
+
+ def open (self, path, mode):
+ return self.filesystem.open (path, mode)
+
+ # returns a producer
+ def listdir (self, path, long=0):
+ return self.filesystem.listdir (path, long)
+
+ def get_dir_list (self, line, long=0):
+ # we need to scan the command line for arguments to '/bin/ls'...
+ args = line[1:]
+ path_args = []
+ for arg in args:
+ if arg[0] != '-':
+ path_args.append (arg)
+ else:
+ # ignore arguments
+ pass
+ if len(path_args) < 1:
+ dir = '.'
+ else:
+ dir = path_args[0]
+ return self.listdir (dir, long)
+
+ # --------------------------------------------------
+ # authorization methods
+ # --------------------------------------------------
+
+ def check_command_authorization (self, command):
+ if command in self.write_commands and self.read_only:
+ return 0
+ else:
+ return 1
+
+ # --------------------------------------------------
+ # utility methods
+ # --------------------------------------------------
+
+ def log (self, message):
+ self.server.logger.log (
+ self.addr[0],
+ '%d %s' % (
+ self.addr[1], message
+ )
+ )
+
+ def respond (self, resp):
+ self.log ('==> %s' % resp)
+ self.push (resp + '\r\n')
+
+ def command_not_understood (self, command):
+ self.respond ("500 '%s': command not understood." % command)
+
+ def command_not_authorized (self, command):
+ self.respond (
+ "530 You are not authorized to perform the '%s' command" % (
+ command
+ )
+ )
+
+ def make_xmit_channel (self):
+ # In PASV mode, the connection may or may _not_ have been made
+ # yet. [although in most cases it is... FTP Explorer being
+ # the only exception I've yet seen]. This gets somewhat confusing
+ # because things may happen in any order...
+ pa = self.passive_acceptor
+ if pa:
+ if pa.ready:
+ # a connection has already been made.
+ conn, addr = self.passive_acceptor.ready
+ cdc = xmit_channel (self, addr)
+ cdc.set_socket (conn)
+ cdc.connected = 1
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ else:
+ # we're still waiting for a connect to the PASV port.
+ cdc = xmit_channel (self)
+ else:
+ # not in PASV mode.
+ ip, port = self.client_addr
+ cdc = xmit_channel (self, self.client_addr)
+ cdc.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+ if self.bind_local_minus_one:
+ cdc.bind (('', self.server.port - 1))
+ try:
+ cdc.connect ((ip, port))
+ except socket.error, why:
+ self.respond ("425 Can't build data connection")
+ self.client_dc = cdc
+
+ # pretty much the same as xmit, but only right on the verge of
+ # being worth a merge.
+ def make_recv_channel (self, fd):
+ pa = self.passive_acceptor
+ if pa:
+ if pa.ready:
+ # a connection has already been made.
+ conn, addr = pa.ready
+ cdc = recv_channel (self, addr, fd)
+ cdc.set_socket (conn)
+ cdc.connected = 1
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ else:
+ # we're still waiting for a connect to the PASV port.
+ cdc = recv_channel (self, None, fd)
+ else:
+ # not in PASV mode.
+ ip, port = self.client_addr
+ cdc = recv_channel (self, self.client_addr, fd)
+ cdc.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ cdc.connect ((ip, port))
+ except socket.error, why:
+ self.respond ("425 Can't build data connection")
+ self.client_dc = cdc
+
+ type_map = {
+ 'a':'ASCII',
+ 'i':'Binary',
+ 'e':'EBCDIC',
+ 'l':'Binary'
+ }
+
+ type_mode_map = {
+ 'a':'t',
+ 'i':'b',
+ 'e':'b',
+ 'l':'b'
+ }
+
+ # --------------------------------------------------
+ # command methods
+ # --------------------------------------------------
+
+ def cmd_type (self, line):
+ 'specify data transfer type'
+ # ascii, ebcdic, image, local <byte size>
+ t = string.lower (line[1])
+ # no support for EBCDIC
+ # if t not in ['a','e','i','l']:
+ if t not in ['a','i','l']:
+ self.command_not_understood (string.join (line))
+ elif t == 'l' and (len(line) > 2 and line[2] != '8'):
+ self.respond ('504 Byte size must be 8')
+ else:
+ self.current_mode = t
+ self.respond ('200 Type set to %s.' % self.type_map[t])
+
+
+ def cmd_quit (self, line):
+ 'terminate session'
+ self.respond ('221 Goodbye.')
+ self.close_when_done()
+
+ def cmd_port (self, line):
+ 'specify data connection port'
+ info = string.split (line[1], ',')
+ ip = string.join (info[:4], '.')
+ port = string.atoi(info[4])*256 + string.atoi(info[5])
+ # how many data connections at a time?
+ # I'm assuming one for now...
+ # TODO: we should (optionally) verify that the
+ # ip number belongs to the client. [wu-ftpd does this?]
+ self.client_addr = (ip, port)
+ self.respond ('200 PORT command successful.')
+
+ def new_passive_acceptor (self):
+ # ensure that only one of these exists at a time.
+ if self.passive_acceptor is not None:
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ self.passive_acceptor = passive_acceptor (self)
+ return self.passive_acceptor
+
+ def cmd_pasv (self, line):
+ 'prepare for server-to-server transfer'
+ pc = self.new_passive_acceptor()
+ port = pc.addr[1]
+ ip_addr = pc.control_channel.getsockname()[0]
+ self.respond (
+ '227 Entering Passive Mode (%s,%d,%d)' % (
+ string.join (string.split (ip_addr, '.'), ','),
+ port/256,
+ port%256
+ )
+ )
+ self.client_dc = None
+
+ def cmd_nlst (self, line):
+ 'give name list of files in directory'
+ # ncftp adds the -FC argument for the user-visible 'nlist'
+ # command. We could try to emulate ls flags, but not just yet.
+ if '-FC' in line:
+ line.remove ('-FC')
+ try:
+ dir_list_producer = self.get_dir_list (line, 0)
+ except os.error, why:
+ self.respond ('550 Could not list directory: %s' % repr(why))
+ return
+ self.respond (
+ '150 Opening %s mode data connection for file list' % (
+ self.type_map[self.current_mode]
+ )
+ )
+ self.make_xmit_channel()
+ self.client_dc.push_with_producer (dir_list_producer)
+ self.client_dc.close_when_done()
+
+ def cmd_list (self, line):
+ 'give list files in a directory'
+ try:
+ dir_list_producer = self.get_dir_list (line, 1)
+ except os.error, why:
+ self.respond ('550 Could not list directory: %s' % repr(why))
+ return
+ self.respond (
+ '150 Opening %s mode data connection for file list' % (
+ self.type_map[self.current_mode]
+ )
+ )
+ self.make_xmit_channel()
+ self.client_dc.push_with_producer (dir_list_producer)
+ self.client_dc.close_when_done()
+
+ def cmd_cwd (self, line):
+ 'change working directory'
+ if self.cwd (line):
+ self.respond ('250 CWD command successful.')
+ else:
+ self.respond ('550 No such directory.')
+
+ def cmd_cdup (self, line):
+ 'change to parent of current working directory'
+ if self.cdup(line):
+ self.respond ('250 CDUP command successful.')
+ else:
+ self.respond ('550 No such directory.')
+
+ def cmd_pwd (self, line):
+ 'print the current working directory'
+ self.respond (
+ '257 "%s" is the current directory.' % (
+ self.filesystem.current_directory()
+ )
+ )
+
+ # modification time
+ # example output:
+ # 213 19960301204320
+ def cmd_mdtm (self, line):
+ 'show last modification time of file'
+ filename = line[1]
+ if not self.filesystem.isfile (filename):
+ self.respond ('550 "%s" is not a file' % filename)
+ else:
+ mtime = time.gmtime(self.filesystem.stat(filename)[stat.ST_MTIME])
+ self.respond (
+ '213 %4d%02d%02d%02d%02d%02d' % (
+ mtime[0],
+ mtime[1],
+ mtime[2],
+ mtime[3],
+ mtime[4],
+ mtime[5]
+ )
+ )
+
+ def cmd_noop (self, line):
+ 'do nothing'
+ self.respond ('200 NOOP command successful.')
+
+ def cmd_size (self, line):
+ 'return size of file'
+ filename = line[1]
+ if not self.filesystem.isfile (filename):
+ self.respond ('550 "%s" is not a file' % filename)
+ else:
+ self.respond (
+ '213 %d' % (self.filesystem.stat(filename)[stat.ST_SIZE])
+ )
+
+ def cmd_retr (self, line):
+ 'retrieve a file'
+ if len(line) < 2:
+ self.command_not_understood (string.join (line))
+ else:
+ file = line[1]
+ if not self.filesystem.isfile (file):
+ self.log_info ('checking %s' % file)
+ self.respond ('550 No such file')
+ else:
+ try:
+ # FIXME: for some reason, 'rt' isn't working on win95
+ mode = 'r'+self.type_mode_map[self.current_mode]
+ fd = self.open (file, mode)
+ except IOError, why:
+ self.respond ('553 could not open file for reading: %s' % (repr(why)))
+ return
+ self.respond (
+ "150 Opening %s mode data connection for file '%s'" % (
+ self.type_map[self.current_mode],
+ file
+ )
+ )
+ self.make_xmit_channel()
+
+ if self.restart_position:
+ # try to position the file as requested, but
+ # give up silently on failure (the 'file object'
+ # may not support seek())
+ try:
+ fd.seek (self.restart_position)
+ except:
+ pass
+ self.restart_position = 0
+
+ self.client_dc.push_with_producer (
+ file_producer (self, self.client_dc, fd)
+ )
+ self.client_dc.close_when_done()
+
+ def cmd_stor (self, line, mode='wb'):
+ 'store a file'
+ if len (line) < 2:
+ self.command_not_understood (string.join (line))
+ else:
+ if self.restart_position:
+ restart_position = 0
+ self.respond ('553 restart on STOR not yet supported')
+ return
+ file = line[1]
+ # todo: handle that type flag
+ try:
+ fd = self.open (file, mode)
+ except IOError, why:
+ self.respond ('553 could not open file for writing: %s' % (repr(why)))
+ return
+ self.respond (
+ '150 Opening %s connection for %s' % (
+ self.type_map[self.current_mode],
+ file
+ )
+ )
+ self.make_recv_channel (fd)
+
+ def cmd_abor (self, line):
+ 'abort operation'
+ if self.client_dc:
+ self.client_dc.close()
+ self.respond ('226 ABOR command successful.')
+
+ def cmd_appe (self, line):
+ 'append to a file'
+ return self.cmd_stor (line, 'ab')
+
+ def cmd_dele (self, line):
+ if len (line) != 2:
+ self.command_not_understood (string.join (line))
+ else:
+ file = line[1]
+ if self.filesystem.isfile (file):
+ try:
+ self.filesystem.unlink (file)
+ self.respond ('250 DELE command successful.')
+ except:
+ self.respond ('550 error deleting file.')
+ else:
+ self.respond ('550 %s: No such file.' % file)
+
+ def cmd_mkd (self, line):
+ if len (line) != 2:
+ self.command.not_understood (string.join (line))
+ else:
+ path = line[1]
+ try:
+ self.filesystem.mkdir (path)
+ self.respond ('257 MKD command successful.')
+ except:
+ self.respond ('550 error creating directory.')
+
+ def cmd_rmd (self, line):
+ if len (line) != 2:
+ self.command.not_understood (string.join (line))
+ else:
+ path = line[1]
+ try:
+ self.filesystem.rmdir (path)
+ self.respond ('250 RMD command successful.')
+ except:
+ self.respond ('550 error removing directory.')
+
+ def cmd_user (self, line):
+ 'specify user name'
+ if len(line) > 1:
+ self.user = line[1]
+ self.respond ('331 Password required.')
+ else:
+ self.command_not_understood (string.join (line))
+
+ def cmd_pass (self, line):
+ 'specify password'
+ if len(line) < 2:
+ pw = ''
+ else:
+ pw = line[1]
+ result, message, fs = self.server.authorizer.authorize (self, self.user, pw)
+ if result:
+ self.respond ('230 %s' % message)
+ self.filesystem = fs
+ self.authorized = 1
+ self.log_info('Successful login: Filesystem=%s' % repr(fs))
+ else:
+ self.respond ('530 %s' % message)
+
+ def cmd_rest (self, line):
+ 'restart incomplete transfer'
+ try:
+ pos = string.atoi (line[1])
+ except ValueError:
+ self.command_not_understood (string.join (line))
+ self.restart_position = pos
+ self.respond (
+ '350 Restarting at %d. Send STORE or RETRIEVE to initiate transfer.' % pos
+ )
+
+ def cmd_stru (self, line):
+ 'obsolete - set file transfer structure'
+ if line[1] in 'fF':
+ # f == 'file'
+ self.respond ('200 STRU F Ok')
+ else:
+ self.respond ('504 Unimplemented STRU type')
+
+ def cmd_mode (self, line):
+ 'obsolete - set file transfer mode'
+ if line[1] in 'sS':
+ # f == 'file'
+ self.respond ('200 MODE S Ok')
+ else:
+ self.respond ('502 Unimplemented MODE type')
+
+# The stat command has two personalities. Normally it returns status
+# information about the current connection. But if given an argument,
+# it is equivalent to the LIST command, with the data sent over the
+# control connection. Strange. But wuftpd, ftpd, and nt's ftp server
+# all support it.
+#
+## def cmd_stat (self, line):
+## 'return status of server'
+## pass
+
+ def cmd_syst (self, line):
+ 'show operating system type of server system'
+ # Replying to this command is of questionable utility, because
+ # this server does not behave in a predictable way w.r.t. the
+ # output of the LIST command. We emulate Unix ls output, but
+ # on win32 the pathname can contain drive information at the front
+ # Currently, the combination of ensuring that os.sep == '/'
+ # and removing the leading slash when necessary seems to work.
+ # [cd'ing to another drive also works]
+ #
+ # This is how wuftpd responds, and is probably
+ # the most expected. The main purpose of this reply is so that
+ # the client knows to expect Unix ls-style LIST output.
+ self.respond ('215 UNIX Type: L8')
+ # one disadvantage to this is that some client programs
+ # assume they can pass args to /bin/ls.
+ # a few typical responses:
+ # 215 UNIX Type: L8 (wuftpd)
+ # 215 Windows_NT version 3.51
+ # 215 VMS MultiNet V3.3
+ # 500 'SYST': command not understood. (SVR4)
+
+ def cmd_help (self, line):
+ 'give help information'
+ # find all the methods that match 'cmd_xxxx',
+ # use their docstrings for the help response.
+ attrs = dir(self.__class__)
+ help_lines = []
+ for attr in attrs:
+ if attr[:4] == 'cmd_':
+ x = getattr (self, attr)
+ if type(x) == type(self.cmd_help):
+ if x.__doc__:
+ help_lines.append ('\t%s\t%s' % (attr[4:], x.__doc__))
+ if help_lines:
+ self.push ('214-The following commands are recognized\r\n')
+ self.push_with_producer (producers.lines_producer (help_lines))
+ self.push ('214\r\n')
+ else:
+ self.push ('214-\r\n\tHelp Unavailable\r\n214\r\n')
+
+class ftp_server (asyncore.dispatcher):
+ # override this to spawn a different FTP channel class.
+ ftp_channel_class = ftp_channel
+
+ SERVER_IDENT = 'FTP Server (V%s)' % VERSION
+
+ def __init__ (
+ self,
+ authorizer,
+ hostname =None,
+ ip ='',
+ port =21,
+ resolver =None,
+ logger_object=logger.file_logger (sys.stdout)
+ ):
+ self.ip = ip
+ self.port = port
+ self.authorizer = authorizer
+
+ if hostname is None:
+ self.hostname = socket.gethostname()
+ else:
+ self.hostname = hostname
+
+ # statistics
+ self.total_sessions = counter()
+ self.closed_sessions = counter()
+ self.total_files_out = counter()
+ self.total_files_in = counter()
+ self.total_bytes_out = counter()
+ self.total_bytes_in = counter()
+ self.total_exceptions = counter()
+ #
+ asyncore.dispatcher.__init__ (self)
+ self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+ self.set_reuse_addr()
+ self.bind ((self.ip, self.port))
+ self.listen (5)
+
+ if not logger_object:
+ logger_object = sys.stdout
+
+ if resolver:
+ self.logger = logger.resolving_logger (resolver, logger_object)
+ else:
+ self.logger = logger.unresolving_logger (logger_object)
+
+ self.log_info('FTP server started at %s\n\tAuthorizer:%s\n\tHostname: %s\n\tPort: %d' % (
+ time.ctime(time.time()),
+ repr (self.authorizer),
+ self.hostname,
+ self.port)
+ )
+
+ def writable (self):
+ return 0
+
+ def handle_read (self):
+ pass
+
+ def handle_connect (self):
+ pass
+
+ def handle_accept (self):
+ conn, addr = self.accept()
+ self.total_sessions.increment()
+ self.log_info('Incoming connection from %s:%d' % (addr[0], addr[1]))
+ self.ftp_channel_class (self, conn, addr)
+
+ # return a producer describing the state of the server
+ def status (self):
+
+ def nice_bytes (n):
+ return string.join (status_handler.english_bytes (n))
+
+ return producers.lines_producer (
+ ['<h2>%s</h2>' % self.SERVER_IDENT,
+ '<br>Listening on <b>Host:</b> %s' % self.hostname,
+ '<b>Port:</b> %d' % self.port,
+ '<br>Sessions',
+ '<b>Total:</b> %s' % self.total_sessions,
+ '<b>Current:</b> %d' % (self.total_sessions.as_long() - self.closed_sessions.as_long()),
+ '<br>Files',
+ '<b>Sent:</b> %s' % self.total_files_out,
+ '<b>Received:</b> %s' % self.total_files_in,
+ '<br>Bytes',
+ '<b>Sent:</b> %s' % nice_bytes (self.total_bytes_out.as_long()),
+ '<b>Received:</b> %s' % nice_bytes (self.total_bytes_in.as_long()),
+ '<br>Exceptions: %s' % self.total_exceptions,
+ ]
+ )
+
+# ======================================================================
+# Data Channel Classes
+# ======================================================================
+
+# This socket accepts a data connection, used when the server has been
+# placed in passive mode. Although the RFC implies that we ought to
+# be able to use the same acceptor over and over again, this presents
+# a problem: how do we shut it off, so that we are accepting
+# connections only when we expect them? [we can't]
+#
+# wuftpd, and probably all the other servers, solve this by allowing
+# only one connection to hit this acceptor. They then close it. Any
+# subsequent data-connection command will then try for the default
+# port on the client side [which is of course never there]. So the
+# 'always-send-PORT/PASV' behavior seems required.
+#
+# Another note: wuftpd will also be listening on the channel as soon
+# as the PASV command is sent. It does not wait for a data command
+# first.
+
+# --- we need to queue up a particular behavior:
+# 1) xmit : queue up producer[s]
+# 2) recv : the file object
+#
+# It would be nice if we could make both channels the same. Hmmm..
+#
+
+class passive_acceptor (asyncore.dispatcher):
+ ready = None
+
+ def __init__ (self, control_channel):
+ # connect_fun (conn, addr)
+ asyncore.dispatcher.__init__ (self)
+ self.control_channel = control_channel
+ self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+ # bind to an address on the interface that the
+ # control connection is coming from.
+ self.bind ((
+ self.control_channel.getsockname()[0],
+ 0
+ ))
+ self.addr = self.getsockname()
+ self.listen (1)
+
+# def __del__ (self):
+# print 'passive_acceptor.__del__()'
+
+ def log (self, *ignore):
+ pass
+
+ def handle_accept (self):
+ conn, addr = self.accept()
+ dc = self.control_channel.client_dc
+ if dc is not None:
+ dc.set_socket (conn)
+ dc.addr = addr
+ dc.connected = 1
+ self.control_channel.passive_acceptor = None
+ else:
+ self.ready = conn, addr
+ self.close()
+
+
+class xmit_channel (asynchat.async_chat):
+
+ # for an ethernet, you want this to be fairly large, in fact, it
+ # _must_ be large for performance comparable to an ftpd. [64k] we
+ # ought to investigate automatically-sized buffers...
+
+ ac_out_buffer_size = 16384
+ bytes_out = 0
+
+ def __init__ (self, channel, client_addr=None):
+ self.channel = channel
+ self.client_addr = client_addr
+ asynchat.async_chat.__init__ (self)
+
+# def __del__ (self):
+# print 'xmit_channel.__del__()'
+
+ def log (*args):
+ pass
+
+ def readable (self):
+ return not self.connected
+
+ def writable (self):
+ return 1
+
+ def send (self, data):
+ result = asynchat.async_chat.send (self, data)
+ self.bytes_out = self.bytes_out + result
+ return result
+
+ def handle_error (self):
+ # usually this is to catch an unexpected disconnect.
+ self.log_info ('unexpected disconnect on data xmit channel', 'error')
+ try:
+ self.close()
+ except:
+ pass
+
+ # TODO: there's a better way to do this. we need to be able to
+ # put 'events' in the producer fifo. to do this cleanly we need
+ # to reposition the 'producer' fifo as an 'event' fifo.
+
+ def close (self):
+ c = self.channel
+ s = c.server
+ c.client_dc = None
+ s.total_files_out.increment()
+ s.total_bytes_out.increment (self.bytes_out)
+ if not len(self.producer_fifo):
+ c.respond ('226 Transfer complete')
+ elif not c.closed:
+ c.respond ('426 Connection closed; transfer aborted')
+ del c
+ del s
+ del self.channel
+ asynchat.async_chat.close (self)
+
+class recv_channel (asyncore.dispatcher):
+ def __init__ (self, channel, client_addr, fd):
+ self.channel = channel
+ self.client_addr = client_addr
+ self.fd = fd
+ asyncore.dispatcher.__init__ (self)
+ self.bytes_in = counter()
+
+ def log (self, *ignore):
+ pass
+
+ def handle_connect (self):
+ pass
+
+ def writable (self):
+ return 0
+
+ def recv (*args):
+ result = apply (asyncore.dispatcher.recv, args)
+ self = args[0]
+ self.bytes_in.increment(len(result))
+ return result
+
+ buffer_size = 8192
+
+ def handle_read (self):
+ block = self.recv (self.buffer_size)
+ if block:
+ try:
+ self.fd.write (block)
+ except IOError:
+ self.log_info ('got exception writing block...', 'error')
+
+ def handle_close (self):
+ s = self.channel.server
+ s.total_files_in.increment()
+ s.total_bytes_in.increment(self.bytes_in.as_long())
+ self.fd.close()
+ self.channel.respond ('226 Transfer complete.')
+ self.close()
+
+import filesys
+
+# not much of a doorman! 8^)
+class dummy_authorizer:
+ def __init__ (self, root='/'):
+ self.root = root
+ def authorize (self, channel, username, password):
+ channel.persona = -1, -1
+ channel.read_only = 1
+ return 1, 'Ok.', filesys.os_filesystem (self.root)
+
+class anon_authorizer:
+ def __init__ (self, root='/'):
+ self.root = root
+
+ def authorize (self, channel, username, password):
+ if username in ('ftp', 'anonymous'):
+ channel.persona = -1, -1
+ channel.read_only = 1
+ return 1, 'Ok.', filesys.os_filesystem (self.root)
+ else:
+ return 0, 'Password invalid.', None
+
+# ===========================================================================
+# Unix-specific improvements
+# ===========================================================================
+
+if os.name == 'posix':
+
+ class unix_authorizer:
+ # return a trio of (success, reply_string, filesystem)
+ def authorize (self, channel, username, password):
+ import crypt
+ import pwd
+ try:
+ info = pwd.getpwnam (username)
+ except KeyError:
+ return 0, 'No such user.', None
+ mangled = info[1]
+ if crypt.crypt (password, mangled[:2]) == mangled:
+ channel.read_only = 0
+ fs = filesys.schizophrenic_unix_filesystem (
+ '/',
+ info[5],
+ persona = (info[2], info[3])
+ )
+ return 1, 'Login successful.', fs
+ else:
+ return 0, 'Password invalid.', None
+
+ def __repr__ (self):
+ return '<standard unix authorizer>'
+
+ # simple anonymous ftp support
+ class unix_authorizer_with_anonymous (unix_authorizer):
+ def __init__ (self, root=None, real_users=0):
+ self.root = root
+ self.real_users = real_users
+
+ def authorize (self, channel, username, password):
+ if string.lower(username) in ['anonymous', 'ftp']:
+ import pwd
+ try:
+ # ok, here we run into lots of confusion.
+ # on some os', anon runs under user 'nobody',
+ # on others as 'ftp'. ownership is also critical.
+ # need to investigate.
+ # linux: new linuxen seem to have nobody's UID=-1,
+ # which is an illegal value. Use ftp.
+ ftp_user_info = pwd.getpwnam ('ftp')
+ if string.lower(os.uname()[0]) == 'linux':
+ nobody_user_info = pwd.getpwnam ('ftp')
+ else:
+ nobody_user_info = pwd.getpwnam ('nobody')
+ channel.read_only = 1
+ if self.root is None:
+ self.root = ftp_user_info[5]
+ fs = filesys.unix_filesystem (self.root, '/')
+ return 1, 'Anonymous Login Successful', fs
+ except KeyError:
+ return 0, 'Anonymous account not set up', None
+ elif self.real_users:
+ return unix_authorizer.authorize (
+ self,
+ channel,
+ username,
+ password
+ )
+ else:
+ return 0, 'User logins not allowed', None
+
+class file_producer:
+ block_size = 16384
+ def __init__ (self, server, dc, fd):
+ self.fd = fd
+ self.done = 0
+
+ def more (self):
+ if self.done:
+ return ''
+ else:
+ block = self.fd.read (self.block_size)
+ if not block:
+ self.fd.close()
+ self.done = 1
+ return block
+
+# usage: ftp_server /PATH/TO/FTP/ROOT PORT
+# for example:
+# $ ftp_server /home/users/ftp 8021
+
+if os.name == 'posix':
+ def test (port='8021'):
+ import sys
+ fs = ftp_server (
+ unix_authorizer(),
+ port=string.atoi (port)
+ )
+ try:
+ asyncore.loop()
+ except KeyboardInterrupt:
+ self.log_info('FTP server shutting down. (received SIGINT)', 'warning')
+ # close everything down on SIGINT.
+ # of course this should be a cleaner shutdown.
+ asyncore.close_all()
+
+ if __name__ == '__main__':
+ test (sys.argv[1])
+# not unix
+else:
+ def test ():
+ fs = ftp_server (dummy_authorizer())
+ if __name__ == '__main__':
+ test ()
+
+# this is the command list from the wuftpd man page
+# '*' means we've implemented it.
+# '!' requires write access
+#
+command_documentation = {
+ 'abor': 'abort previous command', #*
+ 'acct': 'specify account (ignored)',
+ 'allo': 'allocate storage (vacuously)',
+ 'appe': 'append to a file', #*!
+ 'cdup': 'change to parent of current working directory', #*
+ 'cwd': 'change working directory', #*
+ 'dele': 'delete a file', #!
+ 'help': 'give help information', #*
+ 'list': 'give list files in a directory', #*
+ 'mkd': 'make a directory', #!
+ 'mdtm': 'show last modification time of file', #*
+ 'mode': 'specify data transfer mode',
+ 'nlst': 'give name list of files in directory', #*
+ 'noop': 'do nothing', #*
+ 'pass': 'specify password', #*
+ 'pasv': 'prepare for server-to-server transfer', #*
+ 'port': 'specify data connection port', #*
+ 'pwd': 'print the current working directory', #*
+ 'quit': 'terminate session', #*
+ 'rest': 'restart incomplete transfer', #*
+ 'retr': 'retrieve a file', #*
+ 'rmd': 'remove a directory', #!
+ 'rnfr': 'specify rename-from file name', #!
+ 'rnto': 'specify rename-to file name', #!
+ 'site': 'non-standard commands (see next section)',
+ 'size': 'return size of file', #*
+ 'stat': 'return status of server', #*
+ 'stor': 'store a file', #*!
+ 'stou': 'store a file with a unique name', #!
+ 'stru': 'specify data transfer structure',
+ 'syst': 'show operating system type of server system', #*
+ 'type': 'specify data transfer type', #*
+ 'user': 'specify user name', #*
+ 'xcup': 'change to parent of current working directory (deprecated)',
+ 'xcwd': 'change working directory (deprecated)',
+ 'xmkd': 'make a directory (deprecated)', #!
+ 'xpwd': 'print the current working directory (deprecated)',
+ 'xrmd': 'remove a directory (deprecated)', #!
+}
+
+
+# debugging aid (linux)
+def get_vm_size ():
+ return string.atoi (string.split(open ('/proc/self/stat').readline())[22])
+
+def print_vm():
+ print 'vm: %8dk' % (get_vm_size()/1024)
diff --git a/demo/medusa/ftps_server.py b/demo/medusa/ftps_server.py
new file mode 100644
index 0000000..8fee339
--- /dev/null
+++ b/demo/medusa/ftps_server.py
@@ -0,0 +1,438 @@
+"""An FTP/TLS server built on Medusa's ftp_server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# Python
+import socket, string, sys, time
+
+# Medusa
+from counter import counter
+import asynchat, asyncore, ftp_server, logger
+
+# M2Crypto
+from M2Crypto import SSL
+
+VERSION_STRING='0.09'
+
+class ftp_tls_channel(ftp_server.ftp_channel):
+
+ """FTP/TLS server channel for Medusa."""
+
+ def __init__(self, server, ssl_ctx, conn, addr):
+ """Initialise the channel."""
+ self.ssl_ctx = ssl_ctx
+ self.server = server
+ self.current_mode = 'a'
+ self.addr = addr
+ asynchat.async_chat.__init__(self, conn)
+ self.set_terminator('\r\n')
+ self.client_addr = (addr[0], 21)
+ self.client_dc = None
+ self.in_buffer = ''
+ self.closing = 0
+ self.passive_acceptor = None
+ self.passive_connection = None
+ self.filesystem = None
+ self.authorized = 0
+ self._ssl_accepting = 0
+ self._ssl_accepted = 0
+ self._pbsz = None
+ self._prot = None
+ resp = '220 %s M2Crypto (Medusa) FTP/TLS server v%s ready.'
+ self.respond(resp % (self.server.hostname, VERSION_STRING))
+
+ def writable(self):
+ return self._ssl_accepting or self._ssl_accepted
+
+ def handle_read(self):
+ """Handle a read event."""
+ if self._ssl_accepting:
+ self._ssl_accepted = self.socket.accept_ssl()
+ if self._ssl_accepted:
+ self._ssl_accepting = 0
+ else:
+ try:
+ ftp_server.ftp_channel.handle_read(self)
+ except SSL.SSLError, what:
+ if str(what) == 'unexpected eof':
+ self.close()
+ else:
+ raise
+
+ def handle_write(self):
+ """Handle a write event."""
+ if self._ssl_accepting:
+ self._ssl_accepted = self.socket.accept_ssl()
+ if self._ssl_accepted:
+ self._ssl_accepting = 0
+ else:
+ try:
+ ftp_server.ftp_channel.handle_write(self)
+ except SSL.SSLError, what:
+ if str(what) == 'unexpected eof':
+ self.close()
+ else:
+ raise
+
+ def send(self, data):
+ """Send data over SSL."""
+ try:
+ result = self.socket.send(data)
+ if result <= 0:
+ return 0
+ else:
+ return result
+ except SSL.SSLError, what:
+ self.close()
+ self.log_info('send: closing channel %s %s' % (repr(self), what))
+ return 0
+
+ def recv(self, buffer_size):
+ """Receive data over SSL."""
+ try:
+ result = self.socket.recv(buffer_size)
+ if not result:
+ return ''
+ else:
+ return result
+ except SSL.SSLError, what:
+ self.close()
+ self.log_info('recv: closing channel %s %s' % (repr(self), what))
+ return ''
+
+ def found_terminator(self):
+ """Dispatch the FTP command."""
+ line = self.in_buffer
+ if not len(line):
+ return
+
+ sp = string.find(line, ' ')
+ if sp != -1:
+ line = [line[:sp], line[sp+1:]]
+ else:
+ line = [line]
+
+ command = string.lower(line[0])
+ if string.find(command, 'stor') != -1:
+ while command and command[0] not in string.letters:
+ command = command[1:]
+
+ func_name = 'cmd_%s' % command
+ if command != 'pass':
+ self.log('<== %s' % repr(self.in_buffer)[1:-1])
+ else:
+ self.log('<== %s' % line[0]+' <password>')
+
+ self.in_buffer = ''
+ if not hasattr(self, func_name):
+ self.command_not_understood(line[0])
+ return
+
+ func = getattr(self, func_name)
+ if not self.check_command_authorization(command):
+ self.command_not_authorized(command)
+ else:
+ try:
+ result = apply(func, (line,))
+ except:
+ self.server.total_exceptions.increment()
+ (file, func, line), t, v, tbinfo = asyncore.compact_traceback()
+ if self.client_dc:
+ try:
+ self.client_dc_close()
+ except:
+ pass
+ resp = '451 Server error: %s, %s: file %s line: %s'
+ self.respond(resp % (t, v, file, line))
+
+ def make_xmit_channel(self):
+ """Create a connection for sending data."""
+ pa = self.passive_acceptor
+ if pa:
+ if pa.ready:
+ conn, addr = pa.ready
+ if self._prot:
+ cdc = tls_xmit_channel(self, conn, self.ssl_ctx, addr)
+ else:
+ cdc = ftp_server.xmit_channel(self, addr)
+ cdc.set_socket(conn)
+ cdc.connected = 1
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ else:
+ if self._prot:
+ cdc = tls_xmit_channel(self, None, self.ssl_ctx, None)
+ else:
+ cdc = ftp_server.xmit_channel(self)
+ else:
+ if self._prot:
+ cdc = tls_xmit_channel(self, None, self.ssl_ctx, self.client_addr)
+ else:
+ cdc = ftp_server.xmit_channel(self, self.client_addr)
+ cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ if self.bind_local_minus_one:
+ cdc.bind(('', self.server.port - 1))
+ try:
+ cdc.connect(self.client_addr)
+ except socket.error, what:
+ self.respond('425 Cannot build data connection')
+ self.client_dc = cdc
+
+ def make_recv_channel(self, fd):
+ """Create a connection for receiving data."""
+ pa = self.passive_acceptor
+ if pa:
+ if pa.ready:
+ conn, addr = pa.ready
+ if self._prot:
+ cdc = tls_recv_channel(self, conn, self.ssl_ctx, addr, fd)
+ else:
+ cdc = ftp_server.recv_channel(self, addr, fd)
+ cdc.set_socket(conn)
+ cdc.connected = 1
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ else:
+ if self._prot:
+ cdc = tls_recv_channel(self, None, self.ssl_ctx, None, fd)
+ else:
+ cdc = ftp_server.recv_channel(self, None, fd)
+ else:
+ if self._prot:
+ cdc = tls_recv_channel(self, None, self.ssl_ctx, self._prot, self.client_addr, fd)
+ else:
+ cdc = ftp_server.recv_channel(self, self.client_addr, fd)
+ cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ cdc.connect(self.client_addr)
+ except socket.error, what:
+ self.respond('425 Cannot build data connection')
+ self.client_dc = cdc
+
+ def cmd_auth(self, line):
+ """Prepare for TLS operation."""
+ # XXX Handle variations.
+ if line[1] != 'TLS':
+ self.command_not_understood (string.join(line))
+ else:
+ self.respond('234 AUTH TLS successful')
+ self._ssl_accepting = 1
+ self.socket = SSL.Connection(self.ssl_ctx, self.socket)
+ self.socket.setup_addr(self.addr)
+ self.socket.setup_ssl()
+ self.socket.set_accept_state()
+ self._ssl_accepted = self.socket.accept_ssl()
+ if self._ssl_accepted:
+ self._ssl_accepting = 0
+
+ def cmd_pbsz(self, line):
+ """Negotiate size of buffer for secure data transfer. For
+ FTP/TLS the only valid value for the parameter is '0'; any
+ other value is accepted but ignored."""
+ if not (self._ssl_accepting or self._ssl_accepted):
+ return self.respond('503 AUTH TLS must be issued prior to PBSZ')
+ self._pbsz = 1
+ self.respond('200 PBSZ=0 successful.')
+
+ def cmd_prot(self, line):
+ """Negotiate the security level of the data connection."""
+ if self._pbsz is None:
+ return self.respond('503 PBSZ must be issued prior to PROT')
+ if line[1] == 'C':
+ self.respond('200 Protection set to Clear')
+ self._pbsz = None
+ self._prot = None
+ elif line[1] == 'P':
+ self.respond('200 Protection set to Private')
+ self._prot = 1
+ elif line[1] in ('S', 'E'):
+ self.respond('536 PROT %s unsupported' % line[1])
+ else:
+ self.respond('504 PROT %s unsupported' % line[1])
+
+
+class ftp_tls_server(ftp_server.ftp_server):
+
+ """FTP/TLS server for Medusa."""
+
+ SERVER_IDENT = 'M2Crypto FTP/TLS Server (v%s)' % VERSION_STRING
+
+ ftp_channel_class = ftp_tls_channel
+
+ def __init__(self, authz, ssl_ctx, host=None, ip='', port=21, resolver=None, log_obj=None):
+ """Initialise the server."""
+ self.ssl_ctx = ssl_ctx
+ self.ip = ip
+ self.port = port
+ self.authorizer = authz
+
+ if host is None:
+ self.hostname = socket.gethostname()
+ else:
+ self.hostname = host
+
+ self.total_sessions = counter()
+ self.closed_sessions = counter()
+ self.total_files_out = counter()
+ self.total_files_in = counter()
+ self.total_bytes_out = counter()
+ self.total_bytes_in = counter()
+ self.total_exceptions = counter()
+
+ asyncore.dispatcher.__init__(self)
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.set_reuse_addr()
+ self.bind((self.ip, self.port))
+ self.listen(5)
+
+ if log_obj is None:
+ log_obj = sys.stdout
+
+ if resolver:
+ self.logger = logger.resolving_logger(resolver, log_obj)
+ else:
+ self.logger = logger.unresolving_logger(logger.file_logger(sys.stdout))
+
+ l = 'M2Crypto (Medusa) FTP/TLS server started at %s\n\tAuthz: %s\n\tHostname: %s\n\tPort: %d'
+ self.log_info(l % (time.ctime(time.time()), repr(self.authorizer), self.hostname, self.port))
+
+ def handle_accept(self):
+ """Accept a socket and dispatch a channel to handle it."""
+ conn, addr = self.accept()
+ self.total_sessions.increment()
+ self.log_info('Connection from %s:%d' % addr)
+ self.ftp_channel_class(self, self.ssl_ctx, conn, addr)
+
+
+class nbio_ftp_tls_actor:
+
+ """TLS protocol negotiation mixin for FTP/TLS."""
+
+ def tls_init(self, sock, ssl_ctx, client_addr):
+ """Perform TLS protocol negotiation."""
+ self.ssl_ctx = ssl_ctx
+ self.client_addr = client_addr
+ self._ssl_handshaking = 1
+ self._ssl_handshake_ok = 0
+ if sock:
+ self.socket = SSL.Connection(self.ssl_ctx, sock)
+ self.socket.setup_addr(self.client_addr)
+ self.socket.setup_ssl()
+ self._ssl_handshake_ok = self.socket.accept_ssl()
+ if self._ssl_handshake_ok:
+ self._ssl_handshaking = 0
+ self.add_channel()
+ # else the client hasn't connected yet; when that happens,
+ # handle_connect() will be triggered.
+
+ def tls_neg_ok(self):
+ """Return status of TLS protocol negotiation."""
+ if self._ssl_handshaking:
+ self._ssl_handshake_ok = self.socket.accept_ssl()
+ if self._ssl_handshake_ok:
+ self._ssl_handshaking = 0
+ return self._ssl_handshake_ok
+
+ def handle_connect(self):
+ """Handle a data connection that occurs after this instance came
+ into being. When this handler is triggered, self.socket has been
+ created and refers to the underlying connected socket."""
+ self.socket = SSL.Connection(self.ssl_ctx, self.socket)
+ self.socket.setup_addr(self.client_addr)
+ self.socket.setup_ssl()
+ self._ssl_handshake_ok = self.socket.accept_ssl()
+ if self._ssl_handshake_ok:
+ self._ssl_handshaking = 0
+ self.add_channel()
+
+ def send(self, data):
+ """Send data over SSL."""
+ try:
+ result = self.socket.send(data)
+ if result <= 0:
+ return 0
+ else:
+ return result
+ except SSL.SSLError, what:
+ self.close()
+ self.log_info('send: closing channel %s %s' % (repr(self), what))
+ return 0
+
+ def recv(self, buffer_size):
+ """Receive data over SSL."""
+ try:
+ result = self.socket.recv(buffer_size)
+ if not result:
+ return ''
+ else:
+ return result
+ except SSL.SSLError, what:
+ self.close()
+ self.log_info('recv: closing channel %s %s' % (repr(self), what))
+ return ''
+
+
+class tls_xmit_channel(nbio_ftp_tls_actor, ftp_server.xmit_channel):
+
+ """TLS driver for a send-only data connection."""
+
+ def __init__(self, channel, conn, ssl_ctx, client_addr=None):
+ """Initialise the driver."""
+ ftp_server.xmit_channel.__init__(self, channel, client_addr)
+ self.tls_init(conn, ssl_ctx, client_addr)
+
+ def readable(self):
+ """This channel is readable iff TLS negotiation is in progress.
+ (Which implies a connected channel, of course.)"""
+ if not self.connected:
+ return 0
+ else:
+ return self._ssl_handshaking
+
+ def writable(self):
+ """This channel is writable iff TLS negotiation is in progress
+ or the application has data to send."""
+ if self._ssl_handshaking:
+ return 1
+ else:
+ return ftp_server.xmit_channel.writable(self)
+
+ def handle_read(self):
+ """Handle a read event: either continue with TLS negotiation
+ or let the application handle this event."""
+ if self.tls_neg_ok():
+ ftp_server.xmit_channel.handle_read(self)
+
+ def handle_write(self):
+ """Handle a write event: either continue with TLS negotiation
+ or let the application handle this event."""
+ if self.tls_neg_ok():
+ ftp_server.xmit_channel.handle_write(self)
+
+
+class tls_recv_channel(nbio_ftp_tls_actor, ftp_server.recv_channel):
+
+ """TLS driver for a receive-only data connection."""
+
+ def __init__(self, channel, conn, ssl_ctx, client_addr, fd):
+ """Initialise the driver."""
+ ftp_server.recv_channel.__init__(self, channel, client_addr, fd)
+ self.tls_init(conn, ssl_ctx, client_addr)
+
+ def writable(self):
+ """This channel is writable iff TLS negotiation is in progress."""
+ return self._ssl_handshaking
+
+ def handle_read(self):
+ """Handle a read event: either continue with TLS negotiation
+ or let the application handle this event."""
+ if self.tls_neg_ok():
+ ftp_server.recv_channel.handle_read(self)
+
+ def handle_write(self):
+ """Handle a write event: either continue with TLS negotiation
+ or let the application handle this event."""
+ if self.tls_neg_ok():
+ ftp_server.recv_channel.handle_write(self)
+
+
diff --git a/demo/medusa/http_date.py b/demo/medusa/http_date.py
new file mode 100644
index 0000000..c40f70d
--- /dev/null
+++ b/demo/medusa/http_date.py
@@ -0,0 +1,126 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+import re
+import string
+import time
+
+def concat (*args):
+ return ''.join (args)
+
+def join (seq, field=' '):
+ return field.join (seq)
+
+def group (s):
+ return '(' + s + ')'
+
+short_days = ['sun','mon','tue','wed','thu','fri','sat']
+long_days = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday']
+
+short_day_reg = group (join (short_days, '|'))
+long_day_reg = group (join (long_days, '|'))
+
+daymap = {}
+for i in range(7):
+ daymap[short_days[i]] = i
+ daymap[long_days[i]] = i
+
+hms_reg = join (3 * [group('[0-9][0-9]')], ':')
+
+months = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec']
+
+monmap = {}
+for i in range(12):
+ monmap[months[i]] = i+1
+
+months_reg = group (join (months, '|'))
+
+# From draft-ietf-http-v11-spec-07.txt/3.3.1
+# Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
+# Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
+# Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
+
+# rfc822 format
+rfc822_date = join (
+ [concat (short_day_reg,','), # day
+ group('[0-9][0-9]?'), # date
+ months_reg, # month
+ group('[0-9]+'), # year
+ hms_reg, # hour minute second
+ 'gmt'
+ ],
+ ' '
+ )
+
+rfc822_reg = re.compile (rfc822_date)
+
+def unpack_rfc822 (m):
+ g = m.group
+ a = string.atoi
+ return (
+ a(g(4)), # year
+ monmap[g(3)], # month
+ a(g(2)), # day
+ a(g(5)), # hour
+ a(g(6)), # minute
+ a(g(7)), # second
+ 0,
+ 0,
+ 0
+ )
+
+# rfc850 format
+rfc850_date = join (
+ [concat (long_day_reg,','),
+ join (
+ [group ('[0-9][0-9]?'),
+ months_reg,
+ group ('[0-9]+')
+ ],
+ '-'
+ ),
+ hms_reg,
+ 'gmt'
+ ],
+ ' '
+ )
+
+rfc850_reg = re.compile (rfc850_date)
+# they actually unpack the same way
+def unpack_rfc850 (m):
+ g = m.group
+ a = string.atoi
+ return (
+ a(g(4)), # year
+ monmap[g(3)], # month
+ a(g(2)), # day
+ a(g(5)), # hour
+ a(g(6)), # minute
+ a(g(7)), # second
+ 0,
+ 0,
+ 0
+ )
+
+# parsdate.parsedate - ~700/sec.
+# parse_http_date - ~1333/sec.
+
+def build_http_date (when):
+ return time.strftime ('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(when))
+
+def parse_http_date (d):
+ d = string.lower (d)
+ tz = time.timezone
+ m = rfc850_reg.match (d)
+ if m and m.end() == len(d):
+ retval = int (time.mktime (unpack_rfc850(m)) - tz)
+ else:
+ m = rfc822_reg.match (d)
+ if m and m.end() == len(d):
+ retval = int (time.mktime (unpack_rfc822(m)) - tz)
+ else:
+ return 0
+ # Thanks to Craig Silverstein <csilvers@google.com> for pointing
+ # out the DST discrepancy
+ if time.daylight and time.localtime(retval)[-1] == 1: # DST correction
+ retval = retval + (tz - time.altzone)
+ return retval
diff --git a/demo/medusa/http_server.py b/demo/medusa/http_server.py
new file mode 100644
index 0000000..2f261ab
--- /dev/null
+++ b/demo/medusa/http_server.py
@@ -0,0 +1,784 @@
+#! /usr/local/bin/python
+# -*- Mode: Python; tab-width: 4 -*-
+#
+# Author: Sam Rushing <rushing@nightmare.com>
+# Copyright 1996-2000 by Sam Rushing
+# All Rights Reserved.
+#
+
+# python modules
+import os
+import re
+import socket
+import stat
+import string
+import sys
+import time
+
+# async modules
+import asyncore
+import asynchat
+
+# medusa modules
+import http_date
+import producers
+import status_handler
+import logger
+
+VERSION_STRING = '1.1'
+
+from counter import counter
+from urllib import unquote
+
+# ===========================================================================
+# Request Object
+# ===========================================================================
+
+class http_request:
+
+ # default reply code
+ reply_code = 200
+
+ request_counter = counter()
+
+ # Whether to automatically use chunked encoding when
+ #
+ # HTTP version is 1.1
+ # Content-Length is not set
+ # Chunked encoding is not already in effect
+ #
+ # If your clients are having trouble, you might want to disable this.
+ use_chunked = 1
+
+ # by default, this request object ignores user data.
+ collector = None
+
+ def __init__ (self, *args):
+ # unpack information about the request
+ (self.channel, self.request,
+ self.command, self.uri, self.version,
+ self.header) = args
+
+ self.outgoing = fifo()
+ self.reply_headers = {
+ 'Server' : 'Medusa/%s' % VERSION_STRING,
+ 'Date' : http_date.build_http_date (time.time())
+ }
+ self.request_number = http_request.request_counter.increment()
+ self._split_uri = None
+ self._header_cache = {}
+
+ # --------------------------------------------------
+ # reply header management
+ # --------------------------------------------------
+ def __setitem__ (self, key, value):
+ self.reply_headers[key] = value
+
+ def __getitem__ (self, key):
+ return self.reply_headers[key]
+
+ def has_key (self, key):
+ return self.reply_headers.has_key (key)
+
+ def build_reply_header (self):
+ return string.join (
+ [self.response(self.reply_code)] + map (
+ lambda x: '%s: %s' % x,
+ self.reply_headers.items()
+ ),
+ '\r\n'
+ ) + '\r\n\r\n'
+
+ # --------------------------------------------------
+ # split a uri
+ # --------------------------------------------------
+
+ # <path>;<params>?<query>#<fragment>
+ path_regex = re.compile (
+ # path params query fragment
+ r'([^;?#]*)(;[^?#]*)?(\?[^#]*)?(#.*)?'
+ )
+
+ def split_uri (self):
+ if self._split_uri is None:
+ m = self.path_regex.match (self.uri)
+ if m.end() != len(self.uri):
+ raise ValueError, "Broken URI"
+ else:
+ self._split_uri = m.groups()
+ return self._split_uri
+
+ def get_header_with_regex (self, head_reg, group):
+ for line in self.header:
+ m = head_reg.match (line)
+ if m.end() == len(line):
+ return head_reg.group (group)
+ return ''
+
+ def get_header (self, header):
+ header = string.lower (header)
+ hc = self._header_cache
+ if not hc.has_key (header):
+ h = header + ': '
+ hl = len(h)
+ for line in self.header:
+ if string.lower (line[:hl]) == h:
+ r = line[hl:]
+ hc[header] = r
+ return r
+ hc[header] = None
+ return None
+ else:
+ return hc[header]
+
+ # --------------------------------------------------
+ # user data
+ # --------------------------------------------------
+
+ def collect_incoming_data (self, data):
+ if self.collector:
+ self.collector.collect_incoming_data (data)
+ else:
+ self.log_info(
+ 'Dropping %d bytes of incoming request data' % len(data),
+ 'warning'
+ )
+
+ def found_terminator (self):
+ if self.collector:
+ self.collector.found_terminator()
+ else:
+ self.log_info (
+ 'Unexpected end-of-record for incoming request',
+ 'warning'
+ )
+
+ def push (self, thing):
+ if type(thing) == type(''):
+ self.outgoing.push (producers.simple_producer (thing))
+ else:
+ self.outgoing.push (thing)
+
+ def response (self, code=200):
+ message = self.responses[code]
+ self.reply_code = code
+ return 'HTTP/%s %d %s' % (self.version, code, message)
+
+ def error (self, code):
+ self.reply_code = code
+ message = self.responses[code]
+ s = self.DEFAULT_ERROR_MESSAGE % {
+ 'code': code,
+ 'message': message,
+ }
+ self['Content-Length'] = len(s)
+ self['Content-Type'] = 'text/html'
+ # make an error reply
+ self.push (s)
+ self.done()
+
+ # can also be used for empty replies
+ reply_now = error
+
+ def done (self):
+ "finalize this transaction - send output to the http channel"
+
+ # ----------------------------------------
+ # persistent connection management
+ # ----------------------------------------
+
+ # --- BUCKLE UP! ----
+
+ connection = string.lower (get_header (CONNECTION, self.header))
+
+ close_it = 0
+ wrap_in_chunking = 0
+
+ if self.version == '1.0':
+ if connection == 'keep-alive':
+ if not self.has_key ('Content-Length'):
+ close_it = 1
+ else:
+ self['Connection'] = 'Keep-Alive'
+ else:
+ close_it = 1
+ elif self.version == '1.1':
+ if connection == 'close':
+ close_it = 1
+ elif not self.has_key ('Content-Length'):
+ if self.has_key ('Transfer-Encoding'):
+ if not self['Transfer-Encoding'] == 'chunked':
+ close_it = 1
+ elif self.use_chunked:
+ self['Transfer-Encoding'] = 'chunked'
+ wrap_in_chunking = 1
+ else:
+ close_it = 1
+ elif self.version is None:
+ # Although we don't *really* support http/0.9 (because we'd have to
+ # use \r\n as a terminator, and it would just yuck up a lot of stuff)
+ # it's very common for developers to not want to type a version number
+ # when using telnet to debug a server.
+ close_it = 1
+
+ outgoing_header = producers.simple_producer (self.build_reply_header())
+
+ if close_it:
+ self['Connection'] = 'close'
+
+ if wrap_in_chunking:
+ outgoing_producer = producers.chunked_producer (
+ producers.composite_producer (self.outgoing)
+ )
+ # prepend the header
+ outgoing_producer = producers.composite_producer (
+ fifo([outgoing_header, outgoing_producer])
+ )
+ else:
+ # prepend the header
+ self.outgoing.push_front (outgoing_header)
+ outgoing_producer = producers.composite_producer (self.outgoing)
+
+ # apply a few final transformations to the output
+ self.channel.push_with_producer (
+ # globbing gives us large packets
+ producers.globbing_producer (
+ # hooking lets us log the number of bytes sent
+ producers.hooked_producer (
+ outgoing_producer,
+ self.log
+ )
+ )
+ )
+
+ self.channel.current_request = None
+
+ if close_it:
+ self.channel.close_when_done()
+
+ def log_date_string (self, when):
+ return time.strftime (
+ '%d/%b/%Y:%H:%M:%S ',
+ time.gmtime(when)
+ ) + tz_for_log
+
+ def log (self, bytes):
+ self.channel.server.logger.log (
+ self.channel.addr[0],
+ '%d - - [%s] "%s" %d %d\n' % (
+ self.channel.addr[1],
+ self.log_date_string (time.time()),
+ self.request,
+ self.reply_code,
+ bytes
+ )
+ )
+
+ responses = {
+ 100: "Continue",
+ 101: "Switching Protocols",
+ 200: "OK",
+ 201: "Created",
+ 202: "Accepted",
+ 203: "Non-Authoritative Information",
+ 204: "No Content",
+ 205: "Reset Content",
+ 206: "Partial Content",
+ 300: "Multiple Choices",
+ 301: "Moved Permanently",
+ 302: "Moved Temporarily",
+ 303: "See Other",
+ 304: "Not Modified",
+ 305: "Use Proxy",
+ 400: "Bad Request",
+ 401: "Unauthorized",
+ 402: "Payment Required",
+ 403: "Forbidden",
+ 404: "Not Found",
+ 405: "Method Not Allowed",
+ 406: "Not Acceptable",
+ 407: "Proxy Authentication Required",
+ 408: "Request Time-out",
+ 409: "Conflict",
+ 410: "Gone",
+ 411: "Length Required",
+ 412: "Precondition Failed",
+ 413: "Request Entity Too Large",
+ 414: "Request-URI Too Large",
+ 415: "Unsupported Media Type",
+ 500: "Internal Server Error",
+ 501: "Not Implemented",
+ 502: "Bad Gateway",
+ 503: "Service Unavailable",
+ 504: "Gateway Time-out",
+ 505: "HTTP Version not supported"
+ }
+
+ # Default error message
+ DEFAULT_ERROR_MESSAGE = string.join (
+ ['<head>',
+ '<title>Error response</title>',
+ '</head>',
+ '<body>',
+ '<h1>Error response</h1>',
+ '<p>Error code %(code)d.',
+ '<p>Message: %(message)s.',
+ '</body>',
+ ''
+ ],
+ '\r\n'
+ )
+
+
+# ===========================================================================
+# HTTP Channel Object
+# ===========================================================================
+
+class http_channel (asynchat.async_chat):
+
+ # use a larger default output buffer
+ ac_out_buffer_size = 1<<16
+
+ current_request = None
+ channel_counter = counter()
+
+ def __init__ (self, server, conn, addr):
+ self.channel_number = http_channel.channel_counter.increment()
+ self.request_counter = counter()
+ asynchat.async_chat.__init__ (self, conn)
+ self.server = server
+ self.addr = addr
+ self.set_terminator ('\r\n\r\n')
+ self.in_buffer = ''
+ self.creation_time = int (time.time())
+ self.check_maintenance()
+
+ def __repr__ (self):
+ ar = asynchat.async_chat.__repr__(self)[1:-1]
+ return '<%s channel#: %s requests:%s>' % (
+ ar,
+ self.channel_number,
+ self.request_counter
+ )
+
+ # Channel Counter, Maintenance Interval...
+ maintenance_interval = 500
+
+ def check_maintenance (self):
+ if not self.channel_number % self.maintenance_interval:
+ self.maintenance()
+
+ def maintenance (self):
+ self.kill_zombies()
+
+ # 30-minute zombie timeout. status_handler also knows how to kill zombies.
+ zombie_timeout = 30 * 60
+
+ def kill_zombies (self):
+ now = int (time.time())
+ for channel in asyncore.socket_map.values():
+ if channel.__class__ == self.__class__:
+ if (now - channel.creation_time) > channel.zombie_timeout:
+ channel.close()
+
+ # --------------------------------------------------
+ # send/recv overrides, good place for instrumentation.
+ # --------------------------------------------------
+
+ # this information needs to get into the request object,
+ # so that it may log correctly.
+ def send (self, data):
+ result = asynchat.async_chat.send (self, data)
+ self.server.bytes_out.increment (len(data))
+ return result
+
+ def recv (self, buffer_size):
+ try:
+ result = asynchat.async_chat.recv (self, buffer_size)
+ self.server.bytes_in.increment (len(result))
+ return result
+ except MemoryError:
+ # --- Save a Trip to Your Service Provider ---
+ # It's possible for a process to eat up all the memory of
+ # the machine, and put it in an extremely wedged state,
+ # where medusa keeps running and can't be shut down. This
+ # is where MemoryError tends to get thrown, though of
+ # course it could get thrown elsewhere.
+ sys.exit ("Out of Memory!")
+
+ def handle_error (self):
+ t, v = sys.exc_info()[:2]
+ if t is SystemExit:
+ raise t, v
+ else:
+ asynchat.async_chat.handle_error (self)
+
+ def log (self, *args):
+ pass
+
+ # --------------------------------------------------
+ # async_chat methods
+ # --------------------------------------------------
+
+ def collect_incoming_data (self, data):
+ if self.current_request:
+ # we are receiving data (probably POST data) for a request
+ self.current_request.collect_incoming_data (data)
+ else:
+ # we are receiving header (request) data
+ self.in_buffer = self.in_buffer + data
+
+ def found_terminator (self):
+ if self.current_request:
+ self.current_request.found_terminator()
+ else:
+ header = self.in_buffer
+ self.in_buffer = ''
+ lines = string.split (header, '\r\n')
+
+ # --------------------------------------------------
+ # crack the request header
+ # --------------------------------------------------
+
+ while lines and not lines[0]:
+ # as per the suggestion of http-1.1 section 4.1, (and
+ # Eric Parker <eparker@zyvex.com>), ignore a leading
+ # blank lines (buggy browsers tack it onto the end of
+ # POST requests)
+ lines = lines[1:]
+
+ if not lines:
+ self.close_when_done()
+ return
+
+ request = lines[0]
+
+ # unquote path if necessary (thanks to Skip Montaro for pointing
+ # out that we must unquote in piecemeal fashion).
+ if '%' in request:
+ request = unquote (request)
+
+ command, uri, version = crack_request (request)
+ header = join_headers (lines[1:])
+
+ r = http_request (self, request, command, uri, version, header)
+ self.request_counter.increment()
+ self.server.total_requests.increment()
+
+ if command is None:
+ self.log_info ('Bad HTTP request: %s' % repr(request), 'error')
+ r.error (400)
+ return
+
+ # --------------------------------------------------
+ # handler selection and dispatch
+ # --------------------------------------------------
+ for h in self.server.handlers:
+ if h.match (r):
+ try:
+ self.current_request = r
+ # This isn't used anywhere.
+ # r.handler = h # CYCLE
+ h.handle_request (r)
+ except:
+ self.server.exceptions.increment()
+ (file, fun, line), t, v, tbinfo = asyncore.compact_traceback()
+ self.log_info(
+ 'Server Error: %s, %s: file: %s line: %s' % (t,v,file,line),
+ 'error')
+ try:
+ r.error (500)
+ except:
+ pass
+ return
+
+ # no handlers, so complain
+ r.error (404)
+
+ def writable (self):
+ # this is just the normal async_chat 'writable', here for comparison
+ return self.ac_out_buffer or len(self.producer_fifo)
+
+ def writable_for_proxy (self):
+ # this version of writable supports the idea of a 'stalled' producer
+ # [i.e., it's not ready to produce any output yet] This is needed by
+ # the proxy, which will be waiting for the magic combination of
+ # 1) hostname resolved
+ # 2) connection made
+ # 3) data available.
+ if self.ac_out_buffer:
+ return 1
+ elif len(self.producer_fifo):
+ p = self.producer_fifo.first()
+ if hasattr (p, 'stalled'):
+ return not p.stalled()
+ else:
+ return 1
+
+# ===========================================================================
+# HTTP Server Object
+# ===========================================================================
+
+class http_server (asyncore.dispatcher):
+
+ SERVER_IDENT = 'HTTP Server (V%s)' % VERSION_STRING
+
+ channel_class = http_channel
+
+ def __init__ (self, ip, port, resolver=None, logger_object=None):
+ self.ip = ip
+ self.port = port
+ asyncore.dispatcher.__init__ (self)
+ self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+ self.handlers = []
+
+ if not logger_object:
+ logger_object = logger.file_logger (sys.stdout)
+
+ self.set_reuse_addr()
+ self.bind ((ip, port))
+
+ # lower this to 5 if your OS complains
+ self.listen (1024)
+
+ host, port = self.socket.getsockname()
+ if not ip:
+ self.log_info('Computing default hostname', 'warning')
+ ip = socket.gethostbyname (socket.gethostname())
+ try:
+ self.server_name = socket.gethostbyaddr (ip)[0]
+ except socket.error:
+ self.log_info('Cannot do reverse lookup', 'warning')
+ self.server_name = ip # use the IP address as the "hostname"
+
+ self.server_port = port
+ self.total_clients = counter()
+ self.total_requests = counter()
+ self.exceptions = counter()
+ self.bytes_out = counter()
+ self.bytes_in = counter()
+
+ if not logger_object:
+ logger_object = logger.file_logger (sys.stdout)
+
+ if resolver:
+ self.logger = logger.resolving_logger (resolver, logger_object)
+ else:
+ self.logger = logger.unresolving_logger (logger_object)
+
+ self.log_info (
+ 'Medusa (V%s) started at %s'
+ '\n\tHostname: %s'
+ '\n\tPort:%d'
+ '\n' % (
+ VERSION_STRING,
+ time.ctime(time.time()),
+ self.server_name,
+ port,
+ )
+ )
+
+ def writable (self):
+ return 0
+
+ def handle_read (self):
+ pass
+
+ def readable (self):
+ return self.accepting
+
+ def handle_connect (self):
+ pass
+
+ def handle_accept (self):
+ self.total_clients.increment()
+ try:
+ conn, addr = self.accept()
+ except socket.error:
+ # linux: on rare occasions we get a bogus socket back from
+ # accept. socketmodule.c:makesockaddr complains that the
+ # address family is unknown. We don't want the whole server
+ # to shut down because of this.
+ self.log_info ('warning: server accept() threw an exception', 'warning')
+ return
+ except TypeError:
+ # unpack non-sequence. this can happen when a read event
+ # fires on a listening socket, but when we call accept()
+ # we get EWOULDBLOCK, so dispatcher.accept() returns None.
+ # Seen on FreeBSD3.
+ self.log_info ('warning: server accept() threw EWOULDBLOCK', 'warning')
+ return
+
+ self.channel_class (self, conn, addr)
+
+ def install_handler (self, handler, back=0):
+ if back:
+ self.handlers.append (handler)
+ else:
+ self.handlers.insert (0, handler)
+
+ def remove_handler (self, handler):
+ self.handlers.remove (handler)
+
+ def status (self):
+ def nice_bytes (n):
+ return string.join (status_handler.english_bytes (n))
+
+ handler_stats = filter (None, map (maybe_status, self.handlers))
+
+ if self.total_clients:
+ ratio = self.total_requests.as_long() / float(self.total_clients.as_long())
+ else:
+ ratio = 0.0
+
+ return producers.composite_producer (
+ fifo ([producers.lines_producer (
+ ['<h2>%s</h2>' % self.SERVER_IDENT,
+ '<br>Listening on: <b>Host:</b> %s' % self.server_name,
+ '<b>Port:</b> %d' % self.port,
+ '<p><ul>'
+ '<li>Total <b>Clients:</b> %s' % self.total_clients,
+ '<b>Requests:</b> %s' % self.total_requests,
+ '<b>Requests/Client:</b> %.1f' % (ratio),
+ '<li>Total <b>Bytes In:</b> %s' % (nice_bytes (self.bytes_in.as_long())),
+ '<b>Bytes Out:</b> %s' % (nice_bytes (self.bytes_out.as_long())),
+ '<li>Total <b>Exceptions:</b> %s' % self.exceptions,
+ '</ul><p>'
+ '<b>Extension List</b><ul>',
+ ])] + handler_stats + [producers.simple_producer('</ul>')]
+ )
+ )
+
+def maybe_status (thing):
+ if hasattr (thing, 'status'):
+ return thing.status()
+ else:
+ return None
+
+CONNECTION = re.compile ('Connection: (.*)', re.IGNORECASE)
+
+# merge multi-line headers
+# [486dx2: ~500/sec]
+def join_headers (headers):
+ r = []
+ for i in range(len(headers)):
+ if headers[i][0] in ' \t':
+ r[-1] = r[-1] + headers[i][1:]
+ else:
+ r.append (headers[i])
+ return r
+
+def get_header (head_reg, lines, group=1):
+ for line in lines:
+ m = head_reg.match (line)
+ if m and m.end() == len(line):
+ return m.group (group)
+ return ''
+
+def get_header_match (head_reg, lines):
+ for line in lines:
+ m = head_reg.match (line)
+ if m and m.end() == len(line):
+ return m
+ return ''
+
+REQUEST = re.compile ('([^ ]+) ([^ ]+)(( HTTP/([0-9.]+))$|$)')
+
+def crack_request (r):
+ m = REQUEST.match (r)
+ if m.end() == len(r):
+ if m.group(3):
+ version = m.group(5)
+ else:
+ version = None
+ return string.lower (m.group(1)), m.group(2), version
+ else:
+ return None, None, None
+
+class fifo:
+ def __init__ (self, list=None):
+ if not list:
+ self.list = []
+ else:
+ self.list = list
+
+ def __len__ (self):
+ return len(self.list)
+
+ def first (self):
+ return self.list[0]
+
+ def push_front (self, object):
+ self.list.insert (0, object)
+
+ def push (self, data):
+ self.list.append (data)
+
+ def pop (self):
+ if self.list:
+ result = self.list[0]
+ del self.list[0]
+ return (1, result)
+ else:
+ return (0, None)
+
+def compute_timezone_for_log ():
+ if time.daylight:
+ tz = time.altzone
+ else:
+ tz = time.timezone
+ if tz > 0:
+ neg = 1
+ else:
+ neg = 0
+ tz = -tz
+ h, rem = divmod (tz, 3600)
+ m, rem = divmod (rem, 60)
+ if neg:
+ return '-%02d%02d' % (h, m)
+ else:
+ return '+%02d%02d' % (h, m)
+
+# if you run this program over a TZ change boundary, this will be invalid.
+tz_for_log = compute_timezone_for_log()
+
+if __name__ == '__main__':
+ import sys
+ if len(sys.argv) < 2:
+ print 'usage: %s <root> <port>' % (sys.argv[0])
+ else:
+ import monitor
+ import filesys
+ import default_handler
+ import status_handler
+ import ftp_server
+ import chat_server
+ import resolver
+ import logger
+ rs = resolver.caching_resolver ('127.0.0.1')
+ lg = logger.file_logger (sys.stdout)
+ ms = monitor.secure_monitor_server ('fnord', '127.0.0.1', 9999)
+ fs = filesys.os_filesystem (sys.argv[1])
+ dh = default_handler.default_handler (fs)
+ hs = http_server ('', string.atoi (sys.argv[2]), rs, lg)
+ hs.install_handler (dh)
+ ftp = ftp_server.ftp_server (
+ ftp_server.dummy_authorizer(sys.argv[1]),
+ port=8021,
+ resolver=rs,
+ logger_object=lg
+ )
+ cs = chat_server.chat_server ('', 7777)
+ sh = status_handler.status_extension([hs,ms,ftp,cs,rs])
+ hs.install_handler (sh)
+ if ('-p' in sys.argv):
+ def profile_loop ():
+ try:
+ asyncore.loop()
+ except KeyboardInterrupt:
+ pass
+ import profile
+ profile.run ('profile_loop()', 'profile.out')
+ else:
+ asyncore.loop()
diff --git a/demo/medusa/https_server.py b/demo/medusa/https_server.py
new file mode 100644
index 0000000..1492586
--- /dev/null
+++ b/demo/medusa/https_server.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python
+
+"""A https server built on Medusa's http_server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import asynchat, asyncore, http_server, socket, sys
+from M2Crypto import SSL
+
+VERSION_STRING='0.09'
+
+class https_channel(http_server.http_channel):
+
+ def __init__(self, server, conn, addr):
+ http_server.http_channel.__init__(self, server, conn, addr)
+
+ def send(self, data):
+ try:
+ result = self.socket._write_nbio(data)
+ if result <= 0:
+ return 0
+ else:
+ self.server.bytes_out.increment(result)
+ return result
+ except SSL.SSLError, why:
+ self.close()
+ self.log_info('send: closing channel %s %s' % (repr(self), why))
+ return 0
+
+ def recv(self, buffer_size):
+ try:
+ result = self.socket._read_nbio(buffer_size)
+ if result is None:
+ return ''
+ elif result == '':
+ self.close()
+ return ''
+ else:
+ self.server.bytes_in.increment(len(result))
+ return result
+ except SSL.SSLError, why:
+ self.close()
+ self.log_info('recv: closing channel %s %s' % (repr(self), why))
+ return ''
+
+
+class https_server(http_server.http_server):
+
+ SERVER_IDENT='M2Crypto HTTPS Server (v%s)' % VERSION_STRING
+
+ channel_class=https_channel
+
+ def __init__(self, ip, port, ssl_ctx, resolver=None, logger_object=None):
+ http_server.http_server.__init__(self, ip, port, resolver, logger_object)
+ sys.stdout.write(self.SERVER_IDENT + '\n\n')
+ sys.stdout.flush()
+ self.ssl_ctx=ssl_ctx
+
+ def handle_accept(self):
+ # Cribbed from http_server.
+ self.total_clients.increment()
+ try:
+ conn, addr = self.accept()
+ except socket.error:
+ # linux: on rare occasions we get a bogus socket back from
+ # accept. socketmodule.c:makesockaddr complains that the
+ # address family is unknown. We don't want the whole server
+ # to shut down because of this.
+ sys.stderr.write ('warning: server accept() threw an exception\n')
+ return
+
+ # Turn the vanilla socket into an SSL connection.
+ try:
+ ssl_conn=SSL.Connection(self.ssl_ctx, conn)
+ ssl_conn._setup_ssl(addr)
+ ssl_conn.accept_ssl()
+ self.channel_class(self, ssl_conn, addr)
+ except SSL.SSLError:
+ pass
+
+ def writeable(self):
+ return 0
+
diff --git a/demo/medusa/index.html b/demo/medusa/index.html
new file mode 100644
index 0000000..0f7de19
--- /dev/null
+++ b/demo/medusa/index.html
@@ -0,0 +1,6 @@
+<html>
+<title>M2Crypto HTTPS Server</title>
+<body>
+<h1>M2Crypto HTTPS Server - It works!</h1>
+</body>
+</html>
diff --git a/demo/medusa/logger.py b/demo/medusa/logger.py
new file mode 100644
index 0000000..48be665
--- /dev/null
+++ b/demo/medusa/logger.py
@@ -0,0 +1,262 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+import asynchat
+import socket
+import string
+import time # these three are for the rotating logger
+import os # |
+import stat # v
+
+#
+# three types of log:
+# 1) file
+# with optional flushing. Also, one that rotates the log.
+# 2) socket
+# dump output directly to a socket connection. [how do we
+# keep it open?]
+# 3) syslog
+# log to syslog via tcp. this is a per-line protocol.
+#
+
+#
+# The 'standard' interface to a logging object is simply
+# log_object.log (message)
+#
+
+# a file-like object that captures output, and
+# makes sure to flush it always... this could
+# be connected to:
+# o stdio file
+# o low-level file
+# o socket channel
+# o syslog output...
+
+class file_logger:
+
+ # pass this either a path or a file object.
+ def __init__ (self, file, flush=1, mode='a'):
+ if type(file) == type(''):
+ if (file == '-'):
+ import sys
+ self.file = sys.stdout
+ else:
+ self.file = open (file, mode)
+ else:
+ self.file = file
+ self.do_flush = flush
+
+ def __repr__ (self):
+ return '<file logger: %s>' % self.file
+
+ def write (self, data):
+ self.file.write (data)
+ self.maybe_flush()
+
+ def writeline (self, line):
+ self.file.writeline (line)
+ self.maybe_flush()
+
+ def writelines (self, lines):
+ self.file.writelines (lines)
+ self.maybe_flush()
+
+ def maybe_flush (self):
+ if self.do_flush:
+ self.file.flush()
+
+ def flush (self):
+ self.file.flush()
+
+ def softspace (self, *args):
+ pass
+
+ def log (self, message):
+ if message[-1] not in ('\r', '\n'):
+ self.write (message + '\n')
+ else:
+ self.write (message)
+
+# like a file_logger, but it must be attached to a filename.
+# When the log gets too full, or a certain time has passed,
+# it backs up the log and starts a new one. Note that backing
+# up the log is done via "mv" because anything else (cp, gzip)
+# would take time, during which medusa would do nothing else.
+
+class rotating_file_logger (file_logger):
+
+ # If freq is non-None we back up "daily", "weekly", or "monthly".
+ # Else if maxsize is non-None we back up whenever the log gets
+ # to big. If both are None we never back up.
+ def __init__ (self, file, freq=None, maxsize=None, flush=1, mode='a'):
+ self.filename = file
+ self.mode = mode
+ self.file = open (file, mode)
+ self.freq = freq
+ self.maxsize = maxsize
+ self.rotate_when = self.next_backup(self.freq)
+ self.do_flush = flush
+
+ def __repr__ (self):
+ return '<rotating-file logger: %s>' % self.file
+
+ # We back up at midnight every 1) day, 2) monday, or 3) 1st of month
+ def next_backup (self, freq):
+ (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
+ if freq == 'daily':
+ return time.mktime(yr,mo,day+1, 0,0,0, 0,0,-1)
+ elif freq == 'weekly':
+ return time.mktime(yr,mo,day-wd+7, 0,0,0, 0,0,-1) # wd(monday)==0
+ elif freq == 'monthly':
+ return time.mktime(yr,mo+1,1, 0,0,0, 0,0,-1)
+ else:
+ return None # not a date-based backup
+
+ def maybe_flush (self): # rotate first if necessary
+ self.maybe_rotate()
+ if self.do_flush: # from file_logger()
+ self.file.flush()
+
+ def maybe_rotate (self):
+ if self.freq and time.time() > self.rotate_when:
+ self.rotate()
+ self.rotate_when = self.next_backup(self.freq)
+ elif self.maxsize: # rotate when we get too big
+ try:
+ if os.stat(self.filename)[stat.ST_SIZE] > self.maxsize:
+ self.rotate()
+ except os.error: # file not found, probably
+ self.rotate() # will create a new file
+
+ def rotate (self):
+ (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
+ try:
+ self.file.close()
+ newname = '%s.ends%04d%02d%02d' % (self.filename, yr, mo, day)
+ try:
+ open(newname, "r").close() # check if file exists
+ newname = newname + "-%02d%02d%02d" % (hr, min, sec)
+ except: # YEARMODY is unique
+ pass
+ os.rename(self.filename, newname)
+ self.file = open(self.filename, self.mode)
+ except:
+ pass
+
+# syslog is a line-oriented log protocol - this class would be
+# appropriate for FTP or HTTP logs, but not for dumping stderr to.
+
+# TODO: a simple safety wrapper that will ensure that the line sent
+# to syslog is reasonable.
+
+# TODO: async version of syslog_client: now, log entries use blocking
+# send()
+
+import m_syslog
+syslog_logger = m_syslog.syslog_client
+
+class syslog_logger (m_syslog.syslog_client):
+ def __init__ (self, address, facility='user'):
+ m_syslog.syslog_client.__init__ (self, address)
+ self.facility = m_syslog.facility_names[facility]
+ self.address=address
+
+ def __repr__ (self):
+ return '<syslog logger address=%s>' % (repr(self.address))
+
+ def log (self, message):
+ m_syslog.syslog_client.log (
+ self,
+ message,
+ facility=self.facility,
+ priority=m_syslog.LOG_INFO
+ )
+
+# log to a stream socket, asynchronously
+
+class socket_logger (asynchat.async_chat):
+
+ def __init__ (self, address):
+
+ if type(address) == type(''):
+ self.create_socket (socket.AF_UNIX, socket.SOCK_STREAM)
+ else:
+ self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+ self.connect (address)
+ self.address = address
+
+ def __repr__ (self):
+ return '<socket logger: address=%s>' % (self.address)
+
+ def log (self, message):
+ if message[-2:] != '\r\n':
+ self.socket.push (message + '\r\n')
+ else:
+ self.socket.push (message)
+
+# log to multiple places
+class multi_logger:
+ def __init__ (self, loggers):
+ self.loggers = loggers
+
+ def __repr__ (self):
+ return '<multi logger: %s>' % (repr(self.loggers))
+
+ def log (self, message):
+ for logger in self.loggers:
+ logger.log (message)
+
+class resolving_logger:
+ """Feed (ip, message) combinations into this logger to get a
+ resolved hostname in front of the message. The message will not
+ be logged until the PTR request finishes (or fails)."""
+
+ def __init__ (self, resolver, logger):
+ self.resolver = resolver
+ self.logger = logger
+
+ class logger_thunk:
+ def __init__ (self, message, logger):
+ self.message = message
+ self.logger = logger
+
+ def __call__ (self, host, ttl, answer):
+ if not answer:
+ answer = host
+ self.logger.log ('%s:%s' % (answer, self.message))
+
+ def log (self, ip, message):
+ self.resolver.resolve_ptr (
+ ip,
+ self.logger_thunk (
+ message,
+ self.logger
+ )
+ )
+
+class unresolving_logger:
+ "Just in case you don't want to resolve"
+ def __init__ (self, logger):
+ self.logger = logger
+
+ def log (self, ip, message):
+ self.logger.log ('%s:%s' % (ip, message))
+
+
+def strip_eol (line):
+ while line and line[-1] in '\r\n':
+ line = line[:-1]
+ return line
+
+class tail_logger:
+ "Keep track of the last <size> log messages"
+ def __init__ (self, logger, size=500):
+ self.size = size
+ self.logger = logger
+ self.messages = []
+
+ def log (self, message):
+ self.messages.append (strip_eol (message))
+ if len (self.messages) > self.size:
+ del self.messages[0]
+ self.logger.log (message)
diff --git a/demo/medusa/m_syslog.py b/demo/medusa/m_syslog.py
new file mode 100644
index 0000000..bb376db
--- /dev/null
+++ b/demo/medusa/m_syslog.py
@@ -0,0 +1,177 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+# ======================================================================
+# Copyright 1997 by Sam Rushing
+#
+# All Rights Reserved
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose and without fee is hereby
+# granted, provided that the above copyright notice appear in all
+# copies and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of Sam
+# Rushing not be used in advertising or publicity pertaining to
+# distribution of the software without specific, written prior
+# permission.
+#
+# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+# ======================================================================
+
+"""socket interface to unix syslog.
+On Unix, there are usually two ways of getting to syslog: via a
+local unix-domain socket, or via the TCP service.
+
+Usually "/dev/log" is the unix domain socket. This may be different
+for other systems.
+
+>>> my_client = syslog_client ('/dev/log')
+
+Otherwise, just use the UDP version, port 514.
+
+>>> my_client = syslog_client (('my_log_host', 514))
+
+On win32, you will have to use the UDP version. Note that
+you can use this to log to other hosts (and indeed, multiple
+hosts).
+
+This module is not a drop-in replacement for the python
+<syslog> extension module - the interface is different.
+
+Usage:
+
+>>> c = syslog_client()
+>>> c = syslog_client ('/strange/non_standard_log_location')
+>>> c = syslog_client (('other_host.com', 514))
+>>> c.log ('testing', facility='local0', priority='debug')
+
+"""
+
+# TODO: support named-pipe syslog.
+# [see ftp://sunsite.unc.edu/pub/Linux/system/Daemons/syslog-fifo.tar.z]
+
+# from <linux/sys/syslog.h>:
+# ===========================================================================
+# priorities/facilities are encoded into a single 32-bit quantity, where the
+# bottom 3 bits are the priority (0-7) and the top 28 bits are the facility
+# (0-big number). Both the priorities and the facilities map roughly
+# one-to-one to strings in the syslogd(8) source code. This mapping is
+# included in this file.
+#
+# priorities (these are ordered)
+
+LOG_EMERG = 0 # system is unusable
+LOG_ALERT = 1 # action must be taken immediately
+LOG_CRIT = 2 # critical conditions
+LOG_ERR = 3 # error conditions
+LOG_WARNING = 4 # warning conditions
+LOG_NOTICE = 5 # normal but significant condition
+LOG_INFO = 6 # informational
+LOG_DEBUG = 7 # debug-level messages
+
+# facility codes
+LOG_KERN = 0 # kernel messages
+LOG_USER = 1 # random user-level messages
+LOG_MAIL = 2 # mail system
+LOG_DAEMON = 3 # system daemons
+LOG_AUTH = 4 # security/authorization messages
+LOG_SYSLOG = 5 # messages generated internally by syslogd
+LOG_LPR = 6 # line printer subsystem
+LOG_NEWS = 7 # network news subsystem
+LOG_UUCP = 8 # UUCP subsystem
+LOG_CRON = 9 # clock daemon
+LOG_AUTHPRIV = 10 # security/authorization messages (private)
+
+# other codes through 15 reserved for system use
+LOG_LOCAL0 = 16 # reserved for local use
+LOG_LOCAL1 = 17 # reserved for local use
+LOG_LOCAL2 = 18 # reserved for local use
+LOG_LOCAL3 = 19 # reserved for local use
+LOG_LOCAL4 = 20 # reserved for local use
+LOG_LOCAL5 = 21 # reserved for local use
+LOG_LOCAL6 = 22 # reserved for local use
+LOG_LOCAL7 = 23 # reserved for local use
+
+priority_names = {
+ "alert": LOG_ALERT,
+ "crit": LOG_CRIT,
+ "debug": LOG_DEBUG,
+ "emerg": LOG_EMERG,
+ "err": LOG_ERR,
+ "error": LOG_ERR, # DEPRECATED
+ "info": LOG_INFO,
+ "notice": LOG_NOTICE,
+ "panic": LOG_EMERG, # DEPRECATED
+ "warn": LOG_WARNING, # DEPRECATED
+ "warning": LOG_WARNING,
+ }
+
+facility_names = {
+ "auth": LOG_AUTH,
+ "authpriv": LOG_AUTHPRIV,
+ "cron": LOG_CRON,
+ "daemon": LOG_DAEMON,
+ "kern": LOG_KERN,
+ "lpr": LOG_LPR,
+ "mail": LOG_MAIL,
+ "news": LOG_NEWS,
+ "security": LOG_AUTH, # DEPRECATED
+ "syslog": LOG_SYSLOG,
+ "user": LOG_USER,
+ "uucp": LOG_UUCP,
+ "local0": LOG_LOCAL0,
+ "local1": LOG_LOCAL1,
+ "local2": LOG_LOCAL2,
+ "local3": LOG_LOCAL3,
+ "local4": LOG_LOCAL4,
+ "local5": LOG_LOCAL5,
+ "local6": LOG_LOCAL6,
+ "local7": LOG_LOCAL7,
+ }
+
+import socket
+
+class syslog_client:
+ def __init__ (self, address='/dev/log'):
+ self.address = address
+ if type (address) == type(''):
+ self.socket = socket.socket (socket.AF_UNIX, socket.SOCK_STREAM)
+ self.socket.connect (address)
+ self.unix = 1
+ else:
+ self.socket = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
+ self.unix = 0
+
+ # curious: when talking to the unix-domain '/dev/log' socket, a
+ # zero-terminator seems to be required. this string is placed
+ # into a class variable so that it can be overridden if
+ # necessary.
+
+ log_format_string = '<%d>%s\000'
+
+ def log (self, message, facility=LOG_USER, priority=LOG_INFO):
+ message = self.log_format_string % (
+ self.encode_priority (facility, priority),
+ message
+ )
+ if self.unix:
+ self.socket.send (message)
+ else:
+ self.socket.sendto (message, self.address)
+
+ def encode_priority (self, facility, priority):
+ if type(facility) == type(''):
+ facility = facility_names[facility]
+ if type(priority) == type(''):
+ priority = priority_names[priority]
+ return (facility<<3) | priority
+
+ def close (self):
+ if self.unix:
+ self.socket.close()
+
diff --git a/demo/medusa/medusa_gif.py b/demo/medusa/medusa_gif.py
new file mode 100644
index 0000000..005644a
--- /dev/null
+++ b/demo/medusa/medusa_gif.py
@@ -0,0 +1,8 @@
+# -*- Mode: Python -*-
+
+# the medusa icon as a python source file.
+
+width = 97
+height = 61
+
+data = 'GIF89aa\000=\000\204\000\000\000\000\000\255\255\255\245\245\245ssskkkccc111)))\326\326\326!!!\316\316\316\300\300\300\204\204\000\224\224\224\214\214\214\200\200\200RRR\377\377\377JJJ\367\367\367BBB\347\347\347\000\204\000\020\020\020\265\265\265\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000!\371\004\001\000\000\021\000,\000\000\000\000a\000=\000\000\005\376`$\216di\236h\252\256l\353\276p,\317tm\337x\256\357|m\001@\240E\305\000\364\2164\206R)$\005\201\214\007r\012{X\255\312a\004\260\\>\026\3240\353)\224n\001W+X\334\373\231~\344.\303b\216\024\027x<\273\307\255G,rJiWN\014{S}k"?ti\013EdPQ\207G@_%\000\026yy\\\201\202\227\224<\221Fs$pOjWz\241<r@vO\236\231\233k\247M\2544\203F\177\235\236L#\247\256Z\270,\266BxJ[\276\256A]iE\304\305\262\273E\313\201\275i#\\\303\321\'h\203V\\\177\326\276\216\220P~\335\230_\264\013\342\275\344KF\233\360Q\212\352\246\000\367\274s\361\236\334\347T\341;\341\246\2202\177\3142\211`\242o\325@S\202\264\031\252\207\260\323\256\205\311\036\236\270\002\'\013\302\177\274H\010\324X\002\0176\212\037\376\321\360\032\226\207\244\2674(+^\202\346r\205J\0211\375\241Y#\256f\0127\315>\272\002\325\307g\012(\007\205\312#j\317(\012A\200\224.\241\003\346GS\247\033\245\344\264\366\015L\'PXQl]\266\263\243\232\260?\245\316\371\362\225\035\332\243J\273\332Q\263\357-D\241T\327\270\265\013W&\330\010u\371b\322IW0\214\261]\003\033Va\365Z#\207\213a\030k\2647\262\014p\354\024[n\321N\363\346\317\003\037P\000\235C\302\000\3228(\244\363YaA\005\022\255_\237@\260\000A\212\326\256qbp\321\332\266\011\334=T\023\010"!B\005\003A\010\224\020\220 H\002\337#\020 O\276E\357h\221\327\003\\\000b@v\004\351A.h\365\354\342B\002\011\257\025\\ \220\340\301\353\006\000\024\214\200pA\300\353\012\364\241k/\340\033C\202\003\000\310fZ\011\003V\240R\005\007\354\376\026A\000\000\360\'\202\177\024\004\210\003\000\305\215\360\000\000\015\220\240\332\203\027@\'\202\004\025VpA\000%\210x\321\206\032J\341\316\010\262\211H"l\333\341\200\200>"]P\002\212\011\010`\002\0066FP\200\001\'\024p]\004\027(8B\221\306]\000\201w>\002iB\001\007\340\260"v7J1\343(\257\020\251\243\011\242i\263\017\215\337\035\220\200\221\365m4d\015\016D\251\341iN\354\346Ng\253\200I\240\031\35609\245\2057\311I\302\2007t\231"&`\314\310\244\011e\226(\236\010w\212\300\234\011\012HX(\214\253\311@\001\233^\222pg{% \340\035\224&H\000\246\201\362\215`@\001"L\340\004\030\234\022\250\'\015(V:\302\235\030\240q\337\205\224\212h@\177\006\000\250\210\004\007\310\207\337\005\257-P\346\257\367]p\353\203\271\256:\203\236\211F\340\247\010\3329g\244\010\307*=A\000\203\260y\012\304s#\014\007D\207,N\007\304\265\027\021C\233\207%B\366[m\353\006\006\034j\360\306+\357\274a\204\000\000;'
diff --git a/demo/medusa/mime_type_table.py b/demo/medusa/mime_type_table.py
new file mode 100644
index 0000000..dbc64a4
--- /dev/null
+++ b/demo/medusa/mime_type_table.py
@@ -0,0 +1,113 @@
+# -*- Python -*-
+# Converted by ./convert_mime_type_table.py from:
+# /usr/src2/apache_1.2b6/conf/mime.types
+#
+content_type_map = \
+ {
+ 'ai': 'application/postscript',
+ 'aif': 'audio/x-aiff',
+ 'aifc': 'audio/x-aiff',
+ 'aiff': 'audio/x-aiff',
+ 'au': 'audio/basic',
+ 'avi': 'video/x-msvideo',
+ 'bcpio': 'application/x-bcpio',
+ 'bin': 'application/octet-stream',
+ 'cdf': 'application/x-netcdf',
+ 'class': 'application/octet-stream',
+ 'cpio': 'application/x-cpio',
+ 'cpt': 'application/mac-compactpro',
+ 'csh': 'application/x-csh',
+ 'dcr': 'application/x-director',
+ 'dir': 'application/x-director',
+ 'dms': 'application/octet-stream',
+ 'doc': 'application/msword',
+ 'dvi': 'application/x-dvi',
+ 'dxr': 'application/x-director',
+ 'eps': 'application/postscript',
+ 'etx': 'text/x-setext',
+ 'exe': 'application/octet-stream',
+ 'gif': 'image/gif',
+ 'gtar': 'application/x-gtar',
+ 'gz': 'application/x-gzip',
+ 'hdf': 'application/x-hdf',
+ 'hqx': 'application/mac-binhex40',
+ 'htm': 'text/html',
+ 'html': 'text/html',
+ 'ice': 'x-conference/x-cooltalk',
+ 'ief': 'image/ief',
+ 'jpe': 'image/jpeg',
+ 'jpeg': 'image/jpeg',
+ 'jpg': 'image/jpeg',
+ 'kar': 'audio/midi',
+ 'latex': 'application/x-latex',
+ 'lha': 'application/octet-stream',
+ 'lzh': 'application/octet-stream',
+ 'man': 'application/x-troff-man',
+ 'me': 'application/x-troff-me',
+ 'mid': 'audio/midi',
+ 'midi': 'audio/midi',
+ 'mif': 'application/x-mif',
+ 'mov': 'video/quicktime',
+ 'movie': 'video/x-sgi-movie',
+ 'mp2': 'audio/mpeg',
+ 'mpe': 'video/mpeg',
+ 'mpeg': 'video/mpeg',
+ 'mpg': 'video/mpeg',
+ 'mpga': 'audio/mpeg',
+ 'mp3': 'audio/mpeg',
+ 'ms': 'application/x-troff-ms',
+ 'nc': 'application/x-netcdf',
+ 'oda': 'application/oda',
+ 'pbm': 'image/x-portable-bitmap',
+ 'pdb': 'chemical/x-pdb',
+ 'pdf': 'application/pdf',
+ 'pgm': 'image/x-portable-graymap',
+ 'png': 'image/png',
+ 'pnm': 'image/x-portable-anymap',
+ 'ppm': 'image/x-portable-pixmap',
+ 'ppt': 'application/powerpoint',
+ 'ps': 'application/postscript',
+ 'qt': 'video/quicktime',
+ 'ra': 'audio/x-realaudio',
+ 'ram': 'audio/x-pn-realaudio',
+ 'ras': 'image/x-cmu-raster',
+ 'rgb': 'image/x-rgb',
+ 'roff': 'application/x-troff',
+ 'rpm': 'audio/x-pn-realaudio-plugin',
+ 'rtf': 'application/rtf',
+ 'rtx': 'text/richtext',
+ 'sgm': 'text/x-sgml',
+ 'sgml': 'text/x-sgml',
+ 'sh': 'application/x-sh',
+ 'shar': 'application/x-shar',
+ 'sit': 'application/x-stuffit',
+ 'skd': 'application/x-koan',
+ 'skm': 'application/x-koan',
+ 'skp': 'application/x-koan',
+ 'skt': 'application/x-koan',
+ 'snd': 'audio/basic',
+ 'src': 'application/x-wais-source',
+ 'sv4cpio': 'application/x-sv4cpio',
+ 'sv4crc': 'application/x-sv4crc',
+ 't': 'application/x-troff',
+ 'tar': 'application/x-tar',
+ 'tcl': 'application/x-tcl',
+ 'tex': 'application/x-tex',
+ 'texi': 'application/x-texinfo',
+ 'texinfo': 'application/x-texinfo',
+ 'tif': 'image/tiff',
+ 'tiff': 'image/tiff',
+ 'tr': 'application/x-troff',
+ 'tsv': 'text/tab-separated-values',
+ 'txt': 'text/plain',
+ 'ustar': 'application/x-ustar',
+ 'vcd': 'application/x-cdlink',
+ 'vrml': 'x-world/x-vrml',
+ 'wav': 'audio/x-wav',
+ 'wrl': 'x-world/x-vrml',
+ 'xbm': 'image/x-xbitmap',
+ 'xpm': 'image/x-xpixmap',
+ 'xwd': 'image/x-xwindowdump',
+ 'xyz': 'chemical/x-pdb',
+ 'zip': 'application/zip',
+ }
diff --git a/demo/medusa/poison_handler.py b/demo/medusa/poison_handler.py
new file mode 100644
index 0000000..acc78ab
--- /dev/null
+++ b/demo/medusa/poison_handler.py
@@ -0,0 +1,69 @@
+
+import string
+import whrandom
+
+RESP_HEAD="""\
+<HTML><BODY BGCOLOR=\"#ffffff\">
+"""
+
+RESP_MIDDLE="""
+<h2>M2Crypto https server demonstration</h2>
+
+This web page is generated by the "poison" http request handler.
+<br>
+The links just go on and on and on...
+<br><br>
+"""
+
+RESP_TAIL="""
+</BODY></HTML>
+"""
+
+charset='012345678/90ABCDEFGHIJKLM/NOPQRSTUVWXYZabcd/efghijklmnopqrs/tuvwxyz'
+numchar=len(charset)
+
+def makepage(numlinks):
+
+ title='<title>'
+ for u in range(whrandom.randint(3, 15)):
+ pick=whrandom.randint(0, numchar-1)
+ title=title+charset[pick]
+ title=title+'</title>'
+
+ url='\r\n'
+ numlinks=whrandom.randint(2, numlinks)
+ for i in range(numlinks):
+ url=url+'<a href="/poison/'
+ for u in range(whrandom.randint(3, 15)):
+ pick=whrandom.randint(0, numchar-1)
+ ch=charset[pick]
+ if ch=='/' and url[-1]=='/':
+ ch=charset[pick+1]
+ url=url+ch
+ url=url+'/">'
+ for u in range(whrandom.randint(3, 15)):
+ pick=whrandom.randint(0, numchar-1)
+ url=url+charset[pick]
+ url=url+'</a><br>\r\n'
+
+ url=RESP_HEAD+title+RESP_MIDDLE+url+RESP_TAIL
+ return url
+
+
+class poison_handler:
+ """This is a clone of webpoison - every URL returns a page of URLs, each of which
+ returns a page of URLs, each of _which_ returns a page of URLs, ad infinitum.
+ The objective is to sucker address-harvesting bots run by spammers."""
+
+ def __init__(self, numlinks=10):
+ self.numlinks = numlinks
+ self.poison_level = 0
+
+ def match(self, request):
+ return (request.uri[:7] == '/poison')
+
+ def handle_request(self, request):
+ if request.command == 'get':
+ request.push(makepage(self.numlinks))
+ request.done()
+
diff --git a/demo/medusa/producers.py b/demo/medusa/producers.py
new file mode 100644
index 0000000..2138258
--- /dev/null
+++ b/demo/medusa/producers.py
@@ -0,0 +1,329 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+import string
+
+"""
+A collection of producers.
+Each producer implements a particular feature: They can be combined
+in various ways to get interesting and useful behaviors.
+
+For example, you can feed dynamically-produced output into the compressing
+producer, then wrap this with the 'chunked' transfer-encoding producer.
+"""
+
+class simple_producer:
+ "producer for a string"
+ def __init__ (self, data, buffer_size=1024):
+ self.data = data
+ self.buffer_size = buffer_size
+
+ def more (self):
+ if len (self.data) > self.buffer_size:
+ result = self.data[:self.buffer_size]
+ self.data = self.data[self.buffer_size:]
+ return result
+ else:
+ result = self.data
+ self.data = ''
+ return result
+
+class scanning_producer:
+ "like simple_producer, but more efficient for large strings"
+ def __init__ (self, data, buffer_size=1024):
+ self.data = data
+ self.buffer_size = buffer_size
+ self.pos = 0
+
+ def more (self):
+ if self.pos < len(self.data):
+ lp = self.pos
+ rp = min (
+ len(self.data),
+ self.pos + self.buffer_size
+ )
+ result = self.data[lp:rp]
+ self.pos = self.pos + len(result)
+ return result
+ else:
+ return ''
+
+class lines_producer:
+ "producer for a list of lines"
+
+ def __init__ (self, lines):
+ self.lines = lines
+
+ def ready (self):
+ return len(self.lines)
+
+ def more (self):
+ if self.lines:
+ chunk = self.lines[:50]
+ self.lines = self.lines[50:]
+ return string.join (chunk, '\r\n') + '\r\n'
+ else:
+ return ''
+
+class buffer_list_producer:
+ "producer for a list of buffers"
+
+ # i.e., data == string.join (buffers, '')
+
+ def __init__ (self, buffers):
+
+ self.index = 0
+ self.buffers = buffers
+
+ def more (self):
+ if self.index >= len(self.buffers):
+ return ''
+ else:
+ data = self.buffers[self.index]
+ self.index = self.index + 1
+ return data
+
+class file_producer:
+ "producer wrapper for file[-like] objects"
+
+ # match http_channel's outgoing buffer size
+ out_buffer_size = 1<<16
+
+ def __init__ (self, file):
+ self.done = 0
+ self.file = file
+
+ def more (self):
+ if self.done:
+ return ''
+ else:
+ data = self.file.read (self.out_buffer_size)
+ if not data:
+ self.file.close()
+ del self.file
+ self.done = 1
+ return ''
+ else:
+ return data
+
+# A simple output producer. This one does not [yet] have
+# the safety feature builtin to the monitor channel: runaway
+# output will not be caught.
+
+# don't try to print from within any of the methods
+# of this object.
+
+class output_producer:
+ "Acts like an output file; suitable for capturing sys.stdout"
+ def __init__ (self):
+ self.data = ''
+
+ def write (self, data):
+ lines = string.splitfields (data, '\n')
+ data = string.join (lines, '\r\n')
+ self.data = self.data + data
+
+ def writeline (self, line):
+ self.data = self.data + line + '\r\n'
+
+ def writelines (self, lines):
+ self.data = self.data + string.joinfields (
+ lines,
+ '\r\n'
+ ) + '\r\n'
+
+ def ready (self):
+ return (len (self.data) > 0)
+
+ def flush (self):
+ pass
+
+ def softspace (self, *args):
+ pass
+
+ def more (self):
+ if self.data:
+ result = self.data[:512]
+ self.data = self.data[512:]
+ return result
+ else:
+ return ''
+
+class composite_producer:
+ "combine a fifo of producers into one"
+ def __init__ (self, producers):
+ self.producers = producers
+
+ def more (self):
+ while len(self.producers):
+ p = self.producers.first()
+ d = p.more()
+ if d:
+ return d
+ else:
+ self.producers.pop()
+ else:
+ return ''
+
+
+class globbing_producer:
+ """
+ 'glob' the output from a producer into a particular buffer size.
+ helps reduce the number of calls to send(). [this appears to
+ gain about 30% performance on requests to a single channel]
+ """
+
+ def __init__ (self, producer, buffer_size=1<<16):
+ self.producer = producer
+ self.buffer = ''
+ self.buffer_size = buffer_size
+
+ def more (self):
+ while len(self.buffer) < self.buffer_size:
+ data = self.producer.more()
+ if data:
+ self.buffer = self.buffer + data
+ else:
+ break
+ r = self.buffer
+ self.buffer = ''
+ return r
+
+
+class hooked_producer:
+ """
+ A producer that will call <function> when it empties,.
+ with an argument of the number of bytes produced. Useful
+ for logging/instrumentation purposes.
+ """
+
+ def __init__ (self, producer, function):
+ self.producer = producer
+ self.function = function
+ self.bytes = 0
+
+ def more (self):
+ if self.producer:
+ result = self.producer.more()
+ if not result:
+ self.producer = None
+ self.function (self.bytes)
+ else:
+ self.bytes = self.bytes + len(result)
+ return result
+ else:
+ return ''
+
+# HTTP 1.1 emphasizes that an advertised Content-Length header MUST be
+# correct. In the face of Strange Files, it is conceivable that
+# reading a 'file' may produce an amount of data not matching that
+# reported by os.stat() [text/binary mode issues, perhaps the file is
+# being appended to, etc..] This makes the chunked encoding a True
+# Blessing, and it really ought to be used even with normal files.
+# How beautifully it blends with the concept of the producer.
+
+class chunked_producer:
+ """A producer that implements the 'chunked' transfer coding for HTTP/1.1.
+ Here is a sample usage:
+ request['Transfer-Encoding'] = 'chunked'
+ request.push (
+ producers.chunked_producer (your_producer)
+ )
+ request.done()
+ """
+
+ def __init__ (self, producer, footers=None):
+ self.producer = producer
+ self.footers = footers
+
+ def more (self):
+ if self.producer:
+ data = self.producer.more()
+ if data:
+ return '%x\r\n%s\r\n' % (len(data), data)
+ else:
+ self.producer = None
+ if self.footers:
+ return string.join (
+ ['0'] + self.footers,
+ '\r\n'
+ ) + '\r\n\r\n'
+ else:
+ return '0\r\n\r\n'
+ else:
+ return ''
+
+# Unfortunately this isn't very useful right now (Aug 97), because
+# apparently the browsers don't do on-the-fly decompression. Which
+# is sad, because this could _really_ speed things up, especially for
+# low-bandwidth clients (i.e., most everyone).
+
+try:
+ import zlib
+except ImportError:
+ zlib = None
+
+class compressed_producer:
+ """
+ Compress another producer on-the-fly, using ZLIB
+ [Unfortunately, none of the current browsers seem to support this]
+ """
+
+ # Note: It's not very efficient to have the server repeatedly
+ # compressing your outgoing files: compress them ahead of time, or
+ # use a compress-once-and-store scheme. However, if you have low
+ # bandwidth and low traffic, this may make more sense than
+ # maintaining your source files compressed.
+ #
+ # Can also be used for compressing dynamically-produced output.
+
+ def __init__ (self, producer, level=5):
+ self.producer = producer
+ self.compressor = zlib.compressobj (level)
+
+ def more (self):
+ if self.producer:
+ cdata = ''
+ # feed until we get some output
+ while not cdata:
+ data = self.producer.more()
+ if not data:
+ self.producer = None
+ return self.compressor.flush()
+ else:
+ cdata = self.compressor.compress (data)
+ return cdata
+ else:
+ return ''
+
+class escaping_producer:
+
+ "A producer that escapes a sequence of characters"
+ " Common usage: escaping the CRLF.CRLF sequence in SMTP, NNTP, etc..."
+
+ def __init__ (self, producer, esc_from='\r\n.', esc_to='\r\n..'):
+ self.producer = producer
+ self.esc_from = esc_from
+ self.esc_to = esc_to
+ self.buffer = ''
+ from asynchat import find_prefix_at_end
+ self.find_prefix_at_end = find_prefix_at_end
+
+ def more (self):
+ esc_from = self.esc_from
+ esc_to = self.esc_to
+
+ buffer = self.buffer + self.producer.more()
+
+ if buffer:
+ buffer = string.replace (buffer, esc_from, esc_to)
+ i = self.find_prefix_at_end (buffer, esc_from)
+ if i:
+ # we found a prefix
+ self.buffer = buffer[-i:]
+ return buffer[:-i]
+ else:
+ # no prefix, return it all
+ self.buffer = ''
+ return buffer
+ else:
+ return buffer
diff --git a/demo/medusa/put_handler.py b/demo/medusa/put_handler.py
new file mode 100644
index 0000000..488fb42
--- /dev/null
+++ b/demo/medusa/put_handler.py
@@ -0,0 +1,113 @@
+# -*- Mode: Python; tab-width: 4 -*-
+#
+# Author: Sam Rushing <rushing@nightmare.com>
+# Copyright 1996-2000 by Sam Rushing
+# All Rights Reserved.
+#
+
+import re
+import string
+
+import default_handler
+unquote = default_handler.unquote
+get_header = default_handler.get_header
+
+last_request = None
+
+class put_handler:
+ def __init__ (self, filesystem, uri_regex):
+ self.filesystem = filesystem
+ if type (uri_regex) == type(''):
+ self.uri_regex = re.compile (uri_regex)
+ else:
+ self.uri_regex = uri_regex
+
+ def match (self, request):
+ uri = request.uri
+ if request.command == 'put':
+ m = self.uri_regex.match (uri)
+ if m and m.end() == len(uri):
+ return 1
+ return 0
+
+ def handle_request (self, request):
+
+ path, params, query, fragment = request.split_uri()
+
+ # strip off leading slashes
+ while path and path[0] == '/':
+ path = path[1:]
+
+ if '%' in path:
+ path = unquote (path)
+
+ # make sure there's a content-length header
+ cl = get_header (CONTENT_LENGTH, request.header)
+ if not cl:
+ request.error (411)
+ return
+ else:
+ cl = string.atoi (cl)
+
+ # don't let the try to overwrite a directory
+ if self.filesystem.isdir (path):
+ request.error (405)
+ return
+
+ is_update = self.filesystem.isfile (path)
+
+ try:
+ output_file = self.filesystem.open (path, 'wb')
+ except:
+ request.error (405)
+ return
+
+ request.collector = put_collector (output_file, cl, request, is_update)
+
+ # no terminator while receiving PUT data
+ request.channel.set_terminator (None)
+
+ # don't respond yet, wait until we've received the data...
+
+class put_collector:
+ def __init__ (self, file, length, request, is_update):
+ self.file = file
+ self.length = length
+ self.request = request
+ self.is_update = is_update
+ self.bytes_in = 0
+
+ def collect_incoming_data (self, data):
+ ld = len(data)
+ bi = self.bytes_in
+ if (bi + ld) >= self.length:
+ # last bit of data
+ chunk = self.length - bi
+ self.file.write (data[:chunk])
+ self.file.close()
+
+ if chunk != ld:
+ print 'orphaned %d bytes: <%s>' % (ld - chunk, repr(data[chunk:]))
+
+ # do some housekeeping
+ r = self.request
+ ch = r.channel
+ ch.current_request = None
+ # set the terminator back to the default
+ ch.set_terminator ('\r\n\r\n')
+ if self.is_update:
+ r.reply_code = 204 # No content
+ r.done()
+ else:
+ r.reply_now (201) # Created
+ # avoid circular reference
+ del self.request
+ else:
+ self.file.write (data)
+ self.bytes_in = self.bytes_in + ld
+
+ def found_terminator (self):
+ # shouldn't be called
+ pass
+
+CONTENT_LENGTH = re.compile ('Content-Length: ([0-9]+)', re.IGNORECASE)
diff --git a/demo/medusa/redirecting_handler.py b/demo/medusa/redirecting_handler.py
new file mode 100644
index 0000000..e6a1e90
--- /dev/null
+++ b/demo/medusa/redirecting_handler.py
@@ -0,0 +1,44 @@
+# -*- Mode: Python; tab-width: 4 -*-
+#
+# Author: Sam Rushing <rushing@nightmare.com>
+# Copyright 1996-2000 by Sam Rushing
+# All Rights Reserved.
+#
+
+import re
+import counter
+
+class redirecting_handler:
+
+ def __init__ (self, pattern, redirect, regex_flag=re.IGNORECASE):
+ self.pattern = pattern
+ self.redirect = redirect
+ self.patreg = re.compile (pattern, regex_flag)
+ self.hits = counter.counter()
+
+ def match (self, request):
+ m = self.patref.match (request.uri)
+ return (m and (m.end() == len(request.uri)))
+
+ def handle_request (self, request):
+ self.hits.increment()
+ m = self.patreg.match (request.uri)
+ part = m.group(1)
+
+ request['Location'] = self.redirect % part
+ request.error (302) # moved temporarily
+
+ def __repr__ (self):
+ return '<Redirecting Handler at %08x [%s => %s]>' % (
+ id(self),
+ repr(self.pattern),
+ repr(self.redirect)
+ )
+
+ def status (self):
+ import producers
+ return producers.simple_producer (
+ '<li> Redirecting Handler %s => %s <b>Hits</b>: %s' % (
+ self.pattern, self.redirect, self.hits
+ )
+ )
diff --git a/demo/medusa/server.pem b/demo/medusa/server.pem
new file mode 100644
index 0000000..1ee9282
--- /dev/null
+++ b/demo/medusa/server.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/medusa/status_handler.py b/demo/medusa/status_handler.py
new file mode 100644
index 0000000..2e6223e
--- /dev/null
+++ b/demo/medusa/status_handler.py
@@ -0,0 +1,282 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+VERSION_STRING = "$Id: status_handler.py 299 2005-06-09 17:32:28Z heikki $"
+
+#
+# medusa status extension
+#
+
+import string
+import time
+import re
+
+import asyncore
+import http_server
+import medusa_gif
+import producers
+from counter import counter
+
+START_TIME = long(time.time())
+
+class status_extension:
+ hit_counter = counter()
+
+ def __init__ (self, objects, statusdir='/status', allow_emergency_debug=0):
+ self.objects = objects
+ self.statusdir = statusdir
+ self.allow_emergency_debug = allow_emergency_debug
+ # We use /status instead of statusdir here because it's too
+ # hard to pass statusdir to the logger, who makes the HREF
+ # to the object dir. We don't need the security-through-
+ # obscurity here in any case, because the id is obscurity enough
+ self.hyper_regex = re.compile('/status/object/([0-9]+)/.*')
+ self.hyper_objects = []
+ for object in objects:
+ self.register_hyper_object (object)
+
+ def __repr__ (self):
+ return '<Status Extension (%s hits) at %x>' % (
+ self.hit_counter,
+ id(self)
+ )
+
+ def match (self, request):
+ path, params, query, fragment = request.split_uri()
+ # For reasons explained above, we don't use statusdir for /object
+ return (path[:len(self.statusdir)] == self.statusdir or
+ path[:len("/status/object/")] == '/status/object/')
+
+ # Possible Targets:
+ # /status
+ # /status/channel_list
+ # /status/medusa.gif
+
+ # can we have 'clickable' objects?
+ # [yes, we can use id(x) and do a linear search]
+
+ # Dynamic producers:
+ # HTTP/1.0: we must close the channel, because it's dynamic output
+ # HTTP/1.1: we can use the chunked transfer-encoding, and leave
+ # it open.
+
+ def handle_request (self, request):
+ [path, params, query, fragment] = request.split_uri()
+ self.hit_counter.increment()
+ if path == self.statusdir: # and not a subdirectory
+ up_time = string.join (english_time (long(time.time()) - START_TIME))
+ request['Content-Type'] = 'text/html'
+ request.push (
+ '<html>'
+ '<title>Medusa Status Reports</title>'
+ '<body bgcolor="#ffffff">'
+ '<h1>Medusa Status Reports</h1>'
+ '<b>Up:</b> %s' % up_time
+ )
+ for i in range(len(self.objects)):
+ request.push (self.objects[i].status())
+ request.push ('<hr>\r\n')
+ request.push (
+ '<p><a href="%s/channel_list">Channel List</a>'
+ '<hr>'
+ '<img src="%s/medusa.gif" align=right width=%d height=%d>'
+ '</body></html>' % (
+ self.statusdir,
+ self.statusdir,
+ medusa_gif.width,
+ medusa_gif.height
+ )
+ )
+ request.done()
+ elif path == self.statusdir + '/channel_list':
+ request['Content-Type'] = 'text/html'
+ request.push ('<html><body>')
+ request.push(channel_list_producer(self.statusdir))
+ request.push (
+ '<hr>'
+ '<img src="%s/medusa.gif" align=right width=%d height=%d>' % (
+ self.statusdir,
+ medusa_gif.width,
+ medusa_gif.height
+ ) +
+ '</body></html>'
+ )
+ request.done()
+
+ elif path == self.statusdir + '/medusa.gif':
+ request['Content-Type'] = 'image/gif'
+ request['Content-Length'] = len(medusa_gif.data)
+ request.push (medusa_gif.data)
+ request.done()
+
+ elif path == self.statusdir + '/close_zombies':
+ message = (
+ '<h2>Closing all zombie http client connections...</h2>'
+ '<p><a href="%s">Back to the status page</a>' % self.statusdir
+ )
+ request['Content-Type'] = 'text/html'
+ request['Content-Length'] = len (message)
+ request.push (message)
+ now = int (time.time())
+ for channel in asyncore.socket_map.keys():
+ if channel.__class__ == http_server.http_channel:
+ if channel != request.channel:
+ if (now - channel.creation_time) > channel.zombie_timeout:
+ channel.close()
+ request.done()
+
+ # Emergency Debug Mode
+ # If a server is running away from you, don't KILL it!
+ # Move all the AF_INET server ports and perform an autopsy...
+ # [disabled by default to protect the innocent]
+ elif self.allow_emergency_debug and path == self.statusdir + '/emergency_debug':
+ request.push ('<html>Moving All Servers...</html>')
+ request.done()
+ for channel in asyncore.socket_map.keys():
+ if channel.accepting:
+ if type(channel.addr) is type(()):
+ ip, port = channel.addr
+ channel.socket.close()
+ channel.del_channel()
+ channel.addr = (ip, port+10000)
+ fam, typ = channel.family_and_type
+ channel.create_socket (fam, typ)
+ channel.set_reuse_addr()
+ channel.bind (channel.addr)
+ channel.listen(5)
+
+ else:
+ m = self.hyper_regex.match (path)
+ if m:
+ oid = string.atoi (m.group (1))
+ for object in self.hyper_objects:
+ if id (object) == oid:
+ if hasattr (object, 'hyper_respond'):
+ object.hyper_respond (self, path, request)
+ else:
+ request.error (404)
+ return
+
+ def status (self):
+ return producers.simple_producer (
+ '<li>Status Extension <b>Hits</b> : %s' % self.hit_counter
+ )
+
+ def register_hyper_object (self, object):
+ if not object in self.hyper_objects:
+ self.hyper_objects.append (object)
+
+import logger
+
+class logger_for_status (logger.tail_logger):
+
+ def status (self):
+ return 'Last %d log entries for: %s' % (
+ len (self.messages),
+ html_repr (self)
+ )
+
+ def hyper_respond (self, sh, path, request):
+ request['Content-Type'] = 'text/plain'
+ messages = self.messages[:]
+ messages.reverse()
+ request.push (lines_producer (messages))
+ request.done()
+
+class lines_producer:
+ def __init__ (self, lines):
+ self.lines = lines
+
+ def ready (self):
+ return len(self.lines)
+
+ def more (self):
+ if self.lines:
+ chunk = self.lines[:50]
+ self.lines = self.lines[50:]
+ return string.join (chunk, '\r\n') + '\r\n'
+ else:
+ return ''
+
+class channel_list_producer (lines_producer):
+ def __init__ (self, statusdir):
+ channel_reprs = map (
+ lambda x: '&lt;' + repr(x)[1:-1] + '&gt;',
+ asyncore.socket_map.values()
+ )
+ channel_reprs.sort()
+ lines_producer.__init__ (
+ self,
+ ['<h1>Active Channel List</h1>',
+ '<pre>'
+ ] + channel_reprs + [
+ '</pre>',
+ '<p><a href="%s">Status Report</a>' % statusdir
+ ]
+ )
+
+
+# this really needs a full-blown quoter...
+def sanitize (s):
+ if '<' in s:
+ s = string.join (string.split (s, '<'), '&lt;')
+ if '>' in s:
+ s = string.join (string.split (s, '>'), '&gt;')
+ return s
+
+def html_repr (object):
+ so = sanitize (repr (object))
+ if hasattr (object, 'hyper_respond'):
+ return '<a href="/status/object/%d/">%s</a>' % (id (object), so)
+ else:
+ return so
+
+def html_reprs (list, front='', back=''):
+ reprs = map (
+ lambda x,f=front,b=back: '%s%s%s' % (f,x,b),
+ map (lambda x: sanitize (html_repr(x)), list)
+ )
+ reprs.sort()
+ return reprs
+
+# for example, tera, giga, mega, kilo
+# p_d (n, (1024, 1024, 1024, 1024))
+# smallest divider goes first - for example
+# minutes, hours, days
+# p_d (n, (60, 60, 24))
+
+def progressive_divide (n, parts):
+ result = []
+ for part in parts:
+ n, rem = divmod (n, part)
+ result.append (rem)
+ result.append (n)
+ return result
+
+# b,k,m,g,t
+def split_by_units (n, units, dividers, format_string):
+ divs = progressive_divide (n, dividers)
+ result = []
+ for i in range(len(units)):
+ if divs[i]:
+ result.append (format_string % (divs[i], units[i]))
+ result.reverse()
+ if not result:
+ return [format_string % (0, units[0])]
+ else:
+ return result
+
+def english_bytes (n):
+ return split_by_units (
+ n,
+ ('','K','M','G','T'),
+ (1024, 1024, 1024, 1024, 1024),
+ '%d %sB'
+ )
+
+def english_time (n):
+ return split_by_units (
+ n,
+ ('secs', 'mins', 'hours', 'days', 'weeks', 'years'),
+ ( 60, 60, 24, 7, 52),
+ '%d %s'
+ )
diff --git a/demo/medusa/virtual_handler.py b/demo/medusa/virtual_handler.py
new file mode 100644
index 0000000..96f19df
--- /dev/null
+++ b/demo/medusa/virtual_handler.py
@@ -0,0 +1,60 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+import socket
+import default_handler
+import re
+
+HOST = re.compile ('Host: ([^:/]+).*', re.IGNORECASE)
+
+get_header = default_handler.get_header
+
+class virtual_handler:
+
+ """HTTP request handler for an HTTP/1.0-style virtual host. Each
+ Virtual host must have a different IP"""
+
+ def __init__ (self, handler, hostname):
+ self.handler = handler
+ self.hostname = hostname
+ try:
+ self.ip = socket.gethostbyname (hostname)
+ except socket.error:
+ raise ValueError, "Virtual Hostname %s does not appear to be registered in the DNS" % hostname
+
+ def match (self, request):
+ if (request.channel.addr[0] == self.ip):
+ return 1
+ else:
+ return 0
+
+ def handle_request (self, request):
+ return self.handler.handle_request (request)
+
+ def __repr__ (self):
+ return '<virtual request handler for %s>' % self.hostname
+
+
+class virtual_handler_with_host:
+
+ """HTTP request handler for HTTP/1.1-style virtual hosts. This
+ matches by checking the value of the 'Host' header in the request.
+ You actually don't _have_ to support HTTP/1.1 to use this, since
+ many browsers now send the 'Host' header. This is a Good Thing."""
+
+ def __init__ (self, handler, hostname):
+ self.handler = handler
+ self.hostname = hostname
+
+ def match (self, request):
+ host = get_header (HOST, request.header)
+ if host == self.hostname:
+ return 1
+ else:
+ return 0
+
+ def handle_request (self, request):
+ return self.handler.handle_request (request)
+
+ def __repr__ (self):
+ return '<virtual request handler for %s>' % self.hostname
+
diff --git a/demo/medusa/xmlrpc_handler.py b/demo/medusa/xmlrpc_handler.py
new file mode 100644
index 0000000..d56baf2
--- /dev/null
+++ b/demo/medusa/xmlrpc_handler.py
@@ -0,0 +1,104 @@
+# -*- Mode: Python; tab-width: 4 -*-
+
+# See http://www.xml-rpc.com/
+# http://www.pythonware.com/products/xmlrpc/
+
+# Based on "xmlrpcserver.py" by Fredrik Lundh (fredrik@pythonware.com)
+
+VERSION = "$Id: xmlrpc_handler.py 299 2005-06-09 17:32:28Z heikki $"
+
+import http_server
+import xmlrpclib
+
+import string
+import sys
+
+class xmlrpc_handler:
+
+ def match (self, request):
+ # Note: /RPC2 is not required by the spec, so you may override this method.
+ if request.uri[:5] == '/RPC2':
+ return 1
+ else:
+ return 0
+
+ def handle_request (self, request):
+ [path, params, query, fragment] = request.split_uri()
+
+ if request.command in ('post', 'put'):
+ request.collector = collector (self, request)
+ else:
+ request.error (400)
+
+ def continue_request (self, data, request):
+ params, method = xmlrpclib.loads (data)
+ try:
+ # generate response
+ try:
+ response = self.call (method, params)
+ if type(response) != type(()):
+ response = (response,)
+ except:
+ # report exception back to server
+ response = xmlrpclib.dumps (
+ xmlrpclib.Fault (1, "%s:%s" % (sys.exc_type, sys.exc_value))
+ )
+ else:
+ response = xmlrpclib.dumps (response, methodresponse=1)
+ except:
+ # internal error, report as HTTP server error
+ request.error (500)
+ else:
+ # got a valid XML RPC response
+ request['Content-Type'] = 'text/xml'
+ request.push (response)
+ request.done()
+
+ def call (self, method, params):
+ # override this method to implement RPC methods
+ raise "NotYetImplemented"
+
+class collector:
+
+ "gathers input for POST and PUT requests"
+
+ def __init__ (self, handler, request):
+
+ self.handler = handler
+ self.request = request
+ self.data = ''
+
+ # make sure there's a content-length header
+ cl = request.get_header ('content-length')
+
+ if not cl:
+ request.error (411)
+ else:
+ cl = string.atoi (cl)
+ # using a 'numeric' terminator
+ self.request.channel.set_terminator (cl)
+
+ def collect_incoming_data (self, data):
+ self.data = self.data + data
+
+ def found_terminator (self):
+ # set the terminator back to the default
+ self.request.channel.set_terminator ('\r\n\r\n')
+ self.handler.continue_request (self.data, self.request)
+
+if __name__ == '__main__':
+
+ class rpc_demo (xmlrpc_handler):
+
+ def call (self, method, params):
+ print 'method="%s" params=%s' % (method, params)
+ return "Sure, that works"
+
+ import asyncore
+ import http_server
+
+ hs = http_server.http_server ('', 8000)
+ rpc = rpc_demo()
+ hs.install_handler (rpc)
+
+ asyncore.loop()
diff --git a/demo/medusa054/00_README b/demo/medusa054/00_README
new file mode 100644
index 0000000..5701364
--- /dev/null
+++ b/demo/medusa054/00_README
@@ -0,0 +1,30 @@
+
+ 21 Mar 2004
+-------------
+
+M2Crypto HTTPS and FTP/TLS servers
+
+All the files in this directory are from Medusa 0.54, except for the
+following:
+
+- 00_README (this file)
+- server.pem, the server's certificate
+- ca.pem, my CA certificate
+- https_server.py
+- ftps_server.py
+- START.py
+- START_xmlrpc.py
+- index.html, a sample HTML file
+- poison_handler.py, a webpoison clone
+
+By default, http_server listens on port 39080 and https_server port
+39443. Document root is current directory, and serves up index.html.
+
+The xmlrpc server is accessible below '/RPC2'.
+
+The FTP/TLS server listens on port 39021 by default. I've only tested it with
+the 'anonymous' authentication handler.
+
+Medusa files are copyright Sam Rushing. Recent versions are maintained
+by Andrew Kuchling. My files are copyright me.
+
diff --git a/demo/medusa054/START.py b/demo/medusa054/START.py
new file mode 100644
index 0000000..fb08ab3
--- /dev/null
+++ b/demo/medusa054/START.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+# Standard Python library
+import os
+import os.path
+import sys
+
+# Medusa
+import asyncore
+import default_handler
+import filesys
+import ftp_server
+import http_server
+import status_handler
+
+# M2Crypto
+import https_server
+import poison_handler
+import ftps_server
+from M2Crypto import Rand, SSL, threading
+
+HTTP_PORT=39080
+HTTPS_PORT=39443
+FTP_PORT = 39021
+
+hs=http_server.http_server('', HTTP_PORT)
+
+Rand.load_file('../randpool.dat', -1)
+ssl_ctx=SSL.Context('sslv23')
+ssl_ctx.load_cert('server.pem')
+ssl_ctx.load_verify_locations('ca.pem', '')
+ssl_ctx.load_client_CA('ca.pem')
+#ssl_ctx.set_verify(SSL.verify_peer, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_fail_if_no_peer_cert, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_client_once, 10)
+ssl_ctx.set_verify(SSL.verify_none, 10)
+ssl_ctx.set_session_id_ctx('127.0.0.1:39443')
+ssl_ctx.set_tmp_dh('dh1024.pem')
+ssl_ctx.set_info_callback()
+
+hss=https_server.https_server('', HTTPS_PORT, ssl_ctx)
+
+fs=filesys.os_filesystem(os.path.abspath(os.curdir))
+#fs=filesys.os_filesystem('/usr/local/pkg/apache/htdocs')
+#fs=filesys.os_filesystem('c:/pkg/jdk130/docs')
+dh=default_handler.default_handler(fs)
+hs.install_handler(dh)
+hss.install_handler(dh)
+
+#class rpc_demo (xmlrpc_handler.xmlrpc_handler):
+# def call (self, method, params):
+# print 'method="%s" params=%s' % (method, params)
+# return "Sure, that works"
+#rpch = rpc_demo()
+#hs.install_handler(rpch)
+#hss.install_handler(rpch)
+
+ph=poison_handler.poison_handler(10)
+hs.install_handler(ph)
+hss.install_handler(ph)
+
+fauthz = ftp_server.anon_authorizer('/usr/local/pkg/apache/htdocs')
+ftps = ftps_server.ftp_tls_server(fauthz, ssl_ctx, port=FTP_PORT)
+
+sh=status_handler.status_extension([hs, hss, ftps])
+hs.install_handler(sh)
+hss.install_handler(sh)
+
+asyncore.loop()
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/medusa054/START_xmlrpc.py b/demo/medusa054/START_xmlrpc.py
new file mode 100644
index 0000000..65b6d67
--- /dev/null
+++ b/demo/medusa054/START_xmlrpc.py
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+
+# Standard Python library
+import os
+import os.path
+import sys
+
+# Medusa
+import asyncore
+import default_handler
+import filesys
+import http_server
+import status_handler
+
+# M2Crypto
+import https_server
+import poison_handler
+from M2Crypto import Rand, SSL
+
+# XMLrpc
+import xmlrpc_handler
+
+
+HTTP_PORT=39080
+HTTPS_PORT=39443
+
+hs=http_server.http_server('', HTTP_PORT)
+
+Rand.load_file('../randpool.dat', -1)
+ssl_ctx=SSL.Context('sslv23')
+ssl_ctx.load_cert('server.pem')
+#ssl_ctx.load_verify_location('ca.pem')
+#ssl_ctx.load_client_CA('ca.pem')
+#ssl_ctx.set_verify(SSL.verify_peer, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_fail_if_no_peer_cert, 10)
+#ssl_ctx.set_verify(SSL.verify_peer|SSL.verify_client_once, 10)
+ssl_ctx.set_verify(SSL.verify_none, 10)
+ssl_ctx.set_session_id_ctx('127.0.0.1:9443')
+ssl_ctx.set_tmp_dh('dh1024.pem')
+#ssl_ctx.set_info_callback()
+
+hss=https_server.https_server('', HTTPS_PORT, ssl_ctx)
+
+fs=filesys.os_filesystem(os.path.abspath(os.curdir))
+#fs=filesys.os_filesystem('/usr/local/pkg/apache/htdocs')
+#fs=filesys.os_filesystem('c:/pkg/jdk118/docs')
+dh=default_handler.default_handler(fs)
+hs.install_handler(dh)
+hss.install_handler(dh)
+
+# Cribbed from xmlrpc_handler.py.
+# This is where you implement your RPC functionality.
+class rpc_demo (xmlrpc_handler.xmlrpc_handler):
+ def call (self, method, params):
+ print 'method="%s" params=%s' % (method, params)
+ return "Sure, that works"
+
+rpch = rpc_demo()
+hs.install_handler(rpch)
+hss.install_handler(rpch)
+
+ph=poison_handler.poison_handler(10)
+hs.install_handler(ph)
+hss.install_handler(ph)
+
+sh=status_handler.status_extension([hss])
+hs.install_handler(sh)
+hss.install_handler(sh)
+
+asyncore.loop()
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/medusa054/ca.pem b/demo/medusa054/ca.pem
new file mode 100644
index 0000000..b7c84a1
--- /dev/null
+++ b/demo/medusa054/ca.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/medusa054/counter.py b/demo/medusa054/counter.py
new file mode 100644
index 0000000..9dae67c
--- /dev/null
+++ b/demo/medusa054/counter.py
@@ -0,0 +1,51 @@
+# -*- Mode: Python -*-
+
+# It is tempting to add an __int__ method to this class, but it's not
+# a good idea. This class tries to gracefully handle integer
+# overflow, and to hide this detail from both the programmer and the
+# user. Note that the __str__ method can be relied on for printing out
+# the value of a counter:
+#
+# >>> print 'Total Client: %s' % self.total_clients
+#
+# If you need to do arithmetic with the value, then use the 'as_long'
+# method, the use of long arithmetic is a reminder that the counter
+# will overflow.
+
+class counter:
+ "general-purpose counter"
+
+ def __init__ (self, initial_value=0):
+ self.value = initial_value
+
+ def increment (self, delta=1):
+ result = self.value
+ try:
+ self.value = self.value + delta
+ except OverflowError:
+ self.value = long(self.value) + delta
+ return result
+
+ def decrement (self, delta=1):
+ result = self.value
+ try:
+ self.value = self.value - delta
+ except OverflowError:
+ self.value = long(self.value) - delta
+ return result
+
+ def as_long (self):
+ return long(self.value)
+
+ def __nonzero__ (self):
+ return self.value != 0
+
+ def __repr__ (self):
+ return '<counter value=%s at %x>' % (self.value, id(self))
+
+ def __str__ (self):
+ s = str(long(self.value))
+ if s[-1:] == 'L':
+ s = s[:-1]
+ return s
+
diff --git a/demo/medusa054/default_handler.py b/demo/medusa054/default_handler.py
new file mode 100644
index 0000000..9d5e9d7
--- /dev/null
+++ b/demo/medusa054/default_handler.py
@@ -0,0 +1,213 @@
+# -*- Mode: Python -*-
+#
+# Author: Sam Rushing <rushing@nightmare.com>
+# Copyright 1997 by Sam Rushing
+# All Rights Reserved.
+#
+
+# standard python modules
+import mimetypes
+import re
+import stat
+import string
+
+# medusa modules
+import http_date
+import http_server
+import status_handler
+import producers
+
+unquote = http_server.unquote
+
+# This is the 'default' handler. it implements the base set of
+# features expected of a simple file-delivering HTTP server. file
+# services are provided through a 'filesystem' object, the very same
+# one used by the FTP server.
+#
+# You can replace or modify this handler if you want a non-standard
+# HTTP server. You can also derive your own handler classes from
+# it.
+#
+# support for handling POST requests is available in the derived
+# class <default_with_post_handler>, defined below.
+#
+
+from counter import counter
+
+class default_handler:
+
+ valid_commands = ['GET', 'HEAD']
+
+ IDENT = 'Default HTTP Request Handler'
+
+ # Pathnames that are tried when a URI resolves to a directory name
+ directory_defaults = [
+ 'index.html',
+ 'default.html'
+ ]
+
+ default_file_producer = producers.file_producer
+
+ def __init__ (self, filesystem):
+ self.filesystem = filesystem
+ # count total hits
+ self.hit_counter = counter()
+ # count file deliveries
+ self.file_counter = counter()
+ # count cache hits
+ self.cache_counter = counter()
+
+ hit_counter = 0
+
+ def __repr__ (self):
+ return '<%s (%s hits) at %x>' % (
+ self.IDENT,
+ self.hit_counter,
+ id (self)
+ )
+
+ # always match, since this is a default
+ def match (self, request):
+ return 1
+
+ # handle a file request, with caching.
+
+ def handle_request (self, request):
+
+ if request.command not in self.valid_commands:
+ request.error (400) # bad request
+ return
+
+ self.hit_counter.increment()
+
+ path, params, query, fragment = request.split_uri()
+
+ if '%' in path:
+ path = unquote (path)
+
+ # strip off all leading slashes
+ while path and path[0] == '/':
+ path = path[1:]
+
+ if self.filesystem.isdir (path):
+ if path and path[-1] != '/':
+ request['Location'] = 'http://%s/%s/' % (
+ request.channel.server.server_name,
+ path
+ )
+ request.error (301)
+ return
+
+ # we could also generate a directory listing here,
+ # may want to move this into another method for that
+ # purpose
+ found = 0
+ if path and path[-1] != '/':
+ path = path + '/'
+ for default in self.directory_defaults:
+ p = path + default
+ if self.filesystem.isfile (p):
+ path = p
+ found = 1
+ break
+ if not found:
+ request.error (404) # Not Found
+ return
+
+ elif not self.filesystem.isfile (path):
+ request.error (404) # Not Found
+ return
+
+ file_length = self.filesystem.stat (path)[stat.ST_SIZE]
+
+ ims = get_header_match (IF_MODIFIED_SINCE, request.header)
+
+ length_match = 1
+ if ims:
+ length = ims.group (4)
+ if length:
+ try:
+ length = string.atoi (length)
+ if length != file_length:
+ length_match = 0
+ except:
+ pass
+
+ ims_date = 0
+
+ if ims:
+ ims_date = http_date.parse_http_date (ims.group (1))
+
+ try:
+ mtime = self.filesystem.stat (path)[stat.ST_MTIME]
+ except:
+ request.error (404)
+ return
+
+ if length_match and ims_date:
+ if mtime <= ims_date:
+ request.reply_code = 304
+ request.done()
+ self.cache_counter.increment()
+ return
+ try:
+ file = self.filesystem.open (path, 'rb')
+ except IOError:
+ request.error (404)
+ return
+
+ request['Last-Modified'] = http_date.build_http_date (mtime)
+ request['Content-Length'] = file_length
+ self.set_content_type (path, request)
+
+ if request.command == 'GET':
+ request.push (self.default_file_producer (file))
+
+ self.file_counter.increment()
+ request.done()
+
+ def set_content_type (self, path, request):
+ ext = string.lower (get_extension (path))
+ typ, encoding = mimetypes.guess_type(path)
+ if typ is not None:
+ request['Content-Type'] = typ
+ else:
+ # TODO: test a chunk off the front of the file for 8-bit
+ # characters, and use application/octet-stream instead.
+ request['Content-Type'] = 'text/plain'
+
+ def status (self):
+ return producers.simple_producer (
+ '<li>%s' % status_handler.html_repr (self)
+ + '<ul>'
+ + ' <li><b>Total Hits:</b> %s' % self.hit_counter
+ + ' <li><b>Files Delivered:</b> %s' % self.file_counter
+ + ' <li><b>Cache Hits:</b> %s' % self.cache_counter
+ + '</ul>'
+ )
+
+# HTTP/1.0 doesn't say anything about the "; length=nnnn" addition
+# to this header. I suppose its purpose is to avoid the overhead
+# of parsing dates...
+IF_MODIFIED_SINCE = re.compile (
+ 'If-Modified-Since: ([^;]+)((; length=([0-9]+)$)|$)',
+ re.IGNORECASE
+ )
+
+USER_AGENT = re.compile ('User-Agent: (.*)', re.IGNORECASE)
+
+CONTENT_TYPE = re.compile (
+ r'Content-Type: ([^;]+)((; boundary=([A-Za-z0-9\'\(\)+_,./:=?-]+)$)|$)',
+ re.IGNORECASE
+ )
+
+get_header = http_server.get_header
+get_header_match = http_server.get_header_match
+
+def get_extension (path):
+ dirsep = string.rfind (path, '/')
+ dotsep = string.rfind (path, '.')
+ if dotsep > dirsep:
+ return path[dotsep+1:]
+ else:
+ return ''
diff --git a/demo/medusa054/dh1024.pem b/demo/medusa054/dh1024.pem
new file mode 100644
index 0000000..81d43f6
--- /dev/null
+++ b/demo/medusa054/dh1024.pem
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/medusa054/filesys.py b/demo/medusa054/filesys.py
new file mode 100644
index 0000000..1affd8f
--- /dev/null
+++ b/demo/medusa054/filesys.py
@@ -0,0 +1,394 @@
+# -*- Mode: Python -*-
+# $Id: filesys.py 299 2005-06-09 17:32:28Z heikki $
+# Author: Sam Rushing <rushing@nightmare.com>
+#
+# Generic filesystem interface.
+#
+
+# We want to provide a complete wrapper around any and all
+# filesystem operations.
+
+# this class is really just for documentation,
+# identifying the API for a filesystem object.
+
+# opening files for reading, and listing directories, should
+# return a producer.
+
+class abstract_filesystem:
+ def __init__ (self):
+ pass
+
+ def current_directory (self):
+ "Return a string representing the current directory."
+ pass
+
+ def listdir (self, path, long=0):
+ """Return a listing of the directory at 'path' The empty string
+ indicates the current directory. If 'long' is set, instead
+ return a list of (name, stat_info) tuples
+ """
+ pass
+
+ def open (self, path, mode):
+ "Return an open file object"
+ pass
+
+ def stat (self, path):
+ "Return the equivalent of os.stat() on the given path."
+ pass
+
+ def isdir (self, path):
+ "Does the path represent a directory?"
+ pass
+
+ def isfile (self, path):
+ "Does the path represent a plain file?"
+ pass
+
+ def cwd (self, path):
+ "Change the working directory."
+ pass
+
+ def cdup (self):
+ "Change to the parent of the current directory."
+ pass
+
+
+ def longify (self, path):
+ """Return a 'long' representation of the filename
+ [for the output of the LIST command]"""
+ pass
+
+# standard wrapper around a unix-like filesystem, with a 'false root'
+# capability.
+
+# security considerations: can symbolic links be used to 'escape' the
+# root? should we allow it? if not, then we could scan the
+# filesystem on startup, but that would not help if they were added
+# later. We will probably need to check for symlinks in the cwd method.
+
+# what to do if wd is an invalid directory?
+
+import os
+import stat
+import re
+import string
+
+def safe_stat (path):
+ try:
+ return (path, os.stat (path))
+ except:
+ return None
+
+import glob
+
+class os_filesystem:
+ path_module = os.path
+
+ # set this to zero if you want to disable pathname globbing.
+ # [we currently don't glob, anyway]
+ do_globbing = 1
+
+ def __init__ (self, root, wd='/'):
+ self.root = root
+ self.wd = wd
+
+ def current_directory (self):
+ return self.wd
+
+ def isfile (self, path):
+ p = self.normalize (self.path_module.join (self.wd, path))
+ return self.path_module.isfile (self.translate(p))
+
+ def isdir (self, path):
+ p = self.normalize (self.path_module.join (self.wd, path))
+ return self.path_module.isdir (self.translate(p))
+
+ def cwd (self, path):
+ p = self.normalize (self.path_module.join (self.wd, path))
+ translated_path = self.translate(p)
+ if not self.path_module.isdir (translated_path):
+ return 0
+ else:
+ old_dir = os.getcwd()
+ # temporarily change to that directory, in order
+ # to see if we have permission to do so.
+ try:
+ can = 0
+ try:
+ os.chdir (translated_path)
+ can = 1
+ self.wd = p
+ except:
+ pass
+ finally:
+ if can:
+ os.chdir (old_dir)
+ return can
+
+ def cdup (self):
+ return self.cwd ('..')
+
+ def listdir (self, path, long=0):
+ p = self.translate (path)
+ # I think we should glob, but limit it to the current
+ # directory only.
+ ld = os.listdir (p)
+ if not long:
+ return list_producer (ld, None)
+ else:
+ old_dir = os.getcwd()
+ try:
+ os.chdir (p)
+ # if os.stat fails we ignore that file.
+ result = filter (None, map (safe_stat, ld))
+ finally:
+ os.chdir (old_dir)
+ return list_producer (result, self.longify)
+
+ # TODO: implement a cache w/timeout for stat()
+ def stat (self, path):
+ p = self.translate (path)
+ return os.stat (p)
+
+ def open (self, path, mode):
+ p = self.translate (path)
+ return open (p, mode)
+
+ def unlink (self, path):
+ p = self.translate (path)
+ return os.unlink (p)
+
+ def mkdir (self, path):
+ p = self.translate (path)
+ return os.mkdir (p)
+
+ def rmdir (self, path):
+ p = self.translate (path)
+ return os.rmdir (p)
+
+ # utility methods
+ def normalize (self, path):
+ # watch for the ever-sneaky '/+' path element
+ path = re.sub('/+', '/', path)
+ p = self.path_module.normpath (path)
+ # remove 'dangling' cdup's.
+ if len(p) > 2 and p[:3] == '/..':
+ p = '/'
+ return p
+
+ def translate (self, path):
+ # we need to join together three separate
+ # path components, and do it safely.
+ # <real_root>/<current_directory>/<path>
+ # use the operating system's path separator.
+ path = string.join (string.split (path, '/'), os.sep)
+ p = self.normalize (self.path_module.join (self.wd, path))
+ p = self.normalize (self.path_module.join (self.root, p[1:]))
+ return p
+
+ def longify (self, (path, stat_info)):
+ return unix_longify (path, stat_info)
+
+ def __repr__ (self):
+ return '<unix-style fs root:%s wd:%s>' % (
+ self.root,
+ self.wd
+ )
+
+if os.name == 'posix':
+
+ class unix_filesystem (os_filesystem):
+ pass
+
+ class schizophrenic_unix_filesystem (os_filesystem):
+ PROCESS_UID = os.getuid()
+ PROCESS_EUID = os.geteuid()
+ PROCESS_GID = os.getgid()
+ PROCESS_EGID = os.getegid()
+
+ def __init__ (self, root, wd='/', persona=(None, None)):
+ os_filesystem.__init__ (self, root, wd)
+ self.persona = persona
+
+ def become_persona (self):
+ if self.persona is not (None, None):
+ uid, gid = self.persona
+ # the order of these is important!
+ os.setegid (gid)
+ os.seteuid (uid)
+
+ def become_nobody (self):
+ if self.persona is not (None, None):
+ os.seteuid (self.PROCESS_UID)
+ os.setegid (self.PROCESS_GID)
+
+ # cwd, cdup, open, listdir
+ def cwd (self, path):
+ try:
+ self.become_persona()
+ return os_filesystem.cwd (self, path)
+ finally:
+ self.become_nobody()
+
+ def cdup (self, path):
+ try:
+ self.become_persona()
+ return os_filesystem.cdup (self)
+ finally:
+ self.become_nobody()
+
+ def open (self, filename, mode):
+ try:
+ self.become_persona()
+ return os_filesystem.open (self, filename, mode)
+ finally:
+ self.become_nobody()
+
+ def listdir (self, path, long=0):
+ try:
+ self.become_persona()
+ return os_filesystem.listdir (self, path, long)
+ finally:
+ self.become_nobody()
+
+# For the 'real' root, we could obtain a list of drives, and then
+# use that. Doesn't win32 provide such a 'real' filesystem?
+# [yes, I think something like this "\\.\c\windows"]
+
+class msdos_filesystem (os_filesystem):
+ def longify (self, (path, stat_info)):
+ return msdos_longify (path, stat_info)
+
+# A merged filesystem will let you plug other filesystems together.
+# We really need the equivalent of a 'mount' capability - this seems
+# to be the most general idea. So you'd use a 'mount' method to place
+# another filesystem somewhere in the hierarchy.
+
+# Note: this is most likely how I will handle ~user directories
+# with the http server.
+
+class merged_filesystem:
+ def __init__ (self, *fsys):
+ pass
+
+# this matches the output of NT's ftp server (when in
+# MSDOS mode) exactly.
+
+def msdos_longify (file, stat_info):
+ if stat.S_ISDIR (stat_info[stat.ST_MODE]):
+ dir = '<DIR>'
+ else:
+ dir = ' '
+ date = msdos_date (stat_info[stat.ST_MTIME])
+ return '%s %s %8d %s' % (
+ date,
+ dir,
+ stat_info[stat.ST_SIZE],
+ file
+ )
+
+def msdos_date (t):
+ try:
+ info = time.gmtime (t)
+ except:
+ info = time.gmtime (0)
+ # year, month, day, hour, minute, second, ...
+ if info[3] > 11:
+ merid = 'PM'
+ info[3] = info[3] - 12
+ else:
+ merid = 'AM'
+ return '%02d-%02d-%02d %02d:%02d%s' % (
+ info[1],
+ info[2],
+ info[0]%100,
+ info[3],
+ info[4],
+ merid
+ )
+
+months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
+ 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
+
+mode_table = {
+ '0':'---',
+ '1':'--x',
+ '2':'-w-',
+ '3':'-wx',
+ '4':'r--',
+ '5':'r-x',
+ '6':'rw-',
+ '7':'rwx'
+ }
+
+import time
+
+def unix_longify (file, stat_info):
+ # for now, only pay attention to the lower bits
+ mode = ('%o' % stat_info[stat.ST_MODE])[-3:]
+ mode = string.join (map (lambda x: mode_table[x], mode), '')
+ if stat.S_ISDIR (stat_info[stat.ST_MODE]):
+ dirchar = 'd'
+ else:
+ dirchar = '-'
+ date = ls_date (long(time.time()), stat_info[stat.ST_MTIME])
+ return '%s%s %3d %-8d %-8d %8d %s %s' % (
+ dirchar,
+ mode,
+ stat_info[stat.ST_NLINK],
+ stat_info[stat.ST_UID],
+ stat_info[stat.ST_GID],
+ stat_info[stat.ST_SIZE],
+ date,
+ file
+ )
+
+# Emulate the unix 'ls' command's date field.
+# it has two formats - if the date is more than 180
+# days in the past, then it's like this:
+# Oct 19 1995
+# otherwise, it looks like this:
+# Oct 19 17:33
+
+def ls_date (now, t):
+ try:
+ info = time.gmtime (t)
+ except:
+ info = time.gmtime (0)
+ # 15,600,000 == 86,400 * 180
+ if (now - t) > 15600000:
+ return '%s %2d %d' % (
+ months[info[1]-1],
+ info[2],
+ info[0]
+ )
+ else:
+ return '%s %2d %02d:%02d' % (
+ months[info[1]-1],
+ info[2],
+ info[3],
+ info[4]
+ )
+
+# ===========================================================================
+# Producers
+# ===========================================================================
+
+class list_producer:
+ def __init__ (self, list, func=None):
+ self.list = list
+ self.func = func
+
+ # this should do a pushd/popd
+ def more (self):
+ if not self.list:
+ return ''
+ else:
+ # do a few at a time
+ bunch = self.list[:50]
+ if self.func is not None:
+ bunch = map (self.func, bunch)
+ self.list = self.list[50:]
+ return string.joinfields (bunch, '\r\n') + '\r\n'
+
diff --git a/demo/medusa054/ftp_server.py b/demo/medusa054/ftp_server.py
new file mode 100644
index 0000000..859cd65
--- /dev/null
+++ b/demo/medusa054/ftp_server.py
@@ -0,0 +1,1111 @@
+# -*- Mode: Python -*-
+
+# Author: Sam Rushing <rushing@nightmare.com>
+# Copyright 1996-2000 by Sam Rushing
+# All Rights Reserved.
+#
+
+# An extensible, configurable, asynchronous FTP server.
+#
+# All socket I/O is non-blocking, however file I/O is currently
+# blocking. Eventually file I/O may be made non-blocking, too, if it
+# seems necessary. Currently the only CPU-intensive operation is
+# getting and formatting a directory listing. [this could be moved
+# into another process/directory server, or another thread?]
+#
+# Only a subset of RFC 959 is implemented, but much of that RFC is
+# vestigial anyway. I've attempted to include the most commonly-used
+# commands, using the feature set of wu-ftpd as a guide.
+
+import asyncore
+import asynchat
+
+import os
+import socket
+import stat
+import string
+import sys
+import time
+
+#from medusa.producers import file_producer
+from producers import file_producer
+
+# TODO: implement a directory listing cache. On very-high-load
+# servers this could save a lot of disk abuse, and possibly the
+# work of computing emulated unix ls output.
+
+# Potential security problem with the FTP protocol? I don't think
+# there's any verification of the origin of a data connection. Not
+# really a problem for the server (since it doesn't send the port
+# command, except when in PASV mode) But I think a data connection
+# could be spoofed by a program with access to a sniffer - it could
+# watch for a PORT command to go over a command channel, and then
+# connect to that port before the server does.
+
+# Unix user id's:
+# In order to support assuming the id of a particular user,
+# it seems there are two options:
+# 1) fork, and seteuid in the child
+# 2) carefully control the effective uid around filesystem accessing
+# methods, using try/finally. [this seems to work]
+
+VERSION = '1.1'
+
+from counter import counter
+import producers
+import status_handler
+import logger
+
+class ftp_channel (asynchat.async_chat):
+
+ # defaults for a reliable __repr__
+ addr = ('unknown','0')
+
+ # unset this in a derived class in order
+ # to enable the commands in 'self.write_commands'
+ read_only = 1
+ write_commands = ['appe','dele','mkd','rmd','rnfr','rnto','stor','stou']
+
+ restart_position = 0
+
+ # comply with (possibly troublesome) RFC959 requirements
+ # This is necessary to correctly run an active data connection
+ # through a firewall that triggers on the source port (expected
+ # to be 'L-1', or 20 in the normal case).
+ bind_local_minus_one = 0
+
+ def __init__ (self, server, conn, addr):
+ self.server = server
+ self.current_mode = 'a'
+ self.addr = addr
+ asynchat.async_chat.__init__ (self, conn)
+ self.set_terminator ('\r\n')
+
+ # client data port. Defaults to 'the same as the control connection'.
+ self.client_addr = (addr[0], 21)
+
+ self.client_dc = None
+ self.in_buffer = ''
+ self.closing = 0
+ self.passive_acceptor = None
+ self.passive_connection = None
+ self.filesystem = None
+ self.authorized = 0
+ # send the greeting
+ self.respond (
+ '220 %s FTP server (Medusa Async V%s [experimental]) ready.' % (
+ self.server.hostname,
+ VERSION
+ )
+ )
+
+# def __del__ (self):
+# print 'ftp_channel.__del__()'
+
+ # --------------------------------------------------
+ # async-library methods
+ # --------------------------------------------------
+
+ def handle_expt (self):
+ # this is handled below. not sure what I could
+ # do here to make that code less kludgish.
+ pass
+
+ def collect_incoming_data (self, data):
+ self.in_buffer = self.in_buffer + data
+ if len(self.in_buffer) > 4096:
+ # silently truncate really long lines
+ # (possible denial-of-service attack)
+ self.in_buffer = ''
+
+ def found_terminator (self):
+
+ line = self.in_buffer
+
+ if not len(line):
+ return
+
+ sp = string.find (line, ' ')
+ if sp != -1:
+ line = [line[:sp], line[sp+1:]]
+ else:
+ line = [line]
+
+ command = string.lower (line[0])
+ # watch especially for 'urgent' abort commands.
+ if string.find (command, 'abor') != -1:
+ # strip off telnet sync chars and the like...
+ while command and command[0] not in string.letters:
+ command = command[1:]
+ fun_name = 'cmd_%s' % command
+ if command != 'pass':
+ self.log ('<== %s' % repr(self.in_buffer)[1:-1])
+ else:
+ self.log ('<== %s' % line[0]+' <password>')
+ self.in_buffer = ''
+ if not hasattr (self, fun_name):
+ self.command_not_understood (line[0])
+ return
+ fun = getattr (self, fun_name)
+ if (not self.authorized) and (command not in ('user', 'pass', 'help', 'quit')):
+ self.respond ('530 Please log in with USER and PASS')
+ elif (not self.check_command_authorization (command)):
+ self.command_not_authorized (command)
+ else:
+ try:
+ result = apply (fun, (line,))
+ except:
+ self.server.total_exceptions.increment()
+ (file, fun, line), t,v, tbinfo = asyncore.compact_traceback()
+ if self.client_dc:
+ try:
+ self.client_dc.close()
+ except:
+ pass
+ self.respond (
+ '451 Server Error: %s, %s: file: %s line: %s' % (
+ t,v,file,line,
+ )
+ )
+
+ closed = 0
+ def close (self):
+ if not self.closed:
+ self.closed = 1
+ if self.passive_acceptor:
+ self.passive_acceptor.close()
+ if self.client_dc:
+ self.client_dc.close()
+ self.server.closed_sessions.increment()
+ asynchat.async_chat.close (self)
+
+ # --------------------------------------------------
+ # filesystem interface functions.
+ # override these to provide access control or perform
+ # other functions.
+ # --------------------------------------------------
+
+ def cwd (self, line):
+ return self.filesystem.cwd (line[1])
+
+ def cdup (self, line):
+ return self.filesystem.cdup()
+
+ def open (self, path, mode):
+ return self.filesystem.open (path, mode)
+
+ # returns a producer
+ def listdir (self, path, long=0):
+ return self.filesystem.listdir (path, long)
+
+ def get_dir_list (self, line, long=0):
+ # we need to scan the command line for arguments to '/bin/ls'...
+ args = line[1:]
+ path_args = []
+ for arg in args:
+ if arg[0] != '-':
+ path_args.append (arg)
+ else:
+ # ignore arguments
+ pass
+ if len(path_args) < 1:
+ dir = '.'
+ else:
+ dir = path_args[0]
+ return self.listdir (dir, long)
+
+ # --------------------------------------------------
+ # authorization methods
+ # --------------------------------------------------
+
+ def check_command_authorization (self, command):
+ if command in self.write_commands and self.read_only:
+ return 0
+ else:
+ return 1
+
+ # --------------------------------------------------
+ # utility methods
+ # --------------------------------------------------
+
+ def log (self, message):
+ self.server.logger.log (
+ self.addr[0],
+ '%d %s' % (
+ self.addr[1], message
+ )
+ )
+
+ def respond (self, resp):
+ self.log ('==> %s' % resp)
+ self.push (resp + '\r\n')
+
+ def command_not_understood (self, command):
+ self.respond ("500 '%s': command not understood." % command)
+
+ def command_not_authorized (self, command):
+ self.respond (
+ "530 You are not authorized to perform the '%s' command" % (
+ command
+ )
+ )
+
+ def make_xmit_channel (self):
+ # In PASV mode, the connection may or may _not_ have been made
+ # yet. [although in most cases it is... FTP Explorer being
+ # the only exception I've yet seen]. This gets somewhat confusing
+ # because things may happen in any order...
+ pa = self.passive_acceptor
+ if pa:
+ if pa.ready:
+ # a connection has already been made.
+ conn, addr = self.passive_acceptor.ready
+ cdc = xmit_channel (self, addr)
+ cdc.set_socket (conn)
+ cdc.connected = 1
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ else:
+ # we're still waiting for a connect to the PASV port.
+ cdc = xmit_channel (self)
+ else:
+ # not in PASV mode.
+ ip, port = self.client_addr
+ cdc = xmit_channel (self, self.client_addr)
+ cdc.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+ if self.bind_local_minus_one:
+ cdc.bind (('', self.server.port - 1))
+ try:
+ cdc.connect ((ip, port))
+ except socket.error, why:
+ self.respond ("425 Can't build data connection")
+ self.client_dc = cdc
+
+ # pretty much the same as xmit, but only right on the verge of
+ # being worth a merge.
+ def make_recv_channel (self, fd):
+ pa = self.passive_acceptor
+ if pa:
+ if pa.ready:
+ # a connection has already been made.
+ conn, addr = pa.ready
+ cdc = recv_channel (self, addr, fd)
+ cdc.set_socket (conn)
+ cdc.connected = 1
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ else:
+ # we're still waiting for a connect to the PASV port.
+ cdc = recv_channel (self, None, fd)
+ else:
+ # not in PASV mode.
+ ip, port = self.client_addr
+ cdc = recv_channel (self, self.client_addr, fd)
+ cdc.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ cdc.connect ((ip, port))
+ except socket.error, why:
+ self.respond ("425 Can't build data connection")
+ self.client_dc = cdc
+
+ type_map = {
+ 'a':'ASCII',
+ 'i':'Binary',
+ 'e':'EBCDIC',
+ 'l':'Binary'
+ }
+
+ type_mode_map = {
+ 'a':'t',
+ 'i':'b',
+ 'e':'b',
+ 'l':'b'
+ }
+
+ # --------------------------------------------------
+ # command methods
+ # --------------------------------------------------
+
+ def cmd_type (self, line):
+ 'specify data transfer type'
+ # ascii, ebcdic, image, local <byte size>
+ t = string.lower (line[1])
+ # no support for EBCDIC
+ # if t not in ['a','e','i','l']:
+ if t not in ['a','i','l']:
+ self.command_not_understood (string.join (line))
+ elif t == 'l' and (len(line) > 2 and line[2] != '8'):
+ self.respond ('504 Byte size must be 8')
+ else:
+ self.current_mode = t
+ self.respond ('200 Type set to %s.' % self.type_map[t])
+
+
+ def cmd_quit (self, line):
+ 'terminate session'
+ self.respond ('221 Goodbye.')
+ self.close_when_done()
+
+ def cmd_port (self, line):
+ 'specify data connection port'
+ info = string.split (line[1], ',')
+ ip = string.join (info[:4], '.')
+ port = string.atoi(info[4])*256 + string.atoi(info[5])
+ # how many data connections at a time?
+ # I'm assuming one for now...
+ # TODO: we should (optionally) verify that the
+ # ip number belongs to the client. [wu-ftpd does this?]
+ self.client_addr = (ip, port)
+ self.respond ('200 PORT command successful.')
+
+ def new_passive_acceptor (self):
+ # ensure that only one of these exists at a time.
+ if self.passive_acceptor is not None:
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ self.passive_acceptor = passive_acceptor (self)
+ return self.passive_acceptor
+
+ def cmd_pasv (self, line):
+ 'prepare for server-to-server transfer'
+ pc = self.new_passive_acceptor()
+ port = pc.addr[1]
+ ip_addr = pc.control_channel.getsockname()[0]
+ self.respond (
+ '227 Entering Passive Mode (%s,%d,%d)' % (
+ string.replace(ip_addr, '.', ','),
+ port/256,
+ port%256
+ )
+ )
+ self.client_dc = None
+
+ def cmd_nlst (self, line):
+ 'give name list of files in directory'
+ # ncftp adds the -FC argument for the user-visible 'nlist'
+ # command. We could try to emulate ls flags, but not just yet.
+ if '-FC' in line:
+ line.remove ('-FC')
+ try:
+ dir_list_producer = self.get_dir_list (line, 0)
+ except os.error, why:
+ self.respond ('550 Could not list directory: %s' % why)
+ return
+ self.respond (
+ '150 Opening %s mode data connection for file list' % (
+ self.type_map[self.current_mode]
+ )
+ )
+ self.make_xmit_channel()
+ self.client_dc.push_with_producer (dir_list_producer)
+ self.client_dc.close_when_done()
+
+ def cmd_list (self, line):
+ 'give a list of files in a directory'
+ try:
+ dir_list_producer = self.get_dir_list (line, 1)
+ except os.error, why:
+ self.respond ('550 Could not list directory: %s' % why)
+ return
+ self.respond (
+ '150 Opening %s mode data connection for file list' % (
+ self.type_map[self.current_mode]
+ )
+ )
+ self.make_xmit_channel()
+ self.client_dc.push_with_producer (dir_list_producer)
+ self.client_dc.close_when_done()
+
+ def cmd_cwd (self, line):
+ 'change working directory'
+ if self.cwd (line):
+ self.respond ('250 CWD command successful.')
+ else:
+ self.respond ('550 No such directory.')
+
+ def cmd_cdup (self, line):
+ 'change to parent of current working directory'
+ if self.cdup(line):
+ self.respond ('250 CDUP command successful.')
+ else:
+ self.respond ('550 No such directory.')
+
+ def cmd_pwd (self, line):
+ 'print the current working directory'
+ self.respond (
+ '257 "%s" is the current directory.' % (
+ self.filesystem.current_directory()
+ )
+ )
+
+ # modification time
+ # example output:
+ # 213 19960301204320
+ def cmd_mdtm (self, line):
+ 'show last modification time of file'
+ filename = line[1]
+ if not self.filesystem.isfile (filename):
+ self.respond ('550 "%s" is not a file' % filename)
+ else:
+ mtime = time.gmtime(self.filesystem.stat(filename)[stat.ST_MTIME])
+ self.respond (
+ '213 %4d%02d%02d%02d%02d%02d' % (
+ mtime[0],
+ mtime[1],
+ mtime[2],
+ mtime[3],
+ mtime[4],
+ mtime[5]
+ )
+ )
+
+ def cmd_noop (self, line):
+ 'do nothing'
+ self.respond ('200 NOOP command successful.')
+
+ def cmd_size (self, line):
+ 'return size of file'
+ filename = line[1]
+ if not self.filesystem.isfile (filename):
+ self.respond ('550 "%s" is not a file' % filename)
+ else:
+ self.respond (
+ '213 %d' % (self.filesystem.stat(filename)[stat.ST_SIZE])
+ )
+
+ def cmd_retr (self, line):
+ 'retrieve a file'
+ if len(line) < 2:
+ self.command_not_understood (string.join (line))
+ else:
+ file = line[1]
+ if not self.filesystem.isfile (file):
+ self.log_info ('checking %s' % file)
+ self.respond ('550 No such file')
+ else:
+ try:
+ # FIXME: for some reason, 'rt' isn't working on win95
+ mode = 'r'+self.type_mode_map[self.current_mode]
+ fd = self.open (file, mode)
+ except IOError, why:
+ self.respond ('553 could not open file for reading: %s' % (repr(why)))
+ return
+ self.respond (
+ "150 Opening %s mode data connection for file '%s'" % (
+ self.type_map[self.current_mode],
+ file
+ )
+ )
+ self.make_xmit_channel()
+
+ if self.restart_position:
+ # try to position the file as requested, but
+ # give up silently on failure (the 'file object'
+ # may not support seek())
+ try:
+ fd.seek (self.restart_position)
+ except:
+ pass
+ self.restart_position = 0
+
+ self.client_dc.push_with_producer (
+ file_producer (fd)
+ )
+ self.client_dc.close_when_done()
+
+ def cmd_stor (self, line, mode='wb'):
+ 'store a file'
+ if len (line) < 2:
+ self.command_not_understood (string.join (line))
+ else:
+ if self.restart_position:
+ restart_position = 0
+ self.respond ('553 restart on STOR not yet supported')
+ return
+ file = line[1]
+ # todo: handle that type flag
+ try:
+ fd = self.open (file, mode)
+ except IOError, why:
+ self.respond ('553 could not open file for writing: %s' % (repr(why)))
+ return
+ self.respond (
+ '150 Opening %s connection for %s' % (
+ self.type_map[self.current_mode],
+ file
+ )
+ )
+ self.make_recv_channel (fd)
+
+ def cmd_abor (self, line):
+ 'abort operation'
+ if self.client_dc:
+ self.client_dc.close()
+ self.respond ('226 ABOR command successful.')
+
+ def cmd_appe (self, line):
+ 'append to a file'
+ return self.cmd_stor (line, 'ab')
+
+ def cmd_dele (self, line):
+ if len (line) != 2:
+ self.command_not_understood (string.join (line))
+ else:
+ file = line[1]
+ if self.filesystem.isfile (file):
+ try:
+ self.filesystem.unlink (file)
+ self.respond ('250 DELE command successful.')
+ except:
+ self.respond ('550 error deleting file.')
+ else:
+ self.respond ('550 %s: No such file.' % file)
+
+ def cmd_mkd (self, line):
+ if len (line) != 2:
+ self.command_not_understood (string.join (line))
+ else:
+ path = line[1]
+ try:
+ self.filesystem.mkdir (path)
+ self.respond ('257 MKD command successful.')
+ except:
+ self.respond ('550 error creating directory.')
+
+ def cmd_rmd (self, line):
+ if len (line) != 2:
+ self.command_not_understood (string.join (line))
+ else:
+ path = line[1]
+ try:
+ self.filesystem.rmdir (path)
+ self.respond ('250 RMD command successful.')
+ except:
+ self.respond ('550 error removing directory.')
+
+ def cmd_user (self, line):
+ 'specify user name'
+ if len(line) > 1:
+ self.user = line[1]
+ self.respond ('331 Password required.')
+ else:
+ self.command_not_understood (string.join (line))
+
+ def cmd_pass (self, line):
+ 'specify password'
+ if len(line) < 2:
+ pw = ''
+ else:
+ pw = line[1]
+ result, message, fs = self.server.authorizer.authorize (self, self.user, pw)
+ if result:
+ self.respond ('230 %s' % message)
+ self.filesystem = fs
+ self.authorized = 1
+ self.log_info('Successful login: Filesystem=%s' % repr(fs))
+ else:
+ self.respond ('530 %s' % message)
+
+ def cmd_rest (self, line):
+ 'restart incomplete transfer'
+ try:
+ pos = string.atoi (line[1])
+ except ValueError:
+ self.command_not_understood (string.join (line))
+ self.restart_position = pos
+ self.respond (
+ '350 Restarting at %d. Send STORE or RETRIEVE to initiate transfer.' % pos
+ )
+
+ def cmd_stru (self, line):
+ 'obsolete - set file transfer structure'
+ if line[1] in 'fF':
+ # f == 'file'
+ self.respond ('200 STRU F Ok')
+ else:
+ self.respond ('504 Unimplemented STRU type')
+
+ def cmd_mode (self, line):
+ 'obsolete - set file transfer mode'
+ if line[1] in 'sS':
+ # f == 'file'
+ self.respond ('200 MODE S Ok')
+ else:
+ self.respond ('502 Unimplemented MODE type')
+
+# The stat command has two personalities. Normally it returns status
+# information about the current connection. But if given an argument,
+# it is equivalent to the LIST command, with the data sent over the
+# control connection. Strange. But wuftpd, ftpd, and nt's ftp server
+# all support it.
+#
+## def cmd_stat (self, line):
+## 'return status of server'
+## pass
+
+ def cmd_syst (self, line):
+ 'show operating system type of server system'
+ # Replying to this command is of questionable utility, because
+ # this server does not behave in a predictable way w.r.t. the
+ # output of the LIST command. We emulate Unix ls output, but
+ # on win32 the pathname can contain drive information at the front
+ # Currently, the combination of ensuring that os.sep == '/'
+ # and removing the leading slash when necessary seems to work.
+ # [cd'ing to another drive also works]
+ #
+ # This is how wuftpd responds, and is probably
+ # the most expected. The main purpose of this reply is so that
+ # the client knows to expect Unix ls-style LIST output.
+ self.respond ('215 UNIX Type: L8')
+ # one disadvantage to this is that some client programs
+ # assume they can pass args to /bin/ls.
+ # a few typical responses:
+ # 215 UNIX Type: L8 (wuftpd)
+ # 215 Windows_NT version 3.51
+ # 215 VMS MultiNet V3.3
+ # 500 'SYST': command not understood. (SVR4)
+
+ def cmd_help (self, line):
+ 'give help information'
+ # find all the methods that match 'cmd_xxxx',
+ # use their docstrings for the help response.
+ attrs = dir(self.__class__)
+ help_lines = []
+ for attr in attrs:
+ if attr[:4] == 'cmd_':
+ x = getattr (self, attr)
+ if type(x) == type(self.cmd_help):
+ if x.__doc__:
+ help_lines.append ('\t%s\t%s' % (attr[4:], x.__doc__))
+ if help_lines:
+ self.push ('214-The following commands are recognized\r\n')
+ self.push_with_producer (producers.lines_producer (help_lines))
+ self.push ('214\r\n')
+ else:
+ self.push ('214-\r\n\tHelp Unavailable\r\n214\r\n')
+
+class ftp_server (asyncore.dispatcher):
+ # override this to spawn a different FTP channel class.
+ ftp_channel_class = ftp_channel
+
+ SERVER_IDENT = 'FTP Server (V%s)' % VERSION
+
+ def __init__ (
+ self,
+ authorizer,
+ hostname =None,
+ ip ='',
+ port =21,
+ resolver =None,
+ logger_object=logger.file_logger (sys.stdout)
+ ):
+ self.ip = ip
+ self.port = port
+ self.authorizer = authorizer
+
+ if hostname is None:
+ self.hostname = socket.gethostname()
+ else:
+ self.hostname = hostname
+
+ # statistics
+ self.total_sessions = counter()
+ self.closed_sessions = counter()
+ self.total_files_out = counter()
+ self.total_files_in = counter()
+ self.total_bytes_out = counter()
+ self.total_bytes_in = counter()
+ self.total_exceptions = counter()
+ #
+ asyncore.dispatcher.__init__ (self)
+ self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+ self.set_reuse_addr()
+ self.bind ((self.ip, self.port))
+ self.listen (5)
+
+ if not logger_object:
+ logger_object = sys.stdout
+
+ if resolver:
+ self.logger = logger.resolving_logger (resolver, logger_object)
+ else:
+ self.logger = logger.unresolving_logger (logger_object)
+
+ self.log_info('FTP server started at %s\n\tAuthorizer:%s\n\tHostname: %s\n\tPort: %d' % (
+ time.ctime(time.time()),
+ repr (self.authorizer),
+ self.hostname,
+ self.port)
+ )
+
+ def writable (self):
+ return 0
+
+ def handle_read (self):
+ pass
+
+ def handle_connect (self):
+ pass
+
+ def handle_accept (self):
+ conn, addr = self.accept()
+ self.total_sessions.increment()
+ self.log_info('Incoming connection from %s:%d' % (addr[0], addr[1]))
+ self.ftp_channel_class (self, conn, addr)
+
+ # return a producer describing the state of the server
+ def status (self):
+
+ def nice_bytes (n):
+ return string.join (status_handler.english_bytes (n))
+
+ return producers.lines_producer (
+ ['<h2>%s</h2>' % self.SERVER_IDENT,
+ '<br>Listening on <b>Host:</b> %s' % self.hostname,
+ '<b>Port:</b> %d' % self.port,
+ '<br>Sessions',
+ '<b>Total:</b> %s' % self.total_sessions,
+ '<b>Current:</b> %d' % (self.total_sessions.as_long() - self.closed_sessions.as_long()),
+ '<br>Files',
+ '<b>Sent:</b> %s' % self.total_files_out,
+ '<b>Received:</b> %s' % self.total_files_in,
+ '<br>Bytes',
+ '<b>Sent:</b> %s' % nice_bytes (self.total_bytes_out.as_long()),
+ '<b>Received:</b> %s' % nice_bytes (self.total_bytes_in.as_long()),
+ '<br>Exceptions: %s' % self.total_exceptions,
+ ]
+ )
+
+# ======================================================================
+# Data Channel Classes
+# ======================================================================
+
+# This socket accepts a data connection, used when the server has been
+# placed in passive mode. Although the RFC implies that we ought to
+# be able to use the same acceptor over and over again, this presents
+# a problem: how do we shut it off, so that we are accepting
+# connections only when we expect them? [we can't]
+#
+# wuftpd, and probably all the other servers, solve this by allowing
+# only one connection to hit this acceptor. They then close it. Any
+# subsequent data-connection command will then try for the default
+# port on the client side [which is of course never there]. So the
+# 'always-send-PORT/PASV' behavior seems required.
+#
+# Another note: wuftpd will also be listening on the channel as soon
+# as the PASV command is sent. It does not wait for a data command
+# first.
+
+# --- we need to queue up a particular behavior:
+# 1) xmit : queue up producer[s]
+# 2) recv : the file object
+#
+# It would be nice if we could make both channels the same. Hmmm..
+#
+
+class passive_acceptor (asyncore.dispatcher):
+ ready = None
+
+ def __init__ (self, control_channel):
+ # connect_fun (conn, addr)
+ asyncore.dispatcher.__init__ (self)
+ self.control_channel = control_channel
+ self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+ # bind to an address on the interface that the
+ # control connection is coming from.
+ self.bind ((
+ self.control_channel.getsockname()[0],
+ 0
+ ))
+ self.addr = self.getsockname()
+ self.listen (1)
+
+# def __del__ (self):
+# print 'passive_acceptor.__del__()'
+
+ def log (self, *ignore):
+ pass
+
+ def handle_accept (self):
+ conn, addr = self.accept()
+ dc = self.control_channel.client_dc
+ if dc is not None:
+ dc.set_socket (conn)
+ dc.addr = addr
+ dc.connected = 1
+ self.control_channel.passive_acceptor = None
+ else:
+ self.ready = conn, addr
+ self.close()
+
+
+class xmit_channel (asynchat.async_chat):
+
+ # for an ethernet, you want this to be fairly large, in fact, it
+ # _must_ be large for performance comparable to an ftpd. [64k] we
+ # ought to investigate automatically-sized buffers...
+
+ ac_out_buffer_size = 16384
+ bytes_out = 0
+
+ def __init__ (self, channel, client_addr=None):
+ self.channel = channel
+ self.client_addr = client_addr
+ asynchat.async_chat.__init__ (self)
+
+# def __del__ (self):
+# print 'xmit_channel.__del__()'
+
+ def log (self, *args):
+ pass
+
+ def readable (self):
+ return not self.connected
+
+ def writable (self):
+ return 1
+
+ def send (self, data):
+ result = asynchat.async_chat.send (self, data)
+ self.bytes_out = self.bytes_out + result
+ return result
+
+ def handle_error (self):
+ # usually this is to catch an unexpected disconnect.
+ self.log_info ('unexpected disconnect on data xmit channel', 'error')
+ try:
+ self.close()
+ except:
+ pass
+
+ # TODO: there's a better way to do this. we need to be able to
+ # put 'events' in the producer fifo. to do this cleanly we need
+ # to reposition the 'producer' fifo as an 'event' fifo.
+
+ def close (self):
+ c = self.channel
+ s = c.server
+ c.client_dc = None
+ s.total_files_out.increment()
+ s.total_bytes_out.increment (self.bytes_out)
+ if not len(self.producer_fifo):
+ c.respond ('226 Transfer complete')
+ elif not c.closed:
+ c.respond ('426 Connection closed; transfer aborted')
+ del c
+ del s
+ del self.channel
+ asynchat.async_chat.close (self)
+
+class recv_channel (asyncore.dispatcher):
+ def __init__ (self, channel, client_addr, fd):
+ self.channel = channel
+ self.client_addr = client_addr
+ self.fd = fd
+ asyncore.dispatcher.__init__ (self)
+ self.bytes_in = counter()
+
+ def log (self, *ignore):
+ pass
+
+ def handle_connect (self):
+ pass
+
+ def writable (self):
+ return 0
+
+ def recv (*args):
+ result = apply (asyncore.dispatcher.recv, args)
+ self = args[0]
+ self.bytes_in.increment(len(result))
+ return result
+
+ buffer_size = 8192
+
+ def handle_read (self):
+ block = self.recv (self.buffer_size)
+ if block:
+ try:
+ self.fd.write (block)
+ except IOError:
+ self.log_info ('got exception writing block...', 'error')
+
+ def handle_close (self):
+ s = self.channel.server
+ s.total_files_in.increment()
+ s.total_bytes_in.increment(self.bytes_in.as_long())
+ self.fd.close()
+ self.channel.respond ('226 Transfer complete.')
+ self.close()
+
+import filesys
+
+# not much of a doorman! 8^)
+class dummy_authorizer:
+ def __init__ (self, root='/'):
+ self.root = root
+ def authorize (self, channel, username, password):
+ channel.persona = -1, -1
+ channel.read_only = 1
+ return 1, 'Ok.', filesys.os_filesystem (self.root)
+
+class anon_authorizer:
+ def __init__ (self, root='/'):
+ self.root = root
+
+ def authorize (self, channel, username, password):
+ if username in ('ftp', 'anonymous'):
+ channel.persona = -1, -1
+ channel.read_only = 1
+ return 1, 'Ok.', filesys.os_filesystem (self.root)
+ else:
+ return 0, 'Password invalid.', None
+
+# ===========================================================================
+# Unix-specific improvements
+# ===========================================================================
+
+if os.name == 'posix':
+
+ class unix_authorizer:
+ # return a trio of (success, reply_string, filesystem)
+ def authorize (self, channel, username, password):
+ import crypt
+ import pwd
+ try:
+ info = pwd.getpwnam (username)
+ except KeyError:
+ return 0, 'No such user.', None
+ mangled = info[1]
+ if crypt.crypt (password, mangled[:2]) == mangled:
+ channel.read_only = 0
+ fs = filesys.schizophrenic_unix_filesystem (
+ '/',
+ info[5],
+ persona = (info[2], info[3])
+ )
+ return 1, 'Login successful.', fs
+ else:
+ return 0, 'Password invalid.', None
+
+ def __repr__ (self):
+ return '<standard unix authorizer>'
+
+ # simple anonymous ftp support
+ class unix_authorizer_with_anonymous (unix_authorizer):
+ def __init__ (self, root=None, real_users=0):
+ self.root = root
+ self.real_users = real_users
+
+ def authorize (self, channel, username, password):
+ if string.lower(username) in ['anonymous', 'ftp']:
+ import pwd
+ try:
+ # ok, here we run into lots of confusion.
+ # on some os', anon runs under user 'nobody',
+ # on others as 'ftp'. ownership is also critical.
+ # need to investigate.
+ # linux: new linuxen seem to have nobody's UID=-1,
+ # which is an illegal value. Use ftp.
+ ftp_user_info = pwd.getpwnam ('ftp')
+ if string.lower(os.uname()[0]) == 'linux':
+ nobody_user_info = pwd.getpwnam ('ftp')
+ else:
+ nobody_user_info = pwd.getpwnam ('nobody')
+ channel.read_only = 1
+ if self.root is None:
+ self.root = ftp_user_info[5]
+ fs = filesys.unix_filesystem (self.root, '/')
+ return 1, 'Anonymous Login Successful', fs
+ except KeyError:
+ return 0, 'Anonymous account not set up', None
+ elif self.real_users:
+ return unix_authorizer.authorize (
+ self,
+ channel,
+ username,
+ password
+ )
+ else:
+ return 0, 'User logins not allowed', None
+
+# usage: ftp_server /PATH/TO/FTP/ROOT PORT
+# for example:
+# $ ftp_server /home/users/ftp 8021
+
+if os.name == 'posix':
+ def test (port='8021'):
+ fs = ftp_server (
+ unix_authorizer(),
+ port=string.atoi (port)
+ )
+ try:
+ asyncore.loop()
+ except KeyboardInterrupt:
+ fs.log_info('FTP server shutting down. (received SIGINT)', 'warning')
+ # close everything down on SIGINT.
+ # of course this should be a cleaner shutdown.
+ asyncore.close_all()
+
+ if __name__ == '__main__':
+ test (sys.argv[1])
+# not unix
+else:
+ def test ():
+ fs = ftp_server (dummy_authorizer())
+ if __name__ == '__main__':
+ test ()
+
+# this is the command list from the wuftpd man page
+# '*' means we've implemented it.
+# '!' requires write access
+#
+command_documentation = {
+ 'abor': 'abort previous command', #*
+ 'acct': 'specify account (ignored)',
+ 'allo': 'allocate storage (vacuously)',
+ 'appe': 'append to a file', #*!
+ 'cdup': 'change to parent of current working directory', #*
+ 'cwd': 'change working directory', #*
+ 'dele': 'delete a file', #!
+ 'help': 'give help information', #*
+ 'list': 'give list files in a directory', #*
+ 'mkd': 'make a directory', #!
+ 'mdtm': 'show last modification time of file', #*
+ 'mode': 'specify data transfer mode',
+ 'nlst': 'give name list of files in directory', #*
+ 'noop': 'do nothing', #*
+ 'pass': 'specify password', #*
+ 'pasv': 'prepare for server-to-server transfer', #*
+ 'port': 'specify data connection port', #*
+ 'pwd': 'print the current working directory', #*
+ 'quit': 'terminate session', #*
+ 'rest': 'restart incomplete transfer', #*
+ 'retr': 'retrieve a file', #*
+ 'rmd': 'remove a directory', #!
+ 'rnfr': 'specify rename-from file name', #!
+ 'rnto': 'specify rename-to file name', #!
+ 'site': 'non-standard commands (see next section)',
+ 'size': 'return size of file', #*
+ 'stat': 'return status of server', #*
+ 'stor': 'store a file', #*!
+ 'stou': 'store a file with a unique name', #!
+ 'stru': 'specify data transfer structure',
+ 'syst': 'show operating system type of server system', #*
+ 'type': 'specify data transfer type', #*
+ 'user': 'specify user name', #*
+ 'xcup': 'change to parent of current working directory (deprecated)',
+ 'xcwd': 'change working directory (deprecated)',
+ 'xmkd': 'make a directory (deprecated)', #!
+ 'xpwd': 'print the current working directory (deprecated)',
+ 'xrmd': 'remove a directory (deprecated)', #!
+}
+
+
+# debugging aid (linux)
+def get_vm_size ():
+ return string.atoi (string.split(open ('/proc/self/stat').readline())[22])
+
+def print_vm():
+ print 'vm: %8dk' % (get_vm_size()/1024)
diff --git a/demo/medusa054/ftps_server.py b/demo/medusa054/ftps_server.py
new file mode 100644
index 0000000..bf2f5a9
--- /dev/null
+++ b/demo/medusa054/ftps_server.py
@@ -0,0 +1,438 @@
+"""An FTP/TLS server built on Medusa's ftp_server.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+# Python
+import socket, string, sys, time
+
+# Medusa
+from counter import counter
+import asynchat, asyncore, ftp_server, logger
+
+# M2Crypto
+from M2Crypto import SSL, version
+
+VERSION_STRING=version
+
+class ftp_tls_channel(ftp_server.ftp_channel):
+
+ """FTP/TLS server channel for Medusa."""
+
+ def __init__(self, server, ssl_ctx, conn, addr):
+ """Initialise the channel."""
+ self.ssl_ctx = ssl_ctx
+ self.server = server
+ self.current_mode = 'a'
+ self.addr = addr
+ asynchat.async_chat.__init__(self, conn)
+ self.set_terminator('\r\n')
+ self.client_addr = (addr[0], 21)
+ self.client_dc = None
+ self.in_buffer = ''
+ self.closing = 0
+ self.passive_acceptor = None
+ self.passive_connection = None
+ self.filesystem = None
+ self.authorized = 0
+ self._ssl_accepting = 0
+ self._ssl_accepted = 0
+ self._pbsz = None
+ self._prot = None
+ resp = '220 %s M2Crypto (Medusa) FTP/TLS server v%s ready.'
+ self.respond(resp % (self.server.hostname, VERSION_STRING))
+
+ def writable(self):
+ return self._ssl_accepting or self._ssl_accepted
+
+ def handle_read(self):
+ """Handle a read event."""
+ if self._ssl_accepting:
+ self._ssl_accepted = self.socket.accept_ssl()
+ if self._ssl_accepted:
+ self._ssl_accepting = 0
+ else:
+ try:
+ ftp_server.ftp_channel.handle_read(self)
+ except SSL.SSLError, what:
+ if str(what) == 'unexpected eof':
+ self.close()
+ else:
+ raise
+
+ def handle_write(self):
+ """Handle a write event."""
+ if self._ssl_accepting:
+ self._ssl_accepted = self.socket.accept_ssl()
+ if self._ssl_accepted:
+ self._ssl_accepting = 0
+ else:
+ try:
+ ftp_server.ftp_channel.handle_write(self)
+ except SSL.SSLError, what:
+ if str(what) == 'unexpected eof':
+ self.close()
+ else:
+ raise
+
+ def send(self, data):
+ """Send data over SSL."""
+ try:
+ result = self.socket.send(data)
+ if result <= 0:
+ return 0
+ else:
+ return result
+ except SSL.SSLError, what:
+ self.close()
+ self.log_info('send: closing channel %s %s' % (repr(self), what))
+ return 0
+
+ def recv(self, buffer_size):
+ """Receive data over SSL."""
+ try:
+ result = self.socket.recv(buffer_size)
+ if not result:
+ return ''
+ else:
+ return result
+ except SSL.SSLError, what:
+ self.close()
+ self.log_info('recv: closing channel %s %s' % (repr(self), what))
+ return ''
+
+ def found_terminator(self):
+ """Dispatch the FTP command."""
+ line = self.in_buffer
+ if not len(line):
+ return
+
+ sp = string.find(line, ' ')
+ if sp != -1:
+ line = [line[:sp], line[sp+1:]]
+ else:
+ line = [line]
+
+ command = string.lower(line[0])
+ if string.find(command, 'stor') != -1:
+ while command and command[0] not in string.letters:
+ command = command[1:]
+
+ func_name = 'cmd_%s' % command
+ if command != 'pass':
+ self.log('<== %s' % repr(self.in_buffer)[1:-1])
+ else:
+ self.log('<== %s' % line[0]+' <password>')
+
+ self.in_buffer = ''
+ if not hasattr(self, func_name):
+ self.command_not_understood(line[0])
+ return
+
+ func = getattr(self, func_name)
+ if not self.check_command_authorization(command):
+ self.command_not_authorized(command)
+ else:
+ try:
+ result = apply(func, (line,))
+ except:
+ self.server.total_exceptions.increment()
+ (file, func, line), t, v, tbinfo = asyncore.compact_traceback()
+ if self.client_dc:
+ try:
+ self.client_dc_close()
+ except:
+ pass
+ resp = '451 Server error: %s, %s: file %s line: %s'
+ self.respond(resp % (t, v, file, line))
+
+ def make_xmit_channel(self):
+ """Create a connection for sending data."""
+ pa = self.passive_acceptor
+ if pa:
+ if pa.ready:
+ conn, addr = pa.ready
+ if self._prot:
+ cdc = tls_xmit_channel(self, conn, self.ssl_ctx, addr)
+ else:
+ cdc = ftp_server.xmit_channel(self, addr)
+ cdc.set_socket(conn)
+ cdc.connected = 1
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ else:
+ if self._prot:
+ cdc = tls_xmit_channel(self, None, self.ssl_ctx, None)
+ else:
+ cdc = ftp_server.xmit_channel(self)
+ else:
+ if self._prot:
+ cdc = tls_xmit_channel(self, None, self.ssl_ctx, self.client_addr)
+ else:
+ cdc = ftp_server.xmit_channel(self, self.client_addr)
+ cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ if self.bind_local_minus_one:
+ cdc.bind(('', self.server.port - 1))
+ try:
+ cdc.connect(self.client_addr)
+ except socket.error, what:
+ self.respond('425 Cannot build data connection')
+ self.client_dc = cdc
+
+ def make_recv_channel(self, fd):
+ """Create a connection for receiving data."""
+ pa = self.passive_acceptor
+ if pa:
+ if pa.ready:
+ conn, addr = pa.ready
+ if self._prot:
+ cdc = tls_recv_channel(self, conn, self.ssl_ctx, addr, fd)
+ else:
+ cdc = ftp_server.recv_channel(self, addr, fd)
+ cdc.set_socket(conn)
+ cdc.connected = 1
+ self.passive_acceptor.close()
+ self.passive_acceptor = None
+ else:
+ if self._prot:
+ cdc = tls_recv_channel(self, None, self.ssl_ctx, None, fd)
+ else:
+ cdc = ftp_server.recv_channel(self, None, fd)
+ else:
+ if self._prot:
+ cdc = tls_recv_channel(self, None, self.ssl_ctx, self._prot, self.client_addr, fd)
+ else:
+ cdc = ftp_server.recv_channel(self, self.client_addr, fd)
+ cdc.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ try:
+ cdc.connect(self.client_addr)
+ except socket.error, what:
+ self.respond('425 Cannot build data connection')
+ self.client_dc = cdc
+
+ def cmd_auth(self, line):
+ """Prepare for TLS operation."""
+ # XXX Handle variations.
+ if line[1] != 'TLS':
+ self.command_not_understood (string.join(line))
+ else:
+ self.respond('234 AUTH TLS successful')
+ self._ssl_accepting = 1
+ self.socket = SSL.Connection(self.ssl_ctx, self.socket)
+ self.socket.setup_addr(self.addr)
+ self.socket.setup_ssl()
+ self.socket.set_accept_state()
+ self._ssl_accepted = self.socket.accept_ssl()
+ if self._ssl_accepted:
+ self._ssl_accepting = 0
+
+ def cmd_pbsz(self, line):
+ """Negotiate size of buffer for secure data transfer. For
+ FTP/TLS the only valid value for the parameter is '0'; any
+ other value is accepted but ignored."""
+ if not (self._ssl_accepting or self._ssl_accepted):
+ return self.respond('503 AUTH TLS must be issued prior to PBSZ')
+ self._pbsz = 1
+ self.respond('200 PBSZ=0 successful.')
+
+ def cmd_prot(self, line):
+ """Negotiate the security level of the data connection."""
+ if self._pbsz is None:
+ return self.respond('503 PBSZ must be issued prior to PROT')
+ if line[1] == 'C':
+ self.respond('200 Protection set to Clear')
+ self._pbsz = None
+ self._prot = None
+ elif line[1] == 'P':
+ self.respond('200 Protection set to Private')
+ self._prot = 1
+ elif line[1] in ('S', 'E'):
+ self.respond('536 PROT %s unsupported' % line[1])
+ else:
+ self.respond('504 PROT %s unsupported' % line[1])
+
+
+class ftp_tls_server(ftp_server.ftp_server):
+
+ """FTP/TLS server for Medusa."""
+
+ SERVER_IDENT = 'M2Crypto FTP/TLS Server (v%s)' % VERSION_STRING
+
+ ftp_channel_class = ftp_tls_channel
+
+ def __init__(self, authz, ssl_ctx, host=None, ip='', port=21, resolver=None, log_obj=None):
+ """Initialise the server."""
+ self.ssl_ctx = ssl_ctx
+ self.ip = ip
+ self.port = port
+ self.authorizer = authz
+
+ if host is None:
+ self.hostname = socket.gethostname()
+ else:
+ self.hostname = host
+
+ self.total_sessions = counter()
+ self.closed_sessions = counter()
+ self.total_files_out = counter()
+ self.total_files_in = counter()
+ self.total_bytes_out = counter()
+ self.total_bytes_in = counter()
+ self.total_exceptions = counter()
+
+ asyncore.dispatcher.__init__(self)
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.set_reuse_addr()
+ self.bind((self.ip, self.port))
+ self.listen(5)
+
+ if log_obj is None:
+ log_obj = sys.stdout
+
+ if resolver:
+ self.logger = logger.resolving_logger(resolver, log_obj)
+ else:
+ self.logger = logger.unresolving_logger(logger.file_logger(sys.stdout))
+
+ l = 'M2Crypto (Medusa) FTP/TLS server started at %s\n\tAuthz: %s\n\tHostname: %s\n\tPort: %d'
+ self.log_info(l % (time.ctime(time.time()), repr(self.authorizer), self.hostname, self.port))
+
+ def handle_accept(self):
+ """Accept a socket and dispatch a channel to handle it."""
+ conn, addr = self.accept()
+ self.total_sessions.increment()
+ self.log_info('Connection from %s:%d' % addr)
+ self.ftp_channel_class(self, self.ssl_ctx, conn, addr)
+
+
+class nbio_ftp_tls_actor:
+
+ """TLS protocol negotiation mixin for FTP/TLS."""
+
+ def tls_init(self, sock, ssl_ctx, client_addr):
+ """Perform TLS protocol negotiation."""
+ self.ssl_ctx = ssl_ctx
+ self.client_addr = client_addr
+ self._ssl_handshaking = 1
+ self._ssl_handshake_ok = 0
+ if sock:
+ self.socket = SSL.Connection(self.ssl_ctx, sock)
+ self.socket.setup_addr(self.client_addr)
+ self.socket.setup_ssl()
+ self._ssl_handshake_ok = self.socket.accept_ssl()
+ if self._ssl_handshake_ok:
+ self._ssl_handshaking = 0
+ self.add_channel()
+ # else the client hasn't connected yet; when that happens,
+ # handle_connect() will be triggered.
+
+ def tls_neg_ok(self):
+ """Return status of TLS protocol negotiation."""
+ if self._ssl_handshaking:
+ self._ssl_handshake_ok = self.socket.accept_ssl()
+ if self._ssl_handshake_ok:
+ self._ssl_handshaking = 0
+ return self._ssl_handshake_ok
+
+ def handle_connect(self):
+ """Handle a data connection that occurs after this instance came
+ into being. When this handler is triggered, self.socket has been
+ created and refers to the underlying connected socket."""
+ self.socket = SSL.Connection(self.ssl_ctx, self.socket)
+ self.socket.setup_addr(self.client_addr)
+ self.socket.setup_ssl()
+ self._ssl_handshake_ok = self.socket.accept_ssl()
+ if self._ssl_handshake_ok:
+ self._ssl_handshaking = 0
+ self.add_channel()
+
+ def send(self, data):
+ """Send data over SSL."""
+ try:
+ result = self.socket.send(data)
+ if result <= 0:
+ return 0
+ else:
+ return result
+ except SSL.SSLError, what:
+ self.close()
+ self.log_info('send: closing channel %s %s' % (repr(self), what))
+ return 0
+
+ def recv(self, buffer_size):
+ """Receive data over SSL."""
+ try:
+ result = self.socket.recv(buffer_size)
+ if not result:
+ return ''
+ else:
+ return result
+ except SSL.SSLError, what:
+ self.close()
+ self.log_info('recv: closing channel %s %s' % (repr(self), what))
+ return ''
+
+
+class tls_xmit_channel(nbio_ftp_tls_actor, ftp_server.xmit_channel):
+
+ """TLS driver for a send-only data connection."""
+
+ def __init__(self, channel, conn, ssl_ctx, client_addr=None):
+ """Initialise the driver."""
+ ftp_server.xmit_channel.__init__(self, channel, client_addr)
+ self.tls_init(conn, ssl_ctx, client_addr)
+
+ def readable(self):
+ """This channel is readable iff TLS negotiation is in progress.
+ (Which implies a connected channel, of course.)"""
+ if not self.connected:
+ return 0
+ else:
+ return self._ssl_handshaking
+
+ def writable(self):
+ """This channel is writable iff TLS negotiation is in progress
+ or the application has data to send."""
+ if self._ssl_handshaking:
+ return 1
+ else:
+ return ftp_server.xmit_channel.writable(self)
+
+ def handle_read(self):
+ """Handle a read event: either continue with TLS negotiation
+ or let the application handle this event."""
+ if self.tls_neg_ok():
+ ftp_server.xmit_channel.handle_read(self)
+
+ def handle_write(self):
+ """Handle a write event: either continue with TLS negotiation
+ or let the application handle this event."""
+ if self.tls_neg_ok():
+ ftp_server.xmit_channel.handle_write(self)
+
+
+class tls_recv_channel(nbio_ftp_tls_actor, ftp_server.recv_channel):
+
+ """TLS driver for a receive-only data connection."""
+
+ def __init__(self, channel, conn, ssl_ctx, client_addr, fd):
+ """Initialise the driver."""
+ ftp_server.recv_channel.__init__(self, channel, client_addr, fd)
+ self.tls_init(conn, ssl_ctx, client_addr)
+
+ def writable(self):
+ """This channel is writable iff TLS negotiation is in progress."""
+ return self._ssl_handshaking
+
+ def handle_read(self):
+ """Handle a read event: either continue with TLS negotiation
+ or let the application handle this event."""
+ if self.tls_neg_ok():
+ ftp_server.recv_channel.handle_read(self)
+
+ def handle_write(self):
+ """Handle a write event: either continue with TLS negotiation
+ or let the application handle this event."""
+ if self.tls_neg_ok():
+ ftp_server.recv_channel.handle_write(self)
+
+
diff --git a/demo/medusa054/http_date.py b/demo/medusa054/http_date.py
new file mode 100644
index 0000000..d2f90b8
--- /dev/null
+++ b/demo/medusa054/http_date.py
@@ -0,0 +1,126 @@
+# -*- Mode: Python -*-
+
+import re
+import string
+import time
+
+def concat (*args):
+ return ''.join (args)
+
+def join (seq, field=' '):
+ return field.join (seq)
+
+def group (s):
+ return '(' + s + ')'
+
+short_days = ['sun','mon','tue','wed','thu','fri','sat']
+long_days = ['sunday','monday','tuesday','wednesday','thursday','friday','saturday']
+
+short_day_reg = group (join (short_days, '|'))
+long_day_reg = group (join (long_days, '|'))
+
+daymap = {}
+for i in range(7):
+ daymap[short_days[i]] = i
+ daymap[long_days[i]] = i
+
+hms_reg = join (3 * [group('[0-9][0-9]')], ':')
+
+months = ['jan','feb','mar','apr','may','jun','jul','aug','sep','oct','nov','dec']
+
+monmap = {}
+for i in range(12):
+ monmap[months[i]] = i+1
+
+months_reg = group (join (months, '|'))
+
+# From draft-ietf-http-v11-spec-07.txt/3.3.1
+# Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123
+# Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
+# Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format
+
+# rfc822 format
+rfc822_date = join (
+ [concat (short_day_reg,','), # day
+ group('[0-9][0-9]?'), # date
+ months_reg, # month
+ group('[0-9]+'), # year
+ hms_reg, # hour minute second
+ 'gmt'
+ ],
+ ' '
+ )
+
+rfc822_reg = re.compile (rfc822_date)
+
+def unpack_rfc822 (m):
+ g = m.group
+ a = string.atoi
+ return (
+ a(g(4)), # year
+ monmap[g(3)], # month
+ a(g(2)), # day
+ a(g(5)), # hour
+ a(g(6)), # minute
+ a(g(7)), # second
+ 0,
+ 0,
+ 0
+ )
+
+# rfc850 format
+rfc850_date = join (
+ [concat (long_day_reg,','),
+ join (
+ [group ('[0-9][0-9]?'),
+ months_reg,
+ group ('[0-9]+')
+ ],
+ '-'
+ ),
+ hms_reg,
+ 'gmt'
+ ],
+ ' '
+ )
+
+rfc850_reg = re.compile (rfc850_date)
+# they actually unpack the same way
+def unpack_rfc850 (m):
+ g = m.group
+ a = string.atoi
+ return (
+ a(g(4)), # year
+ monmap[g(3)], # month
+ a(g(2)), # day
+ a(g(5)), # hour
+ a(g(6)), # minute
+ a(g(7)), # second
+ 0,
+ 0,
+ 0
+ )
+
+# parsdate.parsedate - ~700/sec.
+# parse_http_date - ~1333/sec.
+
+def build_http_date (when):
+ return time.strftime ('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(when))
+
+def parse_http_date (d):
+ d = string.lower (d)
+ tz = time.timezone
+ m = rfc850_reg.match (d)
+ if m and m.end() == len(d):
+ retval = int (time.mktime (unpack_rfc850(m)) - tz)
+ else:
+ m = rfc822_reg.match (d)
+ if m and m.end() == len(d):
+ retval = int (time.mktime (unpack_rfc822(m)) - tz)
+ else:
+ return 0
+ # Thanks to Craig Silverstein <csilvers@google.com> for pointing
+ # out the DST discrepancy
+ if time.daylight and time.localtime(retval)[-1] == 1: # DST correction
+ retval = retval + (tz - time.altzone)
+ return retval
diff --git a/demo/medusa054/http_server.py b/demo/medusa054/http_server.py
new file mode 100644
index 0000000..8502add
--- /dev/null
+++ b/demo/medusa054/http_server.py
@@ -0,0 +1,749 @@
+#! /usr/local/bin/python
+# -*- Mode: Python -*-
+#
+# Author: Sam Rushing <rushing@nightmare.com>
+# Copyright 1996-2000 by Sam Rushing
+# All Rights Reserved.
+#
+
+# python modules
+import os
+import re
+import socket
+import string
+import sys
+import time
+
+# async modules
+import asyncore
+import asynchat
+
+# medusa modules
+import http_date
+import producers
+import status_handler
+import logger
+
+VERSION_STRING = '1.1'
+
+from counter import counter
+from urllib import unquote, splitquery
+
+# ===========================================================================
+# Request Object
+# ===========================================================================
+
+class http_request:
+
+ # default reply code
+ reply_code = 200
+
+ request_counter = counter()
+
+ # Whether to automatically use chunked encoding when
+ #
+ # HTTP version is 1.1
+ # Content-Length is not set
+ # Chunked encoding is not already in effect
+ #
+ # If your clients are having trouble, you might want to disable this.
+ use_chunked = 1
+
+ # by default, this request object ignores user data.
+ collector = None
+
+ def __init__ (self, *args):
+ # unpack information about the request
+ (self.channel, self.request,
+ self.command, self.uri, self.version,
+ self.header) = args
+
+ self.outgoing = []
+ self.reply_headers = {
+ 'Server' : 'Medusa/%s' % VERSION_STRING,
+ 'Date' : http_date.build_http_date (time.time())
+ }
+ self.request_number = http_request.request_counter.increment()
+ self._split_uri = None
+ self._header_cache = {}
+
+ # --------------------------------------------------
+ # reply header management
+ # --------------------------------------------------
+ def __setitem__ (self, key, value):
+ self.reply_headers[key] = value
+
+ def __getitem__ (self, key):
+ return self.reply_headers[key]
+
+ def has_key (self, key):
+ return self.reply_headers.has_key (key)
+
+ def build_reply_header (self):
+ return string.join (
+ [self.response(self.reply_code)] + map (
+ lambda x: '%s: %s' % x,
+ self.reply_headers.items()
+ ),
+ '\r\n'
+ ) + '\r\n\r\n'
+
+ # --------------------------------------------------
+ # split a uri
+ # --------------------------------------------------
+
+ # <path>;<params>?<query>#<fragment>
+ path_regex = re.compile (
+ # path params query fragment
+ r'([^;?#]*)(;[^?#]*)?(\?[^#]*)?(#.*)?'
+ )
+
+ def split_uri (self):
+ if self._split_uri is None:
+ m = self.path_regex.match (self.uri)
+ if m.end() != len(self.uri):
+ raise ValueError, "Broken URI"
+ else:
+ self._split_uri = m.groups()
+ return self._split_uri
+
+ def get_header_with_regex (self, head_reg, group):
+ for line in self.header:
+ m = head_reg.match (line)
+ if m.end() == len(line):
+ return m.group (group)
+ return ''
+
+ def get_header (self, header):
+ header = string.lower (header)
+ hc = self._header_cache
+ if not hc.has_key (header):
+ h = header + ': '
+ hl = len(h)
+ for line in self.header:
+ if string.lower (line[:hl]) == h:
+ r = line[hl:]
+ hc[header] = r
+ return r
+ hc[header] = None
+ return None
+ else:
+ return hc[header]
+
+ # --------------------------------------------------
+ # user data
+ # --------------------------------------------------
+
+ def collect_incoming_data (self, data):
+ if self.collector:
+ self.collector.collect_incoming_data (data)
+ else:
+ self.log_info(
+ 'Dropping %d bytes of incoming request data' % len(data),
+ 'warning'
+ )
+
+ def found_terminator (self):
+ if self.collector:
+ self.collector.found_terminator()
+ else:
+ self.log_info (
+ 'Unexpected end-of-record for incoming request',
+ 'warning'
+ )
+
+ def push (self, thing):
+ if type(thing) == type(''):
+ self.outgoing.append(producers.simple_producer (thing))
+ else:
+ self.outgoing.append(thing)
+
+ def response (self, code=200):
+ message = self.responses[code]
+ self.reply_code = code
+ return 'HTTP/%s %d %s' % (self.version, code, message)
+
+ def error (self, code):
+ self.reply_code = code
+ message = self.responses[code]
+ s = self.DEFAULT_ERROR_MESSAGE % {
+ 'code': code,
+ 'message': message,
+ }
+ self['Content-Length'] = len(s)
+ self['Content-Type'] = 'text/html'
+ # make an error reply
+ self.push (s)
+ self.done()
+
+ # can also be used for empty replies
+ reply_now = error
+
+ def done (self):
+ "finalize this transaction - send output to the http channel"
+
+ # ----------------------------------------
+ # persistent connection management
+ # ----------------------------------------
+
+ # --- BUCKLE UP! ----
+
+ connection = string.lower (get_header (CONNECTION, self.header))
+
+ close_it = 0
+ wrap_in_chunking = 0
+
+ if self.version == '1.0':
+ if connection == 'keep-alive':
+ if not self.has_key ('Content-Length'):
+ close_it = 1
+ else:
+ self['Connection'] = 'Keep-Alive'
+ else:
+ close_it = 1
+ elif self.version == '1.1':
+ if connection == 'close':
+ close_it = 1
+ elif not self.has_key ('Content-Length'):
+ if self.has_key ('Transfer-Encoding'):
+ if not self['Transfer-Encoding'] == 'chunked':
+ close_it = 1
+ elif self.use_chunked:
+ self['Transfer-Encoding'] = 'chunked'
+ wrap_in_chunking = 1
+ else:
+ close_it = 1
+ elif self.version is None:
+ # Although we don't *really* support http/0.9 (because we'd have to
+ # use \r\n as a terminator, and it would just yuck up a lot of stuff)
+ # it's very common for developers to not want to type a version number
+ # when using telnet to debug a server.
+ close_it = 1
+
+ outgoing_header = producers.simple_producer (self.build_reply_header())
+
+ if close_it:
+ self['Connection'] = 'close'
+
+ if wrap_in_chunking:
+ outgoing_producer = producers.chunked_producer (
+ producers.composite_producer (self.outgoing)
+ )
+ # prepend the header
+ outgoing_producer = producers.composite_producer(
+ [outgoing_header, outgoing_producer]
+ )
+ else:
+ # prepend the header
+ self.outgoing.insert(0, outgoing_header)
+ outgoing_producer = producers.composite_producer (self.outgoing)
+
+ # apply a few final transformations to the output
+ self.channel.push_with_producer (
+ # globbing gives us large packets
+ producers.globbing_producer (
+ # hooking lets us log the number of bytes sent
+ producers.hooked_producer (
+ outgoing_producer,
+ self.log
+ )
+ )
+ )
+
+ self.channel.current_request = None
+
+ if close_it:
+ self.channel.close_when_done()
+
+ def log_date_string (self, when):
+ gmt = time.gmtime(when)
+ if time.daylight and gmt[8]:
+ tz = time.altzone
+ else:
+ tz = time.timezone
+ if tz > 0:
+ neg = 1
+ else:
+ neg = 0
+ tz = -tz
+ h, rem = divmod (tz, 3600)
+ m, rem = divmod (rem, 60)
+ if neg:
+ offset = '-%02d%02d' % (h, m)
+ else:
+ offset = '+%02d%02d' % (h, m)
+
+ return time.strftime ( '%d/%b/%Y:%H:%M:%S ', gmt) + offset
+
+ def log (self, bytes):
+ self.channel.server.logger.log (
+ self.channel.addr[0],
+ '%d - - [%s] "%s" %d %d\n' % (
+ self.channel.addr[1],
+ self.log_date_string (time.time()),
+ self.request,
+ self.reply_code,
+ bytes
+ )
+ )
+
+ responses = {
+ 100: "Continue",
+ 101: "Switching Protocols",
+ 200: "OK",
+ 201: "Created",
+ 202: "Accepted",
+ 203: "Non-Authoritative Information",
+ 204: "No Content",
+ 205: "Reset Content",
+ 206: "Partial Content",
+ 300: "Multiple Choices",
+ 301: "Moved Permanently",
+ 302: "Moved Temporarily",
+ 303: "See Other",
+ 304: "Not Modified",
+ 305: "Use Proxy",
+ 400: "Bad Request",
+ 401: "Unauthorized",
+ 402: "Payment Required",
+ 403: "Forbidden",
+ 404: "Not Found",
+ 405: "Method Not Allowed",
+ 406: "Not Acceptable",
+ 407: "Proxy Authentication Required",
+ 408: "Request Time-out",
+ 409: "Conflict",
+ 410: "Gone",
+ 411: "Length Required",
+ 412: "Precondition Failed",
+ 413: "Request Entity Too Large",
+ 414: "Request-URI Too Large",
+ 415: "Unsupported Media Type",
+ 500: "Internal Server Error",
+ 501: "Not Implemented",
+ 502: "Bad Gateway",
+ 503: "Service Unavailable",
+ 504: "Gateway Time-out",
+ 505: "HTTP Version not supported"
+ }
+
+ # Default error message
+ DEFAULT_ERROR_MESSAGE = string.join (
+ ['<head>',
+ '<title>Error response</title>',
+ '</head>',
+ '<body>',
+ '<h1>Error response</h1>',
+ '<p>Error code %(code)d.',
+ '<p>Message: %(message)s.',
+ '</body>',
+ ''
+ ],
+ '\r\n'
+ )
+
+
+# ===========================================================================
+# HTTP Channel Object
+# ===========================================================================
+
+class http_channel (asynchat.async_chat):
+
+ # use a larger default output buffer
+ ac_out_buffer_size = 1<<16
+
+ current_request = None
+ channel_counter = counter()
+
+ def __init__ (self, server, conn, addr):
+ self.channel_number = http_channel.channel_counter.increment()
+ self.request_counter = counter()
+ asynchat.async_chat.__init__ (self, conn)
+ self.server = server
+ self.addr = addr
+ self.set_terminator ('\r\n\r\n')
+ self.in_buffer = ''
+ self.creation_time = int (time.time())
+ self.check_maintenance()
+
+ def __repr__ (self):
+ ar = asynchat.async_chat.__repr__(self)[1:-1]
+ return '<%s channel#: %s requests:%s>' % (
+ ar,
+ self.channel_number,
+ self.request_counter
+ )
+
+ # Channel Counter, Maintenance Interval...
+ maintenance_interval = 500
+
+ def check_maintenance (self):
+ if not self.channel_number % self.maintenance_interval:
+ self.maintenance()
+
+ def maintenance (self):
+ self.kill_zombies()
+
+ # 30-minute zombie timeout. status_handler also knows how to kill zombies.
+ zombie_timeout = 30 * 60
+
+ def kill_zombies (self):
+ now = int (time.time())
+ for channel in asyncore.socket_map.values():
+ if channel.__class__ == self.__class__:
+ if (now - channel.creation_time) > channel.zombie_timeout:
+ channel.close()
+
+ # --------------------------------------------------
+ # send/recv overrides, good place for instrumentation.
+ # --------------------------------------------------
+
+ # this information needs to get into the request object,
+ # so that it may log correctly.
+ def send (self, data):
+ result = asynchat.async_chat.send (self, data)
+ self.server.bytes_out.increment (len(data))
+ return result
+
+ def recv (self, buffer_size):
+ try:
+ result = asynchat.async_chat.recv (self, buffer_size)
+ self.server.bytes_in.increment (len(result))
+ return result
+ except MemoryError:
+ # --- Save a Trip to Your Service Provider ---
+ # It's possible for a process to eat up all the memory of
+ # the machine, and put it in an extremely wedged state,
+ # where medusa keeps running and can't be shut down. This
+ # is where MemoryError tends to get thrown, though of
+ # course it could get thrown elsewhere.
+ sys.exit ("Out of Memory!")
+
+ def handle_error (self):
+ t, v = sys.exc_info()[:2]
+ if t is SystemExit:
+ raise t, v
+ else:
+ asynchat.async_chat.handle_error (self)
+
+ def log (self, *args):
+ pass
+
+ # --------------------------------------------------
+ # async_chat methods
+ # --------------------------------------------------
+
+ def collect_incoming_data (self, data):
+ if self.current_request:
+ # we are receiving data (probably POST data) for a request
+ self.current_request.collect_incoming_data (data)
+ else:
+ # we are receiving header (request) data
+ self.in_buffer = self.in_buffer + data
+
+ def found_terminator (self):
+ if self.current_request:
+ self.current_request.found_terminator()
+ else:
+ header = self.in_buffer
+ self.in_buffer = ''
+ lines = string.split (header, '\r\n')
+
+ # --------------------------------------------------
+ # crack the request header
+ # --------------------------------------------------
+
+ while lines and not lines[0]:
+ # as per the suggestion of http-1.1 section 4.1, (and
+ # Eric Parker <eparker@zyvex.com>), ignore a leading
+ # blank lines (buggy browsers tack it onto the end of
+ # POST requests)
+ lines = lines[1:]
+
+ if not lines:
+ self.close_when_done()
+ return
+
+ request = lines[0]
+
+ command, uri, version = crack_request (request)
+ header = join_headers (lines[1:])
+
+ # unquote path if necessary (thanks to Skip Montanaro for pointing
+ # out that we must unquote in piecemeal fashion).
+ rpath, rquery = splitquery(uri)
+ if '%' in rpath:
+ if rquery:
+ uri = unquote (rpath) + '?' + rquery
+ else:
+ uri = unquote (rpath)
+
+ r = http_request (self, request, command, uri, version, header)
+ self.request_counter.increment()
+ self.server.total_requests.increment()
+
+ if command is None:
+ self.log_info ('Bad HTTP request: %s' % repr(request), 'error')
+ r.error (400)
+ return
+
+ # --------------------------------------------------
+ # handler selection and dispatch
+ # --------------------------------------------------
+ for h in self.server.handlers:
+ if h.match (r):
+ try:
+ self.current_request = r
+ # This isn't used anywhere.
+ # r.handler = h # CYCLE
+ h.handle_request (r)
+ except:
+ self.server.exceptions.increment()
+ (file, fun, line), t, v, tbinfo = asyncore.compact_traceback()
+ self.log_info(
+ 'Server Error: %s, %s: file: %s line: %s' % (t,v,file,line),
+ 'error')
+ try:
+ r.error (500)
+ except:
+ pass
+ return
+
+ # no handlers, so complain
+ r.error (404)
+
+ def writable_for_proxy (self):
+ # this version of writable supports the idea of a 'stalled' producer
+ # [i.e., it's not ready to produce any output yet] This is needed by
+ # the proxy, which will be waiting for the magic combination of
+ # 1) hostname resolved
+ # 2) connection made
+ # 3) data available.
+ if self.ac_out_buffer:
+ return 1
+ elif len(self.producer_fifo):
+ p = self.producer_fifo.first()
+ if hasattr (p, 'stalled'):
+ return not p.stalled()
+ else:
+ return 1
+
+# ===========================================================================
+# HTTP Server Object
+# ===========================================================================
+
+class http_server (asyncore.dispatcher):
+
+ SERVER_IDENT = 'HTTP Server (V%s)' % VERSION_STRING
+
+ channel_class = http_channel
+
+ def __init__ (self, ip, port, resolver=None, logger_object=None):
+ self.ip = ip
+ self.port = port
+ asyncore.dispatcher.__init__ (self)
+ self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+ self.handlers = []
+
+ if not logger_object:
+ logger_object = logger.file_logger (sys.stdout)
+
+ self.set_reuse_addr()
+ self.bind ((ip, port))
+
+ # lower this to 5 if your OS complains
+ self.listen (1024)
+
+ host, port = self.socket.getsockname()
+ if not ip:
+ self.log_info('Computing default hostname', 'warning')
+ ip = socket.gethostbyname (socket.gethostname())
+ try:
+ self.server_name = socket.gethostbyaddr (ip)[0]
+ except socket.error:
+ self.log_info('Cannot do reverse lookup', 'warning')
+ self.server_name = ip # use the IP address as the "hostname"
+
+ self.server_port = port
+ self.total_clients = counter()
+ self.total_requests = counter()
+ self.exceptions = counter()
+ self.bytes_out = counter()
+ self.bytes_in = counter()
+
+ if not logger_object:
+ logger_object = logger.file_logger (sys.stdout)
+
+ if resolver:
+ self.logger = logger.resolving_logger (resolver, logger_object)
+ else:
+ self.logger = logger.unresolving_logger (logger_object)
+
+ self.log_info (
+ 'Medusa (V%s) started at %s'
+ '\n\tHostname: %s'
+ '\n\tPort:%d'
+ '\n' % (
+ VERSION_STRING,
+ time.ctime(time.time()),
+ self.server_name,
+ port,
+ )
+ )
+
+ def writable (self):
+ return 0
+
+ def handle_read (self):
+ pass
+
+ def readable (self):
+ return self.accepting
+
+ def handle_connect (self):
+ pass
+
+ def handle_accept (self):
+ self.total_clients.increment()
+ try:
+ conn, addr = self.accept()
+ except socket.error:
+ # linux: on rare occasions we get a bogus socket back from
+ # accept. socketmodule.c:makesockaddr complains that the
+ # address family is unknown. We don't want the whole server
+ # to shut down because of this.
+ self.log_info ('warning: server accept() threw an exception', 'warning')
+ return
+ except TypeError:
+ # unpack non-sequence. this can happen when a read event
+ # fires on a listening socket, but when we call accept()
+ # we get EWOULDBLOCK, so dispatcher.accept() returns None.
+ # Seen on FreeBSD3.
+ self.log_info ('warning: server accept() threw EWOULDBLOCK', 'warning')
+ return
+
+ self.channel_class (self, conn, addr)
+
+ def install_handler (self, handler, back=0):
+ if back:
+ self.handlers.append (handler)
+ else:
+ self.handlers.insert (0, handler)
+
+ def remove_handler (self, handler):
+ self.handlers.remove (handler)
+
+ def status (self):
+ def nice_bytes (n):
+ return string.join (status_handler.english_bytes (n))
+
+ handler_stats = filter (None, map (maybe_status, self.handlers))
+
+ if self.total_clients:
+ ratio = self.total_requests.as_long() / float(self.total_clients.as_long())
+ else:
+ ratio = 0.0
+
+ return producers.composite_producer (
+ [producers.lines_producer (
+ ['<h2>%s</h2>' % self.SERVER_IDENT,
+ '<br>Listening on: <b>Host:</b> %s' % self.server_name,
+ '<b>Port:</b> %d' % self.port,
+ '<p><ul>'
+ '<li>Total <b>Clients:</b> %s' % self.total_clients,
+ '<b>Requests:</b> %s' % self.total_requests,
+ '<b>Requests/Client:</b> %.1f' % (ratio),
+ '<li>Total <b>Bytes In:</b> %s' % (nice_bytes (self.bytes_in.as_long())),
+ '<b>Bytes Out:</b> %s' % (nice_bytes (self.bytes_out.as_long())),
+ '<li>Total <b>Exceptions:</b> %s' % self.exceptions,
+ '</ul><p>'
+ '<b>Extension List</b><ul>',
+ ])] + handler_stats + [producers.simple_producer('</ul>')]
+ )
+
+def maybe_status (thing):
+ if hasattr (thing, 'status'):
+ return thing.status()
+ else:
+ return None
+
+CONNECTION = re.compile ('Connection: (.*)', re.IGNORECASE)
+
+# merge multi-line headers
+# [486dx2: ~500/sec]
+def join_headers (headers):
+ r = []
+ for i in range(len(headers)):
+ if headers[i][0] in ' \t':
+ r[-1] = r[-1] + headers[i][1:]
+ else:
+ r.append (headers[i])
+ return r
+
+def get_header (head_reg, lines, group=1):
+ for line in lines:
+ m = head_reg.match (line)
+ if m and m.end() == len(line):
+ return m.group (group)
+ return ''
+
+def get_header_match (head_reg, lines):
+ for line in lines:
+ m = head_reg.match (line)
+ if m and m.end() == len(line):
+ return m
+ return ''
+
+REQUEST = re.compile ('([^ ]+) ([^ ]+)(( HTTP/([0-9.]+))$|$)')
+
+def crack_request (r):
+ m = REQUEST.match (r)
+ if m and m.end() == len(r):
+ if m.group(3):
+ version = m.group(5)
+ else:
+ version = None
+ return m.group(1), m.group(2), version
+ else:
+ return None, None, None
+
+if __name__ == '__main__':
+ import sys
+ if len(sys.argv) < 2:
+ print 'usage: %s <root> <port>' % (sys.argv[0])
+ else:
+ import monitor
+ import filesys
+ import default_handler
+ import status_handler
+ import ftp_server
+ import chat_server
+ import resolver
+ import logger
+ rs = resolver.caching_resolver ('127.0.0.1')
+ lg = logger.file_logger (sys.stdout)
+ ms = monitor.secure_monitor_server ('fnord', '127.0.0.1', 9999)
+ fs = filesys.os_filesystem (sys.argv[1])
+ dh = default_handler.default_handler (fs)
+ hs = http_server ('', string.atoi (sys.argv[2]), rs, lg)
+ hs.install_handler (dh)
+ ftp = ftp_server.ftp_server (
+ ftp_server.dummy_authorizer(sys.argv[1]),
+ port=8021,
+ resolver=rs,
+ logger_object=lg
+ )
+ cs = chat_server.chat_server ('', 7777)
+ sh = status_handler.status_extension([hs,ms,ftp,cs,rs])
+ hs.install_handler (sh)
+ if ('-p' in sys.argv):
+ def profile_loop ():
+ try:
+ asyncore.loop()
+ except KeyboardInterrupt:
+ pass
+ import profile
+ profile.run ('profile_loop()', 'profile.out')
+ else:
+ asyncore.loop()
diff --git a/demo/medusa054/https_server.py b/demo/medusa054/https_server.py
new file mode 100644
index 0000000..f30bc86
--- /dev/null
+++ b/demo/medusa054/https_server.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+
+"""A https server built on Medusa's http_server.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+import asynchat, asyncore, http_server, socket, sys
+from M2Crypto import SSL, version
+
+VERSION_STRING=version
+
+class https_channel(http_server.http_channel):
+
+ ac_in_buffer_size = 1 << 16
+
+ def __init__(self, server, conn, addr):
+ http_server.http_channel.__init__(self, server, conn, addr)
+
+ def send(self, data):
+ try:
+ result = self.socket._write_nbio(data)
+ if result <= 0:
+ return 0
+ else:
+ self.server.bytes_out.increment(result)
+ return result
+ except SSL.SSLError, why:
+ self.close()
+ self.log_info('send: closing channel %s %s' % (repr(self), why))
+ return 0
+
+ def recv(self, buffer_size):
+ try:
+ result = self.socket._read_nbio(buffer_size)
+ if result is None:
+ return ''
+ elif result == '':
+ self.close()
+ return ''
+ else:
+ self.server.bytes_in.increment(len(result))
+ return result
+ except SSL.SSLError, why:
+ self.close()
+ self.log_info('recv: closing channel %s %s' % (repr(self), why))
+ return ''
+
+
+class https_server(http_server.http_server):
+
+ SERVER_IDENT='M2Crypto HTTPS Server (v%s)' % VERSION_STRING
+
+ channel_class=https_channel
+
+ def __init__(self, ip, port, ssl_ctx, resolver=None, logger_object=None):
+ http_server.http_server.__init__(self, ip, port, resolver, logger_object)
+ sys.stdout.write(self.SERVER_IDENT + '\n\n')
+ sys.stdout.flush()
+ self.ssl_ctx=ssl_ctx
+
+ def handle_accept(self):
+ # Cribbed from http_server.
+ self.total_clients.increment()
+ try:
+ conn, addr = self.accept()
+ except socket.error:
+ # linux: on rare occasions we get a bogus socket back from
+ # accept. socketmodule.c:makesockaddr complains that the
+ # address family is unknown. We don't want the whole server
+ # to shut down because of this.
+ sys.stderr.write ('warning: server accept() threw an exception\n')
+ return
+
+ # Turn the vanilla socket into an SSL connection.
+ try:
+ ssl_conn=SSL.Connection(self.ssl_ctx, conn)
+ ssl_conn._setup_ssl(addr)
+ ssl_conn.accept_ssl()
+ self.channel_class(self, ssl_conn, addr)
+ except SSL.SSLError:
+ pass
+
+ def writeable(self):
+ return 0
+
diff --git a/demo/medusa054/index.html b/demo/medusa054/index.html
new file mode 100644
index 0000000..0f7de19
--- /dev/null
+++ b/demo/medusa054/index.html
@@ -0,0 +1,6 @@
+<html>
+<title>M2Crypto HTTPS Server</title>
+<body>
+<h1>M2Crypto HTTPS Server - It works!</h1>
+</body>
+</html>
diff --git a/demo/medusa054/logger.py b/demo/medusa054/logger.py
new file mode 100644
index 0000000..d131ab5
--- /dev/null
+++ b/demo/medusa054/logger.py
@@ -0,0 +1,261 @@
+# -*- Mode: Python -*-
+
+import asynchat
+import socket
+import time # these three are for the rotating logger
+import os # |
+import stat # v
+
+#
+# three types of log:
+# 1) file
+# with optional flushing. Also, one that rotates the log.
+# 2) socket
+# dump output directly to a socket connection. [how do we
+# keep it open?]
+# 3) syslog
+# log to syslog via tcp. this is a per-line protocol.
+#
+
+#
+# The 'standard' interface to a logging object is simply
+# log_object.log (message)
+#
+
+# a file-like object that captures output, and
+# makes sure to flush it always... this could
+# be connected to:
+# o stdio file
+# o low-level file
+# o socket channel
+# o syslog output...
+
+class file_logger:
+
+ # pass this either a path or a file object.
+ def __init__ (self, file, flush=1, mode='a'):
+ if type(file) == type(''):
+ if (file == '-'):
+ import sys
+ self.file = sys.stdout
+ else:
+ self.file = open (file, mode)
+ else:
+ self.file = file
+ self.do_flush = flush
+
+ def __repr__ (self):
+ return '<file logger: %s>' % self.file
+
+ def write (self, data):
+ self.file.write (data)
+ self.maybe_flush()
+
+ def writeline (self, line):
+ self.file.writeline (line)
+ self.maybe_flush()
+
+ def writelines (self, lines):
+ self.file.writelines (lines)
+ self.maybe_flush()
+
+ def maybe_flush (self):
+ if self.do_flush:
+ self.file.flush()
+
+ def flush (self):
+ self.file.flush()
+
+ def softspace (self, *args):
+ pass
+
+ def log (self, message):
+ if message[-1] not in ('\r', '\n'):
+ self.write (message + '\n')
+ else:
+ self.write (message)
+
+# like a file_logger, but it must be attached to a filename.
+# When the log gets too full, or a certain time has passed,
+# it backs up the log and starts a new one. Note that backing
+# up the log is done via "mv" because anything else (cp, gzip)
+# would take time, during which medusa would do nothing else.
+
+class rotating_file_logger (file_logger):
+
+ # If freq is non-None we back up "daily", "weekly", or "monthly".
+ # Else if maxsize is non-None we back up whenever the log gets
+ # to big. If both are None we never back up.
+ def __init__ (self, file, freq=None, maxsize=None, flush=1, mode='a'):
+ self.filename = file
+ self.mode = mode
+ self.file = open (file, mode)
+ self.freq = freq
+ self.maxsize = maxsize
+ self.rotate_when = self.next_backup(self.freq)
+ self.do_flush = flush
+
+ def __repr__ (self):
+ return '<rotating-file logger: %s>' % self.file
+
+ # We back up at midnight every 1) day, 2) monday, or 3) 1st of month
+ def next_backup (self, freq):
+ (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
+ if freq == 'daily':
+ return time.mktime((yr,mo,day+1, 0,0,0, 0,0,-1))
+ elif freq == 'weekly':
+ return time.mktime((yr,mo,day-wd+7, 0,0,0, 0,0,-1)) # wd(monday)==0
+ elif freq == 'monthly':
+ return time.mktime((yr,mo+1,1, 0,0,0, 0,0,-1))
+ else:
+ return None # not a date-based backup
+
+ def maybe_flush (self): # rotate first if necessary
+ self.maybe_rotate()
+ if self.do_flush: # from file_logger()
+ self.file.flush()
+
+ def maybe_rotate (self):
+ if self.freq and time.time() > self.rotate_when:
+ self.rotate()
+ self.rotate_when = self.next_backup(self.freq)
+ elif self.maxsize: # rotate when we get too big
+ try:
+ if os.stat(self.filename)[stat.ST_SIZE] > self.maxsize:
+ self.rotate()
+ except os.error: # file not found, probably
+ self.rotate() # will create a new file
+
+ def rotate (self):
+ (yr, mo, day, hr, min, sec, wd, jday, dst) = time.localtime(time.time())
+ try:
+ self.file.close()
+ newname = '%s.ends%04d%02d%02d' % (self.filename, yr, mo, day)
+ try:
+ open(newname, "r").close() # check if file exists
+ newname = newname + "-%02d%02d%02d" % (hr, min, sec)
+ except: # YEARMODY is unique
+ pass
+ os.rename(self.filename, newname)
+ self.file = open(self.filename, self.mode)
+ except:
+ pass
+
+# syslog is a line-oriented log protocol - this class would be
+# appropriate for FTP or HTTP logs, but not for dumping stderr to.
+
+# TODO: a simple safety wrapper that will ensure that the line sent
+# to syslog is reasonable.
+
+# TODO: async version of syslog_client: now, log entries use blocking
+# send()
+
+import m_syslog
+syslog_logger = m_syslog.syslog_client
+
+class syslog_logger (m_syslog.syslog_client):
+ def __init__ (self, address, facility='user'):
+ m_syslog.syslog_client.__init__ (self, address)
+ self.facility = m_syslog.facility_names[facility]
+ self.address=address
+
+ def __repr__ (self):
+ return '<syslog logger address=%s>' % (repr(self.address))
+
+ def log (self, message):
+ m_syslog.syslog_client.log (
+ self,
+ message,
+ facility=self.facility,
+ priority=m_syslog.LOG_INFO
+ )
+
+# log to a stream socket, asynchronously
+
+class socket_logger (asynchat.async_chat):
+
+ def __init__ (self, address):
+
+ if type(address) == type(''):
+ self.create_socket (socket.AF_UNIX, socket.SOCK_STREAM)
+ else:
+ self.create_socket (socket.AF_INET, socket.SOCK_STREAM)
+
+ self.connect (address)
+ self.address = address
+
+ def __repr__ (self):
+ return '<socket logger: address=%s>' % (self.address)
+
+ def log (self, message):
+ if message[-2:] != '\r\n':
+ self.socket.push (message + '\r\n')
+ else:
+ self.socket.push (message)
+
+# log to multiple places
+class multi_logger:
+ def __init__ (self, loggers):
+ self.loggers = loggers
+
+ def __repr__ (self):
+ return '<multi logger: %s>' % (repr(self.loggers))
+
+ def log (self, message):
+ for logger in self.loggers:
+ logger.log (message)
+
+class resolving_logger:
+ """Feed (ip, message) combinations into this logger to get a
+ resolved hostname in front of the message. The message will not
+ be logged until the PTR request finishes (or fails)."""
+
+ def __init__ (self, resolver, logger):
+ self.resolver = resolver
+ self.logger = logger
+
+ class logger_thunk:
+ def __init__ (self, message, logger):
+ self.message = message
+ self.logger = logger
+
+ def __call__ (self, host, ttl, answer):
+ if not answer:
+ answer = host
+ self.logger.log ('%s:%s' % (answer, self.message))
+
+ def log (self, ip, message):
+ self.resolver.resolve_ptr (
+ ip,
+ self.logger_thunk (
+ message,
+ self.logger
+ )
+ )
+
+class unresolving_logger:
+ "Just in case you don't want to resolve"
+ def __init__ (self, logger):
+ self.logger = logger
+
+ def log (self, ip, message):
+ self.logger.log ('%s:%s' % (ip, message))
+
+
+def strip_eol (line):
+ while line and line[-1] in '\r\n':
+ line = line[:-1]
+ return line
+
+class tail_logger:
+ "Keep track of the last <size> log messages"
+ def __init__ (self, logger, size=500):
+ self.size = size
+ self.logger = logger
+ self.messages = []
+
+ def log (self, message):
+ self.messages.append (strip_eol (message))
+ if len (self.messages) > self.size:
+ del self.messages[0]
+ self.logger.log (message)
diff --git a/demo/medusa054/m_syslog.py b/demo/medusa054/m_syslog.py
new file mode 100644
index 0000000..e212766
--- /dev/null
+++ b/demo/medusa054/m_syslog.py
@@ -0,0 +1,182 @@
+# -*- Mode: Python -*-
+
+# ======================================================================
+# Copyright 1997 by Sam Rushing
+#
+# All Rights Reserved
+#
+# Permission to use, copy, modify, and distribute this software and
+# its documentation for any purpose and without fee is hereby
+# granted, provided that the above copyright notice appear in all
+# copies and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of Sam
+# Rushing not be used in advertising or publicity pertaining to
+# distribution of the software without specific, written prior
+# permission.
+#
+# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
+# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+# ======================================================================
+
+"""socket interface to unix syslog.
+On Unix, there are usually two ways of getting to syslog: via a
+local unix-domain socket, or via the TCP service.
+
+Usually "/dev/log" is the unix domain socket. This may be different
+for other systems.
+
+>>> my_client = syslog_client ('/dev/log')
+
+Otherwise, just use the UDP version, port 514.
+
+>>> my_client = syslog_client (('my_log_host', 514))
+
+On win32, you will have to use the UDP version. Note that
+you can use this to log to other hosts (and indeed, multiple
+hosts).
+
+This module is not a drop-in replacement for the python
+<syslog> extension module - the interface is different.
+
+Usage:
+
+>>> c = syslog_client()
+>>> c = syslog_client ('/strange/non_standard_log_location')
+>>> c = syslog_client (('other_host.com', 514))
+>>> c.log ('testing', facility='local0', priority='debug')
+
+"""
+
+# TODO: support named-pipe syslog.
+# [see ftp://sunsite.unc.edu/pub/Linux/system/Daemons/syslog-fifo.tar.z]
+
+# from <linux/sys/syslog.h>:
+# ===========================================================================
+# priorities/facilities are encoded into a single 32-bit quantity, where the
+# bottom 3 bits are the priority (0-7) and the top 28 bits are the facility
+# (0-big number). Both the priorities and the facilities map roughly
+# one-to-one to strings in the syslogd(8) source code. This mapping is
+# included in this file.
+#
+# priorities (these are ordered)
+
+LOG_EMERG = 0 # system is unusable
+LOG_ALERT = 1 # action must be taken immediately
+LOG_CRIT = 2 # critical conditions
+LOG_ERR = 3 # error conditions
+LOG_WARNING = 4 # warning conditions
+LOG_NOTICE = 5 # normal but significant condition
+LOG_INFO = 6 # informational
+LOG_DEBUG = 7 # debug-level messages
+
+# facility codes
+LOG_KERN = 0 # kernel messages
+LOG_USER = 1 # random user-level messages
+LOG_MAIL = 2 # mail system
+LOG_DAEMON = 3 # system daemons
+LOG_AUTH = 4 # security/authorization messages
+LOG_SYSLOG = 5 # messages generated internally by syslogd
+LOG_LPR = 6 # line printer subsystem
+LOG_NEWS = 7 # network news subsystem
+LOG_UUCP = 8 # UUCP subsystem
+LOG_CRON = 9 # clock daemon
+LOG_AUTHPRIV = 10 # security/authorization messages (private)
+
+# other codes through 15 reserved for system use
+LOG_LOCAL0 = 16 # reserved for local use
+LOG_LOCAL1 = 17 # reserved for local use
+LOG_LOCAL2 = 18 # reserved for local use
+LOG_LOCAL3 = 19 # reserved for local use
+LOG_LOCAL4 = 20 # reserved for local use
+LOG_LOCAL5 = 21 # reserved for local use
+LOG_LOCAL6 = 22 # reserved for local use
+LOG_LOCAL7 = 23 # reserved for local use
+
+priority_names = {
+ "alert": LOG_ALERT,
+ "crit": LOG_CRIT,
+ "debug": LOG_DEBUG,
+ "emerg": LOG_EMERG,
+ "err": LOG_ERR,
+ "error": LOG_ERR, # DEPRECATED
+ "info": LOG_INFO,
+ "notice": LOG_NOTICE,
+ "panic": LOG_EMERG, # DEPRECATED
+ "warn": LOG_WARNING, # DEPRECATED
+ "warning": LOG_WARNING,
+ }
+
+facility_names = {
+ "auth": LOG_AUTH,
+ "authpriv": LOG_AUTHPRIV,
+ "cron": LOG_CRON,
+ "daemon": LOG_DAEMON,
+ "kern": LOG_KERN,
+ "lpr": LOG_LPR,
+ "mail": LOG_MAIL,
+ "news": LOG_NEWS,
+ "security": LOG_AUTH, # DEPRECATED
+ "syslog": LOG_SYSLOG,
+ "user": LOG_USER,
+ "uucp": LOG_UUCP,
+ "local0": LOG_LOCAL0,
+ "local1": LOG_LOCAL1,
+ "local2": LOG_LOCAL2,
+ "local3": LOG_LOCAL3,
+ "local4": LOG_LOCAL4,
+ "local5": LOG_LOCAL5,
+ "local6": LOG_LOCAL6,
+ "local7": LOG_LOCAL7,
+ }
+
+import socket
+
+class syslog_client:
+ def __init__ (self, address='/dev/log'):
+ self.address = address
+ self.stream = 0
+ if isinstance(address, type('')):
+ try:
+ self.socket = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+ self.socket.connect(address)
+ except socket.error:
+ # Some Linux installations have /dev/log
+ # a stream socket instead of a datagram socket.
+ self.socket = socket.socket (socket.AF_UNIX,
+ socket.SOCK_STREAM)
+ self.stream = 1
+ else:
+ self.socket = socket.socket (socket.AF_INET, socket.SOCK_DGRAM)
+
+ # curious: when talking to the unix-domain '/dev/log' socket, a
+ # zero-terminator seems to be required. this string is placed
+ # into a class variable so that it can be overridden if
+ # necessary.
+
+ log_format_string = '<%d>%s\000'
+
+ def log (self, message, facility=LOG_USER, priority=LOG_INFO):
+ message = self.log_format_string % (
+ self.encode_priority (facility, priority),
+ message
+ )
+ if self.stream:
+ self.socket.send (message)
+ else:
+ self.socket.sendto (message, self.address)
+
+ def encode_priority (self, facility, priority):
+ if type(facility) == type(''):
+ facility = facility_names[facility]
+ if type(priority) == type(''):
+ priority = priority_names[priority]
+ return (facility<<3) | priority
+
+ def close (self):
+ if self.stream:
+ self.socket.close()
diff --git a/demo/medusa054/medusa_gif.py b/demo/medusa054/medusa_gif.py
new file mode 100644
index 0000000..005644a
--- /dev/null
+++ b/demo/medusa054/medusa_gif.py
@@ -0,0 +1,8 @@
+# -*- Mode: Python -*-
+
+# the medusa icon as a python source file.
+
+width = 97
+height = 61
+
+data = 'GIF89aa\000=\000\204\000\000\000\000\000\255\255\255\245\245\245ssskkkccc111)))\326\326\326!!!\316\316\316\300\300\300\204\204\000\224\224\224\214\214\214\200\200\200RRR\377\377\377JJJ\367\367\367BBB\347\347\347\000\204\000\020\020\020\265\265\265\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000!\371\004\001\000\000\021\000,\000\000\000\000a\000=\000\000\005\376`$\216di\236h\252\256l\353\276p,\317tm\337x\256\357|m\001@\240E\305\000\364\2164\206R)$\005\201\214\007r\012{X\255\312a\004\260\\>\026\3240\353)\224n\001W+X\334\373\231~\344.\303b\216\024\027x<\273\307\255G,rJiWN\014{S}k"?ti\013EdPQ\207G@_%\000\026yy\\\201\202\227\224<\221Fs$pOjWz\241<r@vO\236\231\233k\247M\2544\203F\177\235\236L#\247\256Z\270,\266BxJ[\276\256A]iE\304\305\262\273E\313\201\275i#\\\303\321\'h\203V\\\177\326\276\216\220P~\335\230_\264\013\342\275\344KF\233\360Q\212\352\246\000\367\274s\361\236\334\347T\341;\341\246\2202\177\3142\211`\242o\325@S\202\264\031\252\207\260\323\256\205\311\036\236\270\002\'\013\302\177\274H\010\324X\002\0176\212\037\376\321\360\032\226\207\244\2674(+^\202\346r\205J\0211\375\241Y#\256f\0127\315>\272\002\325\307g\012(\007\205\312#j\317(\012A\200\224.\241\003\346GS\247\033\245\344\264\366\015L\'PXQl]\266\263\243\232\260?\245\316\371\362\225\035\332\243J\273\332Q\263\357-D\241T\327\270\265\013W&\330\010u\371b\322IW0\214\261]\003\033Va\365Z#\207\213a\030k\2647\262\014p\354\024[n\321N\363\346\317\003\037P\000\235C\302\000\3228(\244\363YaA\005\022\255_\237@\260\000A\212\326\256qbp\321\332\266\011\334=T\023\010"!B\005\003A\010\224\020\220 H\002\337#\020 O\276E\357h\221\327\003\\\000b@v\004\351A.h\365\354\342B\002\011\257\025\\ \220\340\301\353\006\000\024\214\200pA\300\353\012\364\241k/\340\033C\202\003\000\310fZ\011\003V\240R\005\007\354\376\026A\000\000\360\'\202\177\024\004\210\003\000\305\215\360\000\000\015\220\240\332\203\027@\'\202\004\025VpA\000%\210x\321\206\032J\341\316\010\262\211H"l\333\341\200\200>"]P\002\212\011\010`\002\0066FP\200\001\'\024p]\004\027(8B\221\306]\000\201w>\002iB\001\007\340\260"v7J1\343(\257\020\251\243\011\242i\263\017\215\337\035\220\200\221\365m4d\015\016D\251\341iN\354\346Ng\253\200I\240\031\35609\245\2057\311I\302\2007t\231"&`\314\310\244\011e\226(\236\010w\212\300\234\011\012HX(\214\253\311@\001\233^\222pg{% \340\035\224&H\000\246\201\362\215`@\001"L\340\004\030\234\022\250\'\015(V:\302\235\030\240q\337\205\224\212h@\177\006\000\250\210\004\007\310\207\337\005\257-P\346\257\367]p\353\203\271\256:\203\236\211F\340\247\010\3329g\244\010\307*=A\000\203\260y\012\304s#\014\007D\207,N\007\304\265\027\021C\233\207%B\366[m\353\006\006\034j\360\306+\357\274a\204\000\000;'
diff --git a/demo/medusa054/poison_handler.py b/demo/medusa054/poison_handler.py
new file mode 100644
index 0000000..acc78ab
--- /dev/null
+++ b/demo/medusa054/poison_handler.py
@@ -0,0 +1,69 @@
+
+import string
+import whrandom
+
+RESP_HEAD="""\
+<HTML><BODY BGCOLOR=\"#ffffff\">
+"""
+
+RESP_MIDDLE="""
+<h2>M2Crypto https server demonstration</h2>
+
+This web page is generated by the "poison" http request handler.
+<br>
+The links just go on and on and on...
+<br><br>
+"""
+
+RESP_TAIL="""
+</BODY></HTML>
+"""
+
+charset='012345678/90ABCDEFGHIJKLM/NOPQRSTUVWXYZabcd/efghijklmnopqrs/tuvwxyz'
+numchar=len(charset)
+
+def makepage(numlinks):
+
+ title='<title>'
+ for u in range(whrandom.randint(3, 15)):
+ pick=whrandom.randint(0, numchar-1)
+ title=title+charset[pick]
+ title=title+'</title>'
+
+ url='\r\n'
+ numlinks=whrandom.randint(2, numlinks)
+ for i in range(numlinks):
+ url=url+'<a href="/poison/'
+ for u in range(whrandom.randint(3, 15)):
+ pick=whrandom.randint(0, numchar-1)
+ ch=charset[pick]
+ if ch=='/' and url[-1]=='/':
+ ch=charset[pick+1]
+ url=url+ch
+ url=url+'/">'
+ for u in range(whrandom.randint(3, 15)):
+ pick=whrandom.randint(0, numchar-1)
+ url=url+charset[pick]
+ url=url+'</a><br>\r\n'
+
+ url=RESP_HEAD+title+RESP_MIDDLE+url+RESP_TAIL
+ return url
+
+
+class poison_handler:
+ """This is a clone of webpoison - every URL returns a page of URLs, each of which
+ returns a page of URLs, each of _which_ returns a page of URLs, ad infinitum.
+ The objective is to sucker address-harvesting bots run by spammers."""
+
+ def __init__(self, numlinks=10):
+ self.numlinks = numlinks
+ self.poison_level = 0
+
+ def match(self, request):
+ return (request.uri[:7] == '/poison')
+
+ def handle_request(self, request):
+ if request.command == 'get':
+ request.push(makepage(self.numlinks))
+ request.done()
+
diff --git a/demo/medusa054/producers.py b/demo/medusa054/producers.py
new file mode 100644
index 0000000..a04906e
--- /dev/null
+++ b/demo/medusa054/producers.py
@@ -0,0 +1,323 @@
+# -*- Mode: Python -*-
+
+"""
+A collection of producers.
+Each producer implements a particular feature: They can be combined
+in various ways to get interesting and useful behaviors.
+
+For example, you can feed dynamically-produced output into the compressing
+producer, then wrap this with the 'chunked' transfer-encoding producer.
+"""
+
+import string
+from asynchat import find_prefix_at_end
+
+class simple_producer:
+ "producer for a string"
+ def __init__ (self, data, buffer_size=1024):
+ self.data = data
+ self.buffer_size = buffer_size
+
+ def more (self):
+ if len (self.data) > self.buffer_size:
+ result = self.data[:self.buffer_size]
+ self.data = self.data[self.buffer_size:]
+ return result
+ else:
+ result = self.data
+ self.data = ''
+ return result
+
+class scanning_producer:
+ "like simple_producer, but more efficient for large strings"
+ def __init__ (self, data, buffer_size=1024):
+ self.data = data
+ self.buffer_size = buffer_size
+ self.pos = 0
+
+ def more (self):
+ if self.pos < len(self.data):
+ lp = self.pos
+ rp = min (
+ len(self.data),
+ self.pos + self.buffer_size
+ )
+ result = self.data[lp:rp]
+ self.pos = self.pos + len(result)
+ return result
+ else:
+ return ''
+
+class lines_producer:
+ "producer for a list of lines"
+
+ def __init__ (self, lines):
+ self.lines = lines
+
+ def more (self):
+ if self.lines:
+ chunk = self.lines[:50]
+ self.lines = self.lines[50:]
+ return string.join (chunk, '\r\n') + '\r\n'
+ else:
+ return ''
+
+class buffer_list_producer:
+ "producer for a list of strings"
+
+ # i.e., data == string.join (buffers, '')
+
+ def __init__ (self, buffers):
+
+ self.index = 0
+ self.buffers = buffers
+
+ def more (self):
+ if self.index >= len(self.buffers):
+ return ''
+ else:
+ data = self.buffers[self.index]
+ self.index = self.index + 1
+ return data
+
+class file_producer:
+ "producer wrapper for file[-like] objects"
+
+ # match http_channel's outgoing buffer size
+ out_buffer_size = 1<<16
+
+ def __init__ (self, file):
+ self.done = 0
+ self.file = file
+
+ def more (self):
+ if self.done:
+ return ''
+ else:
+ data = self.file.read (self.out_buffer_size)
+ if not data:
+ self.file.close()
+ del self.file
+ self.done = 1
+ return ''
+ else:
+ return data
+
+# A simple output producer. This one does not [yet] have
+# the safety feature builtin to the monitor channel: runaway
+# output will not be caught.
+
+# don't try to print from within any of the methods
+# of this object.
+
+class output_producer:
+ "Acts like an output file; suitable for capturing sys.stdout"
+ def __init__ (self):
+ self.data = ''
+
+ def write (self, data):
+ lines = string.splitfields (data, '\n')
+ data = string.join (lines, '\r\n')
+ self.data = self.data + data
+
+ def writeline (self, line):
+ self.data = self.data + line + '\r\n'
+
+ def writelines (self, lines):
+ self.data = self.data + string.joinfields (
+ lines,
+ '\r\n'
+ ) + '\r\n'
+
+ def flush (self):
+ pass
+
+ def softspace (self, *args):
+ pass
+
+ def more (self):
+ if self.data:
+ result = self.data[:512]
+ self.data = self.data[512:]
+ return result
+ else:
+ return ''
+
+class composite_producer:
+ "combine a fifo of producers into one"
+ def __init__ (self, producers):
+ self.producers = producers
+
+ def more (self):
+ while len(self.producers):
+ p = self.producers[0]
+ d = p.more()
+ if d:
+ return d
+ else:
+ self.producers.pop(0)
+ else:
+ return ''
+
+
+class globbing_producer:
+ """
+ 'glob' the output from a producer into a particular buffer size.
+ helps reduce the number of calls to send(). [this appears to
+ gain about 30% performance on requests to a single channel]
+ """
+
+ def __init__ (self, producer, buffer_size=1<<16):
+ self.producer = producer
+ self.buffer = ''
+ self.buffer_size = buffer_size
+
+ def more (self):
+ while len(self.buffer) < self.buffer_size:
+ data = self.producer.more()
+ if data:
+ self.buffer = self.buffer + data
+ else:
+ break
+ r = self.buffer
+ self.buffer = ''
+ return r
+
+
+class hooked_producer:
+ """
+ A producer that will call <function> when it empties,.
+ with an argument of the number of bytes produced. Useful
+ for logging/instrumentation purposes.
+ """
+
+ def __init__ (self, producer, function):
+ self.producer = producer
+ self.function = function
+ self.bytes = 0
+
+ def more (self):
+ if self.producer:
+ result = self.producer.more()
+ if not result:
+ self.producer = None
+ self.function (self.bytes)
+ else:
+ self.bytes = self.bytes + len(result)
+ return result
+ else:
+ return ''
+
+# HTTP 1.1 emphasizes that an advertised Content-Length header MUST be
+# correct. In the face of Strange Files, it is conceivable that
+# reading a 'file' may produce an amount of data not matching that
+# reported by os.stat() [text/binary mode issues, perhaps the file is
+# being appended to, etc..] This makes the chunked encoding a True
+# Blessing, and it really ought to be used even with normal files.
+# How beautifully it blends with the concept of the producer.
+
+class chunked_producer:
+ """A producer that implements the 'chunked' transfer coding for HTTP/1.1.
+ Here is a sample usage:
+ request['Transfer-Encoding'] = 'chunked'
+ request.push (
+ producers.chunked_producer (your_producer)
+ )
+ request.done()
+ """
+
+ def __init__ (self, producer, footers=None):
+ self.producer = producer
+ self.footers = footers
+
+ def more (self):
+ if self.producer:
+ data = self.producer.more()
+ if data:
+ return '%x\r\n%s\r\n' % (len(data), data)
+ else:
+ self.producer = None
+ if self.footers:
+ return string.join (
+ ['0'] + self.footers,
+ '\r\n'
+ ) + '\r\n\r\n'
+ else:
+ return '0\r\n\r\n'
+ else:
+ return ''
+
+# Unfortunately this isn't very useful right now (Aug 97), because
+# apparently the browsers don't do on-the-fly decompression. Which
+# is sad, because this could _really_ speed things up, especially for
+# low-bandwidth clients (i.e., most everyone).
+
+try:
+ import zlib
+except ImportError:
+ zlib = None
+
+class compressed_producer:
+ """
+ Compress another producer on-the-fly, using ZLIB
+ [Unfortunately, none of the current browsers seem to support this]
+ """
+
+ # Note: It's not very efficient to have the server repeatedly
+ # compressing your outgoing files: compress them ahead of time, or
+ # use a compress-once-and-store scheme. However, if you have low
+ # bandwidth and low traffic, this may make more sense than
+ # maintaining your source files compressed.
+ #
+ # Can also be used for compressing dynamically-produced output.
+
+ def __init__ (self, producer, level=5):
+ self.producer = producer
+ self.compressor = zlib.compressobj (level)
+
+ def more (self):
+ if self.producer:
+ cdata = ''
+ # feed until we get some output
+ while not cdata:
+ data = self.producer.more()
+ if not data:
+ self.producer = None
+ return self.compressor.flush()
+ else:
+ cdata = self.compressor.compress (data)
+ return cdata
+ else:
+ return ''
+
+class escaping_producer:
+
+ "A producer that escapes a sequence of characters"
+ " Common usage: escaping the CRLF.CRLF sequence in SMTP, NNTP, etc..."
+
+ def __init__ (self, producer, esc_from='\r\n.', esc_to='\r\n..'):
+ self.producer = producer
+ self.esc_from = esc_from
+ self.esc_to = esc_to
+ self.buffer = ''
+ self.find_prefix_at_end = find_prefix_at_end
+
+ def more (self):
+ esc_from = self.esc_from
+ esc_to = self.esc_to
+
+ buffer = self.buffer + self.producer.more()
+
+ if buffer:
+ buffer = string.replace (buffer, esc_from, esc_to)
+ i = self.find_prefix_at_end (buffer, esc_from)
+ if i:
+ # we found a prefix
+ self.buffer = buffer[-i:]
+ return buffer[:-i]
+ else:
+ # no prefix, return it all
+ self.buffer = ''
+ return buffer
+ else:
+ return buffer
diff --git a/demo/medusa054/server.pem b/demo/medusa054/server.pem
new file mode 100644
index 0000000..1ee9282
--- /dev/null
+++ b/demo/medusa054/server.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/medusa054/status_handler.py b/demo/medusa054/status_handler.py
new file mode 100644
index 0000000..8d2583e
--- /dev/null
+++ b/demo/medusa054/status_handler.py
@@ -0,0 +1,272 @@
+# -*- Mode: Python -*-
+
+VERSION_STRING = "$Id: status_handler.py 299 2005-06-09 17:32:28Z heikki $"
+
+#
+# medusa status extension
+#
+
+import string
+import time
+import re
+from cgi import escape
+
+import asyncore
+import http_server
+import medusa_gif
+import producers
+from counter import counter
+
+START_TIME = long(time.time())
+
+class status_extension:
+ hit_counter = counter()
+
+ def __init__ (self, objects, statusdir='/status', allow_emergency_debug=0):
+ self.objects = objects
+ self.statusdir = statusdir
+ self.allow_emergency_debug = allow_emergency_debug
+ # We use /status instead of statusdir here because it's too
+ # hard to pass statusdir to the logger, who makes the HREF
+ # to the object dir. We don't need the security-through-
+ # obscurity here in any case, because the id is obscurity enough
+ self.hyper_regex = re.compile('/status/object/([0-9]+)/.*')
+ self.hyper_objects = []
+ for object in objects:
+ self.register_hyper_object (object)
+
+ def __repr__ (self):
+ return '<Status Extension (%s hits) at %x>' % (
+ self.hit_counter,
+ id(self)
+ )
+
+ def match (self, request):
+ path, params, query, fragment = request.split_uri()
+ # For reasons explained above, we don't use statusdir for /object
+ return (path[:len(self.statusdir)] == self.statusdir or
+ path[:len("/status/object/")] == '/status/object/')
+
+ # Possible Targets:
+ # /status
+ # /status/channel_list
+ # /status/medusa.gif
+
+ # can we have 'clickable' objects?
+ # [yes, we can use id(x) and do a linear search]
+
+ # Dynamic producers:
+ # HTTP/1.0: we must close the channel, because it's dynamic output
+ # HTTP/1.1: we can use the chunked transfer-encoding, and leave
+ # it open.
+
+ def handle_request (self, request):
+ [path, params, query, fragment] = request.split_uri()
+ self.hit_counter.increment()
+ if path == self.statusdir: # and not a subdirectory
+ up_time = string.join (english_time (long(time.time()) - START_TIME))
+ request['Content-Type'] = 'text/html'
+ request.push (
+ '<html>'
+ '<title>Medusa Status Reports</title>'
+ '<body bgcolor="#ffffff">'
+ '<h1>Medusa Status Reports</h1>'
+ '<b>Up:</b> %s' % up_time
+ )
+ for i in range(len(self.objects)):
+ request.push (self.objects[i].status())
+ request.push ('<hr>\r\n')
+ request.push (
+ '<p><a href="%s/channel_list">Channel List</a>'
+ '<hr>'
+ '<img src="%s/medusa.gif" align=right width=%d height=%d>'
+ '</body></html>' % (
+ self.statusdir,
+ self.statusdir,
+ medusa_gif.width,
+ medusa_gif.height
+ )
+ )
+ request.done()
+ elif path == self.statusdir + '/channel_list':
+ request['Content-Type'] = 'text/html'
+ request.push ('<html><body>')
+ request.push(channel_list_producer(self.statusdir))
+ request.push (
+ '<hr>'
+ '<img src="%s/medusa.gif" align=right width=%d height=%d>' % (
+ self.statusdir,
+ medusa_gif.width,
+ medusa_gif.height
+ ) +
+ '</body></html>'
+ )
+ request.done()
+
+ elif path == self.statusdir + '/medusa.gif':
+ request['Content-Type'] = 'image/gif'
+ request['Content-Length'] = len(medusa_gif.data)
+ request.push (medusa_gif.data)
+ request.done()
+
+ elif path == self.statusdir + '/close_zombies':
+ message = (
+ '<h2>Closing all zombie http client connections...</h2>'
+ '<p><a href="%s">Back to the status page</a>' % self.statusdir
+ )
+ request['Content-Type'] = 'text/html'
+ request['Content-Length'] = len (message)
+ request.push (message)
+ now = int (time.time())
+ for channel in asyncore.socket_map.keys():
+ if channel.__class__ == http_server.http_channel:
+ if channel != request.channel:
+ if (now - channel.creation_time) > channel.zombie_timeout:
+ channel.close()
+ request.done()
+
+ # Emergency Debug Mode
+ # If a server is running away from you, don't KILL it!
+ # Move all the AF_INET server ports and perform an autopsy...
+ # [disabled by default to protect the innocent]
+ elif self.allow_emergency_debug and path == self.statusdir + '/emergency_debug':
+ request.push ('<html>Moving All Servers...</html>')
+ request.done()
+ for channel in asyncore.socket_map.keys():
+ if channel.accepting:
+ if type(channel.addr) is type(()):
+ ip, port = channel.addr
+ channel.socket.close()
+ channel.del_channel()
+ channel.addr = (ip, port+10000)
+ fam, typ = channel.family_and_type
+ channel.create_socket (fam, typ)
+ channel.set_reuse_addr()
+ channel.bind (channel.addr)
+ channel.listen(5)
+
+ else:
+ m = self.hyper_regex.match (path)
+ if m:
+ oid = string.atoi (m.group (1))
+ for object in self.hyper_objects:
+ if id (object) == oid:
+ if hasattr (object, 'hyper_respond'):
+ object.hyper_respond (self, path, request)
+ else:
+ request.error (404)
+ return
+
+ def status (self):
+ return producers.simple_producer (
+ '<li>Status Extension <b>Hits</b> : %s' % self.hit_counter
+ )
+
+ def register_hyper_object (self, object):
+ if not object in self.hyper_objects:
+ self.hyper_objects.append (object)
+
+import logger
+
+class logger_for_status (logger.tail_logger):
+
+ def status (self):
+ return 'Last %d log entries for: %s' % (
+ len (self.messages),
+ html_repr (self)
+ )
+
+ def hyper_respond (self, sh, path, request):
+ request['Content-Type'] = 'text/plain'
+ messages = self.messages[:]
+ messages.reverse()
+ request.push (lines_producer (messages))
+ request.done()
+
+class lines_producer:
+ def __init__ (self, lines):
+ self.lines = lines
+
+ def more (self):
+ if self.lines:
+ chunk = self.lines[:50]
+ self.lines = self.lines[50:]
+ return string.join (chunk, '\r\n') + '\r\n'
+ else:
+ return ''
+
+class channel_list_producer (lines_producer):
+ def __init__ (self, statusdir):
+ channel_reprs = map (
+ lambda x: '&lt;' + repr(x)[1:-1] + '&gt;',
+ asyncore.socket_map.values()
+ )
+ channel_reprs.sort()
+ lines_producer.__init__ (
+ self,
+ ['<h1>Active Channel List</h1>',
+ '<pre>'
+ ] + channel_reprs + [
+ '</pre>',
+ '<p><a href="%s">Status Report</a>' % statusdir
+ ]
+ )
+
+
+def html_repr (object):
+ so = escape (repr (object))
+ if hasattr (object, 'hyper_respond'):
+ return '<a href="/status/object/%d/">%s</a>' % (id (object), so)
+ else:
+ return so
+
+def html_reprs (list, front='', back=''):
+ reprs = map (
+ lambda x,f=front,b=back: '%s%s%s' % (f,x,b),
+ map (lambda x: escape (html_repr(x)), list)
+ )
+ reprs.sort()
+ return reprs
+
+# for example, tera, giga, mega, kilo
+# p_d (n, (1024, 1024, 1024, 1024))
+# smallest divider goes first - for example
+# minutes, hours, days
+# p_d (n, (60, 60, 24))
+
+def progressive_divide (n, parts):
+ result = []
+ for part in parts:
+ n, rem = divmod (n, part)
+ result.append (rem)
+ result.append (n)
+ return result
+
+# b,k,m,g,t
+def split_by_units (n, units, dividers, format_string):
+ divs = progressive_divide (n, dividers)
+ result = []
+ for i in range(len(units)):
+ if divs[i]:
+ result.append (format_string % (divs[i], units[i]))
+ result.reverse()
+ if not result:
+ return [format_string % (0, units[0])]
+ else:
+ return result
+
+def english_bytes (n):
+ return split_by_units (
+ n,
+ ('','K','M','G','T'),
+ (1024, 1024, 1024, 1024, 1024),
+ '%d %sB'
+ )
+
+def english_time (n):
+ return split_by_units (
+ n,
+ ('secs', 'mins', 'hours', 'days', 'weeks', 'years'),
+ ( 60, 60, 24, 7, 52),
+ '%d %s'
+ )
diff --git a/demo/medusa054/xmlrpc_handler.py b/demo/medusa054/xmlrpc_handler.py
new file mode 100644
index 0000000..15057cf
--- /dev/null
+++ b/demo/medusa054/xmlrpc_handler.py
@@ -0,0 +1,103 @@
+# -*- Mode: Python -*-
+
+# See http://www.xml-rpc.com/
+# http://www.pythonware.com/products/xmlrpc/
+
+# Based on "xmlrpcserver.py" by Fredrik Lundh (fredrik@pythonware.com)
+
+VERSION = "$Id: xmlrpc_handler.py 299 2005-06-09 17:32:28Z heikki $"
+
+import http_server
+import xmlrpclib
+
+import string
+import sys
+
+class xmlrpc_handler:
+
+ def match (self, request):
+ # Note: /RPC2 is not required by the spec, so you may override this method.
+ if request.uri[:5] == '/RPC2':
+ return 1
+ else:
+ return 0
+
+ def handle_request (self, request):
+ [path, params, query, fragment] = request.split_uri()
+
+ if request.command == 'POST':
+ request.collector = collector (self, request)
+ else:
+ request.error (400)
+
+ def continue_request (self, data, request):
+ params, method = xmlrpclib.loads (data)
+ try:
+ # generate response
+ try:
+ response = self.call (method, params)
+ if type(response) != type(()):
+ response = (response,)
+ except:
+ # report exception back to server
+ response = xmlrpclib.dumps (
+ xmlrpclib.Fault (1, "%s:%s" % (sys.exc_type, sys.exc_value))
+ )
+ else:
+ response = xmlrpclib.dumps (response, methodresponse=1)
+ except:
+ # internal error, report as HTTP server error
+ request.error (500)
+ else:
+ # got a valid XML RPC response
+ request['Content-Type'] = 'text/xml'
+ request.push (response)
+ request.done()
+
+ def call (self, method, params):
+ # override this method to implement RPC methods
+ raise "NotYetImplemented"
+
+class collector:
+
+ "gathers input for POST and PUT requests"
+
+ def __init__ (self, handler, request):
+
+ self.handler = handler
+ self.request = request
+ self.data = ''
+
+ # make sure there's a content-length header
+ cl = request.get_header ('content-length')
+
+ if not cl:
+ request.error (411)
+ else:
+ cl = string.atoi (cl)
+ # using a 'numeric' terminator
+ self.request.channel.set_terminator (cl)
+
+ def collect_incoming_data (self, data):
+ self.data = self.data + data
+
+ def found_terminator (self):
+ # set the terminator back to the default
+ self.request.channel.set_terminator ('\r\n\r\n')
+ self.handler.continue_request (self.data, self.request)
+
+if __name__ == '__main__':
+
+ class rpc_demo (xmlrpc_handler):
+
+ def call (self, method, params):
+ print 'method="%s" params=%s' % (method, params)
+ return "Sure, that works"
+
+ import asyncore
+
+ hs = http_server.http_server ('', 8000)
+ rpc = rpc_demo()
+ hs.install_handler (rpc)
+
+ asyncore.loop()
diff --git a/demo/perf/memio.py b/demo/perf/memio.py
new file mode 100644
index 0000000..238d85c
--- /dev/null
+++ b/demo/perf/memio.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python2.0
+
+"""A comparison of Python's cStringIO and M2Crypto's MemoryBuffer,
+the outcome of which is that MemoryBuffer suffers from doing too much
+in Python.
+
+Two way to optimise MemoryBuffer:
+1. Create MemoryBufferIn and MemoryBufferOut a la StringI and StringO.
+2. Have MemoryBuffer do all internal work with cStringIO. ;-)
+"""
+
+from cStringIO import StringIO
+from M2Crypto.BIO import MemoryBuffer
+from M2Crypto import m2
+import profile
+
+txt = 'Python, Smalltalk, Haskell, Scheme, Lisp, Self, Erlang, ML, ...'
+
+def stringi(iter, txt=txt):
+ buf = StringIO()
+ for i in range(iter):
+ buf.write(txt)
+ out = buf.getvalue()
+
+def membufi(iter, txt=txt):
+ buf = MemoryBuffer()
+ for i in range(iter):
+ buf.write(txt)
+ out = buf.getvalue()
+
+def membuf2i(iter, txt=txt):
+ buf = MemoryBuffer()
+ buf.write(txt * iter)
+ out = buf.getvalue()
+
+def cmembufi(iter, txt=txt):
+ buf = m2.bio_new(m2.bio_s_mem())
+ for i in range(iter):
+ m2.bio_write(buf, txt)
+ m2.bio_set_mem_eof_return(buf, 0)
+ out = m2.bio_read(buf, m2.bio_ctrl_pending(buf))
+
+if __name__ == '__main__':
+ profile.run('stringi(10000)')
+ profile.run('cmembufi(10000)')
+ profile.run('membufi(10000)')
+ profile.run('membuf2i(10000)')
+
+
diff --git a/demo/perf/sha1.py b/demo/perf/sha1.py
new file mode 100644
index 0000000..6d85289
--- /dev/null
+++ b/demo/perf/sha1.py
@@ -0,0 +1,46 @@
+#!/usr/bin/env python2.0
+
+"""A comparison of Python's sha and M2Crypto.EVP.MessageDigest,
+the outcome of which is that EVP.MessageDigest suffers from doing
+too much in Python."""
+
+import profile
+
+from sha import sha
+import M2Crypto
+from M2Crypto import m2
+from M2Crypto.EVP import MessageDigest
+
+txt = 'Python, Smalltalk, Haskell, Scheme, Lisp, Self, Erlang, ML, ...'
+
+def py_sha(iter, txt=txt):
+ s = sha()
+ for i in range(iter):
+ s.update(txt)
+ out = s.digest()
+
+def m2_sha(iter, txt=txt):
+ s = MessageDigest('sha1')
+ for i in range(iter):
+ s.update(txt)
+ out = s.digest()
+
+def m2_sha_2(iter, txt=txt):
+ s = MessageDigest('sha1')
+ s.update(txt * iter)
+ out = s.digest()
+
+def m2c_sha(iter, txt=txt):
+ ctx = m2.md_ctx_new()
+ m2.digest_init(ctx, m2.sha1())
+ for i in range(iter):
+ m2.digest_update(ctx, txt)
+ out = m2.digest_final(ctx)
+
+if __name__ == '__main__':
+ profile.run('py_sha(10000)')
+ profile.run('m2_sha(10000)')
+ profile.run('m2_sha_2(10000)')
+ profile.run('m2c_sha(10000)')
+
+
diff --git a/demo/pgp/pgpstep.py b/demo/pgp/pgpstep.py
new file mode 100644
index 0000000..ff5b135
--- /dev/null
+++ b/demo/pgp/pgpstep.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python
+
+"""pgpstep - steps through a pgp2 packet stream.
+
+Copyright (c) 1999 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import PGP, util
+import time
+
+def desc_public_key(pkt):
+ print 'packet = public_key'
+ print 'version =', pkt.version()
+ print 'created = ', time.asctime(time.gmtime(pkt.timestamp()))
+ print 'validity code =', pkt.validity()
+ print 'pkc type =', `pkt.pkc()`
+ #e, n = pkt.pubkey()
+ print 'e =', `pkt._e`
+ print 'n =', `pkt._n`
+ print
+
+def desc_trust(pkt):
+ print 'packet = trust'
+ print 'trustworthiness = <ignored>'
+ print
+
+def desc_userid(pkt):
+ print 'packet = user_id'
+ print 'user_id =', pkt.userid()
+ print
+
+def desc_signature(pkt):
+ print 'packet = signature'
+ print 'version =', pkt.version()
+ print 'classification =', `pkt._classification`
+ print 'created = ', time.asctime(time.gmtime(pkt.timestamp()))
+ print 'keyid =', `pkt._keyid`
+ print 'pkc type =', `pkt.pkc()`
+ print 'md_algo =', `pkt._md_algo`
+ print 'md_chksum =', `pkt._md_chksum`
+ print 'sig =', `pkt._sig`
+ print
+
+def desc_private_key(pkt):
+ print 'packet = private key'
+ print 'version =', pkt.version()
+ print 'created = ', time.asctime(time.gmtime(pkt.timestamp()))
+ print 'validity code =', pkt.validity()
+ print 'pkc type =', `pkt.pkc()`
+ print 'e =', `pkt._e`
+ print 'n =', `pkt._n`
+ print 'cipher =', `pkt._cipher`
+ if pkt._cipher == '\001':
+ print 'following attributes are encrypted'
+ print 'iv =', `pkt._iv`
+ print 'd =', `pkt._d`
+ print 'p =', `pkt._p`
+ print 'q =', `pkt._q`
+ print 'u =', `pkt._u`
+ print 'checksum =', `pkt._cksum`
+ print
+
+def desc_cke(pkt):
+ print 'packet = cke'
+ print 'iv =', `pkt.iv`
+ print 'checksum =', `pkt.cksum`
+ print 'ciphertext =', `pkt.ctxt`
+ print
+
+def desc_pke(pkt):
+ print 'packet = pke'
+ print 'version =', pkt.version
+ print 'keyid =', `pkt.keyid`
+ print 'pkc type =', pkt.pkc_type
+ print 'dek =', hex(pkt.dek)[:-1]
+ print
+
+def desc_literal(pkt):
+ print 'packet = literal data'
+ print 'mode =', `pkt.fmode`
+ print 'filename =', pkt.fname
+ print 'time = ', time.asctime(time.gmtime(pkt.ftime))
+ print 'data = <%d octets of literal data>' % (len(pkt.data),)
+ print
+
+DESC = {
+ PGP.public_key_packet: desc_public_key,
+ PGP.trust_packet: desc_trust,
+ PGP.userid_packet: desc_userid,
+ PGP.signature_packet: desc_signature,
+ PGP.private_key_packet: desc_private_key,
+ PGP.cke_packet: desc_cke,
+ PGP.pke_packet: desc_pke,
+ PGP.literal_packet: desc_literal,
+}
+
+if __name__ == '__main__':
+ import sys
+ count = 0
+ for arg in sys.argv[1:]:
+ f = open(arg, 'rb')
+ ps = PGP.packet_stream(f)
+ while 1:
+ pkt = ps.read()
+ if pkt is None:
+ break
+ elif pkt:
+ print '-'*70
+ DESC[pkt.__class__](pkt)
+ count = count + ps.count()
+ ps.close()
+ print '-'*70
+ print 'Total octets processed =', count
+
diff --git a/demo/pgp/pubring.pgp b/demo/pgp/pubring.pgp
new file mode 100644
index 0000000..fbe4863
--- /dev/null
+++ b/demo/pgp/pubring.pgp
Binary files differ
diff --git a/demo/pgp/secring.pgp b/demo/pgp/secring.pgp
new file mode 100644
index 0000000..7eca123
--- /dev/null
+++ b/demo/pgp/secring.pgp
Binary files differ
diff --git a/demo/pkcs7/pkcs7-thawte.pem b/demo/pkcs7/pkcs7-thawte.pem
new file mode 100644
index 0000000..a444627
--- /dev/null
+++ b/demo/pkcs7/pkcs7-thawte.pem
@@ -0,0 +1,45 @@
+-----BEGIN PKCS7-----
+MIAGCSqGSIb3DQEHAqCAMIIH1wIBATELMAkGBSsOAwIaBQAwgAYJKoZIhvcNAQcB
+AACgggXgMIICxDCCAi2gAwIBAgIDAphPMA0GCSqGSIb3DQEBBAUAMIGUMQswCQYD
+VQQGEwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRQwEgYDVQQHEwtEdXJiYW52
+aWxsZTEPMA0GA1UEChMGVGhhd3RlMR0wGwYDVQQLExRDZXJ0aWZpY2F0ZSBTZXJ2
+aWNlczEoMCYGA1UEAxMfUGVyc29uYWwgRnJlZW1haWwgUlNBIDE5OTkuOS4xNjAe
+Fw0wMDA1MTgxMTA4MTJaFw0wMTA1MTgxMTA4MTJaMGUxETAPBgNVBAQTCFN0cm9l
+ZGVyMRAwDgYDVQQqEwdNaWNoYWVsMRkwFwYDVQQDExBNaWNoYWVsIFN0cm9lZGVy
+MSMwIQYJKoZIhvcNAQkBFhRtaWNoYWVsQHN0cm9lZGVyLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAqzxf+ZpWoELOD7sXes/G9O6dSCkGeNL/G5l0k/b4
+C9QnZyEh2S0vWI+g43G1uGhu6sgI5Y1SfrG08xGIUEcmexxnl6krTR/QlHr+t9If
+UXFTmEPAnuIpfPRUqhOQlNFB1DfwKVLb4J3b2947ulv4vpb4owVMC+WjDM5fo765
+RXMCAwEAAaNSMFAwHwYDVR0RBBgwFoEUbWljaGFlbEBzdHJvZWRlci5jb20wDAYD
+VR0TAQH/BAIwADAfBgNVHSMEGDAWgBSIq/Fgg2ZV9ORYx0YdwGG9I9fDjDANBgkq
+hkiG9w0BAQQFAAOBgQCxx7Uvsnq1F/yOIPiUmlaTXLFAvvTj2trHAF4UFhkQGe6V
+IBHmDk62H1xJhA4AHNVz3Pe250S8Fu75OgOh2pRJ8KfB6cJU0gxz9ahRt48Qy6+m
+XUHk9135ru2u8I505nj4i37T6zK3+O/lZU+P79qdPHYwO6dNsYyaRFYLxGyXTjCC
+AxQwggJ9oAMCAQICAQswDQYJKoZIhvcNAQEEBQAwgdExCzAJBgNVBAYTAlpBMRUw
+EwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEaMBgGA1UE
+ChMRVGhhd3RlIENvbnN1bHRpbmcxKDAmBgNVBAsTH0NlcnRpZmljYXRpb24gU2Vy
+dmljZXMgRGl2aXNpb24xJDAiBgNVBAMTG1RoYXd0ZSBQZXJzb25hbCBGcmVlbWFp
+bCBDQTErMCkGCSqGSIb3DQEJARYccGVyc29uYWwtZnJlZW1haWxAdGhhd3RlLmNv
+bTAeFw05OTA5MTYxNDAxNDBaFw0wMTA5MTUxNDAxNDBaMIGUMQswCQYDVQQGEwJa
+QTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRQwEgYDVQQHEwtEdXJiYW52aWxsZTEP
+MA0GA1UEChMGVGhhd3RlMR0wGwYDVQQLExRDZXJ0aWZpY2F0ZSBTZXJ2aWNlczEo
+MCYGA1UEAxMfUGVyc29uYWwgRnJlZW1haWwgUlNBIDE5OTkuOS4xNjCBnzANBgkq
+hkiG9w0BAQEFAAOBjQAwgYkCgYEAs2lal9TQFgt6tcVd6SGcI3LNEkxL937Px/vK
+ciT0QlKsV5Xje2F6F4Tn/XI5OJS06u1lp5IGXr3gZfYZu5R5dkw+uWhwdYQc9BF0
+ALwFLE8JAxcxzPRB1HLGpl3iiESwiy7ETfHw1oU+bPOVlHiRfkDpnNGNFVeOwnPl
+MN5G9U8CAwEAAaM3MDUwEgYDVR0TAQH/BAgwBgEB/wIBADAfBgNVHSMEGDAWgBRy
+ScJzNMZV9At2coF+d/SH58ayDjANBgkqhkiG9w0BAQQFAAOBgQBrxlnpMfrptuyx
+A9jfcnL+kWBI6sZV3XvwZ47GYXDnbcKlN9idtxcoVgWL3Vx1b8aRkMZsZnET0BB8
+a5FvhuAhNi3B1+qyCa3PLW3Gg1Kb+7v+nIed/LfpdJLkXJeu/H6syg1vcnpnLGtz
+9Yb5nfUAbvQdB86dnoJjKe+TCX5V3jGCAdAwggHMAgEBMIGcMIGUMQswCQYDVQQG
+EwJaQTEVMBMGA1UECBMMV2VzdGVybiBDYXBlMRQwEgYDVQQHEwtEdXJiYW52aWxs
+ZTEPMA0GA1UEChMGVGhhd3RlMR0wGwYDVQQLExRDZXJ0aWZpY2F0ZSBTZXJ2aWNl
+czEoMCYGA1UEAxMfUGVyc29uYWwgRnJlZW1haWwgUlNBIDE5OTkuOS4xNgIDAphP
+MAkGBSsOAwIaBQCggYowGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG
+9w0BCQUxDxcNMDAwNzE2MTQ0MzQ0WjAjBgkqhkiG9w0BCQQxFgQU2jmj7l5rSw0y
+Vb/vlWAYkK/YBwkwKwYJKoZIhvcNAQkPMR4wHDAKBggqhkiG9w0DBzAOBggqhkiG
+9w0DAgICAIAwDQYJKoZIhvcNAQEBBQAEgYBagl1y4nRwW+GLhZ5tElkh9Z/yFirz
+ZXI0jMywML/ADxsAf1yV2JIYCCk0V33QdrYy8MVqgErGXxGdA5aKnm5545Wf4Noz
+CLWF4FJc6RDSFp7dv2631TrswdFRRCHNds+dvP5v5EAiADvmEOYaDiWM07jp73jx
+TkkzxW9cevZDaQAAAAA=
+-----END PKCS7-----
diff --git a/demo/pkcs7/test.py b/demo/pkcs7/test.py
new file mode 100644
index 0000000..afa7477
--- /dev/null
+++ b/demo/pkcs7/test.py
@@ -0,0 +1,6 @@
+from M2Crypto import BIO, SMIME
+
+pf = BIO.openfile('pkcs7-thawte.pem')
+p7 = SMIME.load_pkcs7_bio(pf)
+print p7.type(1)
+
diff --git a/demo/rsa.priv.pem b/demo/rsa.priv.pem
new file mode 100644
index 0000000..ed34db6
--- /dev/null
+++ b/demo/rsa.priv.pem
@@ -0,0 +1,9 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOwIBAAJBANQNY7RD9BarYRsmMazM1hd7a+u3QeMPFZQ7Ic+BmmeWHvvVP4Yj
+yu1t6vAut7mKkaDeKbT3yiGVUgAEUaWMXqECAwEAAQJAIHCz8h37N4ScZHThYJgt
+oIYHKpZsg/oIyRaKw54GKxZq5f7YivcWoZ8j7IQ65lHVH3gmaqKOvqdAVVt5imKZ
+KQIhAPPsr9i3FxU+Mac0pvQKhFVJUzAFfKiG3ulVUdHgAaw/AiEA3ozHKzfZWKxH
+gs8v8ZQ/FnfI7DwYYhJC0YsXb6NSvR8CIHymwLo73mTxsogjBQqDcVrwLL3GoAyz
+V6jf+/8HvXMbAiEAj1b3FVQEboOQD6WoyJ1mQO9n/xf50HjYhqRitOnp6ZsCIQDS
+AvkvYKc6LG8IANmVv93g1dyKZvU/OQkAZepqHZB2MQ==
+-----END RSA PRIVATE KEY-----
diff --git a/demo/rsa.priv0.pem b/demo/rsa.priv0.pem
new file mode 100644
index 0000000..e2b93a1
--- /dev/null
+++ b/demo/rsa.priv0.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-CBC,244DDAFE6F991A53
+
+h1/NnMxq2ku9UkBG2tfM7hCFWmOvwMbVQBYgS4jLm3ENvWRgkm7pCqjCug99Uo5o
+r+NhzqyvSBH+eX3ojVAfpMxkL0TIjMrogRq2TD75v6hTGu/fd4Yrw7vZaKRbLzoh
+rG6m7zdbiQwNyh0bTbbo3WmQ07FXkXrDqihLaZTJOvHNLS1lKwRjIS0MqtyhOfPj
+NNwuNEs6AFz4k6UxNMRXhyU2SXn5SOgZZB12SCIsYA034rwKFKqNRoneaLSlhtut
+1z/BlJaLiZDHJmtxtgz5h3Ss9fQ9J0pLnybx+EM70TPM3hz47/8cQiEYIVg7+QFW
+jNYaGIA6IOcyB7fwX5e/zFy5yXAsmuIw/iP0sYa29hdUG++T8Mp2knrBBwQO77OS
+WRz219dMQQRyyn2jpN5x23AZyz4gHj4YKMm3KO06Y2k=
+-----END RSA PRIVATE KEY-----
+
+This is the same key as rsa.priv.pem. Passphrase is 'qwerty'.
+
diff --git a/demo/rsa.pub.pem b/demo/rsa.pub.pem
new file mode 100644
index 0000000..42379aa
--- /dev/null
+++ b/demo/rsa.pub.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANQNY7RD9BarYRsmMazM1hd7a+u3QeMP
+FZQ7Ic+BmmeWHvvVP4Yjyu1t6vAut7mKkaDeKbT3yiGVUgAEUaWMXqECAwEAAQ==
+-----END PUBLIC KEY-----
diff --git a/demo/rsa1024pvtkey.pem b/demo/rsa1024pvtkey.pem
new file mode 100644
index 0000000..481f191
--- /dev/null
+++ b/demo/rsa1024pvtkey.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDE3rnXDvA4lrIUj7ZXQM2ODLw0NnjKXh7+O3GPttvoFoacK1qk
+BRO+aO4hPhvQumySTz+N2RkIsT9PTx6yfyWWwYC1zbbv2cxlCBisGAvrUSL6IfHX
+ND2/vVN+QmOd/b4hfC/ekRo2ylrnX0pOXv/JxiblV9WumiZsBEuhUvxCnQIDAQAB
+AoGBALdaLEDkM8ywZQiLVCptO0RaDgqe1N68zCbBXCGaD7NXD2VxZ0itRdcnyOiC
+/MroZWfakPleQVd8JNeLe66IhosAJd7Zbjf/npYnD4zD1x54zRrSuBquXhz+ev7U
+tZONPxU5QCf/aow4z1s/M+knLyy+0lAUlWwTgOrd5VCQ8suBAkEA5/v48HZW8pQC
+2zxQUFfsnRu6DxJuQ1UvWp51FNUYZM2LjpwgGyCNkCw+nuxPgM1HnOTAMVXkZdwL
+7XG0Gm5ZoQJBANlAKKUc7ItOfN5TeD154Qz9FLtZUgWnWfawvEqiHJ0cPrzyv9v6
+wu19QxP6ORZBWyr8c20/p2fNrk+7YOZAH30CQQDZ9p1HEWlQMlEcu+aaFoJyewKt
+9pszGG6NriRDlpR84cMmEvr3gfaAZ5HOsCli031dpHAP6qvWKJHsXtDhpJ0BAkBs
+fANP4A+myLzF8Hx8hl4BNGej3kh9FkJwU3TS9/y935rck4OG/8NTAFf8o9jZ6izy
+XDnvdffMeALxQapzj9WpAkBQt2yjU3V3bKkNl0XwTk/VK8WGEWTBF1byZv/6VZOb
+q/vOPSexrQmu+T8+lljraJISfIpMSWZhR4oarmnGVRPX
+-----END RSA PRIVATE KEY-----
diff --git a/demo/rsa_bench.py b/demo/rsa_bench.py
new file mode 100644
index 0000000..b0adacc
--- /dev/null
+++ b/demo/rsa_bench.py
@@ -0,0 +1,183 @@
+#!/usr/bin/env python
+
+"""
+ RSA signing demo and benchmark.
+
+ Usage: python -O rsa_bench.py [option option option ...]
+ where options may include:
+ makenewkey showdigest showprofile
+ md5 sha1 sha256 sha512
+ <key length>
+
+ Larry Bugbee
+ November 2006
+
+
+ Some portions are Copyright (c) 1999-2003 Ng Pheng Siong.
+ All rights reserved.
+
+ Portions created by Open Source Applications Foundation
+ (OSAF) are Copyright (C) 2004 OSAF. All Rights Reserved.
+
+"""
+
+from M2Crypto import RSA, EVP, Rand
+from M2Crypto.EVP import MessageDigest
+import sys, base64
+
+# --------------------------------------------------------------
+# program parameters
+
+makenewkey = 0 # 1 = make/save new key, 0 = use existing
+showpubkey = 0 # 1 = show the public key value
+showdigest = 0 # 1 = show the digest value
+showprofile = 0 # 1 = use the python profiler
+
+hashalgs = ['md5', 'ripemd160', 'sha1',
+ 'sha224', 'sha256', 'sha384', 'sha512']
+
+# default hashing algorithm
+hashalg = 'sha1'
+
+# default key parameters
+keylen = 1024
+exponent = 65537
+'''
+ There is some temptation to use an RSA exponent of 3
+ because 1) it is easy to remember and 2) it minimizes the
+ effort of signature verification. Unfortunately there
+ a couple of attacks based on the use of 3. From a draft
+ RFC (Easklake, Dec 2000):
+ A public exponent of 3 minimizes the effort needed to
+ verify a signature. Use of 3 as the public exponent is
+ weak for confidentiality uses since, if the same data
+ can be collected encrypted under three different keys
+ with an exponent of 3 then, using the Chinese Remainder
+ Theorem [NETSEC], the original plain text can be easily
+ recovered.
+ This applies to confidentiality so it is not of major
+ concern here. The second attack is a protocol implementation
+ weakness and can be patched, but has the patch been applied?
+ ...correctly? It is arguably better to get into the habit
+ of using a stronger exponent and avoiding these and possible
+ future attacks based on 3. I suggest getting in the habit
+ of using something stronger. Some suggest using 65537.
+'''
+
+# number of speed test loops
+N1 = N2 = 100
+
+# --------------------------------------------------------------
+# functions
+
+def test(rsa, dgst):
+ print ' testing signing and verification...',
+ try:
+ sig = rsa.sign(dgst)
+ except Exception, e:
+ print '\n\n *** %s *** \n' % e
+ sys.exit()
+ if not rsa.verify(dgst, sig):
+ print 'not ok'
+ else:
+ print 'ok'
+
+def test_asn1(rsa, dgst):
+ print ' testing asn1 signing and verification...',
+ blob = rsa.sign_asn1(dgst)
+ if not rsa.verify_asn1(dgst, blob):
+ print 'not ok'
+ else:
+ print 'ok'
+
+def speed():
+ from time import time
+ t1 = time()
+ for i in range(N1):
+ sig = rsa.sign(dgst)
+ print ' %d signings: %8.2fs' % (N1, (time() - t1))
+ t1 = time()
+ for i in range(N2):
+ rsa.verify(dgst, sig)
+ print ' %d verifications: %8.2fs' % (N2, (time() - t1))
+
+def test_speed(rsa, dgst):
+ print ' measuring speed...'
+ if showprofile:
+ import profile
+ profile.run('speed()')
+ else:
+ speed()
+ print
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+def main(keylen, hashalg):
+ global rsa, dgst # this exists ONLY for speed testing
+
+ Rand.load_file('randpool.dat', -1)
+
+ pvtkeyfilename = 'rsa%dpvtkey.pem' % (keylen)
+ pubkeyfilename = 'rsa%dpubkey.pem' % (keylen)
+
+ if makenewkey:
+ print ' making and saving a new key'
+ rsa = RSA.gen_key(keylen, exponent)
+ rsa.save_key(pvtkeyfilename, None ) # no pswd callback
+ rsa.save_pub_key(pubkeyfilename)
+ else:
+ print ' loading an existing key'
+ rsa = RSA.load_key(pvtkeyfilename)
+ print ' rsa key length:', len(rsa)
+
+ if not rsa.check_key():
+ raise 'key is not initialised'
+
+ # since we are testing signing and verification, let's not
+ # be fussy about the digest. Just make one.
+ md = EVP.MessageDigest(hashalg)
+ md.update('can you spell subliminal channel?')
+ dgst = md.digest()
+ print ' hash algorithm: %s' % hashalg
+ if showdigest:
+ print ' %s digest: \n%s' % (hashalg, base64.encodestring(dgst))
+
+ test(rsa, dgst)
+# test_asn1(rsa, dgst)
+ test_speed(rsa, dgst)
+ Rand.save_file('randpool.dat')
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+def print_usage():
+ print """
+ Usage: python -O %s [option option option ...]
+ where options may include:
+ makenewkey showdigest showprofile
+ md5 sha1 sha256 sha512
+ <key length>
+""" % sys.argv[0]
+ sys.exit()
+
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+
+if __name__=='__main__':
+ for arg in sys.argv[1:]:
+ if arg in hashalgs: hashalg = arg; continue
+ if arg == 'makenewkey': makenewkey = 1; continue
+ if arg == 'showpubkey': showpubkey = 1; continue
+ if arg == 'showdigest': showdigest = 1; continue
+ if arg == 'showprofile': showprofile = 1; continue
+ try:
+ keylen = int(arg)
+ except:
+ print '\n *** argument "%s" not understood ***' % arg
+ print_usage()
+
+ main(keylen, hashalg)
+
+
+# --------------------------------------------------------------
+# --------------------------------------------------------------
+# --------------------------------------------------------------
diff --git a/demo/rsatest.py b/demo/rsatest.py
new file mode 100644
index 0000000..4277c4d
--- /dev/null
+++ b/demo/rsatest.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+
+"""RSA demonstration.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import RSA, EVP, Rand
+
+msg="The magic words are squeamish ossifrage."
+sha1=EVP.MessageDigest('sha1')
+sha1.update(msg)
+dgst=sha1.digest()
+
+priv=RSA.load_key('rsa.priv.pem')
+pub=RSA.load_pub_key('rsa.pub.pem')
+
+def test_encrypt(padding):
+ print 'testing public-key encryption:', padding
+ padding=eval('RSA.'+padding)
+ ctxt=pub.public_encrypt(dgst, padding)
+ ptxt=priv.private_decrypt(ctxt, padding)
+ if ptxt!=dgst:
+ print 'public_encrypt -> private_decrypt: not ok'
+
+def test_sign(padding):
+ print 'testing private-key signing:', padding
+ padding=eval('RSA.'+padding)
+ ctxt=priv.private_encrypt(dgst, padding)
+ ptxt=pub.public_decrypt(ctxt, padding)
+ if ptxt!=dgst:
+ print 'private_decrypt -> public_encrypt: not ok'
+
+def test0():
+ print 'testing misc.'
+ print `pub.e`, `pub.n`
+ print `priv.e`, `priv.n`
+
+if __name__=='__main__':
+ Rand.load_file('randpool.dat', -1)
+ test_encrypt('pkcs1_padding')
+ test_encrypt('pkcs1_oaep_padding')
+ #test_encrypt('sslv23_padding')
+ test_sign('pkcs1_padding')
+ Rand.save_file('randpool.dat')
+
diff --git a/demo/smime.howto/README b/demo/smime.howto/README
new file mode 100644
index 0000000..2b494d7
--- /dev/null
+++ b/demo/smime.howto/README
@@ -0,0 +1,5 @@
+ 01 Apr 2001
+-------------
+
+These are the example programs from the HOWTO.
+
diff --git a/demo/smime.howto/decrypt.py b/demo/smime.howto/decrypt.py
new file mode 100644
index 0000000..617f4ab
--- /dev/null
+++ b/demo/smime.howto/decrypt.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+"""S/MIME HOWTO demo program.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, SMIME, X509
+
+# Instantiate an SMIME object.
+s = SMIME.SMIME()
+
+# Load private key and cert.
+s.load_key('recipient_key.pem', 'recipient.pem')
+
+# Load the encrypted data.
+p7, data = SMIME.smime_load_pkcs7('encrypt.p7')
+
+# Decrypt p7.
+out = s.decrypt(p7)
+
+print out
+
diff --git a/demo/smime.howto/dv.py b/demo/smime.howto/dv.py
new file mode 100644
index 0000000..94223a8
--- /dev/null
+++ b/demo/smime.howto/dv.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+"""S/MIME HOWTO demo program.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, SMIME, X509
+
+# Instantiate an SMIME object.
+s = SMIME.SMIME()
+
+# Load private key and cert.
+s.load_key('recipient_key.pem', 'recipient.pem')
+
+# Load the signed/encrypted data.
+p7, data = SMIME.smime_load_pkcs7('se.p7')
+
+# After the above step, 'data' == None.
+# Decrypt p7. 'out' now contains a PKCS #7 signed blob.
+out = s.decrypt(p7)
+
+# Load the signer's cert.
+x509 = X509.load_cert('signer.pem')
+sk = X509.X509_Stack()
+sk.push(x509)
+s.set_x509_stack(sk)
+
+# Load the signer's CA cert. In this case, because the signer's
+# cert is self-signed, it is the signer's cert itself.
+st = X509.X509_Store()
+st.load_info('signer.pem')
+s.set_x509_store(st)
+
+# Recall 'out' contains a PKCS #7 blob.
+# Transform 'out'; verify the resulting PKCS #7 blob.
+p7_bio = BIO.MemoryBuffer(out)
+p7, data = SMIME.smime_load_pkcs7_bio(p7_bio)
+v = s.verify(p7)
+
+print v
+
diff --git a/demo/smime.howto/encrypt.p7 b/demo/smime.howto/encrypt.p7
new file mode 100644
index 0000000..eb8b8a5
--- /dev/null
+++ b/demo/smime.howto/encrypt.p7
@@ -0,0 +1,17 @@
+From: sender@example.dom
+To: recipient@example.dom
+MIME-Version: 1.0
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+
+MIIBVwYJKoZIhvcNAQcDoIIBSDCCAUQCAQAxggEAMIH9AgEAMGYwYTELMAkGA1UE
+BhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRkwFwYDVQQDExBTL01JTUUgUmVjaXBp
+ZW50MSQwIgYJKoZIhvcNAQkBFhVyZWNpcGllbnRAZXhhbXBsZS5kb20CAQAwDQYJ
+KoZIhvcNAQEBBQAEgYCdBULuQ/kfjgk17dMwBHdY+vXfqR/B+vJnF0gXhK6gc5d4
+gCkRefEyVu7lGAD2MOdD0cOLFvHdmGHFJqyJ8T+A1mmqrxMAiJImtqN586YnoTUb
+Cw5FaMEd20d6hpGeEVBFHkam0vQKdR1GLG1VPiFn+ytB06g7NNphsI5/uFGyLDA7
+BgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECPfuW3hoPVr0gBiA4CiFG4m74CT4wHPu
+q1fhEkG3zjraxn4=
+
+
diff --git a/demo/smime.howto/encrypt.py b/demo/smime.howto/encrypt.py
new file mode 100644
index 0000000..ae37526
--- /dev/null
+++ b/demo/smime.howto/encrypt.py
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+"""S/MIME HOWTO demo program.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME, X509
+
+def makebuf(text):
+ return BIO.MemoryBuffer(text)
+
+# Make a MemoryBuffer of the message.
+buf = makebuf('a sign of our times')
+
+# Seed the PRNG.
+Rand.load_file('randpool.dat', -1)
+
+# Instantiate an SMIME object.
+s = SMIME.SMIME()
+
+# Load target cert to encrypt to.
+x509 = X509.load_cert('recipient.pem')
+sk = X509.X509_Stack()
+sk.push(x509)
+s.set_x509_stack(sk)
+
+# Set cipher: 3-key triple-DES in CBC mode.
+s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+
+# Encrypt the buffer.
+p7 = s.encrypt(buf)
+
+# Output p7 in mail-friendly format.
+out = BIO.MemoryBuffer()
+out.write('From: sender@example.dom\n')
+out.write('To: recipient@example.dom\n')
+out.write('Subject: M2Crypto S/MIME testing\n')
+s.write(out, p7)
+
+print out.read()
+
+# Save the PRNG's state.
+Rand.save_file('randpool.dat')
+
diff --git a/demo/smime.howto/recipient.pem b/demo/smime.howto/recipient.pem
new file mode 100644
index 0000000..78b6c14
--- /dev/null
+++ b/demo/smime.howto/recipient.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC9zCCAmCgAwIBAgIBADANBgkqhkiG9w0BAQQFADBhMQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xGTAXBgNVBAMTEFMvTUlNRSBSZWNpcGllbnQxJDAi
+BgkqhkiG9w0BCQEWFXJlY2lwaWVudEBleGFtcGxlLmRvbTAeFw0wMTAzMzExMTQy
+MTVaFw0wMjAzMzExMTQyMTVaMGExCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNy
+eXB0bzEZMBcGA1UEAxMQUy9NSU1FIFJlY2lwaWVudDEkMCIGCSqGSIb3DQEJARYV
+cmVjaXBpZW50QGV4YW1wbGUuZG9tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
+gQDQwLwQSshbTn/GUZXnZUQEUDc61OUd+qcPpHfi76Y7ar+2NwsalQ3bu2i7edEK
+IZZWMFRnrOwE9PhmJHJIzfYDswgpHWtRy0P/Oyzt5kLBjvJYuMIqu8gZtWFz0G28
+Q8tGvIuPdWba+9TT3LOv4CXNF1V0k0KgAPd1Uq2FUcBa2QIDAQABo4G+MIG7MB0G
+A1UdDgQWBBQe7b4CDEBuMJyiscil27YBdZBr9zCBiwYDVR0jBIGDMIGAgBQe7b4C
+DEBuMJyiscil27YBdZBr96FlpGMwYTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0y
+Q3J5cHRvMRkwFwYDVQQDExBTL01JTUUgUmVjaXBpZW50MSQwIgYJKoZIhvcNAQkB
+FhVyZWNpcGllbnRAZXhhbXBsZS5kb22CAQAwDAYDVR0TBAUwAwEB/zANBgkqhkiG
+9w0BAQQFAAOBgQAJbQXzP7AK9u2kyvl8oeZAeJQDRsip4PVMkYd64HW3Hq/9ud3g
+hj/laeUyfcga+c1c1yPUug5Ab+loeHhEEKsL9LqYFXzpFU1lXaID02zcqG7g3PWe
+r9RKsUqrn4ZbRQ+clidnIx4nYLuG6CPQ6ME/uFrYHMsmQEO/+KoJONf/cg==
+-----END CERTIFICATE-----
diff --git a/demo/smime.howto/recipient_key.pem b/demo/smime.howto/recipient_key.pem
new file mode 100644
index 0000000..0cb61f6
--- /dev/null
+++ b/demo/smime.howto/recipient_key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDQwLwQSshbTn/GUZXnZUQEUDc61OUd+qcPpHfi76Y7ar+2Nwsa
+lQ3bu2i7edEKIZZWMFRnrOwE9PhmJHJIzfYDswgpHWtRy0P/Oyzt5kLBjvJYuMIq
+u8gZtWFz0G28Q8tGvIuPdWba+9TT3LOv4CXNF1V0k0KgAPd1Uq2FUcBa2QIDAQAB
+AoGAVOMrFozycH65YtHmXVRGlmJwMxJDoS8+JBRDVBsTw/Gix9wWPdcC7amF60ac
+BLynv6Cjkg01ZMahBBgqCQUH1rii6Kg20MJJtpqvt1X+CAZkytVsQqwutSQXHj+g
+TzZVDxQiuPKMyVhKTSVqutqs2EyFgSKcYuodfms5xDk2EyECQQD6vnEAl2PHBoia
+5wrauujbWTM6H5oioWvJgLaUNgUhJ86/Y+ewKoGxLdYaxx99KhKxN/04i2chIHk0
+c53THOt9AkEA1SD1Rdm93FUMEor+BYEQgiN/4pWnSIsgUjyqV7lPv9QegdDTbVfm
+WuPNev6Z+qo9mpDWbvhCZhH159q7uGfzjQJAe88dLRWThuqK+TGsAmTYJbbdvI1u
+JjteZZjQjk4+KijlxUsnU60pbLsdRQudWMg1gpwKxKjQu2K1dljATUWyYQJARI83
+l2K1+py5J3XixS6BevukdeUiTOnEWe/98/4+szyvG59rg+8UwQQq43fnXIVLD9+r
+u0LNSTxZ2F26qVV3OQJBAIBG0Gv9C44UlCPiJhmMqcpzexX20erOEGu+UiCUhHAC
+ZdWdFaD2dlmk0O/E82LxPPivkGv5DtkNpzCl+3Vo+kI=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/smime.howto/se.p7 b/demo/smime.howto/se.p7
new file mode 100644
index 0000000..df4af0f
--- /dev/null
+++ b/demo/smime.howto/se.p7
@@ -0,0 +1,56 @@
+From: sender@example.dom
+To: recipient@example.dom
+MIME-Version: 1.0
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+
+MIIIwwYJKoZIhvcNAQcDoIIItDCCCLACAQAxggEAMIH9AgEAMGYwYTELMAkGA1UE
+BhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRkwFwYDVQQDExBTL01JTUUgUmVjaXBp
+ZW50MSQwIgYJKoZIhvcNAQkBFhVyZWNpcGllbnRAZXhhbXBsZS5kb20CAQAwDQYJ
+KoZIhvcNAQEBBQAEgYCn4qL7Z8QlmVEMbO2zU5RUtBqw1cYPzxd/4ZF9H+djlL7f
+EDIYHidvTBksXwcBXSTxsKsSusGF95NtwaZf4D3eiR1ChdkeXaiWCUPAQr0Mw4+A
+EJa9eGBuLH+LXH0pDN5OhYb8hsL/EcV2OpOEjOe4aM/u3Ot5u1kK1582oikhCDCC
+B6UGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQImrxQEPwEHHeAggeACeVVKqKjySva
+zos9ahumXC7bMIcfesxQF0L2CcR4wQtnwuzESXNAnQabtlN6cXKLNFGWmilztaEZ
+zHFEEDqtqEeP5BmEkjnNFakhr49yrK/SZpVYYScsUHNcMDOwXMPKKxkk3qOKUlqQ
+m+WvvyaWwZ8kbZNkO+YY4BpOVDEQRH3HG7ngtLP3y/LSx7kcpTl0wi8On9pigZ46
+4u9Odac7bNh5FVMOFiXbIXMuFJ1epy9agtX09eOK9JC9ZHnIl9IgGEL833d+l8MQ
+OdeKqvSTnNf0JKZs52uzUqluI4pywNIiJT1OeU3Ch2vdfvcOcTQohGhIEUb7NWEn
+Z02pBiZ0Tzg/T/sr8B60rPcRSSnGYAfQZPS86QgAMIEaqoHQVox+Qgigr2dynvWQ
+KO0ny4iJLhtRoxD7W7LxaZat6fWvbC4WXKwgis2UJnEWwe8DsoZ3XEnYid3ke2/F
+2+ejHtugd6E4o05vRAO/pOVZUbc4mbZm2zoPgzwm0M9GqQcTOVy1tkpEc6mhrH6y
+AjbSeP/1YkZakDB9bl+TCw24lRquIUbGyNI9GM+oFYyciIYu6RfPhfoh9YU9wqS7
+KcnHk3WG5AxJZ6tZAsz/i/DhtD5zxFsldKMy5OaeBjh6txmQ0aHXPYLmIEdxPSa0
+BnrAeTam/1iOboXT1O2GkPiLJAJjWRAvK/QXsxJMGtSYEnwT+kkAZtU75FOyBuxI
+gQOHO98CGGEy5CI/e/Th+1yMqyiimqDmfVktN4KgzZXbjyv4xzpgNE6o85v7fUg1
+1WoTF38GmbcIbVfBHaRwdWmSxeAsFltHb38u7pP8D24+3hC5OKsNLXxGFJhfp3HW
+4A51fNAi3CaWIh9aZb+SYof8MmtZp9mjL0dKB1OZTyTXqkQ/yU3PIHE+ytM6men3
+tzQefsJlnk/t/zBhxdZo+yCna/ZJe3KmrShoD4ob9prWs48ohDhG7cY4vfW0NuxX
+Im30y+JZe3A0Ktamq1VmpWDhW6Emo+Wk04nDTtU2AcClMoKWgcvIUp0HAgWDhrrx
+6Rf1CiRZngJuCEheCMAvKT8IgcTt7vWRUpRoJrhlKR23GKEVA6T6DF9q8L4F82ul
+yzQ2kdec6s8bD1EQT7e0QcIqEn8RNoSa2aLxRG2JBWqv0AZflKyCjwx35mCv7AmN
+17zPENkGdDgE9xcEEdEe4rkigJ8uaYauEofQsDgSHWQ2oAk91g5FLNImsRn3Z7uy
+qtjc9JTXOymowDXbdYebqNZTxJhXDUnrbawWqyCgohtytnVdW61wKsXV3oEp7PZc
+aL0ZZvVkVkUofaf/2gIGvQ9WnTfpUJ/bEAr0hDdtnmg1QPVu/v/l9eXAnbkh71GX
+OdFpIfvf0zVm/tn/7Vi+HfHRjupHSJBO+bIVQBt3uMLlovU48axSRXUnpIXdHYwN
+3b/MabDQnzV1qvhGTRRj+o39RbDrueNSpXIleA7SzFqZO2LnaDZ/FKbca1CAL+Iy
+KsByJUz7cReE4gU8N3SKKv7ivaibmFG63cvwA1GMUsWZgIYD/xy2nCB4jPCodiFB
+wWUgAblxMcaidKox6Z1zK9oZA62QIX4XGl+0mE/xwxmt2fE5lSBhY8q7N7MvS/Nv
+RDLEgULBMRvcjgIwL4MZZYkUl7oAqQ2aHL45j7vp7U9vIK2j8bcp9v5K3jeccENJ
+qYn/35awflQ66lrrSUWH3evs5IO0FhKSn9c1GGTaZDA5TABzJKySmvphyAh1m24W
+ylzgXQkp/Zi2oWajZ50txiIBgB14dWhuCS4vcwkWjyKur/yGpkY5DILSHZyguqDk
+cwOgKRrC5myL7Zr0/5CwN6sBofOGb2Qhcx+4Cg6DnPwyMXKVTKYnXPvcTj2KvVW+
+qzTIhur0rwC13lK1+sMs/+Esel99fohCTz7+tiebqkPaFcnx8hUgMrllYfGCMXQ/
+hiY+1BVEyDWHWOjj5lxPwzNtPqbVz3uA//Rut/BozOpD/hEjCMVRbadSFZTM+x6+
+nr39DHGZsOn3XSEZSU2C93v77Ls/vdJi6IdJTwCD/yoEK1OY60ciBvCpJ/lLVSQl
+PAdozKEAjqP+fX95MeAENtysXTsqbWawWeS7H/zXmwpWn3LnSJ48pymvwV6nJrYL
+YXHaiuMGIZim9jOT04AyK5LPZ2E9omwVFaHgi10iRigc9y5MixHvN9P5WQ9nP+/c
+kflFQXuph3RqBEWfG8vPFKFvqVQTN0lxUXoOXkKhZ7/Doe3orbfRw/xZUyKnfrzs
+O7MU7PVAubVNUXjKHs2UCYtVMUH0tSuDMkuWUd6d6cmkPht86KvopoXha6zRw1pu
+SE/nGq/o6bproWMPRfoCCN+xow9jeipTD7xkYbiw1+g/9hYaqUYm7QUbuYOrBtLe
+RAlm69yornWrh7+n3GXUSCFQz3nj6sE4JjXciQzhUfEFrCn+qDrgaviFKe0yVYbQ
+LRr3qtSM0gTVWn2XnOlnNuLdvx2UWV7KNNPrnRIoK3KvnfOJTl/RzkT1g+HaEvC3
+MFNlSJNXN6Gavdr8pAdtnVGivdnM1iw0yFKuuzoF1F+cuN8rGMmp
+
+
diff --git a/demo/smime.howto/se.py b/demo/smime.howto/se.py
new file mode 100644
index 0000000..cd4df60
--- /dev/null
+++ b/demo/smime.howto/se.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+
+"""S/MIME HOWTO demo program.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME, X509
+
+def makebuf(text):
+ return BIO.MemoryBuffer(text)
+
+# Make a MemoryBuffer of the message.
+buf = makebuf('a sign of our times')
+
+# Seed the PRNG.
+Rand.load_file('randpool.dat', -1)
+
+# Instantiate an SMIME object.
+s = SMIME.SMIME()
+
+# Load signer's key and cert. Sign the buffer.
+s.load_key('signer_key.pem', 'signer.pem')
+p7 = s.sign(buf)
+
+# Load target cert to encrypt the signed message to.
+x509 = X509.load_cert('recipient.pem')
+sk = X509.X509_Stack()
+sk.push(x509)
+s.set_x509_stack(sk)
+
+# Set cipher: 3-key triple-DES in CBC mode.
+s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+
+# Create a temporary buffer.
+tmp = BIO.MemoryBuffer()
+
+# Write the signed message into the temporary buffer.
+s.write(tmp, p7)
+
+# Encrypt the temporary buffer.
+p7 = s.encrypt(tmp)
+
+# Output p7 in mail-friendly format.
+out = BIO.MemoryBuffer()
+out.write('From: sender@example.dom\n')
+out.write('To: recipient@example.dom\n')
+out.write('Subject: M2Crypto S/MIME testing\n')
+s.write(out, p7)
+
+print out.read()
+
+# Save the PRNG's state.
+Rand.save_file('randpool.dat')
+
diff --git a/demo/smime.howto/sendsmime.py b/demo/smime.howto/sendsmime.py
new file mode 100644
index 0000000..6df9a89
--- /dev/null
+++ b/demo/smime.howto/sendsmime.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+"""S/MIME sender.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME, X509
+import smtplib, string, sys
+
+def sendsmime(from_addr, to_addrs, subject, msg, from_key, from_cert=None, to_certs=None, smtpd='localhost'):
+
+ msg_bio = BIO.MemoryBuffer(msg)
+ sign = from_key
+ encrypt = to_certs
+
+ s = SMIME.SMIME()
+ if sign:
+ s.load_key(from_key, from_cert)
+ p7 = s.sign(msg_bio, flags=SMIME.PKCS7_TEXT)
+ msg_bio = BIO.MemoryBuffer(msg) # Recreate coz sign() has consumed it.
+
+ if encrypt:
+ sk = X509.X509_Stack()
+ for x in to_certs:
+ sk.push(X509.load_cert(x))
+ s.set_x509_stack(sk)
+ s.set_cipher(SMIME.Cipher('rc2_40_cbc'))
+ tmp_bio = BIO.MemoryBuffer()
+ if sign:
+ s.write(tmp_bio, p7)
+ else:
+ tmp_bio.write(msg)
+ p7 = s.encrypt(tmp_bio)
+
+ out = BIO.MemoryBuffer()
+ out.write('From: %s\r\n' % from_addr)
+ out.write('To: %s\r\n' % string.join(to_addrs, ", "))
+ out.write('Subject: %s\r\n' % subject)
+ if encrypt:
+ s.write(out, p7)
+ else:
+ if sign:
+ s.write(out, p7, msg_bio, SMIME.PKCS7_TEXT)
+ else:
+ out.write('\r\n')
+ out.write(msg)
+ out.close()
+
+ smtp = smtplib.SMTP()
+ smtp.connect(smtpd)
+ smtp.sendmail(from_addr, to_addrs, out.read())
+ smtp.quit()
+
+ # XXX Cleanup the stack and store.
+
+
+msg = """
+S/MIME - Secure Multipurpose Internet Mail Extensions [RFC 2311, RFC 2312] -
+provides a consistent way to send and receive secure MIME data. Based on the
+popular Internet MIME standard, S/MIME provides the following cryptographic
+security services for electronic messaging applications - authentication,
+message integrity and non-repudiation of origin (using digital signatures)
+and privacy and data security (using encryption).
+
+S/MIME is built on the PKCS #7 standard. [PKCS7]
+
+S/MIME is implemented in Netscape Messenger and Microsoft Outlook.
+"""
+
+
+if __name__ == '__main__':
+ Rand.load_file('../randpool.dat', -1)
+ sendsmime(from_addr = 'ngps@post1.com',
+ to_addrs = ['popuser@nova.dyndns.org'],
+ subject = 'S/MIME testing',
+ msg = msg,
+ #from_key = 'signer.pem',
+ from_key = None,
+ #to_certs = None)
+ to_certs = ['recipient.pem'])
+ Rand.save_file('../randpool.dat')
+
diff --git a/demo/smime.howto/sign.p7 b/demo/smime.howto/sign.p7
new file mode 100644
index 0000000..7a39eaa
--- /dev/null
+++ b/demo/smime.howto/sign.p7
@@ -0,0 +1,46 @@
+From: sender@example.dom
+To: recipient@example.dom
+Subject: M2Crypto S/MIME testing
+MIME-Version: 1.0
+Content-Type: multipart/signed ; protocol="application/x-pkcs7-signature" ; micalg=sha1 ; boundary="----2221986D060512556B8AC18AAA106F39"
+
+This is an S/MIME signed message
+
+------2221986D060512556B8AC18AAA106F39
+a sign of our times
+------2221986D060512556B8AC18AAA106F39
+Content-Type: application/x-pkcs7-signature; name="smime.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7s"
+
+MIIE8AYJKoZIhvcNAQcCoIIE4TCCBN0CAQExCzAJBgUrDgMCGgUAMCIGCSqGSIb3
+DQEHAaAVBBNhIHNpZ24gb2Ygb3VyIHRpbWVzoIIC5zCCAuMwggJMoAMCAQICAQAw
+DQYJKoZIhvcNAQEEBQAwWzELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRYwFAYDVQQDEw1TL01JTUUgU2VuZGVyMSEwHwYJKoZIhvcNAQkBFhJzZW5kZXJA
+ZXhhbXBsZS5kb20wHhcNMDEwMzMxMTE0MDMzWhcNMDIwMzMxMTE0MDMzWjBbMQsw
+CQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBT
+ZW5kZXIxITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmRvbTCBnzANBgkq
+hkiG9w0BAQEFAAOBjQAwgYkCgYEA5c5Tj1CHTSOxa1q2q0FYiwMWYHptJpJcvtZm
+UwrgU5sHrA8OnCM0cDXEj0KPf3cfNjHffB8HWMzI4UEgNmFXQNsxoGZ+iqwxLlNj
+y9Mh7eFW/Bjq5hNXbouSlQ0rWBRkoxV64y+t6lQehb32WfYXQbKFxFJSXzSxOx3R
+8YhSPd0CAwEAAaOBtjCBszAdBgNVHQ4EFgQUXOyolL1t4jaBwZFRM7MS8nBLzUow
+gYMGA1UdIwR8MHqAFFzsqJS9beI2gcGRUTOzEvJwS81KoV+kXTBbMQswCQYDVQQG
+EwJTRzERMA8GA1UEChMITTJDcnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBTZW5kZXIx
+ITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmRvbYIBADAMBgNVHRMEBTAD
+AQH/MA0GCSqGSIb3DQEBBAUAA4GBAHo3DrCHR86fSTVAvfiXdSswWqKtCEhUHRdC
+TLFGl4hDk2GyZxaFuqZwiURz/H7nMicymI2wkz8H/wyHFg8G3BIehURpj2v/ZWXY
+eovbgS7EZALVVkDj4hNl/IIHWd6Gtv1UODf7URbxtl3hQ9/eTWITrefT1heuPnar
+8czydsOLMYIBujCCAbYCAQEwYDBbMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJD
+cnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBTZW5kZXIxITAfBgkqhkiG9w0BCQEWEnNl
+bmRlckBleGFtcGxlLmRvbQIBADAJBgUrDgMCGgUAoIGxMBgGCSqGSIb3DQEJAzEL
+BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTAxMDMzMTE0NTgyMVowIwYJKoZI
+hvcNAQkEMRYEFOoeRUd8ExIYXfQq8BTFuKWrSP3iMFIGCSqGSIb3DQEJDzFFMEMw
+CgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsO
+AwIHMA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABIGAKMjEdSteeZg9ji1C
+Y2r6zuxDVVftpSLy9ZhYOblGYY2b3PhzI0XytdUoLdb+GlPImqE/F5FJCPvTAovq
+uLElbx/FuijA2ly7PQF2ekyFJ0EhvNWT1gcLhi1m0W+Hk/jgCQx7YRojT53Pa9fj
+zuHk7ZkSKe5rZlOJvPJtYREnvD8=
+
+------2221986D060512556B8AC18AAA106F39--
+
+
diff --git a/demo/smime.howto/sign.py b/demo/smime.howto/sign.py
new file mode 100644
index 0000000..38c4bab
--- /dev/null
+++ b/demo/smime.howto/sign.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+"""S/MIME HOWTO demo program.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME
+
+def makebuf(text):
+ return BIO.MemoryBuffer(text)
+
+# Make a MemoryBuffer of the message.
+buf = makebuf('a sign of our times')
+
+# Seed the PRNG.
+Rand.load_file('randpool.dat', -1)
+
+# Instantiate an SMIME object; set it up; sign the buffer.
+s = SMIME.SMIME()
+s.load_key('signer_key.pem', 'signer.pem')
+p7 = s.sign(buf)
+
+# Recreate buf.
+buf = makebuf('a sign of our times')
+
+# Output p7 in mail-friendly format.
+out = BIO.MemoryBuffer()
+out.write('From: sender@example.dom\n')
+out.write('To: recipient@example.dom\n')
+out.write('Subject: M2Crypto S/MIME testing\n')
+s.write(out, p7, buf)
+
+print out.read()
+
+# Save the PRNG's state.
+Rand.save_file('randpool.dat')
+
diff --git a/demo/smime.howto/signer.pem b/demo/smime.howto/signer.pem
new file mode 100644
index 0000000..9189aaa
--- /dev/null
+++ b/demo/smime.howto/signer.pem
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC4zCCAkygAwIBAgIBADANBgkqhkiG9w0BAQQFADBbMQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBTZW5kZXIxITAfBgkq
+hkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmRvbTAeFw0wMTAzMzExMTQwMzNaFw0w
+MjAzMzExMTQwMzNaMFsxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEW
+MBQGA1UEAxMNUy9NSU1FIFNlbmRlcjEhMB8GCSqGSIb3DQEJARYSc2VuZGVyQGV4
+YW1wbGUuZG9tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDlzlOPUIdNI7Fr
+WrarQViLAxZgem0mkly+1mZTCuBTmwesDw6cIzRwNcSPQo9/dx82Md98HwdYzMjh
+QSA2YVdA2zGgZn6KrDEuU2PL0yHt4Vb8GOrmE1dui5KVDStYFGSjFXrjL63qVB6F
+vfZZ9hdBsoXEUlJfNLE7HdHxiFI93QIDAQABo4G2MIGzMB0GA1UdDgQWBBRc7KiU
+vW3iNoHBkVEzsxLycEvNSjCBgwYDVR0jBHwweoAUXOyolL1t4jaBwZFRM7MS8nBL
+zUqhX6RdMFsxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEWMBQGA1UE
+AxMNUy9NSU1FIFNlbmRlcjEhMB8GCSqGSIb3DQEJARYSc2VuZGVyQGV4YW1wbGUu
+ZG9tggEAMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAejcOsIdHzp9J
+NUC9+Jd1KzBaoq0ISFQdF0JMsUaXiEOTYbJnFoW6pnCJRHP8fucyJzKYjbCTPwf/
+DIcWDwbcEh6FRGmPa/9lZdh6i9uBLsRkAtVWQOPiE2X8ggdZ3oa2/VQ4N/tRFvG2
+XeFD395NYhOt59PWF64+dqvxzPJ2w4s=
+-----END CERTIFICATE-----
diff --git a/demo/smime.howto/signer_key.pem b/demo/smime.howto/signer_key.pem
new file mode 100644
index 0000000..ad15a87
--- /dev/null
+++ b/demo/smime.howto/signer_key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDlzlOPUIdNI7FrWrarQViLAxZgem0mkly+1mZTCuBTmwesDw6c
+IzRwNcSPQo9/dx82Md98HwdYzMjhQSA2YVdA2zGgZn6KrDEuU2PL0yHt4Vb8GOrm
+E1dui5KVDStYFGSjFXrjL63qVB6FvfZZ9hdBsoXEUlJfNLE7HdHxiFI93QIDAQAB
+AoGBALqc4OgZSbYPjQyTfpD1IJTKLgqsgCR5aE0kR7WZuG7MDt/e3ktWn0ebsgFv
+2J12u2bD+yqM++dVbK7WtvTR+QpMhb/0XMhXNsvmn5gOLdKlJjS0RXDDs2DzIS6p
+JNzAmn5zqTVteZAMDLk7ygkO++iGzwRz713ZgxRaKr5YWiLVAkEA+3ev1TTXNEOk
+wQ9fbukMrfUXesqwgrx9VZ1z1X5we42RIIMTYI1edpcujXYvgS3/jdzAWDS1Nqta
+9QB3uy91ywJBAOnysIIQhHn+4zvaOA5vh85WczPhN9C+yRmV70eyL9h+aThUFS4c
+kg2jQOLp8MaxAkmk4xRbZBgehjmDr45b5fcCQQDpIGNlYFBmhpN129+YffugRgjX
+cJNFEKONPKRHd6mmEW9K2dmb+FNr0+p3gOq3csJpbQ7wdyTMov13B1D4ux4TAkAR
+URB1oCleKlrBjGaH0wOXZ1jBp1MNVYHnLez3Pp5CBSFetQKYVi8NaV8dLLnQyztj
+Hhxc3mLrUh8XVMMC45SDAkEAxRCKmkneLceIdwixLIUF0zr0OzJtgyhxMxvHu3ET
+gJcZqNN0y3EgPwcNihpBw7rjpp5e5sjlRNVqLqn8a5/Fog==
+-----END RSA PRIVATE KEY-----
diff --git a/demo/smime.howto/verify.py b/demo/smime.howto/verify.py
new file mode 100644
index 0000000..9430add
--- /dev/null
+++ b/demo/smime.howto/verify.py
@@ -0,0 +1,30 @@
+#!/usr/bin/env python
+
+"""S/MIME HOWTO demo program.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import SMIME, X509
+
+# Instantiate an SMIME object.
+s = SMIME.SMIME()
+
+# Load the signer's cert.
+x509 = X509.load_cert('signer.pem')
+sk = X509.X509_Stack()
+sk.push(x509)
+s.set_x509_stack(sk)
+
+# Load the signer's CA cert. In this case, because the signer's
+# cert is self-signed, it is the signer's cert itself.
+st = X509.X509_Store()
+st.load_info('signer.pem')
+s.set_x509_store(st)
+
+# Load the data, verify it.
+p7, data = SMIME.smime_load_pkcs7('sign.p7')
+v = s.verify(p7)
+print v
+print data
+print data.read()
+
diff --git a/demo/smime/README b/demo/smime/README
new file mode 100644
index 0000000..736e632
--- /dev/null
+++ b/demo/smime/README
@@ -0,0 +1,54 @@
+ 29 Nov 2000
+-------------
+- test.py works.
+
+- makesmime.py is sendsmime.py modified to not send
+ SMTP, because I do not now have an SMTP server handy.
+
+- sendsmime.py should still work, because makesmime.py
+ does.
+
+- unsmime.py doesn't work, possibly because the certificates
+ used to generate the tested PKCS7 objects are no longer
+ available.
+
+
+
+ 20 Nov 2000
+-------------
+
+This directory contains various programs and supporting files
+demonstrating M2Crypto's S/MIME functionality.
+
+- test.py exercises the various S/MIME functionality.
+- sendsmime.py (optionally) signs and/or encrypts a message,
+ then sends the output via SMTP.
+- makesmime.py is exactly like sendsmime.py, except it writes
+ its output to sys.stdout.
+- unsmime.py decrypts and verifies an S/MIME SignedAndEnveloped
+ message. It handles the S/MIME output of Netscape Messenger
+ successfully.
+
+- ca.pem is M2Crypto's test CA certificate.
+- client.pem and client2.pem contain user certificates and their
+ corresponding private keys.
+
+- clear.p7 is a clear-signed S/MIME message.
+- opaque.p7 is a signed S/MIME message.
+- ns.p7 is a clear-signed S/MIME message produced by Messenger.
+- ns.se.p7 is a signed-then-encrypted S/MIME message produced
+ by Messenger.
+- m2.se.p7 is a signed-then-encrypted S/MIME message produced
+ by sendsmime.py.
+
+
+I tested with export and strong versions of Netscape Communicator
+4.7x.
+
+I have also done some interoperability testing with Sampo Kellomaki's
+smime tool.
+
+I am interested in interoperability testing with Outlook and other
+S/MIME tools. Write me <ngps@post1.com> if you want to collaborate.
+
+
diff --git a/demo/smime/ca.pem b/demo/smime/ca.pem
new file mode 100644
index 0000000..b7c84a1
--- /dev/null
+++ b/demo/smime/ca.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/smime/clear.p7 b/demo/smime/clear.p7
new file mode 100644
index 0000000..c878407
--- /dev/null
+++ b/demo/smime/clear.p7
@@ -0,0 +1,67 @@
+To: ngps@post1.com
+From: ngps@post1.com
+Subject: testing
+MIME-Version: 1.0
+Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg=sha1; boundary="----F0DD27AD8CDFF9AB55B87EE3A626E6D7"
+
+This is an S/MIME signed message
+
+------F0DD27AD8CDFF9AB55B87EE3A626E6D7
+
+S/MIME - Secure Multipurpose Internet Mail Extensions [RFC 2311, RFC 2312] -
+provides a consistent way to send and receive secure MIME data. Based on the
+popular Internet MIME standard, S/MIME provides the following cryptographic
+security services for electronic messaging applications - authentication,
+message integrity and non-repudiation of origin (using digital signatures)
+and privacy and data security (using encryption).
+
+S/MIME is built on the PKCS #7 standard. [PKCS7]
+
+S/MIME is implemented in Netscape Messenger and Microsoft Outlook.
+
+------F0DD27AD8CDFF9AB55B87EE3A626E6D7
+Content-Type: application/x-pkcs7-signature; name="smime.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7s"
+
+MIIHHAYJKoZIhvcNAQcCoIIHDTCCBwkCAQExCzAJBgUrDgMCGgUAMIICQwYJKoZI
+hvcNAQcBoIICNASCAjANClMvTUlNRSAtIFNlY3VyZSBNdWx0aXB1cnBvc2UgSW50
+ZXJuZXQgTWFpbCBFeHRlbnNpb25zIFtSRkMgMjMxMSwgUkZDIDIzMTJdIC0gDQpw
+cm92aWRlcyBhIGNvbnNpc3RlbnQgd2F5IHRvIHNlbmQgYW5kIHJlY2VpdmUgc2Vj
+dXJlIE1JTUUgZGF0YS4gQmFzZWQgb24gdGhlDQpwb3B1bGFyIEludGVybmV0IE1J
+TUUgc3RhbmRhcmQsIFMvTUlNRSBwcm92aWRlcyB0aGUgZm9sbG93aW5nIGNyeXB0
+b2dyYXBoaWMNCnNlY3VyaXR5IHNlcnZpY2VzIGZvciBlbGVjdHJvbmljIG1lc3Nh
+Z2luZyBhcHBsaWNhdGlvbnMgLSBhdXRoZW50aWNhdGlvbiwNCm1lc3NhZ2UgaW50
+ZWdyaXR5IGFuZCBub24tcmVwdWRpYXRpb24gb2Ygb3JpZ2luICh1c2luZyBkaWdp
+dGFsIHNpZ25hdHVyZXMpDQphbmQgcHJpdmFjeSBhbmQgZGF0YSBzZWN1cml0eSAo
+dXNpbmcgZW5jcnlwdGlvbikuDQoNClMvTUlNRSBpcyBidWlsdCBvbiB0aGUgUEtD
+UyAjNyBzdGFuZGFyZC4gW1BLQ1M3XQ0KDQpTL01JTUUgaXMgaW1wbGVtZW50ZWQg
+aW4gTmV0c2NhcGUgTWVzc2VuZ2VyIGFuZCBNaWNyb3NvZnQgT3V0bG9vay4NCqCC
+AxAwggMMMIICdaADAgECAgECMA0GCSqGSIb3DQEBBAUAMHsxCzAJBgNVBAYTAlNH
+MREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0ExJDAiBgNV
+BAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEdMBsGCSqGSIb3DQEJARYO
+bmdwc0Bwb3N0MS5jb20wHhcNMDAwOTEwMDk1ODIwWhcNMDIwOTEwMDk1ODIwWjBZ
+MQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xGDAWBgNVBAMTD00yQ3J5
+cHRvIENsaWVudDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20wXDANBgkq
+hkiG9w0BAQEFAANLADBIAkEAoz3zUF0dmxSU+1fso+eTdmjDY71gWNeXWX28qsBJ
+0UFmq4JCtw7Gv4fJ0TZgQHVIrXgKrUvzsquu8eiVjuP/NwIDAQABo4IBBDCCAQAw
+CQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2Vy
+dGlmaWNhdGUwHQYDVR0OBBYEFMcQhEeJ1x9d+8Rzag9yjCiutYKOMIGlBgNVHSME
+gZ0wgZqAFPuHI2nrnDqTFeXFvylRT/7tKDgBoX+kfTB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tggEAMA0GCSqGSIb3DQEBBAUAA4GBAKy2cWa2BF6cbBPE4ici
+//wOqkLDbsI3YZyUSj7ZPnefghx9EwRJfLB3/sXEf78OHL7yV6IMrvEVEAJCYs+3
+w/lspCMJC0hOomxnt0vjyCCd0JeaEwihQGbOo9V0reXzrUy8yNkwo1w8mMSbIvqh
++D5uTB0jKL/ml1EVLw3NJf68MYIBmjCCAZYCAQEwgYAwezELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5u
+Z3BzQHBvc3QxLmNvbQIBAjAJBgUrDgMCGgUAoIGxMBgGCSqGSIb3DQEJAzELBgkq
+hkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTAyMTIwODE3NTIyMVowIwYJKoZIhvcN
+AQkEMRYEFI/KcwJXhIg0bRzYLfAtDhxRMzghMFIGCSqGSIb3DQEJDzFFMEMwCgYI
+KoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsOAwIH
+MA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABEBrhZ8JVqGZ+S7dh+xKDwbF
+yRXiOQWEa2uxD1O5fD02VfGEzDSrV1sPdQ8AcM3o+ny5AyC11E4Fns2cIkXwZEwz
+
+------F0DD27AD8CDFF9AB55B87EE3A626E6D7--
+
diff --git a/demo/smime/client.p12 b/demo/smime/client.p12
new file mode 100644
index 0000000..b7b985a
--- /dev/null
+++ b/demo/smime/client.p12
Binary files differ
diff --git a/demo/smime/client.pem b/demo/smime/client.pem
new file mode 100644
index 0000000..0eeeefb
--- /dev/null
+++ b/demo/smime/client.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDDDCCAnWgAwIBAgIBAjANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTgyMFoXDTAyMDkxMDA5NTgyMFowWTEL
+MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRgwFgYDVQQDEw9NMkNyeXB0
+byBDbGllbnQxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZI
+hvcNAQEBBQADSwAwSAJBAKM981BdHZsUlPtX7KPnk3Zow2O9YFjXl1l9vKrASdFB
+ZquCQrcOxr+HydE2YEB1SK14Cq1L87KrrvHolY7j/zcCAwEAAaOCAQQwggEAMAkG
+A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp
+ZmljYXRlMB0GA1UdDgQWBBTHEIRHidcfXfvEc2oPcoworrWCjjCBpQYDVR0jBIGd
+MIGagBT7hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAP
+BgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMb
+TTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3Bz
+QHBvc3QxLmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQCstnFmtgRenGwTxOInIv/8
+DqpCw27CN2GclEo+2T53n4IcfRMESXywd/7FxH+/Dhy+8leiDK7xFRACQmLPt8P5
+bKQjCQtITqJsZ7dL48ggndCXmhMIoUBmzqPVdK3l861MvMjZMKNcPJjEmyL6ofg+
+bkwdIyi/5pdRFS8NzSX+vA==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOgIBAAJBAKM981BdHZsUlPtX7KPnk3Zow2O9YFjXl1l9vKrASdFBZquCQrcO
+xr+HydE2YEB1SK14Cq1L87KrrvHolY7j/zcCAwEAAQJAUkHlWZmSUZMNf5nOpMkM
+hZ5E1v2Wjy4UFgRGDcTXbZm401Avwrc8006qQxQjoH94pVkwPYjEyAMubhusMFt4
+AQIhANeB4qzW/YsABYpt66x1ByiuUJE1p+QMLngeESJbc989AiEAweoMtXsNRJi4
+1xBzlKB9zljQfESYIt59SctURPfX/4MCICS9kwyOdplM/qTUCprTNM49saSf9iiN
+3xpBXgBygPWtAiEAmoIeDEB26vBxX1OJdKSIeXFE9a9GNYpn8/OiOq3smncCIGIg
+Hj9MtdZ/1uPk7jx5oZVfzN1DM7GYZHAeYeYhVR13
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBEzCBvgIBADBZMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xGDAW
+BgNVBAMTD00yQ3J5cHRvIENsaWVudDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0
+MS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEAoz3zUF0dmxSU+1fso+eTdmjD
+Y71gWNeXWX28qsBJ0UFmq4JCtw7Gv4fJ0TZgQHVIrXgKrUvzsquu8eiVjuP/NwID
+AQABoAAwDQYJKoZIhvcNAQEEBQADQQAQho0UsOhmuUUhJrx7uXIEbo984OrOVpa6
+giAU7socmyjCzJvihmr/Nnqub+Md7rkSfDytGDN6CianGL5MROjr
+-----END CERTIFICATE REQUEST-----
diff --git a/demo/smime/client2.pem b/demo/smime/client2.pem
new file mode 100644
index 0000000..166919a
--- /dev/null
+++ b/demo/smime/client2.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDDjCCAnegAwIBAgIBAzANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tMB4XDTAwMTEyMDEzMDMwNVoXDTAyMTEyMDEzMDMwNVowWzEL
+MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRowGAYDVQQDExFNMkNyeXB0
+byBDbGllbnQgMjEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20wXDANBgkq
+hkiG9w0BAQEFAANLADBIAkEAn9qneRYTKPokme3obiJa2NTz1Z2kcF3NHVh60Qod
+/TV/q4olPrZdFR2TDWt63Lgnygcsgf3u9pnhcEGk6IvntwIDAQABo4IBBDCCAQAw
+CQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2Vy
+dGlmaWNhdGUwHQYDVR0OBBYEFDKWFe6VWMhtRTE3/78+hAnSGxmvMIGlBgNVHSME
+gZ0wgZqAFPuHI2nrnDqTFeXFvylRT/7tKDgBoX+kfTB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tggEAMA0GCSqGSIb3DQEBBAUAA4GBABpE9xt1Hlq2dQZUXHuX
+HI57vc2mlWnhhM0wnNhsNZFwfXRHCZOo/JJBhEIT3Rgyz0ErrbOr1SN96HNDKXOD
+z6bh4NxB5DZ9sRPKEBj66zDsWJVMlom+Lkeal+GkVy36vpAyP1r+cTXyc9M2Gw/o
+FBMinMHH/BXvF5GJ+UleheZe
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOgIBAAJBAJ/ap3kWEyj6JJnt6G4iWtjU89WdpHBdzR1YetEKHf01f6uKJT62
+XRUdkw1rety4J8oHLIH97vaZ4XBBpOiL57cCAwEAAQJANXfspprUo9MvpPEn2pbR
+Lk/kk2IcW510e0laI0uwBj50djfHqvsU5ccuVLrxowngLGrFmM3G4lnMknR2NvH8
+0QIhAMsK0AwStUNM/KyvIMikHHBOE9PrK7ARgKvlKl+0ieWPAiEAyYwonIVAtr1f
+M8vmrc6TM2YxzSq4+jyYktaaNhYw11kCIA5pmhMBUPSSBm2LkNwtKgeewzGLw/If
+i+6nubZJbnBpAiEAvJQvy4PCsTkvQr+d7zJB+O2920IGId1gxMOXNtQ8jsECIGvn
+Uz54oonshmTg+Kj2DxnUKQEzFAmQLbtFslp1m47v
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBFTCBwAIBADBbMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xGjAY
+BgNVBAMTEU0yQ3J5cHRvIENsaWVudCAyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBv
+c3QxLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCf2qd5FhMo+iSZ7ehuIlrY
+1PPVnaRwXc0dWHrRCh39NX+riiU+tl0VHZMNa3rcuCfKByyB/e72meFwQaToi+e3
+AgMBAAGgADANBgkqhkiG9w0BAQQFAANBAHI5KXfL6kIRoNjR8G9/uKiPUt4uVBKF
+ecGp87M5t2a92Z0KpWOMXSHZ0LLQKqwWzALvWcPPIj6S8F6ENdwpfMk=
+-----END CERTIFICATE REQUEST-----
diff --git a/demo/smime/m2.se.p7 b/demo/smime/m2.se.p7
new file mode 100644
index 0000000..3a5bb1b
--- /dev/null
+++ b/demo/smime/m2.se.p7
@@ -0,0 +1,84 @@
+MIME-Version: 1.0
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+
+MIAGCSqGSIb3DQEHA6CAMIIOBwIBADGCAVkwggFVAgEAMIG9MIG3MQswCQYDVQQG
+EwJTRzEvMC0GA1UEChMmRmFzdCBTZWN1cmUgQ2hlYXAgQ2VydGlmaWNhdGVzIFB0
+ZSBMdGQxMjAwBgNVBAsTKUZhc3QgU2VjdXJlIENoZWFwIENlcnRpZmljYXRpb24g
+QXV0aG9yaXR5MSQwIgYDVQQDExtGYXN0IFNlY3VyZSBDaGVhcCBLZXlNYXN0ZXIx
+HTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tAgEDMA0GCSqGSIb3DQEBAQUA
+BIGAaH1UOF0vZjCfEDbzWAy+kni/T0AVVbTdWeCZAaADIgNrUqVJKkV8BIrAFRUO
+jNdz23gra1TRgoe+sq4qn9WBi61+f+8JUV4UHZYl4DdcIkcGvdEI7utPLFNFif2m
+oqd+kr39wuGWMonoBpzFdycSN/FRpFTrwoyXEIHj9VPCwvUwggyjBgkqhkiG9w0B
+BwEwGgYIKoZIhvcNAwIwDgICAKAECCZMYhSx21tlgIIMeGyY4Sgm4uv0a0Zu5gnx
+x1bA6wPY2W8oOqFC3jEAc6j0aP9TzxFW33W5fT/wCWCuIaS8Bm2nCozt7yhBofV2
+9mEtlahbEV1KeqwgI/RteTHD2WUvz6277O9lsB/xsreKM63WzxkpTWi0BUQBDjC1
+ncwy/74ujmZhdAD28HJie5cqhxKQd8if5/Ild3B786sxpGaSBKbJAtJYG30imbHL
+B7JTqDciCKQZzFeQBc1TOcld9VqFP2dRexmcPMxcyJN1iJe8R0g6PYpfAmLfmN6B
+fAImcgJYBpq8PYzmTi7cpJCMmzlPn7urm8Nx4DUQtzzMArmSuMfWGhu2wWLM+2Mn
+TB1cAVuO0bGgsHnr0BKW5gfQEJEIZY41Kq179RKAzI6OdJYvnKXoadEtzYI07Tup
+D+HJXLdAuU/B6E+09IS8mg2RydNyRP+lp+sOVve6L4LAH8h5pt7hoES5/qgiew09
+FPpx5xmhcGCTHJ9Ad2Xec6zAuYkVWAUfLpiy2A2NDTZnQ6EWaeWHtoAizMjHlqQY
+ZJYeXINzE4/u9ph5DTvXV2Sd57eOoaaaHmWwoV/887cdW33cQQ8LXciuzduI/b4V
+LWFm3X9eVdGEeQFoNDP5D7xwEK67flhufYClB99lt3tCfRLvvcclsd39heOex5/M
+6XeA8TeEbuukMhEe6bW2o+E736XNpziFm7hL1UGBXqbv26sN3A30H9LRMXGlZZxj
+raGBl8uxzUPi/SxERMbRrYC72x3Dev5m5I458wXEyc94GIqDqcHL0Q+za+3iyC2g
+496OsVeqtob9oshXr3rI0FjV+ejNMBcnhWeaPKLl6MOnqI76kczARZR8WrO+Dyy9
+FpgwX/DNKstuw0rLzzN7Zb+J4NPIhlHDt2Hh3KLjoFAiXPSDugKGQiceV6mg0IuS
+8HPq8rTiKFEnFtPTWxvAKIOa3dKaOiBoCue1Qm+ffV/2AB4Q4Xf0SpzgDBa6ruRF
+MImgcdeNjzu5MggGGHQtYU1uMMqhX4o2GIafVmAO5zUVYHsbQ2Lht+/x+m8nBamV
+H8AUzJQBDL10bbmZW8fh4JqwxXsdmoTKSSSKEk1+mP55DeBnP+GPA1EGbZWoI+mO
+FoHd1QKDedI/r8o9y1SkfhPxrKl0XKG+VLIUmmP+qev/7s3d5PEfg+ShkAAf2ccP
+2o+poSQKHYeOVM/RxLjS7AR96MtMl+46Cyinbl1quRjYLh84iQSP9RNVhVM06fi5
+63RHv8BAlJRnEcyiFNV/uDBYTFQ/2Gf8V9gBjYWyajnISsTsD2ZQ93jT/sjL6KpH
+Ouv7LQplVrxkZHXQgOoqd9fI1QR8qyBjoMUivv0DSKPKZo0MT/KfqUxG+DsFgOi/
+a7WW9J+XA7N2hGiP3CEqmm1pPi6IUtQj5kGmjlcEtco9syTwPSh5w+tcmVtQeu1+
+wwg53wSLHUdRUmVGqyCVV55ItkirSQt37JCDkXoivCfbdi5BI7cQ6IBhRvpBw2uV
+IA0O9/AfhXhUred974WInrjjlo0RQ4rGZutwDYWrPzWjzuPPvomih6rKQ0HH1bgb
+vdAZ+UjXtfSdory36LMg9Yb/oFejm/77NRXAo9AAf2MQvHVN00zKzDoMm4CRiJUc
+ZCC2+3ocFEs9l6jOO2tPrQpH+E69XXyqQcer18FyM3rTv81O2SbOz0IfL9vBFEsL
+ZZVifPKLt9LfRRjoqehXTxkmfQ2NmHUyksHO4aE2/OrtMVXBv35WFzLPIsX9C+yX
+r6+QPQrkG2Uad2e0YM0ipNPk4OIy5Bb+Ab6fIbHvLgLBRv0yBZ+e9OoEZ1UFC6sR
+EncKSV1fkRlemGgG2cEP6Ceq+x0zbS4A97nqQO5Hks5Hv5PqVuSaWSIjAEFihmpt
+wB41IunnO7b/jzJiNLK28e83uICuYgrPPEeYbbdGrEn65ZzdvhOQduwj0baJRp3V
+0z4boIs7oODYq0IXklvoMzwoaAPbMo2IBamMbZbY7HQbPOeI9QQTkBmKjavFRg/X
+fismhuFuVbyMQPc9uV27xpI2nRSkfN1X2L1lz+e5MMEwHqVRER3tGB1/g+WMrSGV
+ORvXam6fmkIgC4bCSAGBPtqHr2G2rzphsxESw2pbk/7ll8lGuYf3ZxovXJiLb2RB
+9j9m5TFoZ72Asplc04NDKykVOzZGWvNJCCV9zUHTyZ6X4qx+C4P6g58OvyZEf2VB
+arkD40QoEBkhNP6QLfacdFNAegWp6MwJHCDg2YIeLMfgCKkDVP+/SijiT3x7ahHO
+n3ZxKsq1bC6N7McwHB6gzkdpSSqWdtSv+fh5TNlMf7oCH0Laa/2k8S7NDpNbIeqr
+X46ECkmqPTjt5DOuEp3T5q4sOXV2Y3m7FegD2J2WrpXy+/GNgSVB2Kb7xzhR13ve
+Ipu5V+FFa6Q/jbXxp864ZFE6WuZYBYjrkBns2MaF+9RpUSIzyTYxJmB9pY0SwrzX
+2WWnxCCt7cwB1JL0jbrCz0vZvZwDRbtCyepBYel0xqKOhp1dFPCw2p45ZbQJwhKx
+nFMDUzlDZjiAoUdq5oV8TmqcjbGDmLndYawHPqD8m3g+glsCaFI89Dny8NsT0zv2
+vHCt6IbbpVY6j/Pwg9SFzWwnzGr2s5aL5MEbpzEyfiuYAe1hPdue2gWQO1rvdIBB
+zJwdqbrYb3qzJqrqrPQewSpsIzEWJmc5NjATIJOEtgM+Q4+FFTf4f1rZ2HAsK7MG
+aIMZFyjoyvqBHG/IwCh+GBLLIw3IPRJtDcKf/nDZqUCw0wHpTcXu1nHJllNmtNcI
+Xw03k7WctqlxTUBFUjiC6XcU/yAlLZ8UeyLF3+/tSyyV+Ve17cZfAH63WH+oaWL8
+yiC+UH5tpZQWCSBvCz81ezHOBefefOxeIvq5nZyEyP23ltaV/DJqj1a/5pn2VH63
+IrRlQF04ATaUAowxlNIaqD5Sk6tzPKp6nxOO3vP8xRGT1SFKMaNCuJRaZXXQvYcg
+zyiQ0vtli0g8cdbR8dSSTAP7b6CAqb2dYHwtJVUzPzWbWDIpRzrXOdoI/lze+fwA
+xYwAFoTuLLrKgbkOB8HE02ZN4exCp2dcMfwt+GCgYgCtE1/xCHd4RMg3yHxBBDQQ
+uk6dL6dXSD8cw2TqzrJRU8Xk/pLHeQCf694W2flROD/RF3avgcjnJ+0E+CJXmU9c
++uAhFRmdgRzM6G+h2p/MF8HjZ4MZTR6vA4v6mvTLLBbwIUfVNtXIwXpFqQPXVRvA
+4Q8HmLIX2fXGzR2Nw9UU0GBitT4r1isMHCQwVK5kqw3XyH15oIXCCsE1/ftBun8n
+SCQ2OkLHuZ4JWMIYVpTtDn3jd0Gyu6x8hBK+HpX62wnKZZA8mxzMoInAFK08s0Ie
+G3m+bjX7WsdE1eM0TSBvYv2d8CNqaqxJkF/ZlxkAKV7NoP0isuRJSSHH0IcrWNR7
+9mtfxV+ccBUqqO7BmlzLYPjnN+7hOJbYsm0ZJnDZIn6Oc6t8ti+oF4QmUBQ2u7As
+8uBJLbd7TghA7afcrurQAY8gJTq4Q7/lpYZSZK+W0I4fY04YhAfjFuln8r6w9gqr
++Iwg58dbq7MomCsg0F687kApYygyYQeCsio64tb3sXkCmDX2rdRoAELLf3AYccn+
+BW9iru372oWru/kTQTj6Uziv4TiyjUGQEYhtjweTNFTXmYrxYHgb2Hzu6TNIG9Ev
+4ORq9KL1uWSK8zJ6eL9CZ0kSr/4+SBHFbGupfGXz+fZysu3nFJ1oO86vMOFG/UU9
+24+jbRWklu0fyXZfeNzdY6arTLOX5Gpc9PRNZILoFHGyB2M9ZmMYz3hSNzY8acpk
+XDi+2ycHYeWFGxGCLio9Lc2H6IOMqJTTCpUduWvBX4tfysanUNjOuKJ3FWKWLxda
+IX1jJbPjvrQ3bg/NU/MqliLwFiMA1WkJd/paeK//J2tcOIMyhc40sigu4z65VoJ5
+AsMHlvh1dcfjz645NOx6JOpwdd04hoOxlMDtRTE/b8yjwaV2EVqXnI5WkNwCQESU
+ayIJiQqDfn35EFY8Wkmt7f6YGkpqoBDJkyNadsrcd2YVz9paAhgcM3XjC7zPi8AH
+g9UiTQiowRLcvnxkHK0LxelsQTE+ENL0rwYCt+qHBBJlcLVZhDF+qAchv+G5hmxp
+CClPyvbzOsx7ykcvItE2fpBWJu27l48dFxFviOKY7ecvJlpcTAX83N79k0aJpOC+
+TEPMdMZjIeGn004FiVj9Mo1899e2IlOlNdmj8Mv/jW4sFKpJ8m5fHv8i/ax7W33D
+0GNMrEghOH1w+QAAAAA=
+
+
+
diff --git a/demo/smime/ns.p7 b/demo/smime/ns.p7
new file mode 100644
index 0000000..d2ab1ac
--- /dev/null
+++ b/demo/smime/ns.p7
@@ -0,0 +1,53 @@
+From: Ng Pheng Siong <ngps@post1.com>
+X-Mailer: Mozilla 4.5 [en] (WinNT; I)
+X-Accept-Language: en
+MIME-Version: 1.0
+To: ngps@post1.com
+Subject: S/MIME signing
+Content-Type: multipart/signed; protocol="application/x-pkcs7-signature"; micalg=sha1; boundary="------------msD996346BF03A805B450DC20D"
+
+This is a cryptographically signed message in MIME format.
+
+--------------msD996346BF03A805B450DC20D
+Content-Type: text/plain; charset=us-ascii
+Content-Transfer-Encoding: 7bit
+
+smime -vs- pgp
+war over?
+--------------msD996346BF03A805B450DC20D
+Content-Type: application/x-pkcs7-signature; name="smime.p7s"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7s"
+Content-Description: S/MIME Cryptographic Signature
+
+MIIF8gYJKoZIhvcNAQcCoIIF4zCCBd8CAQExCzAJBgUrDgMCGgUAMAsGCSqGSIb3DQEHAaCC
+A9cwggPTMIIDPKADAgECAgEDMA0GCSqGSIb3DQEBBAUAMIG3MQswCQYDVQQGEwJTRzEvMC0G
+A1UEChMmRmFzdCBTZWN1cmUgQ2hlYXAgQ2VydGlmaWNhdGVzIFB0ZSBMdGQxMjAwBgNVBAsT
+KUZhc3QgU2VjdXJlIENoZWFwIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtG
+YXN0IFNlY3VyZSBDaGVhcCBLZXlNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEu
+Y29tMB4XDTk5MDkxNzA1NTAzOFoXDTAwMDkxNjA1NTAzOFowYDELMAkGA1UEBhMCU0cxGDAW
+BgNVBAoTD00yQ3J5cHRvIENsaWVudDEYMBYGA1UEAxMPTTJDcnlwdG8gQ2xpZW50MR0wGwYJ
+KoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
+vp8eCa+KpzNCv0MWyicUImP+WHxlrxm5EummI1Qe77U4w0k8IQuue7QIURWKDjdZI97izzeQ
+SozHvgSsjCvuJlqifMoV0v7U4iUQoZkrXO3hzwM5VNr875M95SYeBjqWDUc0v3R6tka3xhg3
+dMoEL3QR6gsiardPEwygdL7/FN0CAwEAAaOCAUMwggE/MAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTEHU/WosFL
+yRo8NT9ViQ8APbKBXTCB5AYDVR0jBIHcMIHZgBQOTokwY/jsgXmHJoyQ5DmTVN0c8KGBvaSB
+ujCBtzELMAkGA1UEBhMCU0cxLzAtBgNVBAoTJkZhc3QgU2VjdXJlIENoZWFwIENlcnRpZmlj
+YXRlcyBQdGUgTHRkMTIwMAYDVQQLEylGYXN0IFNlY3VyZSBDaGVhcCBDZXJ0aWZpY2F0aW9u
+IEF1dGhvcml0eTEkMCIGA1UEAxMbRmFzdCBTZWN1cmUgQ2hlYXAgS2V5TWFzdGVyMR0wGwYJ
+KoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQBN4w1l4IXm
+DJke8XVyo3SFlre1nL/bjDB+hpYCgTQnceHAmSi6L8FljGs2KYGFePWLSCsOxA99q8lVtl7a
+sdoOtYkTluccWGqnWloyusOzlO2xgpoQxZ2iue3PT4Y4u8czMTs2AyanZyd6NdQoaYGfI2g0
+tpgiDhSqcX8r/JYYVzGCAeMwggHfAgEBMIG9MIG3MQswCQYDVQQGEwJTRzEvMC0GA1UEChMm
+RmFzdCBTZWN1cmUgQ2hlYXAgQ2VydGlmaWNhdGVzIFB0ZSBMdGQxMjAwBgNVBAsTKUZhc3Qg
+U2VjdXJlIENoZWFwIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSQwIgYDVQQDExtGYXN0IFNl
+Y3VyZSBDaGVhcCBLZXlNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tAgED
+MAkGBSsOAwIaBQCgfTAYBgkqhkiG9w0BCQMxCwYJKoZIhvcNAQcBMBwGCSqGSIb3DQEJBTEP
+Fw0wMDAzMzEwNTExNTJaMB4GCSqGSIb3DQEJDzERMA8wDQYIKoZIhvcNAwICASgwIwYJKoZI
+hvcNAQkEMRYEFDIu42hsp6w5K/x7ejaVgiqgPYk4MA0GCSqGSIb3DQEBAQUABIGAkipjxn6+
+NhIyRPGgrqY9zpDC8AGdt7sGFMKfxhJ+UoSb+lBH0imHSBhA/BnlfysyyDXCpit1Gxy/W/jn
+vHpI0lMLvvcKOtSQp9HUPAtawdeF6Zau8SovSBroUnxxY7DILJoKnaHheHF7G2MbYusyZCmi
+r3xZ4P0Ps3fhNEAmrH0=
+--------------msD996346BF03A805B450DC20D--
+
diff --git a/demo/smime/ns.se.p7 b/demo/smime/ns.se.p7
new file mode 100644
index 0000000..a7a75f5
--- /dev/null
+++ b/demo/smime/ns.se.p7
@@ -0,0 +1,75 @@
+MIME-Version: 1.0
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Description: S/MIME Encrypted Message
+
+MIAGCSqGSIb3DQEHA6CAMIACAQAxggKyMIIBVQIBADCBvTCBtzELMAkGA1UEBhMCU0cxLzAt
+BgNVBAoTJkZhc3QgU2VjdXJlIENoZWFwIENlcnRpZmljYXRlcyBQdGUgTHRkMTIwMAYDVQQL
+EylGYXN0IFNlY3VyZSBDaGVhcCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEkMCIGA1UEAxMb
+RmFzdCBTZWN1cmUgQ2hlYXAgS2V5TWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx
+LmNvbQIBAzANBgkqhkiG9w0BAQEFAASBgERIsZhX2nmvB4kOtcfqUpuWjK9njs8YixtPSS4w
+ugmAx3LhBMTgY1OlsbKi7etVPwEU2jSqgkwCHPeYeOQt6M9aQ66tcue7/fWjfMBfZ+tetwOb
+kpqlw//GyjhkJx2QBGPuw3Ye84ll1KA7x/CGxmk8g0YBHoLo9Xy1W1XHVUanMIIBVQIBADCB
+vTCBtzELMAkGA1UEBhMCU0cxLzAtBgNVBAoTJkZhc3QgU2VjdXJlIENoZWFwIENlcnRpZmlj
+YXRlcyBQdGUgTHRkMTIwMAYDVQQLEylGYXN0IFNlY3VyZSBDaGVhcCBDZXJ0aWZpY2F0aW9u
+IEF1dGhvcml0eTEkMCIGA1UEAxMbRmFzdCBTZWN1cmUgQ2hlYXAgS2V5TWFzdGVyMR0wGwYJ
+KoZIhvcNAQkBFg5uZ3BzQHBvc3QxLmNvbQIBBzANBgkqhkiG9w0BAQEFAASBgKFEdyFcaj2z
+7LBp/rdEiDRNkyvjZtpEc3F1tvml2jmOuRZq/Gd0Ib4ELmAuC2qZ7AG+MNwlMeCQp1PKGjCk
+3Af9hjcOX27c+qF3pSK35WOPrdQTaVLjqdNwejInEoBjGlHa83V1L/MTiD5ABOXGIZKOFS8m
+G0tPxoc3AhlYbHspMIAGCSqGSIb3DQEHATARBgUrDgMCBwQIvFFOv2EOARiggASBsBBa97DM
+xPCX8mZ2K/jjVl2LKtBP0uwG+nd48IiaoBERbJohEdzxTlIU3pj+QZ4ktmSSYo/4Os0eSCfK
+v2mG3d5SPxcE6HztA4FttvQ4IiByTQUU61vJhMoirV5Nvfkxk8i8xPMKMlJSruFBnIOteXQv
+vwVwBCIAkMhxBE9Sj+OfW5966d+LmB3yGEZa9GEW//4VbR6DNs1frEz7riUod0om5CA66bou
+8kXxGTZ5fYydBDDdrsgcU7DBTlw2XayUmYS3Gp0vQvW4l/iiYEeYh+OrKkGYQGdsb5CcZr0F
+FfT2PRAEICA58XUUrt//zi0TzBTwDKAWoJWe/QfOXXEVQsfqCl1iBBhACmC4JLQRSulBCCpA
+F3E3daztLz81ycQEIDyHXxMGZFzZQCvy51fN7eRLl+yACoXKxKtVs6EKpJq9BBDDGI8CWqTZ
+DIMC98l4AV76BIIBAFKj+L6ZG8Sew6IpE5UVq1oS42UrmUDQRrYpZX3OHC8I9pr7mtlU/V0H
+mA55PmixsjcchT6wOF2wVD9G0rBCOlUfuYNTkhR/k51floP9fk9Oy69+jXxUb7r0AF1M2b13
+XQpmnz6YPQPdPxgi8i34iT1oqRY1d4Nntmgl/3xbQ/9MGbgZQSDbduRiy+Zk3JPZzMSVWEVP
+Pt7anOrPmCKDAvlzpX9yKdyE4Ug68s2///S1JjCjGd65007oPXME3S+7WWylQXdjCe8EM/Li
+IE33DcRUFvbuo7q1ftxbwRKn32+Z1gkiG4j+DQ4fTO0RrhnTLUpaaOaX4N3psjSA/3Y+vKYE
+CAkYtbSSwRr5BAhmVVsaf0vm7QQIE77+DXI3Gt0ECIs/MQuPnwd6BAjpq83cshzQ1QQI3Soo
+r1XwQYIECES6t6POe3yxBAgmObWcpGTawQQI+6e28OKubqkESBlD4aGRdfOXkGSw5zN1pVK0
+s37qLWOsO+jClvd4EmfB/JI/ABieNmfBHx2jweIazMA2b9wylRvYp/TMiDFkRl/thDqyj/sC
+7QRIypPg5i7a745PX+HcyHhcy5lUxkpnK5pjAAHSUPTXNc/3H6PpU/8+qiPncfqNXVdoiiSa
+VMSmFMZI+YGDyf4twVuGZbdR5JV2BFCbQNJeSkEhEnZiMKshmNWXbjs4DzYlTO0Pq5D+9RYJ
+1iy/kahcms+2nDNlrVf6jH/lCTpPCM/OJWrjaTdpiFzp/eEY39qbWk5AcIFYQ7xIcARIHVPX
+uhyuO+5BmO0MdMMOL/KlhRtosKHHRG7PH9huyWYFloA4aeoiOxeXkDJk3y0NkG+nJjdM/gWT
+1J2HJi8opt0F0bn8t7MsBEgt2VgTZmkHT/yaJDInQEqrpe9PJ412M+2/kXAwt4VfYrEC3ocQ
+WQuNEpFc1kb6ZGJSYz0TvnW8DbP/GoZqQpF17p1vs1T0QhIESC1qr8SbwXjHjOdAh94Lp5jG
+b8kAl20P0hqqL+5qq661JzAdGotFN7sMOe07Xn3eaYEVe4htVHlIfYMPH2B0CYyHu9LH2ueG
+gwRQ7Dv1hygub3ioK/s9CkgAi83qUENz5RWYMtIiP7tbv34zq6DKwv0ioBY4MEKfR4cCuGzS
+6I67vTvCgXPVxW9qHtoTTDfufUR6yOOa/gvuZt4ESN3fo5Kgpm+74AMeOVR0RZ1lZ+75E9KV
+wmiBGm5ORHGSmyt3MQMW+5UEloz8mxK39O6RtKBL7iKH3d0clJn63KXvUsf2l7/8swRIIVzc
+1jGzpwOsUseKHaeLQuknjTuXAlyCTQYa3C0gEcEBElJY/TMevV+ZrlBB/nCwyaYbmH+WxxfQ
++xF79+eIZ1+L5zGAlSNUBEhO1Gy1T77JUa+s4UUqGP+BNNsGDuR4YXWfCIA2vBfCFZVggkaD
+v1+uX2Rgim39wzMCLkXQ8ZXLWdWo7/XcllOrxJSH84uPat8EULZTtkbY1oW90JzGEJKbOkEA
+YaoQ7I0dKXnkLsY3Y54p1WW7fPQ4JLLhld2LpmZbYOntRJmn8LZWGWv9J3uYlAk9ujAfF10f
+7/qV2zDB//oKBEih8QUPicP+mSdpvRlws5RfiKmcf9W7btoNALqqA2COEOHjjJHdX9b3EVlk
+vftz5n2AbaS7fenhIl1/rbhmBfub3eHwmInRoUEESHcpUKHCEe3dtGe6vB2VXR4U/MvKScZV
+RpHv6KGj0pFZwNasoZ9W5RB0dwAbhuD+xiimlv/m9XwhIQihgTUmarVdEbYcbl1aHgRIFMEp
+bOJ4D49qYR9DXZprlST9nZTgU/R1KTcZe2LsXBNa9xxlO9NYvZCSQSuRndIIwbAXjgezK1d0
+fk+JSIyC8kaQzWBwDGdXBFDA3tUZzKed3JM+R/a8319iAemyyYgvk/izKKXncpOfCdBtVgHc
+cOC7LPs1BGxqGCyOzgD2KMQSclRZJo+siuRvJ2wlKCPjd3DHMk0IJ6EYWgRI3XuDbhKfLzEQ
+h9G5n3wQr8qizabBZ+xZEmJk7AUxA7ykEdSuLlV7qP/ujj9sZ9e/AaHG+mfjGRI01OPquZxW
+Pwt9nRpg1KqCBEjFQqB+hbkfT218E5+mFX/aotUq+tEoZ1Tg3Lq5WENZVwMNR0eFR69vN+gL
+SZU3nnLPEchS3pasz7ZBohL+kEBad4fZfKNWUa4ESO9adCvusxTvjlvf8Gu/lXU17gZuAHsj
+bMwSWyQeZKObCp7kOtK1nQCtLHkWHEpmx16eQWG57+makKYpjD4BSXFXGEwMf3Pw9AQQd6Po
+6JLMr7k5LjkOcyCe/gQIJ2ORlLJDWKMECHz5jFYLkHrLBAhPDugA9fP+MgQIQq/w/QD4YBIE
+CGeYC0G4o7ElBAj3O1FpJvdU+wQIiOZHO6By7rAECN9TZKOJa8YbBDD/7QgWqJSNhck7fNEY
+tHmopW9OWfXfXUInzaQFBtLauNRj0C2OW/zB5dgsC3+9/jYECG3bevzH5HiDBBBHiuTo8yYJ
+1FFJ/y4nIIXaBDC92swMrVUYZUpoNUjf02DsBiVP53Fon+CnKnEQ8gBCncIkb8DBnlZYeZd2
+XISBWmEECNOMqXZKxL2vBBDDg2nHASKLEobcHqmPoIQFBBhcrb6prkWNKColNlf5ss49ocnK
+68UgsNEECNJJEPZOUMW1BBApQ6CzF2t+QiJTdQwoApPzBBg8ZCeSHgypK9CWcWb2iMWiFuKc
+KUmIuskECFr2TQ/U6cWbBAisYHDp2/w4kgQISMieG/GdQlAEEGLN6GdGVz2wf1Hx93PJ4VsE
+EJRdLeaU3FsCwXEICvSKZPMEEIcEdqQkAhmsxfNoKA90QlUECDdUY0tvC48sBBBNOevphvxS
+0dwE2Lzf6gLGBAjoSuhhSYuZBAQIRKWk1TxshoAECFUuzdHGuR3zBBjlvJQjSDrEPqg7unh2
+ZaNy11cUKB07vPMECIbZbuiAcb81BBCyb6N/Aymqo24VwyE9/lTDBCCR0Sn7D8OfmpwUp0MJ
+zEdp+4eTPVu9W6bNj5cTwcG0VQQI9Z1/nYjZ93cECCZ7u+8p0rP8BAg6S5MaaL8mTQQIMt+W
+TF2XgOMEQLOZQ0FOc4oD9mBoXzN/NDmlEz0QWnZPnO/7OhVcuKPi6rtE+5RfdGDL/qxWGU6G
+0qvFT0zTodnwjpzKnYAnoeMEUDPxz4djUsMXhHzpye9vc7N2/B8QUBubRaFPp/FoLv3jjwga
++AawOtkkW1uLku0m6zdN7YYzXTsKlMhwDd3nZS4Vm/v11AnFChiDC2LFmLw0BCC/X36boypP
+pTObDByKlN6IldnnPEi4tl/6Cxr2+28LWgQohCdg9nl3s5COEnwQW+ggcvKodHHK9SdVRscS
+J4ykS0lWZ0ns4/g6/wQIKIXduWxdKJMAAAAAAAAAAAAA
+
diff --git a/demo/smime/opaque.p7 b/demo/smime/opaque.p7
new file mode 100644
index 0000000..a242446
--- /dev/null
+++ b/demo/smime/opaque.p7
@@ -0,0 +1,47 @@
+To: ngps@post1.com
+From: ngps@mpost1.com
+Subject: testing
+MIME-Version: 1.0
+Content-Disposition: attachment; filename="smime.p7m"
+Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+Content-Transfer-Encoding: base64
+
+MIIHHAYJKoZIhvcNAQcCoIIHDTCCBwkCAQExCzAJBgUrDgMCGgUAMIICQwYJKoZI
+hvcNAQcBoIICNASCAjANClMvTUlNRSAtIFNlY3VyZSBNdWx0aXB1cnBvc2UgSW50
+ZXJuZXQgTWFpbCBFeHRlbnNpb25zIFtSRkMgMjMxMSwgUkZDIDIzMTJdIC0gDQpw
+cm92aWRlcyBhIGNvbnNpc3RlbnQgd2F5IHRvIHNlbmQgYW5kIHJlY2VpdmUgc2Vj
+dXJlIE1JTUUgZGF0YS4gQmFzZWQgb24gdGhlDQpwb3B1bGFyIEludGVybmV0IE1J
+TUUgc3RhbmRhcmQsIFMvTUlNRSBwcm92aWRlcyB0aGUgZm9sbG93aW5nIGNyeXB0
+b2dyYXBoaWMNCnNlY3VyaXR5IHNlcnZpY2VzIGZvciBlbGVjdHJvbmljIG1lc3Nh
+Z2luZyBhcHBsaWNhdGlvbnMgLSBhdXRoZW50aWNhdGlvbiwNCm1lc3NhZ2UgaW50
+ZWdyaXR5IGFuZCBub24tcmVwdWRpYXRpb24gb2Ygb3JpZ2luICh1c2luZyBkaWdp
+dGFsIHNpZ25hdHVyZXMpDQphbmQgcHJpdmFjeSBhbmQgZGF0YSBzZWN1cml0eSAo
+dXNpbmcgZW5jcnlwdGlvbikuDQoNClMvTUlNRSBpcyBidWlsdCBvbiB0aGUgUEtD
+UyAjNyBzdGFuZGFyZC4gW1BLQ1M3XQ0KDQpTL01JTUUgaXMgaW1wbGVtZW50ZWQg
+aW4gTmV0c2NhcGUgTWVzc2VuZ2VyIGFuZCBNaWNyb3NvZnQgT3V0bG9vay4NCqCC
+AxAwggMMMIICdaADAgECAgECMA0GCSqGSIb3DQEBBAUAMHsxCzAJBgNVBAYTAlNH
+MREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0ExJDAiBgNV
+BAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEdMBsGCSqGSIb3DQEJARYO
+bmdwc0Bwb3N0MS5jb20wHhcNMDAwOTEwMDk1ODIwWhcNMDIwOTEwMDk1ODIwWjBZ
+MQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xGDAWBgNVBAMTD00yQ3J5
+cHRvIENsaWVudDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20wXDANBgkq
+hkiG9w0BAQEFAANLADBIAkEAoz3zUF0dmxSU+1fso+eTdmjDY71gWNeXWX28qsBJ
+0UFmq4JCtw7Gv4fJ0TZgQHVIrXgKrUvzsquu8eiVjuP/NwIDAQABo4IBBDCCAQAw
+CQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2Vy
+dGlmaWNhdGUwHQYDVR0OBBYEFMcQhEeJ1x9d+8Rzag9yjCiutYKOMIGlBgNVHSME
+gZ0wgZqAFPuHI2nrnDqTFeXFvylRT/7tKDgBoX+kfTB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tggEAMA0GCSqGSIb3DQEBBAUAA4GBAKy2cWa2BF6cbBPE4ici
+//wOqkLDbsI3YZyUSj7ZPnefghx9EwRJfLB3/sXEf78OHL7yV6IMrvEVEAJCYs+3
+w/lspCMJC0hOomxnt0vjyCCd0JeaEwihQGbOo9V0reXzrUy8yNkwo1w8mMSbIvqh
++D5uTB0jKL/ml1EVLw3NJf68MYIBmjCCAZYCAQEwgYAwezELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5u
+Z3BzQHBvc3QxLmNvbQIBAjAJBgUrDgMCGgUAoIGxMBgGCSqGSIb3DQEJAzELBgkq
+hkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTAyMTIwODE3NTIyMVowIwYJKoZIhvcN
+AQkEMRYEFI/KcwJXhIg0bRzYLfAtDhxRMzghMFIGCSqGSIb3DQEJDzFFMEMwCgYI
+KoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsOAwIH
+MA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABEBrhZ8JVqGZ+S7dh+xKDwbF
+yRXiOQWEa2uxD1O5fD02VfGEzDSrV1sPdQ8AcM3o+ny5AyC11E4Fns2cIkXwZEwz
+
diff --git a/demo/smime/sendsmime.py b/demo/smime/sendsmime.py
new file mode 100644
index 0000000..9cc446b
--- /dev/null
+++ b/demo/smime/sendsmime.py
@@ -0,0 +1,82 @@
+#!/usr/bin/env python
+
+"""S/MIME sender.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME, X509
+import smtplib, string, sys
+
+def sendsmime(from_addr, to_addrs, subject, msg, from_key, from_cert=None, to_certs=None, smtpd='localhost'):
+
+ msg_bio = BIO.MemoryBuffer(msg)
+ sign = from_key
+ encrypt = to_certs
+
+ s = SMIME.SMIME()
+ if sign:
+ s.load_key(from_key, from_cert)
+ p7 = s.sign(msg_bio, flags=SMIME.PKCS7_TEXT)
+ msg_bio = BIO.MemoryBuffer(msg) # Recreate coz sign() has consumed it.
+
+ if encrypt:
+ sk = X509.X509_Stack()
+ for x in to_certs:
+ sk.push(X509.load_cert(x))
+ s.set_x509_stack(sk)
+ s.set_cipher(SMIME.Cipher('rc2_40_cbc'))
+ tmp_bio = BIO.MemoryBuffer()
+ if sign:
+ s.write(tmp_bio, p7)
+ else:
+ tmp_bio.write(msg)
+ p7 = s.encrypt(tmp_bio)
+
+ out = BIO.MemoryBuffer()
+ out.write('From: %s\r\n' % from_addr)
+ out.write('To: %s\r\n' % string.join(to_addrs, ", "))
+ out.write('Subject: %s\r\n' % subject)
+ if encrypt:
+ s.write(out, p7)
+ else:
+ if sign:
+ s.write(out, p7, msg_bio, SMIME.PKCS7_TEXT)
+ else:
+ out.write('\r\n')
+ out.write(msg)
+ out.close()
+
+ smtp = smtplib.SMTP()
+ smtp.connect(smtpd)
+ smtp.sendmail(from_addr, to_addrs, out.read())
+ smtp.quit()
+
+ # XXX Cleanup the stack and store.
+
+
+msg = """
+S/MIME - Secure Multipurpose Internet Mail Extensions [RFC 2311, RFC 2312] -
+provides a consistent way to send and receive secure MIME data. Based on the
+popular Internet MIME standard, S/MIME provides the following cryptographic
+security services for electronic messaging applications - authentication,
+message integrity and non-repudiation of origin (using digital signatures)
+and privacy and data security (using encryption).
+
+S/MIME is built on the PKCS #7 standard. [PKCS7]
+
+S/MIME is implemented in Netscape Messenger and Microsoft Outlook.
+"""
+
+
+if __name__ == '__main__':
+ Rand.load_file('../randpool.dat', -1)
+ sendsmime(from_addr = 'ngps@post1.com',
+ to_addrs = ['jerry','ngps@post1.com'],
+ subject = 'S/MIME testing',
+ msg = msg,
+ from_key = 'client2.pem',
+ #from_key = None,
+ #to_certs = None)
+ to_certs = ['client.pem'])
+ Rand.save_file('../randpool.dat')
+
diff --git a/demo/smime/test.py b/demo/smime/test.py
new file mode 100644
index 0000000..a59cf9f
--- /dev/null
+++ b/demo/smime/test.py
@@ -0,0 +1,192 @@
+#!/usr/bin/env python
+
+"""S/MIME demo.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME, X509
+
+ptxt = """
+S/MIME - Secure Multipurpose Internet Mail Extensions [RFC 2311, RFC 2312] -
+provides a consistent way to send and receive secure MIME data. Based on the
+popular Internet MIME standard, S/MIME provides the following cryptographic
+security services for electronic messaging applications - authentication,
+message integrity and non-repudiation of origin (using digital signatures)
+and privacy and data security (using encryption).
+
+S/MIME is built on the PKCS #7 standard. [PKCS7]
+
+S/MIME is implemented in Netscape Messenger and Microsoft Outlook.
+"""
+
+def makebuf():
+ buf = BIO.MemoryBuffer(ptxt)
+ return buf
+
+def sign():
+ print 'test sign & save...',
+ buf = makebuf()
+ s = SMIME.SMIME()
+ s.load_key('client.pem')
+ p7 = s.sign(buf)
+ out = BIO.openfile('clear.p7', 'w')
+ out.write('To: ngps@post1.com\n')
+ out.write('From: ngps@post1.com\n')
+ out.write('Subject: testing\n')
+ buf = makebuf() # Recreate buf, because sign() has consumed it.
+ s.write(out, p7, buf)
+ out.close()
+
+ buf = makebuf()
+ p7 = s.sign(buf)
+ out = BIO.openfile('opaque.p7', 'w')
+ out.write('To: ngps@post1.com\n')
+ out.write('From: ngps@mpost1.com\n')
+ out.write('Subject: testing\n')
+ s.write(out, p7)
+ out.close()
+ print 'ok'
+
+def verify_clear():
+ print 'test load & verify clear...',
+ s = SMIME.SMIME()
+ x509 = X509.load_cert('client.pem')
+ sk = X509.X509_Stack()
+ sk.push(x509)
+ s.set_x509_stack(sk)
+ st = X509.X509_Store()
+ st.load_info('ca.pem')
+ s.set_x509_store(st)
+ p7, data = SMIME.smime_load_pkcs7('clear.p7')
+ v = s.verify(p7)
+ if v:
+ print 'ok'
+ else:
+ print 'not ok'
+
+def verify_opaque():
+ print 'test load & verify opaque...',
+ s = SMIME.SMIME()
+ x509 = X509.load_cert('client.pem')
+ sk = X509.X509_Stack()
+ sk.push(x509)
+ s.set_x509_stack(sk)
+ st = X509.X509_Store()
+ st.load_info('ca.pem')
+ s.set_x509_store(st)
+ p7, data = SMIME.smime_load_pkcs7('opaque.p7')
+ v = s.verify(p7, data)
+ if v:
+ print 'ok'
+ else:
+ print 'not ok'
+
+def verify_netscape():
+ print 'test load & verify netscape messager output...',
+ s = SMIME.SMIME()
+ #x509 = X509.load_cert('client.pem')
+ sk = X509.X509_Stack()
+ #sk.push(x509)
+ s.set_x509_stack(sk)
+ st = X509.X509_Store()
+ st.load_info('ca.pem')
+ s.set_x509_store(st)
+ p7, data = SMIME.smime_load_pkcs7('ns.p7')
+ v = s.verify(p7, data)
+ print '\n', v, '\n...ok'
+
+
+def sv():
+ print 'test sign/verify...',
+ buf = makebuf()
+ s = SMIME.SMIME()
+
+ # Load a private key.
+ s.load_key('client.pem')
+
+ # Sign.
+ p7 = s.sign(buf)
+
+ # Output the stuff.
+ bio = BIO.MemoryBuffer()
+ s.write(bio, p7, buf)
+
+ # Plumbing for verification: CA's cert.
+ st = X509.X509_Store()
+ st.load_info('ca.pem')
+ s.set_x509_store(st)
+
+ # Plumbing for verification: Signer's cert.
+ x509 = X509.load_cert('client.pem')
+ sk = X509.X509_Stack()
+ sk.push(x509)
+ s.set_x509_stack(sk)
+
+ # Verify.
+ p7, buf = SMIME.smime_load_pkcs7_bio(bio)
+ v = s.verify(p7, flags=SMIME.PKCS7_DETACHED)
+
+ if v:
+ print 'ok'
+ else:
+ print 'not ok'
+
+def ed():
+ print 'test encrypt/decrypt...',
+ buf = makebuf()
+ s = SMIME.SMIME()
+
+ # Load target cert to encrypt to.
+ x509 = X509.load_cert('client.pem')
+ sk = X509.X509_Stack()
+ sk.push(x509)
+ s.set_x509_stack(sk)
+
+ # Add a cipher.
+ s.set_cipher(SMIME.Cipher('bf_cbc'))
+
+ # Encrypt.
+ p7 = s.encrypt(buf)
+
+ # Load target's private key.
+ s.load_key('client.pem')
+
+ # Decrypt.
+ data = s.decrypt(p7)
+
+ if data:
+ print 'ok'
+ else:
+ print 'not ok'
+
+
+def zope_test():
+ print 'test zophistry...'
+ f = open('client.pem')
+ cert_str = f.read()
+ key_bio = BIO.MemoryBuffer(cert_str)
+ cert_bio = BIO.MemoryBuffer(cert_str) # XXX Kludge.
+ s = SMIME.SMIME()
+ s.load_key_bio(key_bio, cert_bio)
+ # XXX unfinished...
+
+
+def leak_test():
+ # Seems ok, not leaking.
+ while 1:
+ ed()
+ #sv()
+
+
+if __name__ == '__main__':
+ Rand.load_file('../randpool.dat', -1)
+ ed()
+ sign()
+ verify_opaque()
+ verify_clear()
+ #verify_netscape()
+ sv()
+ #zope_test()
+ #leak_test()
+ Rand.save_file('../randpool.dat')
+
diff --git a/demo/smime/unsmime.py b/demo/smime/unsmime.py
new file mode 100644
index 0000000..ffc40ad
--- /dev/null
+++ b/demo/smime/unsmime.py
@@ -0,0 +1,50 @@
+#!/usr/bin/env python
+
+"""S/MIME demo.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import BIO, Rand, SMIME, X509
+import sys
+
+def decrypt_verify(p7file, recip_key, signer_cert, ca_cert):
+ s = SMIME.SMIME()
+
+ # Load decryption private key.
+ s.load_key(recip_key)
+
+ # Extract PKCS#7 blob from input.
+ p7, bio = SMIME.smime_load_pkcs7_bio(p7file)
+
+ # Decrypt.
+ data = s.decrypt(p7)
+
+ # Because we passed in a SignAndEnveloped blob, the output
+ # of our decryption is a Signed blob. We now verify it.
+
+ # Load the signer's cert.
+ sk = X509.X509_Stack()
+ s.set_x509_stack(sk)
+
+ # Load the CA cert.
+ st = X509.X509_Store()
+ st.load_info(ca_cert)
+ s.set_x509_store(st)
+
+ # Verify.
+ p7, bio = SMIME.smime_load_pkcs7_bio(BIO.MemoryBuffer(data))
+ if bio is not None:
+ # Netscape Messenger clear-signs, when also encrypting.
+ data = s.verify(p7, bio)
+ else:
+ # M2Crypto's sendsmime.py opaque-signs, when also encrypting.
+ data = s.verify(p7)
+
+ print data
+
+
+if __name__ == '__main__':
+ Rand.load_file('../randpool.dat', -1)
+ decrypt_verify(BIO.File(sys.stdin), 'client.pem', 'client2.pem','ca.pem')
+ Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/README b/demo/ssl/README
new file mode 100644
index 0000000..07e43b8
--- /dev/null
+++ b/demo/ssl/README
@@ -0,0 +1,25 @@
+ 29 Nov 00
+-----------
+
+This directory contains SSL clients and servers written as demos
+and testbeds.
+
+The oldest ones are s_server.py and s_client.py: these are modeled
+after the eponymous demo programs in OpenSSL. Once I got them going,
+I moved on to Medusa and other "real-world" servers. The pair has been
+in a state of neglect and quite likely no longer work with the current
+M2Crypto.
+
+My current testbeds are the echod-* servers and echo.py. These should
+always work. Note that Python on the three platforms that I test on
+are built --with-threads.
+
+
+Python 2.0's httplib introduces HTTP/1.1 functionality and an interface
+substantially different from Python 1.5.2's HTTP/1.0 interface.
+
+M2Crypto.httpslib provides both interfaces. The demo programs are
+https_cli.py and urllib_cli.py; these have been tested with
+Apache/1.3.14 (Unix) mod_ssl/2.7.1 OpenSSL/0.9.6.
+
+
diff --git a/demo/ssl/c.py b/demo/ssl/c.py
new file mode 100644
index 0000000..bd03e6e
--- /dev/null
+++ b/demo/ssl/c.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+"""C programming in Python. Have SWIG sweat the pointers. ;-)
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from socket import *
+import sys
+
+from M2Crypto import SSL, m2
+
+HOST = '127.0.0.1'
+PORT = 9443
+req_10 = 'GET / HTTP/1.0\r\n\r\n'
+req_11 = 'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'
+
+
+def c_10():
+ c_style(HOST, PORT, req_10)
+
+
+def c_11():
+ c_style(HOST, PORT, req_11)
+
+
+def c_style(HOST, PORT, req):
+
+ # Set up SSL context.
+ ctx = m2.ssl_ctx_new(m2.sslv3_method())
+ m2.ssl_ctx_use_cert(ctx, 'client.pem')
+ m2.ssl_ctx_use_privkey(ctx, 'client.pem')
+
+ # Make the socket connection.
+ s = socket(AF_INET, SOCK_STREAM)
+ s.connect((HOST, PORT))
+
+ # Set up the SSL connection.
+ sbio = m2.bio_new_socket(s.fileno(), 0)
+ ssl = m2.ssl_new(ctx)
+ m2.ssl_set_bio(ssl, sbio, sbio)
+ m2.ssl_connect(ssl)
+ sslbio = m2.bio_new(m2.bio_f_ssl())
+ m2.bio_set_ssl(sslbio, ssl, 0)
+
+ # Push a buffering BIO over the SSL BIO.
+ iobuf = m2.bio_new(m2.bio_f_buffer())
+ topbio = m2.bio_push(iobuf, sslbio)
+
+ # Send the request.
+ m2.bio_write(sslbio, req)
+
+ # Receive the response.
+ while 1:
+ data = m2.bio_gets(topbio, 4096)
+ if not data: break
+ sys.stdout.write(data)
+
+ # Cleanup. May be missing some necessary steps. ;-|
+ m2.bio_pop(topbio)
+ m2.bio_free(iobuf)
+ m2.ssl_shutdown(ssl)
+ m2.ssl_free(ssl)
+ m2.ssl_ctx_free(ctx)
+ s.close()
+
+
+if __name__ == '__main__':
+ c_10()
+ c_11()
+
diff --git a/demo/ssl/c_bio.py b/demo/ssl/c_bio.py
new file mode 100644
index 0000000..1c20e91
--- /dev/null
+++ b/demo/ssl/c_bio.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+"""C programming in Python. Have SWIG sweat the pointers. ;-)
+
+Copyright (c) 1999-2000 Ng Pheng Siong. All rights reserved."""
+
+from socket import *
+import sys
+
+from M2Crypto import SSL, m2
+
+HOST = '127.0.0.1'
+PORT = 9443
+req_10 = 'GET / HTTP/1.0\r\n\r\n'
+req_11 = 'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n'
+
+
+def c_10():
+ c_style(HOST, PORT, req_10)
+
+
+def c_11():
+ c_style(HOST, PORT, req_11)
+
+
+def c_style(HOST, PORT, req):
+
+ # Set up SSL context.
+ ctx = m2.ssl_ctx_new(m2.sslv3_method())
+ m2.ssl_ctx_use_cert(ctx, 'client.pem')
+ m2.ssl_ctx_use_privkey(ctx, 'client.pem')
+
+ # Make the socket connection.
+ s = socket(AF_INET, SOCK_STREAM)
+ s.connect((HOST, PORT))
+
+ # Set up the SSL connection.
+ sbio = m2.bio_new_socket(s.fileno(), 0)
+ ssl = m2.ssl_new(ctx)
+ m2.ssl_set_bio(ssl, sbio, sbio)
+ m2.ssl_connect(ssl)
+ sslbio = m2.bio_new(m2.bio_f_ssl())
+ m2.bio_set_ssl(sslbio, ssl, 0)
+
+ # Push a buffering BIO over the SSL BIO.
+ iobuf = m2.bio_new(m2.bio_f_buffer())
+ topbio = m2.bio_push(iobuf, sslbio)
+
+ # Send the request.
+ m2.bio_write(sslbio, req)
+
+ # Receive the response.
+ while 1:
+ data = m2.bio_gets(topbio, 4096)
+ if not data: break
+ sys.stdout.write(data)
+
+ # Cleanup. May be missing some necessary steps. ;-|
+ m2.bio_pop(topbio)
+ m2.bio_free(iobuf)
+ m2.ssl_shutdown(ssl)
+ m2.ssl_free(ssl)
+ m2.ssl_ctx_free(ctx)
+ s.close()
+
+
+if __name__ == '__main__':
+ #c_10()
+ c_11()
+
diff --git a/demo/ssl/ca.der b/demo/ssl/ca.der
new file mode 100644
index 0000000..8f4ed80
--- /dev/null
+++ b/demo/ssl/ca.der
Binary files differ
diff --git a/demo/ssl/ca.pem b/demo/ssl/ca.pem
new file mode 100644
index 0000000..b7c84a1
--- /dev/null
+++ b/demo/ssl/ca.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIIDWTCCAsKgAwIBAgIBADANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAxMTIxNTA1NTU0NloXDTA0MTIxNDA1NTU0
+NlowgYAxCzAJBgNVBAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxML
+TTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3Rl
+cjEiMCAGCSqGSIb3DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbTCBnzANBgkqhkiG
+9w0BAQEFAAOBjQAwgYkCgYEAx8soJbS719LHK62VVVIQeC3oW0HvFArwPnA0LuEK
+q+LaqMOJg1rS7hvFdX03diV+XJw7cC0iECZYJNG4ii1xbY6KRmufkInaAwm54E3N
+e+YYVocaqUkcN6xVf6fwnLfPXbpFS/K2Umg11ObKMmi80JmiIdjcjRRCQZC7g1hf
+q+kCAwEAAaOB4DCB3TAdBgNVHQ4EFgQU6/qcBzEtQphfXLhiOHbt2KqBwMIwga0G
+A1UdIwSBpTCBooAU6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNV
+BAYTAlNHMREwDwYDVQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0Ex
+JDAiBgNVBAMTG00yQ3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3
+DQEJARYTbmdwc0BuZXRtZW1ldGljLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqG
+SIb3DQEBBAUAA4GBAD+I14GuS5vJmyv1k7mUMbAicsWRHZ+zrGOq9L/L2LsA+lKQ
+dAzEZE2+Zv8LBPJVltbJJhcFNJS/ZMAjEm4xlJuCpvXVMxd/M5AM29aqekWlIK7J
+vsdDL8IuzpRkMniUiNKPhmB6IPIOslvUKx6QofcE0wDh6pg4VvIbCjkpZ7gf
+-----END CERTIFICATE-----
diff --git a/demo/ssl/client.p12 b/demo/ssl/client.p12
new file mode 100644
index 0000000..9c7a640
--- /dev/null
+++ b/demo/ssl/client.p12
Binary files differ
diff --git a/demo/ssl/client.pem b/demo/ssl/client.pem
new file mode 100644
index 0000000..c0274ad
--- /dev/null
+++ b/demo/ssl/client.pem
@@ -0,0 +1,35 @@
+-----BEGIN CERTIFICATE-----
+MIIDPzCCAqigAwIBAgIBBTANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzYyNFoXDTA0MDYyMTEzMzYy
+NFowOjELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRgwFgYDVQQDEw9N
+MkNyeXB0byBDbGllbnQwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAOrvRK8h
+lTNZ6rSWfIR/DuNpq+gQ42FCem/ai/dLKCxWB2BMHLHVkfCtEsJpeltigIFgZ28E
+1h9Dx8RjP8R3lbTdJhv37j+3iUUq5lNXrlIWoKkyJsI4c1gC5kXke2z4uloj3W1E
+dWwOPjKxCi2OGKqwRxNzLrIlnBJ6WsP53PghAgMBAAGjggEMMIIBCDAJBgNVHRME
+AjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRlZCBDZXJ0aWZpY2F0
+ZTAdBgNVHQ4EFgQUMwLQdUUrkDAlEuZGt9fw/6N3mKswga0GA1UdIwSBpTCBooAU
+6/qcBzEtQphfXLhiOHbt2KqBwMKhgYakgYMwgYAxCzAJBgNVBAYTAlNHMREwDwYD
+VQQKEwhNMkNyeXB0bzEUMBIGA1UECxMLTTJDcnlwdG8gQ0ExJDAiBgNVBAMTG00y
+Q3J5cHRvIENlcnRpZmljYXRlIE1hc3RlcjEiMCAGCSqGSIb3DQEJARYTbmdwc0Bu
+ZXRtZW1ldGljLmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQBzJ2Q06/QM2f/OHBzo
+T0QpqAdWFmuclFecuKkzXOdh93cuCpJBrxBsMJMhvW10c7M9kEjW8GEt+obv2prt
+39WDZVejg83O2YUexJlUnhmA3Zc5izkOT0+VasIGSKgt/eQm9p6klk/LKcVEZGzh
+mI26dvftjo0HisrW5zfwEjzbdA==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDq70SvIZUzWeq0lnyEfw7jaavoEONhQnpv2ov3SygsVgdgTByx
+1ZHwrRLCaXpbYoCBYGdvBNYfQ8fEYz/Ed5W03SYb9+4/t4lFKuZTV65SFqCpMibC
+OHNYAuZF5Hts+LpaI91tRHVsDj4ysQotjhiqsEcTcy6yJZwSelrD+dz4IQIDAQAB
+AoGBAL8VDRBEiE3T/IoVPAGoNjvRXvjJg6c/osYHQ4BHqM0my6kPPueFhcXzfyaR
+E+vwGgUgnAA4NtAHGRwqfVsWyLNbeogKO6kEfGoLY+6wjxQrrLohSeS9EUyvqFzW
+dOC082piwzo61OK6q3iLplHPuQ6RK10VR1PQVVtpF/OuaxaBAkEA+6HpW7JTZswu
+WCxMkpT0mXBa6rEqVnkv6WiZyQoFNsqgsSotqifpcdH39mixji+sO9gaUI6uKtcU
+6SSlKRCYzQJBAO8DKaKh9Dq8U+nngc+s9NrkvfDDD0zzdoMznPmV8qQj50sPoJZF
+1nK0wMjaIRrKiMKceOHnFuJnpchKiZiEbKUCQQCk0QJ2azExjd91JV7qS+KCdhM2
+0eA3T51QNpE0GvobT1E9ebD7WLURNkRCA4T46sTXVc62oR33NXWe17/OS+6pAkAB
+FjiYPrhHlBellqHmedjbLfMXJyvoo6rESfXKxL3HtUoV80o9pK+m8d92ildgMc+R
+YvjBvjVCbko4sO4TPXbpAkAE53+iOITLFBeF3dTBh77LvDDHy4Z94knsWSzZv/aL
+26Zvjl5iZjHK6ECIe35kXzVYCWOgghI4ixHSMWOV3bt4
+-----END RSA PRIVATE KEY-----
diff --git a/demo/ssl/dh1024.pem b/demo/ssl/dh1024.pem
new file mode 100644
index 0000000..81d43f6
--- /dev/null
+++ b/demo/ssl/dh1024.pem
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAJf2QmHKtQXdKCjhPx1ottPb0PMTBH9A6FbaWMsTuKG/K3g6TG1Z1fkq
+/Gz/PWk/eLI9TzFgqVAuPvr3q14a1aZeVUMTgo2oO5/y2UHe6VaJ+trqCTat3xlx
+/mNbIK9HA2RgPC3gWfVLZQrY+gz3ASHHR5nXWHEyvpuZm7m3h+irAgEC
+-----END DH PARAMETERS-----
diff --git a/demo/ssl/echo-eg.py b/demo/ssl/echo-eg.py
new file mode 100644
index 0000000..f7975d4
--- /dev/null
+++ b/demo/ssl/echo-eg.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+
+"""Demo SSL client #1 for the HOWTO.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+import getopt, sys
+from socket import gethostname
+from M2Crypto import Err, Rand, SSL, X509, threading
+
+host = '127.0.0.1'
+port = 9999
+
+optlist, optarg = getopt.getopt(sys.argv[1:], 'h:p:')
+for opt in optlist:
+ if '-h' in opt:
+ host = opt[1]
+ elif '-p' in opt:
+ port = int(opt[1])
+
+Rand.load_file('../randpool.dat', -1)
+
+ctx = SSL.Context('sslv3')
+ctx.load_cert('client.pem')
+#ctx.load_verify_info('ca.pem')
+ctx.set_verify(SSL.verify_peer, 10)
+ctx.set_info_callback()
+
+s = SSL.Connection(ctx)
+s.connect((host, port))
+print 'Host =', gethostname()
+print 'Cipher =', s.get_cipher().name()
+
+peer = s.get_peer_cert()
+print 'Server =', peer.get_subject().CN
+
+while 1:
+ data = s.recv()
+ if not data:
+ break
+ sys.stdout.write(data)
+ sys.stdout.flush()
+ buf = sys.stdin.readline()
+ if not buf:
+ break
+ s.send(buf)
+
+s.close()
+
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/echo.py b/demo/ssl/echo.py
new file mode 100644
index 0000000..912e248
--- /dev/null
+++ b/demo/ssl/echo.py
@@ -0,0 +1,59 @@
+#!/usr/bin/env python
+
+"""A simple SSL 'echo' client.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import getopt, sys
+from socket import gethostname
+from M2Crypto import Err, Rand, SSL, X509
+
+host = '127.0.0.1'
+port = 9999
+
+optlist, optarg = getopt.getopt(sys.argv[1:], 'h:p:')
+for opt in optlist:
+ if '-h' in opt:
+ host = opt[1]
+ elif '-p' in opt:
+ port = int(opt[1])
+
+Rand.load_file('../randpool.dat', -1)
+
+ctx = SSL.Context('sslv3')
+ctx.load_cert_chain('client.pem')
+#ctx.set_verify(SSL.verify_none, 10)
+ctx.set_verify(SSL.verify_peer, 10, SSL.cb.ssl_verify_callback)
+ctx.load_verify_locations('ca.pem')
+#ctx.set_allow_unknown_ca(1)
+ctx.set_info_callback()
+
+s = SSL.Connection(ctx)
+s.connect((host, port))
+print 'Host =', gethostname()
+print 'Cipher =', s.get_cipher().name()
+
+## 2003-06-28, ngps: Depends on ctx.set_verify() above, RTFM for details.
+## v = s.get_verify_result()
+## if v != X509.V_OK:
+## s.close()
+## raise SystemExit, 'Server verification failed'
+
+peer = s.get_peer_cert()
+print 'Server =', str(peer.get_subject())
+
+while 1:
+ data = s.recv()
+ if not data:
+ break
+ sys.stdout.write(data)
+ sys.stdout.flush()
+ buf = sys.stdin.readline()
+ if not buf:
+ break
+ s.send(buf)
+
+s.close()
+
+Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/echod-async.py b/demo/ssl/echod-async.py
new file mode 100644
index 0000000..0881c76
--- /dev/null
+++ b/demo/ssl/echod-async.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python
+
+"""An asyncore-based SSL 'echo' server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import asyncore, errno, socket, time
+from M2Crypto import Rand, SSL
+import echod_lib
+
+class ssl_echo_channel(asyncore.dispatcher):
+
+ buffer = 'Ye Olde Echo Servre\r\n'
+
+ def __init__(self, conn):
+ asyncore.dispatcher.__init__(self, conn)
+ self._ssl_accepting = 1
+ self.peer = self.get_peer_cert()
+
+ def handle_connect(self):
+ pass
+
+ def handle_close(self):
+ self.close()
+
+ def writable(self):
+ return self._ssl_accepting or (len(self.buffer) > 0)
+
+ def handle_write(self):
+ if self._ssl_accepting:
+ s = self.socket.accept_ssl()
+ if s:
+ self._ssl_accepting = 0
+ else:
+ try:
+ n = self.send(self.buffer)
+ if n == -1:
+ pass
+ elif n == 0:
+ self.handle_close()
+ else:
+ self.buffer = self.buffer[n:]
+ except SSL.SSLError, what:
+ if str(what) == 'unexpected eof':
+ self.handle_close()
+ return
+ else:
+ raise
+
+ def readable(self):
+ return 1
+
+ def handle_read(self):
+ if self._ssl_accepting:
+ s = self.socket.accept_ssl()
+ if s:
+ self._ssl_accepting = 0
+ else:
+ try:
+ blob = self.recv(4096)
+ if blob is None:
+ pass
+ elif blob == '':
+ self.handle_close()
+ else:
+ self.buffer = self.buffer + blob
+ except SSL.SSLError, what:
+ if str(what) == 'unexpected eof':
+ self.handle_close()
+ return
+ else:
+ raise
+
+
+class ssl_echo_server(SSL.ssl_dispatcher):
+
+ channel_class=ssl_echo_channel
+
+ def __init__(self, addr, port, ssl_context):
+ SSL.ssl_dispatcher.__init__(self)
+ self.create_socket(ssl_context)
+ self.set_reuse_addr()
+ self.socket.setblocking(0)
+ self.bind((addr, port))
+ self.listen(5)
+ self.ssl_ctx=ssl_context
+
+ def handle_accept(self):
+ try:
+ sock, addr = self.socket.accept()
+ self.channel_class(sock)
+ except:
+ print '-'*40
+ import traceback
+ traceback.print_exc()
+ print '-'*40
+ return
+
+ def writable(self):
+ return 0
+
+
+if __name__=='__main__':
+ Rand.load_file('../randpool.dat', -1)
+ ctx = echod_lib.init_context('sslv23', 'server.pem', 'ca.pem', \
+ #SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+ SSL.verify_none)
+ ctx.set_tmp_dh('dh1024.pem')
+ ssl_echo_server('', 9999, ctx)
+ asyncore.loop()
+ Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/echod-eg1.py b/demo/ssl/echod-eg1.py
new file mode 100644
index 0000000..87358b7
--- /dev/null
+++ b/demo/ssl/echod-eg1.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""Demo SSL server #1 for the HOWTO.
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+import SocketServer
+from M2Crypto import Err, Rand, SSL, threading
+
+def init_context(protocol, dhpfile, certfile, cafile, verify, verify_depth=10):
+ ctx = SSL.Context(protocol)
+ ctx.set_tmp_dh(dhpfile)
+ ctx.load_cert(certfile)
+ #ctx.load_verify_info(cafile)
+ ctx.set_verify(verify, verify_depth)
+ ctx.set_session_id_ctx('echod')
+ ctx.set_info_callback()
+ return ctx
+
+class ssl_echo_handler(SocketServer.BaseRequestHandler):
+
+ buffer = 'Ye Olde Echo Servre\r\n'
+
+ def handle(self):
+ peer = self.request.get_peer_cert()
+ if peer is not None:
+ print 'Client CA =', peer.get_issuer().O
+ print 'Client Subject =', peer.get_subject().CN
+ self.request.write(self.buffer)
+ while 1:
+ buf = self.request.read()
+ if not buf:
+ break
+ self.request.write(buf)
+
+ def finish(self):
+ self.request.set_shutdown(SSL.SSL_SENT_SHUTDOWN|SSL.SSL_RECEIVED_SHUTDOWN)
+ self.request.close()
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ threading.init()
+ ctx = init_context('sslv23', 'dh1024.pem', 'server.pem', 'ca.pem', SSL.verify_peer)
+ s = SSL.SSLServer(('', 9999), ssl_echo_handler, ctx)
+ s.serve_forever()
+ threading.cleanup()
+ Rand.save_file('randpool.dat')
+
+
diff --git a/demo/ssl/echod-forking.py b/demo/ssl/echod-forking.py
new file mode 100644
index 0000000..43faab3
--- /dev/null
+++ b/demo/ssl/echod-forking.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+"""A forking SSL 'echo' server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import DH, Rand, SSL
+import echod_lib
+
+class ssl_echo_handler(echod_lib.ssl_echo_handler):
+ buffer = 'Ye Olde Forking Echo Servre\r\n'
+
+
+if __name__ == '__main__':
+ Rand.load_file('../randpool.dat', -1)
+ ctx = echod_lib.init_context('sslv23', 'server.pem', 'ca.pem',
+ SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+ ctx.set_tmp_dh('dh1024.pem')
+ s = SSL.ForkingSSLServer(('', 9999), ssl_echo_handler, ctx)
+ s.serve_forever()
+ Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/echod-iterative.py b/demo/ssl/echod-iterative.py
new file mode 100644
index 0000000..2e68699
--- /dev/null
+++ b/demo/ssl/echod-iterative.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+
+"""A simple iterative SSL 'echo' server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import Rand, SSL
+import echod_lib
+
+class ssl_echo_handler(echod_lib.ssl_echo_handler):
+ buffer='Ye Olde One-At-A-Time Echo Servre\r\n'
+
+
+if __name__=='__main__':
+ Rand.load_file('../randpool.dat', -1)
+ ctx=echod_lib.init_context('sslv23', 'server.pem', 'ca.pem', \
+ SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+ #SSL.verify_none)
+ ctx.set_tmp_dh('dh1024.pem')
+ s=SSL.SSLServer(('', 9999), ssl_echo_handler, ctx)
+ s.serve_forever()
+ Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/echod-thread.py b/demo/ssl/echod-thread.py
new file mode 100644
index 0000000..65db811
--- /dev/null
+++ b/demo/ssl/echod-thread.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+
+"""Another multi-threading SSL 'echo' server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import Rand, SSL, threading
+import echod_lib
+
+import thread
+from socket import *
+
+buffer='Ye Newe Threading Echo Servre\r\n'
+
+def echo_handler(sslctx, sock, addr):
+ sslconn = SSL.Connection(sslctx, sock)
+ sslconn.setup_addr(addr)
+ sslconn.setup_ssl()
+ sslconn.set_accept_state()
+ sslconn.accept_ssl()
+ sslconn.write(buffer)
+ while 1:
+ try:
+ buf = sslconn.read()
+ if not buf:
+ break
+ sslconn.write(buf)
+ except SSL.SSLError, what:
+ if str(what) == 'unexpected eof':
+ break
+ else:
+ raise
+ except:
+ break
+ # Threading servers need this.
+ sslconn.set_shutdown(SSL.SSL_RECEIVED_SHUTDOWN|SSL.SSL_SENT_SHUTDOWN)
+ sslconn.close()
+
+
+if __name__=='__main__':
+ threading.init()
+ Rand.load_file('../randpool.dat', -1)
+ ctx=echod_lib.init_context('sslv23', 'server.pem', 'ca.pem',
+ SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+ ctx.set_tmp_dh('dh1024.pem')
+ sock = socket(AF_INET, SOCK_STREAM)
+ sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
+ sock.bind(('', 9999))
+ sock.listen(5)
+ while 1:
+ conn, addr = sock.accept()
+ thread.start_new_thread(echo_handler, (ctx, conn, addr))
+ Rand.save_file('../randpool.dat')
+ threading.cleanup()
+
diff --git a/demo/ssl/echod-threading.py b/demo/ssl/echod-threading.py
new file mode 100644
index 0000000..81eaef3
--- /dev/null
+++ b/demo/ssl/echod-threading.py
@@ -0,0 +1,32 @@
+#!/usr/bin/env python
+
+"""A multi-threading SSL 'echo' server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import DH, Rand, SSL, threading
+import echod_lib
+
+class ssl_echo_handler(echod_lib.ssl_echo_handler):
+
+ buffer='Ye Olde Threading Echo Servre\r\n'
+
+ def finish(self):
+ # Threading servers need this.
+ self.request.set_shutdown(SSL.SSL_SENT_SHUTDOWN|SSL.SSL_RECEIVED_SHUTDOWN)
+ self.request.close()
+
+
+if __name__=='__main__':
+ try:
+ threading.init()
+ Rand.load_file('../randpool.dat', -1)
+ ctx=echod_lib.init_context('sslv23', 'server.pem', 'ca.pem',
+ SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+ ctx.set_tmp_dh('dh1024.pem')
+ s=SSL.ThreadingSSLServer(('', 9999), ssl_echo_handler, ctx)
+ s.serve_forever()
+ Rand.save_file('../randpool.dat')
+ except:
+ threading.cleanup()
+
diff --git a/demo/ssl/echod_lib.py b/demo/ssl/echod_lib.py
new file mode 100644
index 0000000..7bea683
--- /dev/null
+++ b/demo/ssl/echod_lib.py
@@ -0,0 +1,45 @@
+"""Support routines for the various SSL 'echo' servers.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import SocketServer
+from M2Crypto import SSL
+
+def init_context(protocol, certfile, cafile, verify, verify_depth=10):
+ ctx = SSL.Context(protocol)
+ ctx.load_cert_chain(certfile)
+ ctx.load_verify_locations(cafile)
+ ctx.set_client_CA_list_from_file(cafile)
+ ctx.set_verify(verify, verify_depth)
+ #ctx.set_allow_unknown_ca(1)
+ ctx.set_session_id_ctx('echod')
+ ctx.set_info_callback()
+ return ctx
+
+
+class ssl_echo_handler(SocketServer.BaseRequestHandler):
+
+ buffer = 'Ye Olde Echo Servre\r\n'
+
+ def handle(self):
+ peer = self.request.get_peer_cert()
+ if peer is not None:
+ print 'Client CA =', peer.get_issuer().O
+ print 'Client Subject =', peer.get_subject().CN
+ x = self.request.write(self.buffer)
+ while 1:
+ try:
+ buf = self.request.read()
+ if not buf:
+ break
+ self.request.write(buf)
+ except SSL.SSLError, what:
+ if str(what) == 'unexpected eof':
+ break
+ else:
+ raise
+
+ def finish(self):
+ self.request.close()
+
+
diff --git a/demo/ssl/ftp_tls.py b/demo/ssl/ftp_tls.py
new file mode 100644
index 0000000..163d87c
--- /dev/null
+++ b/demo/ssl/ftp_tls.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+"""Demo for M2Crypto.ftpslib's FTP/TLS client.
+
+This client interoperates with M2Crypto's Medusa-based FTP/TLS
+server as well as Peter Runestig's patched-for-TLS OpenBSD FTP
+server.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import SSL, ftpslib, threading
+
+def passive():
+ ctx = SSL.Context('sslv23')
+ f = ftpslib.FTP_TLS(ssl_ctx=ctx)
+ f.connect('127.0.0.1', 39021)
+ f.auth_tls()
+ f.set_pasv(1)
+ f.login('ftp', 'ngps@')
+ f.prot_p()
+ f.retrlines('LIST')
+ f.quit()
+
+def active():
+ ctx = SSL.Context('sslv23')
+ f = ftpslib.FTP_TLS(ssl_ctx=ctx)
+ f.connect('127.0.0.1', 39021)
+ f.auth_tls()
+ f.set_pasv(0)
+ f.login('ftp', 'ngps@')
+ f.prot_p()
+ f.retrlines('LIST')
+ f.quit()
+
+
+if __name__ == '__main__':
+ threading.init()
+ active()
+ passive()
+ threading.cleanup()
+
diff --git a/demo/ssl/http_cli_20.py b/demo/ssl/http_cli_20.py
new file mode 100644
index 0000000..0ce4cef
--- /dev/null
+++ b/demo/ssl/http_cli_20.py
@@ -0,0 +1,22 @@
+import httplib, sys
+
+def test_httplib():
+ h = httplib.HTTPConnection('127.0.0.1', 80)
+ h.set_debuglevel(1)
+ h.putrequest('GET', '/')
+ h.putheader('Accept', 'text/html')
+ h.putheader('Accept', 'text/plain')
+ h.putheader('Connection', 'close')
+ h.endheaders()
+ resp = h.getresponse()
+ f = resp.fp
+ while 1:
+ data = f.readline()
+ if not data:
+ break
+ sys.stdout.write(data)
+ f.close()
+ h.close()
+
+if __name__=='__main__':
+ test_httplib()
diff --git a/demo/ssl/https_cli.py b/demo/ssl/https_cli.py
new file mode 100644
index 0000000..543ba8f
--- /dev/null
+++ b/demo/ssl/https_cli.py
@@ -0,0 +1,45 @@
+#!/usr/bin/env python
+
+"""Demonstrations of M2Crypto.httpslib.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import sys
+from M2Crypto import Rand, SSL, httpslib, threading
+
+
+def test_httpslib():
+ ctx = SSL.Context('sslv23')
+ ctx.load_cert_chain('client.pem')
+ ctx.load_verify_locations('ca.pem', '')
+ ctx.set_verify(SSL.verify_peer, 10)
+ ctx.set_info_callback()
+ h = httpslib.HTTPSConnection('localhost', 19443, ssl_context=ctx)
+ h.set_debuglevel(1)
+ h.putrequest('GET', '/')
+ h.putheader('Accept', 'text/html')
+ h.putheader('Accept', 'text/plain')
+ h.putheader('Connection', 'close')
+ h.endheaders()
+ resp = h.getresponse()
+ f = resp.fp
+ c = 0
+ while 1:
+ # Either of following two works.
+ #data = f.readline(4096)
+ data = resp.read(4096)
+ if not data: break
+ c = c + len(data)
+ #print data
+ sys.stdout.write(data)
+ sys.stdout.flush()
+ f.close()
+ h.close()
+
+if __name__=='__main__':
+ Rand.load_file('../randpool.dat', -1)
+ #threading.init()
+ test_httpslib()
+ #threading.cleanup()
+ Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/https_cli_async.py b/demo/ssl/https_cli_async.py
new file mode 100644
index 0000000..9e7a5d0
--- /dev/null
+++ b/demo/ssl/https_cli_async.py
@@ -0,0 +1,106 @@
+#!/usr/bin/env python
+
+"""Demo for client-side ssl_dispatcher usage. Note that connect()
+is blocking. (Need fix?)
+
+This isn't really a HTTPS client; it's just a toy.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import asyncore, sys, time
+from M2Crypto import Rand, SSL
+
+class https_client(SSL.ssl_dispatcher):
+
+ def __init__(self, host, path, ssl_ctx):
+ SSL.ssl_dispatcher.__init__(self)
+ self.path = path
+ self.buffer = 'GET %s HTTP/1.0\r\n\r\n' % self.path
+ self.create_socket(ssl_ctx)
+ self.socket.connect((host, 19443))
+ self._can_read = 1
+ self._count = 0
+
+ def handle_connect(self):
+ pass
+
+ def readable(self):
+ return self._can_read
+
+ def handle_read(self):
+ try:
+ result = self.recv()
+ if result is None:
+ return
+ elif result == '':
+ self._can_read = 0
+ sys.stdout.write('%s: total: %5d\n' % (self.path, self._count,))
+ sys.stdout.flush()
+ self.close()
+ else:
+ #print result
+ l = len(result)
+ self._count = self._count + l
+ display = (time.time(), l, self.path)
+ sys.stdout.write('%14.3f: read %5d from %s\n' % display)
+ sys.stdout.flush()
+ except SSL.SSLError, why:
+ print 'handle_read:', why
+ self.close()
+ raise
+
+ def writable(self):
+ return (len(self.buffer) > 0)
+
+ def handle_write(self):
+ try:
+ sent = self.send(self.buffer)
+ self.buffer = self.buffer[sent:]
+ except SSL.SSLError, why:
+ print 'handle_write:', why
+ self.close()
+
+
+if __name__ == '__main__':
+ Rand.load_file('../randpool.dat', -1)
+ ctx = SSL.Context()
+ url = ('/jdk118/api/u-names.html',
+ '/postgresql/xfunc-c.html',
+ '/python2.1/modindex.html')
+ for u in url:
+ https_client('localhost', u, ctx)
+ asyncore.loop()
+ Rand.save_file('../randpool.dat')
+
+
+# Here's a sample output. Server is Apache+mod_ssl on localhost.
+# $ python https_cli_async.py
+# 991501090.682: read 278 from /python2.1/modindex.html
+# 991501090.684: read 278 from /postgresql/xfunc-c.html
+# 991501090.742: read 4096 from /postgresql/xfunc-c.html
+# 991501090.743: read 4096 from /postgresql/xfunc-c.html
+# 991501090.744: read 4096 from /postgresql/xfunc-c.html
+# 991501090.744: read 4096 from /postgresql/xfunc-c.html
+# 991501090.755: read 4096 from /postgresql/xfunc-c.html
+# 991501090.756: read 278 from /jdk118/api/u-names.html
+# 991501090.777: read 4096 from /postgresql/xfunc-c.html
+# 991501090.778: read 4096 from /postgresql/xfunc-c.html
+# 991501090.778: read 4096 from /postgresql/xfunc-c.html
+# 991501090.782: read 4096 from /postgresql/xfunc-c.html
+# 991501090.813: read 4096 from /python2.1/modindex.html
+# 991501090.839: read 4096 from /jdk118/api/u-names.html
+# 991501090.849: read 4096 from /python2.1/modindex.html
+# 991501090.873: read 3484 from /postgresql/xfunc-c.html
+# 991501090.874: read 4096 from /jdk118/api/u-names.html
+# 991501090.874: read 4096 from /python2.1/modindex.html
+#/postgresql/xfunc-c.html: total: 40626
+# 991501090.886: read 4096 from /jdk118/api/u-names.html
+# 991501090.886: read 2958 from /python2.1/modindex.html
+# 991501090.887: read 4096 from /jdk118/api/u-names.html
+#/python2.1/modindex.html: total: 15524
+# 991501090.893: read 4096 from /jdk118/api/u-names.html
+# 991501090.894: read 2484 from /jdk118/api/u-names.html
+#/jdk118/api/u-names.html: total: 23242
+# $
+
+
diff --git a/demo/ssl/https_srv.py b/demo/ssl/https_srv.py
new file mode 100644
index 0000000..6d12d80
--- /dev/null
+++ b/demo/ssl/https_srv.py
@@ -0,0 +1,150 @@
+"""This server extends BaseHTTPServer and SimpleHTTPServer thusly:
+1. One thread per connection.
+2. Generates directory listings.
+
+In addition, it has the following properties:
+1. Works over HTTPS only.
+2. Displays SSL handshaking and SSL session info.
+3. Performs SSL renegotiation when a magic url is requested.
+
+TODO:
+1. Cache stat() of directory entries.
+2. Fancy directory indexing.
+3. Interface ZPublisher.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+"""
+
+import os, sys
+from SimpleHTTPServer import SimpleHTTPRequestHandler
+
+from M2Crypto import Rand, SSL
+from M2Crypto.SSL.SSLServer import ThreadingSSLServer
+
+try:
+ from cStringIO import StringIO
+except ImportError:
+ from StringIO import StringIO
+
+
+def mkdirlist(path, url):
+ dirlist = os.listdir(path)
+ dirlist.sort()
+ f = StringIO()
+ f.write('<title>Index listing for %s</title>\r\n' % (url,))
+ f.write('<h1>Index listing for %s</h1>\r\n' % (url,))
+ f.write('<pre>\r\n')
+ for d in dirlist:
+ if os.path.isdir(os.path.join(path, d)):
+ d2 = d + '/'
+ else:
+ d2 = d
+ if url == '/':
+ f.write('<a href="/%s">%s</a><br>\r\n' % (d, d2))
+ else:
+ f.write('<a href="%s/%s">%s</a><br>\r\n' % (url, d, d2))
+ f.write('</pre>\r\n\r\n')
+ f.reset()
+ return f
+
+
+class HTTP_Handler(SimpleHTTPRequestHandler):
+
+ server_version = "https_srv/0.1"
+ reneg = 0
+
+ # Cribbed from SimpleHTTPRequestHander to add the ".der" entry,
+ # which facilitates installing your own certificates into browsers.
+ extensions_map = {
+ '': 'text/plain', # Default, *must* be present
+ '.html': 'text/html',
+ '.htm': 'text/html',
+ '.gif': 'image/gif',
+ '.jpg': 'image/jpeg',
+ '.jpeg': 'image/jpeg',
+ '.der': 'application/x-x509-ca-cert'
+ }
+
+ def send_head(self):
+ if self.path[1:8] == '_reneg_':
+ self.reneg = 1
+ self.path = self.path[8:]
+ path = self.translate_path(self.path)
+ if os.path.isdir(path):
+ f = mkdirlist(path, self.path)
+ filetype = 'text/html'
+ else:
+ try:
+ f = open(path, 'rb')
+ filetype = self.guess_type(path)
+ except IOError:
+ self.send_error(404, "File not found")
+ return None
+ self.send_response(200)
+ self.send_header("Content-type", filetype)
+ self.end_headers()
+ return f
+
+ def do_GET(self):
+ #sess = self.request.get_session()
+ #self.log_message('\n%s', sess.as_text())
+ f = self.send_head()
+ if self.reneg:
+ self.reneg = 0
+ self.request.renegotiate()
+ sess = self.request.get_session()
+ self.log_message('\n%s', sess.as_text())
+ if f:
+ self.copyfile(f, self.wfile)
+ f.close()
+
+ def do_HEAD(self):
+ #sess = self.request.get_session()
+ #self.log_message('\n%s', sess.as_text())
+ f = self.send_head()
+ if f:
+ f.close()
+
+
+class HTTPS_Server(ThreadingSSLServer):
+ def __init__(self, server_addr, handler, ssl_ctx):
+ ThreadingSSLServer.__init__(self, server_addr, handler, ssl_ctx)
+ self.server_name = server_addr[0]
+ self.server_port = server_addr[1]
+
+ def finish(self):
+ self.request.set_shutdown(SSL.SSL_RECEIVED_SHUTDOWN | SSL.SSL_SENT_SHUTDOWN)
+ self.request.close()
+
+
+def init_context(protocol, certfile, cafile, verify, verify_depth=10):
+ ctx=SSL.Context(protocol)
+ ctx.load_cert(certfile)
+ ctx.load_client_ca(cafile)
+ ctx.load_verify_info(cafile)
+ ctx.set_verify(verify, verify_depth)
+ ctx.set_allow_unknown_ca(1)
+ ctx.set_session_id_ctx('https_srv')
+ ctx.set_info_callback()
+ return ctx
+
+
+if __name__ == '__main__':
+ from M2Crypto import threading as m2threading
+ m2threading.init()
+ if len(sys.argv) < 2:
+ wdir = '.'
+ else:
+ wdir = sys.argv[1]
+ Rand.load_file('../randpool.dat', -1)
+ ctx = init_context('sslv23', 'server.pem', 'ca.pem', \
+ SSL.verify_none)
+ #SSL.verify_peer | SSL.verify_fail_if_no_peer_cert)
+ ctx.set_tmp_dh('dh1024.pem')
+ os.chdir(wdir)
+ httpsd = HTTPS_Server(('', 19443), HTTP_Handler, ctx)
+ httpsd.serve_forever()
+ Rand.save_file('../randpool.dat')
+ m2threading.cleanup()
+
+
diff --git a/demo/ssl/myapp.py b/demo/ssl/myapp.py
new file mode 100644
index 0000000..bb024d8
--- /dev/null
+++ b/demo/ssl/myapp.py
@@ -0,0 +1,30 @@
+"""
+Sample application for socklib and somelib.
+
+Copyright (c) 2007 Open Source Applications Foundation.
+All rights reserved.
+"""
+
+# Sample application that uses socklib to override socket.ssl in order
+# to make the 3rd party library use M2Crypto for SSL, instead of python
+# stdlib SSL.
+
+import socklib # sets M2Crypto.SSL.Connection as socket.ssl
+
+# Set up the secure context for socklib
+from M2Crypto import SSL
+
+def getContext():
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('ca.pem')
+ return ctx
+
+socklib.setSSLContextFactory(getContext)
+
+# Import and use 3rd party lib
+import somelib
+
+if __name__ == '__main__':
+ c = somelib.HttpsGetSlash()
+ c.get('verisign.com', 443)
diff --git a/demo/ssl/s_client.py b/demo/ssl/s_client.py
new file mode 100644
index 0000000..021e3db
--- /dev/null
+++ b/demo/ssl/s_client.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+
+"""An M2Crypto implementation of OpenSSL's s_client.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from socket import *
+import getopt
+import string
+import sys
+
+from M2Crypto import SSL
+
+# s_server -www
+HOST='127.0.0.1'
+PORT=4433
+REQ='GET / HTTP/1.0\r\n\r\n'
+
+class Config:
+ pass
+
+def config(args):
+ options=['connect=', 'verify=', 'cert=', 'key=', 'CApath=', 'CAfile=', \
+ 'reconnect', 'pause', 'showcerts', 'debug', 'nbio_test', 'state', \
+ 'nbio', 'crlf', 'sslv2', 'sslv3', 'tlsv1', 'no_sslv2', 'no_sslv3', \
+ 'no_tlsv1', 'bugs', 'cipher=', 'Verify=']
+ optlist, optarg=getopt.getopt(args, '', options)
+
+ cfg=Config()
+ for opt in optlist:
+ setattr(cfg, opt[0][2:], opt[1])
+ for x in (('tlsv1','no_tlsv1'),('sslv3','no_sslv3'),('sslv2','no_sslv2')):
+ if hasattr(cfg, x[0]) and hasattr(cfg, x[1]):
+ raise ValueError, 'mutually exclusive: %s and %s' % x
+
+ if hasattr(cfg, 'connect'):
+ (host, port)=string.split(cfg.connect, ':')
+ cfg.connect=(host, int(port))
+ else:
+ cfg.connect=(HOST, PORT)
+
+ cfg.protocol=[]
+ # First protocol found will be used.
+ # Permutate the following tuple for preference.
+ for p in ('tlsv1', 'sslv3', 'sslv2'):
+ if hasattr(cfg, p):
+ cfg.protocol.append(p)
+ cfg.protocol.append('sslv23')
+
+ return cfg
+
+def make_context(config):
+ ctx=SSL.Context(config.protocol[0])
+ if hasattr(config, 'cert'):
+ cert=config.cert
+ else:
+ cert='client.pem'
+ if hasattr(config, 'key'):
+ key=config.key
+ else:
+ key='client.pem'
+ #ctx.load_cert(cert, key)
+
+ if hasattr(config, 'verify'):
+ verify=SSL.verify_peer
+ depth=int(config.verify)
+ elif hasattr(config, 'Verify'):
+ verify=SSL.verify_peer | SSL.verify_fail_if_no_peer_cert
+ depth=int(config.Verify)
+ else:
+ verify=SSL.verify_none
+ depth=10
+ config.verify=verify
+ config.verify_depth=depth
+ ctx.set_verify(verify, depth)
+
+ if hasattr(config, 'CAfile'):
+ cafile=config.CAfile
+ else:
+ cafile='ca.pem'
+ ctx.load_verify_location(cafile)
+
+ return ctx
+
+def s_client(config):
+ ctx=make_context(config)
+ s=SSL.Connection(ctx)
+ s.connect(config.connect)
+ if config.verify != SSL.verify_none and not s.verify_ok():
+ print 'peer verification failed'
+ peer=s.get_peer_cert()
+ if peer is None:
+ print 'unable to get peer certificate'
+ else:
+ print 'peer.as_text()'
+ raise SystemExit
+ s.send(REQ)
+ while 1:
+ data=s.recv()
+ if not data:
+ break
+ print data
+ s.close()
+
+if __name__=='__main__':
+ cfg=config(sys.argv[1:])
+ s_client(cfg)
+
diff --git a/demo/ssl/s_server.py b/demo/ssl/s_server.py
new file mode 100644
index 0000000..84789a1
--- /dev/null
+++ b/demo/ssl/s_server.py
@@ -0,0 +1,203 @@
+#!/usr/bin/env python
+
+"""An M2Crypto implementation of OpenSSL's s_server.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from socket import *
+import asyncore
+import cStringIO
+import getopt
+import string
+import sys
+
+from M2Crypto import SSL, BIO, DH, Err
+
+# s_server -www
+HOST=''
+PORT=4433
+
+class Config:
+ pass
+
+def config(args):
+ options=['accept=', 'context=', 'verify=', 'Verify=', 'cert=', 'key=', \
+ 'dcert=', 'dkey=', 'nocert', 'crlf', 'debug', 'CApath=', 'CAfile=', \
+ 'quiet', 'no_tmp_rsa', 'state', 'sslv2', 'sslv3', 'tlsv1', \
+ 'no_sslv2', 'no_sslv3', 'no_tlsv1', 'bugs', 'cipher=']
+ optlist, optarg=getopt.getopt(args, '', options)
+
+ cfg=Config()
+ for opt in optlist:
+ setattr(cfg, opt[0][2:], opt[1])
+ for x in (('tlsv1','no_tlsv1'),('sslv3','no_sslv3'),('sslv2','no_sslv2')):
+ if hasattr(cfg, x[0]) and hasattr(cfg, x[1]):
+ raise ValueError, 'mutually exclusive: %s and %s' % x
+
+ if hasattr(cfg, 'accept'):
+ cfg.accept=string.split(cfg.connect, ':')
+ else:
+ cfg.accept=(HOST, PORT)
+
+ cfg.protocol=[]
+ # First protocol found will be used.
+ # Permutate the following tuple for preference.
+ for p in ('tlsv1', 'sslv3', 'sslv2'):
+ if hasattr(cfg, p):
+ cfg.protocol.append(p)
+ cfg.protocol.append('sslv23')
+
+ return cfg
+
+RESP_HEAD="""\
+HTTP/1.0 200 ok
+Content-type: text/html
+
+<HTML><BODY BGCOLOR=\"#ffffff\">
+<pre>
+
+Emulating s_server -www
+Ciphers supported in s_server.py
+"""
+
+RESP_TAIL="""\
+</pre>
+</BODY></HTML>
+"""
+
+class channel(SSL.ssl_dispatcher):
+
+ def __init__(self, conn, debug):
+ SSL.ssl_dispatcher.__init__(self, conn)
+ self.socket.setblocking(0)
+ self.buffer=self.fixup_buffer()
+ self.debug=debug
+
+ def fixup_buffer(self):
+ even=0
+ buffer=cStringIO.StringIO()
+ buffer.write(RESP_HEAD)
+ for c in self.get_ciphers():
+ # This formatting works for around 80 columns.
+ buffer.write('%-11s:%-28s' % (c.version(), c.name()))
+ if even:
+ buffer.write('\r\n')
+ even=1-even
+ buffer.write('\r\n%s' % RESP_TAIL)
+ return buffer.getvalue()
+
+ def handle_connect(self):
+ pass
+
+ def handle_close(self):
+ self.close()
+
+ def handle_error(self, exc_type, exc_value, exc_traceback):
+ if self.debug:
+ print 'handle_error()'
+ #print exc_type, exc_value, exc_traceback
+ print Err.get_error()
+ self.handle_close()
+
+
+ def writeable(self):
+ return len(self.buffer)
+
+ def handle_write(self):
+ n=self.send(self.buffer)
+ if n==-1:
+ pass
+ elif n==0:
+ self.handle_close()
+ else:
+ self.buffer=self.buffer[n:]
+ if self.debug:
+ print 'handle_write():', n
+
+ def readable(self):
+ return 1
+
+ def handle_read(self):
+ blob=self.recv()
+ if blob is None:
+ pass
+ elif blob=='':
+ self.handle_close()
+ else:
+ pass
+ if self.debug:
+ print 'handle_read():', blob
+
+
+class server(SSL.ssl_dispatcher):
+
+ channel_class=channel
+
+ def __init__(self, addr, port, config, ssl_context):
+ asyncore.dispatcher.__init__(self)
+ self.create_socket(ssl_context)
+ self.set_reuse_addr()
+ self.socket.setblocking(0)
+ self.bind((addr, port))
+ self.listen(5)
+ self.config=config
+ self.debug=config.debug
+ self.ssl_ctx=ssl_context
+
+ def handle_accept(self):
+ sock, addr=self.accept()
+ print self.ssl_ctx.get_verify_mode()
+ if (self.ssl_ctx.get_verify_mode() is SSL.verify_none) or sock.verify_ok():
+ self.channel_class(sock, self.debug)
+ else:
+ print 'client verification failed'
+ sock.close()
+
+ def writeable(self):
+ return 0
+
+def s_server(config):
+ ctx=SSL.Context(config.protocol[0])
+
+ if hasattr(config, 'debug'):
+ config.debug=1
+ else:
+ config.debug=0
+
+ if hasattr(config, 'cert'):
+ cert=config.cert
+ else:
+ cert='server.pem'
+ if hasattr(config, 'key'):
+ cert=config.key
+ else:
+ cert='server.pem'
+ ctx.load_cert(cert)
+
+ if hasattr(config, 'CAfile'):
+ cafile=config.CAfile
+ else:
+ cafile='ca.pem'
+ ctx.load_verify_location(cafile)
+
+ if hasattr(config, 'verify'):
+ verify=SSL.verify_peer
+ depth=int(config.verify)
+ elif hasattr(config, 'Verify'):
+ verify=SSL.verify_peer | SSL.verify_fail_if_no_peer_cert
+ depth=int(config.Verify)
+ else:
+ verify=SSL.verify_none
+ depth=0
+ ctx.set_verify(verify, depth)
+
+ ctx.set_tmp_dh('dh1024.pem')
+ #ctx.set_info_callback()
+
+ server(cfg.accept[0], cfg.accept[1], cfg, ctx)
+ asyncore.loop()
+
+if __name__=='__main__':
+ cfg=config(sys.argv[1:])
+ s_server(cfg)
+
diff --git a/demo/ssl/server.pem b/demo/ssl/server.pem
new file mode 100644
index 0000000..1ee9282
--- /dev/null
+++ b/demo/ssl/server.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/ssl/server3.py b/demo/ssl/server3.py
new file mode 100644
index 0000000..99fbe97
--- /dev/null
+++ b/demo/ssl/server3.py
@@ -0,0 +1,121 @@
+#!/usr/bin/env python
+"""
+server3 from the book 'Network Security with OpenSSL', but modified to
+Python/M2Crypto from the original C implementation.
+
+Copyright (c) 2004-2005 Open Source Applications Foundation.
+Author: Heikki Toivonen
+"""
+from M2Crypto import SSL, Rand, threading, DH
+import thread
+from socket import *
+
+verbose_debug = 1
+
+def verify_callback(ok, store):
+ if not ok:
+ print "***Verify Not ok"
+ return ok
+
+dh1024 = None
+
+def init_dhparams():
+ global dh1024
+ dh1024 = DH.load_params('dh1024.pem')
+
+def tmp_dh_callback(ssl, is_export, keylength):
+ global dh1024
+ if not dh1024:
+ init_dhparams()
+ return dh1024._ptr()
+
+def setup_server_ctx():
+ ctx = SSL.Context('sslv23')
+ if ctx.load_verify_locations('ca.pem') != 1:
+ print "***No CA file"
+ #if ctx.set_default_verify_paths() != 1:
+ # print "***No default verify paths"
+ ctx.load_cert_chain('server.pem')
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert,
+ 10, verify_callback)
+ ctx.set_options(SSL.op_all | SSL.op_no_sslv2)
+ ctx.set_tmp_dh_callback(tmp_dh_callback)
+ #ctx.set_tmp_dh('dh1024.pem')
+ if ctx.set_cipher_list('ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH') != 1:
+ print "***No valid ciphers"
+ if verbose_debug:
+ ctx.set_info_callback()
+ return ctx
+
+def post_connection_check(peerX509, expectedHost):
+ if peerX509 is None:
+ print "***No peer certificate"
+ # Not sure if we can do any other checks
+ return 1
+
+def do_server_loop(conn):
+ while 1:
+ try:
+ buf = conn.read()
+ if not buf:
+ break
+ print buf
+ except SSL.SSLError, what:
+ if str(what) == 'unexpected eof':
+ break
+ else:
+ raise
+ except:
+ break
+
+ if conn.get_shutdown():
+ return 1
+ return 0
+
+# How about something like:
+#def server_thread(ctx, ssl, addr):
+# conn = SSL.Connection(ctx, None)
+# conn.ssl = ssl
+# conn.setup_addr(addr)
+def server_thread(ctx, sock, addr):
+ conn = SSL.Connection(ctx, sock)
+ conn.set_post_connection_check_callback(post_connection_check)
+ conn.setup_addr(addr)
+ conn.set_accept_state()
+ conn.setup_ssl()
+ conn.accept_ssl()
+
+ post_connection_check(conn)
+
+ print 'SSL Connection opened'
+ if do_server_loop(conn):
+ conn.close()
+ else:
+ conn.clear()
+ print 'SSL Connection closed'
+
+
+if __name__=='__main__':
+ threading.init()
+ Rand.load_file('../randpool.dat', -1)
+
+ ctx = setup_server_ctx()
+
+ # How about something like this?
+ #conn_root = SSL.Connection(ctx)
+ #conn_root.bind(('127.0.0.1', 9999))
+ #conn_root.listen(5)
+ #while 1:
+ # ssl, addr = conn_root.accept()
+ # thread.start_new_thread(server_thread, (ctx, ssl, addr))
+
+ sock = socket(AF_INET, SOCK_STREAM)
+ sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
+ sock.bind(('', 9999))
+ sock.listen(5)
+ while 1:
+ conn, addr = sock.accept()
+ thread.start_new_thread(server_thread, (ctx, conn, addr))
+
+ Rand.save_file('../randpool.dat')
+ threading.cleanup()
diff --git a/demo/ssl/sess.py b/demo/ssl/sess.py
new file mode 100644
index 0000000..23428fc
--- /dev/null
+++ b/demo/ssl/sess.py
@@ -0,0 +1,89 @@
+"""M2Crypto.SSL.Session client demo: This program requests a URL from
+a HTTPS server, saves the negotiated SSL session id, parses the HTML
+returned by the server, then requests each HREF in a separate thread
+using the saved SSL session id.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import Err, Rand, SSL, X509, threading
+m2_threading = threading; del threading
+
+import formatter, getopt, htmllib, sys
+from threading import Thread
+from socket import gethostname
+
+
+def handler(sslctx, host, port, href, recurs=0, sslsess=None):
+
+ s = SSL.Connection(sslctx)
+ if sslsess:
+ s.set_session(sslsess)
+ s.connect((host, port))
+ else:
+ s.connect((host, port))
+ sslsess = s.get_session()
+ #print sslsess.as_text()
+
+ if recurs:
+ p = htmllib.HTMLParser(formatter.NullFormatter())
+
+ f = s.makefile("rw")
+ f.write(href)
+ f.flush()
+
+ while 1:
+ data = f.read()
+ if not data:
+ break
+ if recurs:
+ p.feed(data)
+
+ if recurs:
+ p.close()
+
+ f.close()
+
+ if recurs:
+ for a in p.anchorlist:
+ req = 'GET %s HTTP/1.0\r\n\r\n' % a
+ thr = Thread(target=handler,
+ args=(sslctx, host, port, req, recurs-1, sslsess))
+ print "Thread =", thr.getName()
+ thr.start()
+
+
+if __name__ == '__main__':
+
+ m2_threading.init()
+ Rand.load_file('../randpool.dat', -1)
+
+ host = '127.0.0.1'
+ port = 9443
+ req = '/'
+
+ optlist, optarg = getopt.getopt(sys.argv[1:], 'h:p:r:')
+ for opt in optlist:
+ if '-h' in opt:
+ host = opt[1]
+ elif '-p' in opt:
+ port = int(opt[1])
+ elif '-r' in opt:
+ req = opt[1]
+
+ ctx = SSL.Context('sslv3')
+ ctx.load_cert('client.pem')
+ ctx.load_verify_info('ca.pem')
+ ctx.load_client_ca('ca.pem')
+ ctx.set_verify(SSL.verify_none, 10)
+
+ req = 'GET %s HTTP/1.0\r\n\r\n' % req
+
+ start = Thread(target=handler, args=(ctx, host, port, req, 1))
+ print "Thread =", start.getName()
+ start.start()
+ start.join()
+
+ m2_threading.cleanup()
+ Rand.save_file('../randpool.dat')
+
+
diff --git a/demo/ssl/sess2.py b/demo/ssl/sess2.py
new file mode 100644
index 0000000..a3c2bf4
--- /dev/null
+++ b/demo/ssl/sess2.py
@@ -0,0 +1,78 @@
+"""M2Crypto.SSL.Session client demo2: This program creates two sockets, each
+bound to a different local address. The first creates an SSL connection, the
+second then creates another SSL connection using the first's SSL session id.
+
+(This program only works if you've ifconfig'ed your interfaces correctly,
+of course.)
+
+Copyright (c) 1999-2001 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import Err, Rand, SSL, X509, threading
+m2_threading = threading; del threading
+
+import formatter, getopt, htmllib, sys
+from threading import Thread
+from socket import gethostname
+
+ADDR1 = '127.0.0.1', 9999
+ADDR2 = '127.0.0.2', 9999
+
+def handler(addr, sslctx, host, port, req, sslsess=None):
+
+ s = SSL.Connection(sslctx)
+ s.bind(addr)
+ if sslsess:
+ s.set_session(sslsess)
+ s.connect((host, port))
+ else:
+ s.connect((host, port))
+ sslsess = s.get_session()
+ s.write(req)
+ while 1:
+ data = s.read(4096)
+ if not data:
+ break
+
+ if addr != ADDR2:
+ thr = Thread(target=handler,
+ args=(ADDR2, sslctx, host, port, req, sslsess))
+ print "Thread =", thr.getName()
+ thr.start()
+
+ s.close()
+
+
+if __name__ == '__main__':
+
+ m2_threading.init()
+ Rand.load_file('../randpool.dat', -1)
+
+ host = '127.0.0.1'
+ port = 443
+ req = '/'
+
+ optlist, optarg = getopt.getopt(sys.argv[1:], 'h:p:r:')
+ for opt in optlist:
+ if '-h' in opt:
+ host = opt[1]
+ elif '-p' in opt:
+ port = int(opt[1])
+ elif '-r' in opt:
+ req = opt[1]
+
+ ctx = SSL.Context('sslv3')
+ ctx.load_cert('client.pem')
+ ctx.load_verify_info('ca.pem')
+ ctx.set_verify(SSL.verify_none, 10)
+
+ req = 'GET %s HTTP/1.0\r\n\r\n' % req
+
+ start = Thread(target=handler, args=(ADDR1, ctx, host, port, req))
+ print "Thread =", start.getName()
+ start.start()
+ start.join()
+
+ m2_threading.cleanup()
+ Rand.save_file('../randpool.dat')
+
+
diff --git a/demo/ssl/sess2.ssldump.out b/demo/ssl/sess2.ssldump.out
new file mode 100644
index 0000000..9b383ee
--- /dev/null
+++ b/demo/ssl/sess2.ssldump.out
@@ -0,0 +1,112 @@
+New TCP connection #1: localhost(9999) <-> localhost(443)
+1 1 0.0061 (0.0061) C>S Handshake
+ ClientHello
+ Version 3.0
+ cipher suites
+ SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
+ SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA
+ SSL_RSA_WITH_3DES_EDE_CBC_SHA
+ SSL_DHE_DSS_WITH_RC4_128_SHA
+ SSL_RSA_WITH_IDEA_CBC_SHA
+ SSL_RSA_WITH_RC4_128_SHA
+ SSL_RSA_WITH_RC4_128_MD5
+ SSL_DHE_DSS_WITH_RC2_56_CBC_SHA
+ SSL_RSA_EXPORT1024_WITH_RC4_56_SHA
+ SSL_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA
+ SSL_RSA_EXPORT1024_WITH_DES_CBC_SHA
+ SSL_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5
+ SSL_RSA_EXPORT1024_WITH_RC4_56_MD5
+ SSL_DHE_RSA_WITH_DES_CBC_SHA
+ SSL_DHE_DSS_WITH_DES_CBC_SHA
+ SSL_RSA_WITH_DES_CBC_SHA
+ SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
+ SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
+ SSL_RSA_EXPORT_WITH_DES40_CBC_SHA
+ SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5
+ SSL_RSA_EXPORT_WITH_RC4_40_MD5
+ compression methods
+ NULL
+1 2 0.0068 (0.0006) S>C Handshake
+ ServerHello
+ Version 3.0
+ session_id[32]=
+ f2 6e ab c1 e6 db fa 55 4d 77 97 be 0d 28 23 fe
+ 53 8a d5 3b 31 58 1d 93 35 65 10 a9 06 6b a2 a6
+ cipherSuite SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
+ compressionMethod NULL
+1 3 0.0846 (0.0778) S>C Handshake
+ Certificate
+1 4 0.0848 (0.0001) S>C Handshake
+ ServerKeyExchange
+1 5 0.0848 (0.0000) S>C Handshake
+ ServerHelloDone
+1 6 0.2475 (0.1627) C>S Handshake
+ ClientKeyExchange
+ DiffieHellmanClientPublicValue[128]=
+ 3c 8f 0f 93 8a 3a 1e 5c 07 cf 40 2f 7d bf 25 7a
+ e8 2b ba 54 46 d5 54 40 22 90 38 f2 88 c3 62 8a
+ ec b1 b7 f1 06 6d fa ba 46 dd f1 92 5f 44 18 44
+ 8e df 33 30 64 79 e0 77 07 1f bc 10 dc 0d 6e 4b
+ 4d 68 01 af 4a bf 83 62 de 87 d7 98 6c e3 9b af
+ a2 a6 60 67 18 46 89 29 fa 1a 72 df 92 2d 9e 4f
+ 2c b2 02 b3 ef b7 03 07 49 69 c5 b2 37 ae a1 0e
+ 10 e1 79 25 a4 70 02 15 69 d9 47 2c c8 48 23 67
+1 7 0.2475 (0.0000) C>S ChangeCipherSpec
+1 8 0.2475 (0.0000) C>S Handshake
+1 9 0.3239 (0.0763) S>C ChangeCipherSpec
+1 10 0.3239 (0.0000) S>C Handshake
+1 11 0.3266 (0.0027) C>S application_data
+1 12 0.3566 (0.0299) S>C application_data
+1 13 0.3571 (0.0005) S>C Alert
+1 0.3574 (0.0003) S>C TCP FIN
+New TCP connection #2: 127.0.0.2(9999) <-> localhost(443)
+2 1 0.0039 (0.0039) C>S Handshake
+ ClientHello
+ Version 3.0
+ resume [32]=
+ f2 6e ab c1 e6 db fa 55 4d 77 97 be 0d 28 23 fe
+ 53 8a d5 3b 31 58 1d 93 35 65 10 a9 06 6b a2 a6
+ cipher suites
+ SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
+ SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA
+ SSL_RSA_WITH_3DES_EDE_CBC_SHA
+ SSL_DHE_DSS_WITH_RC4_128_SHA
+ SSL_RSA_WITH_IDEA_CBC_SHA
+ SSL_RSA_WITH_RC4_128_SHA
+ SSL_RSA_WITH_RC4_128_MD5
+ SSL_DHE_DSS_WITH_RC2_56_CBC_SHA
+ SSL_RSA_EXPORT1024_WITH_RC4_56_SHA
+ SSL_DHE_DSS_EXPORT1024_WITH_DES_CBC_SHA
+ SSL_RSA_EXPORT1024_WITH_DES_CBC_SHA
+ SSL_RSA_EXPORT1024_WITH_RC2_CBC_56_MD5
+ SSL_RSA_EXPORT1024_WITH_RC4_56_MD5
+ SSL_DHE_RSA_WITH_DES_CBC_SHA
+ SSL_DHE_DSS_WITH_DES_CBC_SHA
+ SSL_RSA_WITH_DES_CBC_SHA
+ SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA
+ SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA
+ SSL_RSA_EXPORT_WITH_DES40_CBC_SHA
+ SSL_RSA_EXPORT_WITH_RC2_CBC_40_MD5
+ SSL_RSA_EXPORT_WITH_RC4_40_MD5
+ compression methods
+ NULL
+2 2 0.0055 (0.0016) S>C Handshake
+ ServerHello
+ Version 3.0
+ session_id[32]=
+ f2 6e ab c1 e6 db fa 55 4d 77 97 be 0d 28 23 fe
+ 53 8a d5 3b 31 58 1d 93 35 65 10 a9 06 6b a2 a6
+ cipherSuite SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
+ compressionMethod NULL
+2 3 0.0055 (0.0000) S>C ChangeCipherSpec
+2 4 0.0055 (0.0000) S>C Handshake
+2 5 0.0066 (0.0010) C>S ChangeCipherSpec
+2 6 0.0066 (0.0000) C>S Handshake
+1 14 0.3698 (0.0124) C>S Alert
+1 0.3702 (0.0004) C>S TCP FIN
+2 7 0.0996 (0.0930) C>S application_data
+2 8 0.1224 (0.0227) S>C application_data
+2 9 0.1244 (0.0019) S>C Alert
+2 10 0.1248 (0.0004) C>S Alert
+2 0.1254 (0.0005) C>S TCP FIN
+2 0.1300 (0.0046) S>C TCP FIN
diff --git a/demo/ssl/socklib.py b/demo/ssl/socklib.py
new file mode 100644
index 0000000..ebaf6d3
--- /dev/null
+++ b/demo/ssl/socklib.py
@@ -0,0 +1,46 @@
+"""
+socklib provides a way to transparently replace socket.ssl with
+M2Crypto.SSL.Connection.
+
+Usage: Import socklib before the 3rd party module that uses socket.ssl. Also,
+ call socketlib.setSSLContextFactory() to set it up with a way to get
+ secure SSL contexts.
+
+Copyright (c) 2007 Open Source Applications Foundation.
+All rights reserved.
+"""
+
+sslContextFactory = None
+
+def setSSLContextFactory(factory):
+ global sslContextFactory
+ sslContextFactory = factory
+
+from M2Crypto.SSL import Connection, Checker
+import socket
+
+class ssl_socket(socket.socket):
+ def connect(self, addr, *args):
+ self.addr = addr
+ return super(ssl_socket, self).connect(addr, *args)
+
+ def close(self):
+ if hasattr(self, 'conn'):
+ self.conn.close()
+ socket.socket.close(self)
+
+def ssl(sock):
+ sock.conn = Connection(ctx=sslContextFactory(), sock=sock)
+ sock.conn.addr = sock.addr
+ sock.conn.setup_ssl()
+ sock.conn.set_connect_state()
+ sock.conn.connect_ssl()
+ check = getattr(sock.conn, 'postConnectionCheck', sock.conn.clientPostConnectionCheck)
+ if check is not None:
+ if not check(sock.conn.get_peer_cert(), sock.conn.addr[0]):
+ raise Checker.SSLVerificationError, 'post connection check failed'
+ return sock.conn
+
+socket.socket = ssl_socket
+socket.ssl = ssl
+
diff --git a/demo/ssl/somelib.py b/demo/ssl/somelib.py
new file mode 100644
index 0000000..21b3491
--- /dev/null
+++ b/demo/ssl/somelib.py
@@ -0,0 +1,21 @@
+"""
+Sample 3rd party lib to use with socklib and myapp.
+
+Copyright (c) 2007 Open Source Applications Foundation.
+All rights reserved.
+"""
+# This represents some 3rd party library we don't want to modify
+
+import socket
+
+class HttpsGetSlash(object):
+ def __init__(self):
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+
+ def get(self, host, port):
+ self.socket.connect((host, port))
+ ssl_sock = socket.ssl(self.socket)
+ ssl_sock.write('GET / HTTP/1.0\n\n')
+ print ssl_sock.read()
+ self.socket.close() \ No newline at end of file
diff --git a/demo/ssl/ss.py b/demo/ssl/ss.py
new file mode 100644
index 0000000..3db6752
--- /dev/null
+++ b/demo/ssl/ss.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+import os, popen2, time
+from socket import *
+
+def main0():
+ cin, cout = popen2.popen2('openssl s_server')
+ cout.write('Q\n')
+ cout.flush()
+ s = socket(AF_INET, SOCK_STREAM)
+ s.connect(('', 4433))
+ s.close()
+
+def main():
+ pid = os.fork()
+ if pid:
+ time.sleep(1)
+ os.kill(pid, 1)
+ os.waitpid(pid, 0)
+ else:
+ os.execvp('openssl', ('s_server',))
+
+if __name__ == '__main__':
+ main()
+
diff --git a/demo/ssl/twistedsslclient.py b/demo/ssl/twistedsslclient.py
new file mode 100755
index 0000000..e4b79c0
--- /dev/null
+++ b/demo/ssl/twistedsslclient.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+"""
+Demonstrates M2Crypto.SSL.TwistedProtocolWrapper
+
+Copyright (c) 2005 Open Source Applications Foundation. All rights reserved.
+"""
+
+import twisted.internet.protocol as protocol
+import twisted.protocols.basic as basic
+import twisted.internet.reactor as reactor
+import M2Crypto.SSL.TwistedProtocolWrapper as wrapper
+import M2Crypto.SSL as SSL
+
+class EchoClient(basic.LineReceiver):
+ def connectionMade(self):
+ self.sendLine('Hello World!')
+
+ def lineReceived(self, line):
+ print 'received: "%s"' % line
+ self.transport.loseConnection()
+
+
+class EchoClientFactory(protocol.ClientFactory):
+ protocol = EchoClient
+
+ def clientConnectionFailed(self, connector, reason):
+ print 'connection failed'
+ reactor.stop()
+
+ def clientConnectionLost(self, connector, reason):
+ print 'connection lost'
+ reactor.stop()
+
+
+class ContextFactory:
+ def getContext(self):
+ return SSL.Context()
+
+
+if __name__ == '__main__':
+ factory = EchoClientFactory()
+ wrapper.connectSSL('localhost', 8000, factory, ContextFactory())
+ reactor.run() # This will block until reactor.stop() is called
diff --git a/demo/ssl/twistedsslserver.py b/demo/ssl/twistedsslserver.py
new file mode 100755
index 0000000..04897a0
--- /dev/null
+++ b/demo/ssl/twistedsslserver.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+"""
+Demonstrates M2Crypto.SSL.TwistedProtocolWrapper
+
+Copyright (c) 2005 Open Source Applications Foundation. All rights reserved.
+"""
+
+import sys
+import M2Crypto.SSL as SSL
+import M2Crypto.SSL.TwistedProtocolWrapper as wrapper
+import twisted.internet.protocol as protocol
+import twisted.internet.reactor as reactor
+import twisted.python.log as log
+
+
+class Echo(protocol.Protocol):
+ def dataReceived(self, data):
+ print 'received: "%s"' % data
+ self.transport.write(data)
+
+ def connectionMade(self):
+ print 'connection made'
+
+
+class ContextFactory:
+ def getContext(self):
+ ctx = SSL.Context()
+ ctx.load_cert('server.pem')
+ return ctx
+
+
+if __name__ == '__main__':
+ log.startLogging(sys.stdout)
+ factory = protocol.Factory()
+ factory.protocol = Echo
+ wrapper.listenSSL(8000, factory, ContextFactory())
+ reactor.run()
diff --git a/demo/ssl/xmlrpc_cli.py b/demo/ssl/xmlrpc_cli.py
new file mode 100644
index 0000000..69647d7
--- /dev/null
+++ b/demo/ssl/xmlrpc_cli.py
@@ -0,0 +1,26 @@
+#!/usr/bin/env python
+
+"""Demonstration of M2Crypto.xmlrpclib2.
+
+Copyright (c) 1999-2004 Ng Pheng Siong. All rights reserved."""
+
+from M2Crypto import Rand
+from M2Crypto.m2xmlrpclib import Server, SSL_Transport
+
+def ZServerSSL():
+ # Server is Zope-2.6.4 on ZServerSSL/0.12.
+ zs = Server('https://127.0.0.1:8443/', SSL_Transport())
+ print zs.propertyMap()
+
+def xmlrpc_srv():
+ # Server is ../https/START_xmlrpc.py or ./xmlrpc_srv.py.
+ zs = Server('https://127.0.0.1:39443', SSL_Transport())
+ print zs.Testing(1, 2, 3)
+ print zs.BringOn('SOAP')
+
+if __name__ == '__main__':
+ Rand.load_file('../randpool.dat', -1)
+ #ZServerSSL()
+ xmlrpc_srv()
+ Rand.save_file('../randpool.dat')
+
diff --git a/demo/ssl/xmlrpc_srv.py b/demo/ssl/xmlrpc_srv.py
new file mode 100644
index 0000000..3811992
--- /dev/null
+++ b/demo/ssl/xmlrpc_srv.py
@@ -0,0 +1,26 @@
+"""Server demonstration of M2Crypto.xmlrpclib2.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+# M2Crypto
+from M2Crypto import DH, SSL
+from echod_lib import init_context
+
+# /F's xmlrpcserver.py.
+from xmlrpcserver import RequestHandler
+
+class xmlrpc_handler(RequestHandler):
+ def call(self, method, params):
+ print "XMLRPC call:", method, params
+ return params
+
+ def finish(self):
+ self.request.set_shutdown(SSL.SSL_RECEIVED_SHUTDOWN | SSL.SSL_SENT_SHUTDOWN)
+ self.request.close()
+
+if __name__ == '__main__':
+ ctx = init_context('sslv23', 'server.pem', 'ca.pem', SSL.verify_none)
+ ctx.set_tmp_dh('dh1024.pem')
+ s = SSL.ThreadingSSLServer(('', 9443), xmlrpc_handler, ctx)
+ s.serve_forever()
+
diff --git a/demo/tinderbox/build_lib.py b/demo/tinderbox/build_lib.py
new file mode 100644
index 0000000..2babb14
--- /dev/null
+++ b/demo/tinderbox/build_lib.py
@@ -0,0 +1,162 @@
+# Copyright (c) 2006-2007 Open Source Applications Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Trimmed down for M2Crypto build purposes
+
+import os, sys
+import glob
+import fnmatch
+import shutil
+import fileinput
+import errno
+import subprocess
+import killableprocess
+import tempfile
+
+
+_logFilename = 'tbox.log'
+_logPrefix = ''
+_logFile = None
+_logEcho = True
+_logEchoErrors = False
+
+
+def initLog(filename, prefix='', echo=True, echoErrors=False):
+ """
+ Initialize log file and store log parameters
+
+ Note: initLog assumes it is called only once per program
+ """
+ global _logFilename, _logPrefix, _logFile, _logEcho, _logEchoErrors
+
+ _logFilename = filename or _logFilename
+ _logEcho = echo
+ _logEchoErrors = echoErrors
+ _logPrefix = prefix
+
+ try:
+ _logFile = open(_logFilename, 'w+')
+ result = True
+ except:
+ result = False
+
+ return result
+
+
+def closeLog():
+ """Need to close log to flush all data."""
+ _logFile.close()
+
+
+def log(msg, error=False, newline='\n'):
+ """
+ Output log message to an open log file or to StdOut
+ """
+ echo = _logEcho
+
+ if _logFile is None:
+ if error or _logEcho:
+ echo = True
+ else:
+ _logFile.write('%s%s%s' % (_logPrefix, msg, newline))
+
+ if error and _logEchoErrors:
+ sys.stderr.write('%s%s%s' % (_logPrefix, msg, newline))
+
+ if echo:
+ sys.stdout.write('%s%s%s' % (_logPrefix, msg, newline))
+ sys.stdout.flush()
+
+
+def setpgid_preexec_fn():
+ os.setpgid(0, 0)
+
+
+def runCommand(cmd, env=None, timeout=-1, logger=log, ignorepreexec=False):
+ """
+ Execute the given command and log all output
+
+ Success and failure codes:
+
+ >>> runCommand(['true'])
+ 0
+ >>> runCommand(['false'])
+ 1
+
+ Interleaved stdout and stderr messages:
+
+ >>> runCommand(['python', '-c', r'print 1;import sys;sys.stdout.flush();print >>sys.stderr, 2;print 3'])
+ 1
+ 2
+ 3
+ 0
+
+ Now with timeout:
+
+ >>> runCommand(['python', '-c', r'print 1;import sys;sys.stdout.flush();print >>sys.stderr, 2;print 3'], timeout=5)
+ 1
+ 2
+ 3
+ 0
+
+ Setting environment variable:
+
+ >>> runCommand(['python', '-c', 'import os;print os.getenv("ENVTEST")'], env={'ENVTEST': '42'})
+ 42
+ 0
+
+ Timeout:
+ >>> runCommand(['sleep', '60'], timeout=5)
+ -9
+ """
+ redirect = True
+
+ if logger == log and _logFile is None:
+ redirect = False
+ else:
+ if timeout == -1:
+ output = subprocess.PIPE
+ else:
+ output = tempfile.TemporaryFile()
+
+ if ignorepreexec:
+ preexec_fn = None
+ else:
+ preexec_fn = setpgid_preexec_fn
+
+ if redirect:
+ p = killableprocess.Popen(cmd, env=env, stdin=subprocess.PIPE, stdout=output, stderr=subprocess.STDOUT, preexec_fn=preexec_fn)
+ else:
+ p = killableprocess.Popen(cmd, env=env, stdin=subprocess.PIPE, preexec_fn=preexec_fn)
+
+ try:
+ if timeout == -1 and redirect:
+ for line in p.stdout:
+ logger(line[:-1])
+
+ p.wait(timeout=timeout, group=True)
+
+ except KeyboardInterrupt:
+ try:
+ p.kill(group=True)
+
+ except OSError:
+ p.wait(30)
+
+ if timeout != -1 and redirect:
+ output.seek(0)
+ for line in output:
+ logger(line[:-1])
+
+ return p.returncode
diff --git a/demo/tinderbox/killableprocess.py b/demo/tinderbox/killableprocess.py
new file mode 100644
index 0000000..9c4fdb0
--- /dev/null
+++ b/demo/tinderbox/killableprocess.py
@@ -0,0 +1,212 @@
+# killableprocess - subprocesses which can be reliably killed
+#
+# Parts of this module are copied from the subprocess.py file contained
+# in the Python distribution.
+#
+# Copyright (c) 2003-2004 by Peter Astrand <astrand@lysator.liu.se>
+#
+# Additions and modifications written by Benjamin Smedberg
+# <benjamin@smedbergs.us> are Copyright (c) 2006 by the Mozilla Foundation
+# <http://www.mozilla.org/>
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of the
+# author not be used in advertising or publicity pertaining to
+# distribution of the software without specific, written prior
+# permission.
+#
+# THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR
+# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
+# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
+# WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+r"""killableprocess - Subprocesses which can be reliably killed
+
+This module is a subclass of the builtin "subprocess" module. It allows
+processes that launch subprocesses to be reliably killed on Windows (via the Popen.kill() method.
+
+It also adds a timeout argument to Wait() for a limited period of time before
+forcefully killing the process.
+
+Note: On Windows, this module requires Windows 2000 or higher (no support for
+Windows 95, 98, or NT 4.0). It also requires ctypes, which is bundled with
+Python 2.5+ or available from http://python.net/crew/theller/ctypes/
+"""
+
+import subprocess
+import sys
+import os
+import time
+import types
+
+try:
+ from subprocess import CalledProcessError
+except ImportError:
+ # Python 2.4 doesn't implement CalledProcessError
+ class CalledProcessError(Exception):
+ """This exception is raised when a process run by check_call() returns
+ a non-zero exit status. The exit status will be stored in the
+ returncode attribute."""
+ def __init__(self, returncode, cmd):
+ self.returncode = returncode
+ self.cmd = cmd
+ def __str__(self):
+ return "Command '%s' returned non-zero exit status %d" % (self.cmd, self.returncode)
+
+mswindows = (sys.platform == "win32")
+
+if mswindows:
+ import winprocess
+else:
+ import signal
+
+def call(*args, **kwargs):
+ waitargs = {}
+ if "timeout" in kwargs:
+ waitargs["timeout"] = kwargs.pop("timeout")
+
+ return Popen(*args, **kwargs).wait(**waitargs)
+
+def check_call(*args, **kwargs):
+ """Call a program with an optional timeout. If the program has a non-zero
+ exit status, raises a CalledProcessError."""
+
+ retcode = call(*args, **kwargs)
+ if retcode:
+ cmd = kwargs.get("args")
+ if cmd is None:
+ cmd = args[0]
+ raise CalledProcessError(retcode, cmd)
+
+if not mswindows:
+ def DoNothing(*args):
+ pass
+
+class Popen(subprocess.Popen):
+ if mswindows:
+ def _execute_child(self, args, executable, preexec_fn, close_fds,
+ cwd, env, universal_newlines, startupinfo,
+ creationflags, shell,
+ p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite):
+ if not isinstance(args, types.StringTypes):
+ args = subprocess.list2cmdline(args)
+
+ if startupinfo is None:
+ startupinfo = winprocess.STARTUPINFO()
+
+ if None not in (p2cread, c2pwrite, errwrite):
+ startupinfo.dwFlags |= winprocess.STARTF_USESTDHANDLES
+
+ startupinfo.hStdInput = int(p2cread)
+ startupinfo.hStdOutput = int(c2pwrite)
+ startupinfo.hStdError = int(errwrite)
+ if shell:
+ startupinfo.dwFlags |= winprocess.STARTF_USESHOWWINDOW
+ startupinfo.wShowWindow = winprocess.SW_HIDE
+ comspec = os.environ.get("COMSPEC", "cmd.exe")
+ args = comspec + " /c " + args
+
+ # We create a new job for this process, so that we can kill
+ # the process and any sub-processes
+ self._job = winprocess.CreateJobObject()
+
+ creationflags |= winprocess.CREATE_SUSPENDED
+ creationflags |= winprocess.CREATE_UNICODE_ENVIRONMENT
+
+ hp, ht, pid, tid = winprocess.CreateProcess(
+ executable, args,
+ None, None, # No special security
+ 1, # Must inherit handles!
+ creationflags,
+ winprocess.EnvironmentBlock(env),
+ cwd, startupinfo)
+
+ self._child_created = True
+ self._handle = hp
+ self._thread = ht
+ self.pid = pid
+
+ winprocess.AssignProcessToJobObject(self._job, hp)
+ winprocess.ResumeThread(ht)
+
+ if p2cread is not None:
+ p2cread.Close()
+ if c2pwrite is not None:
+ c2pwrite.Close()
+ if errwrite is not None:
+ errwrite.Close()
+
+ def kill(self, group=True):
+ """Kill the process. If group=True, all sub-processes will also be killed."""
+ if mswindows:
+ if group:
+ winprocess.TerminateJobObject(self._job, 127)
+ else:
+ winprocess.TerminateProcess(self._handle, 127)
+ self.returncode = 127
+ else:
+ if sys.platform == 'cygwin':
+ cmd = "taskkill /f /pid " + str(self.pid)
+ if group:
+ cmd += " /t"
+ os.system(cmd)
+ elif group:
+ os.killpg(self.pid, signal.SIGKILL)
+ else:
+ os.kill(self.pid, signal.SIGKILL)
+ self.returncode = -9
+
+ def wait(self, timeout=-1, group=True):
+ """Wait for the process to terminate. Returns returncode attribute.
+ If timeout seconds are reached and the process has not terminated,
+ it will be forcefully killed. If timeout is -1, wait will not
+ time out."""
+
+ if self.returncode is not None:
+ return self.returncode
+
+ if mswindows:
+ if timeout != -1:
+ timeout = timeout * 1000
+ rc = winprocess.WaitForSingleObject(self._handle, timeout)
+ if rc == winprocess.WAIT_TIMEOUT:
+ self.kill(group)
+ else:
+ self.returncode = winprocess.GetExitCodeProcess(self._handle)
+ else:
+ if timeout == -1:
+ subprocess.Popen.wait(self)
+ return self.returncode
+
+ starttime = time.time()
+
+ # Make sure there is a signal handler for SIGCHLD installed
+ oldsignal = signal.signal(signal.SIGCHLD, DoNothing)
+
+ while time.time() < starttime + timeout - 0.01:
+ pid, sts = os.waitpid(self.pid, os.WNOHANG)
+ if pid != 0:
+ self._handle_exitstatus(sts)
+ signal.signal(signal.SIGCHLD, oldsignal)
+ return self.returncode
+
+ # time.sleep is interrupted by signals (good!)
+ newtimeout = timeout - time.time() + starttime
+ time.sleep(newtimeout)
+ self.kill(group)
+ signal.signal(signal.SIGCHLD, oldsignal)
+ subprocess.Popen.wait(self)
+
+ return self.returncode
diff --git a/demo/tinderbox/slave.py b/demo/tinderbox/slave.py
new file mode 100755
index 0000000..eaa64c0
--- /dev/null
+++ b/demo/tinderbox/slave.py
@@ -0,0 +1,172 @@
+#!/usr/bin/env python
+#
+"""
+This is a sample Tinderbox2 buildslave script.
+
+NOTE: WAIT at least 6 minutes after the last build before starting
+ the next build!
+
+Create config.ini file with the following contents:
+
+[build]
+name = identify your build slave, for example Ubuntu 8.04 32-bit
+;;optional fields:
+;;uname = uname -a
+;;swig = swig -version
+;;cc = gcc --version
+;;openssl = openssl version
+;;python = python --version
+;;clean = rm -fr m2crypto
+;;svn = svn co http://svn.osafoundation.org/m2crypto/trunk m2crypto
+;;patch =
+;;build = python setup.py clean --all build
+;; OR another way to do tests without setuptools:
+;;build = PYTHONPATH=build/lib-something python tests/alltests.py
+;;test = python setup.py test
+;;wait = 3600
+;;timeout = 180
+
+[email]
+from = your email
+to = Email Heikki Toivonen to get the address
+user = smtp username
+password = smtp password
+server = smtp server
+port = smtp port
+"""
+
+import time, smtplib, os, ConfigParser, tempfile
+import build_lib as bl
+
+# Change to True when you are troubleshooting this build script
+debug_script = False
+
+# These commands assume we are running on a unix-like system where default
+# build options work and all prerequisites are installed and in PATH etc.
+DEFAULT_COMMANDS = {
+ 'uname': ['uname', '-a'],
+ 'swig': ['swig', '-version'],
+ 'cc': ['gcc', '--version'],
+ 'openssl': ['openssl', 'version'],
+ 'python': ['python', '--version'],
+ 'clean': ['rm', '-rf', 'm2crypto'],
+ 'svn': ['svn', 'co', 'http://svn.osafoundation.org/m2crypto/trunk', 'm2crypto'],
+ 'patch': [],
+ 'build': ['python', 'setup.py', 'clean', '--all', 'build'],
+ 'test': ['python', 'setup.py', 'test']
+}
+
+def load_config(cfg='config.ini'):
+ config = {}
+ cp = ConfigParser.ConfigParser()
+ cp.read(cfg)
+ for section in cp.sections():
+ for option in cp.options(section):
+ config[option] = cp.get(section, option).strip()
+ return config
+
+# XXX copied from test_ssl
+def zap_servers():
+ s = 's_server'
+ fn = tempfile.mktemp()
+ cmd = 'ps | egrep %s > %s' % (s, fn)
+ os.system(cmd)
+ f = open(fn)
+ while 1:
+ ps = f.readline()
+ if not ps:
+ break
+ chunk = string.split(ps)
+ pid, cmd = chunk[0], chunk[4]
+ if cmd == s:
+ os.kill(int(pid), 1)
+ f.close()
+ os.unlink(fn)
+
+def build(commands, config):
+ status = 'success'
+
+ cwd = os.getcwd()
+ timeout = int(config.get('timeout') or 180)
+
+ bl.initLog('tbox.log', echo=debug_script)
+
+ starttime = int(time.time())
+
+ for command in commands:
+ cmd = config.get(command)
+ if not cmd:
+ cmd = DEFAULT_COMMANDS[command]
+ if not cmd:
+ continue
+ else:
+ cmd = cmd.split()
+
+ bl.log('*** %s, timeout=%ds' % (' '.join(cmd), timeout))
+
+ exit_code = bl.runCommand(cmd, timeout=timeout)
+ if exit_code:
+ bl.log('*** error exit code = %d' % exit_code)
+ if command == 'test':
+ status = 'test_failed'
+ if os.name != 'nt':
+ try:
+ # If tests were killed due to timeout, we may have left
+ # openssl processes running, so try killing
+ zap_servers()
+ except Exception, e:
+ bl.log('*** error: tried to zap_servers: ' + str(e))
+ else:
+ status = 'build_failed'
+ break
+ if command == 'svn':
+ os.chdir('m2crypto')
+
+ timenow = int(time.time())
+
+ bl.closeLog()
+
+ os.chdir(cwd)
+
+ return 'tbox.log', starttime, timenow, status
+
+
+def email(logpath, starttime, timenow, status, config):
+ msg = """From: %(from)s
+To: %(to)s
+Subject: tree: M2Crypto
+
+
+tinderbox: tree: M2Crypto
+tinderbox: starttime: %(starttime)d
+tinderbox: timenow: %(timenow)d
+tinderbox: status: %(status)s
+tinderbox: buildname: %(buildname)s
+tinderbox: errorparser: unix
+tinderbox: END
+
+""" % {'from': config['from'], 'to': config['to'],
+ 'starttime': starttime, 'timenow': timenow,
+ 'status': status,
+ 'buildname': config['name']}
+
+ msg += open(logpath).read()
+
+ server = smtplib.SMTP(host=config['server'], port=int(config['port']))
+ if debug_script:
+ server.set_debuglevel(1)
+ server.starttls() # if your server supports STARTTLS
+ if config.get('user'):
+ server.login(config['user'], config['password'])
+ server.sendmail(config['from'], config['to'], msg)
+ server.quit()
+
+
+if __name__ == '__main__':
+ config = load_config()
+
+ commands = ['uname', 'swig', 'cc', 'openssl', 'python', 'clean', 'svn',
+ 'patch', 'build', 'test']
+
+ logpath, starttime, timenow, status = build(commands, config)
+ email(logpath, starttime, timenow, status, config)
diff --git a/demo/tinderbox/winprocess.py b/demo/tinderbox/winprocess.py
new file mode 100644
index 0000000..4d867fe
--- /dev/null
+++ b/demo/tinderbox/winprocess.py
@@ -0,0 +1,262 @@
+# A module to expose various thread/process/job related structures and
+# methods from kernel32
+#
+# The MIT License
+#
+# Copyright (c) 2006 the Mozilla Foundation <http://www.mozilla.org>
+#
+# Permission is hereby granted, free of charge, to any person obtaining a
+# copy of this software and associated documentation files (the "Software"),
+# to deal in the Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish, distribute, sublicense,
+# and/or sell copies of the Software, and to permit persons to whom the
+# Software is furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+# DEALINGS IN THE SOFTWARE.
+
+from ctypes import c_void_p, POINTER, sizeof, Structure, windll, WinError, WINFUNCTYPE
+from ctypes.wintypes import BOOL, BYTE, DWORD, HANDLE, LPCWSTR, LPWSTR, UINT, WORD
+
+LPVOID = c_void_p
+LPBYTE = POINTER(BYTE)
+LPDWORD = POINTER(DWORD)
+
+def ErrCheckBool(result, func, args):
+ """errcheck function for Windows functions that return a BOOL True
+ on success"""
+ if not result:
+ raise WinError()
+ return args
+
+# CloseHandle()
+
+CloseHandleProto = WINFUNCTYPE(BOOL, HANDLE)
+CloseHandle = CloseHandleProto(("CloseHandle", windll.kernel32))
+CloseHandle.errcheck = ErrCheckBool
+
+# AutoHANDLE
+
+class AutoHANDLE(HANDLE):
+ """Subclass of HANDLE which will call CloseHandle() on deletion."""
+ def Close(self):
+ if self.value:
+ CloseHandle(self)
+ self.value = 0
+
+ def __del__(self):
+ self.Close()
+
+ def __int__(self):
+ return self.value
+
+def ErrCheckHandle(result, func, args):
+ """errcheck function for Windows functions that return a HANDLE."""
+ if not result:
+ raise WinError()
+ return AutoHANDLE(result)
+
+# PROCESS_INFORMATION structure
+
+class PROCESS_INFORMATION(Structure):
+ _fields_ = [("hProcess", HANDLE),
+ ("hThread", HANDLE),
+ ("dwProcessID", DWORD),
+ ("dwThreadID", DWORD)]
+
+ def __init__(self):
+ Structure.__init__(self)
+
+ self.cb = sizeof(self)
+
+LPPROCESS_INFORMATION = POINTER(PROCESS_INFORMATION)
+
+# STARTUPINFO structure
+
+class STARTUPINFO(Structure):
+ _fields_ = [("cb", DWORD),
+ ("lpReserved", LPWSTR),
+ ("lpDesktop", LPWSTR),
+ ("lpTitle", LPWSTR),
+ ("dwX", DWORD),
+ ("dwY", DWORD),
+ ("dwXSize", DWORD),
+ ("dwYSize", DWORD),
+ ("dwXCountChars", DWORD),
+ ("dwYCountChars", DWORD),
+ ("dwFillAttribute", DWORD),
+ ("dwFlags", DWORD),
+ ("wShowWindow", WORD),
+ ("cbReserved2", WORD),
+ ("lpReserved2", LPBYTE),
+ ("hStdInput", HANDLE),
+ ("hStdOutput", HANDLE),
+ ("hStdError", HANDLE)
+ ]
+LPSTARTUPINFO = POINTER(STARTUPINFO)
+
+STARTF_USESHOWWINDOW = 0x01
+STARTF_USESIZE = 0x02
+STARTF_USEPOSITION = 0x04
+STARTF_USECOUNTCHARS = 0x08
+STARTF_USEFILLATTRIBUTE = 0x10
+STARTF_RUNFULLSCREEN = 0x20
+STARTF_FORCEONFEEDBACK = 0x40
+STARTF_FORCEOFFFEEDBACK = 0x80
+STARTF_USESTDHANDLES = 0x100
+
+# EnvironmentBlock
+
+class EnvironmentBlock:
+ """An object which can be passed as the lpEnv parameter of CreateProcess.
+ It is initialized with a dictionary."""
+
+ def __init__(self, dict):
+ if not dict:
+ self._as_parameter_ = None
+ else:
+ values = ["%s=%s" % (key, value)
+ for (key, value) in dict.iteritems()]
+ values.append("")
+ self._as_parameter_ = LPCWSTR("\0".join(values))
+
+# CreateProcess()
+
+CreateProcessProto = WINFUNCTYPE(BOOL, # Return type
+ LPCWSTR, # lpApplicationName
+ LPWSTR, # lpCommandLine
+ LPVOID, # lpProcessAttributes
+ LPVOID, # lpThreadAttributes
+ BOOL, # bInheritHandles
+ DWORD, # dwCreationFlags
+ LPVOID, # lpEnvironment
+ LPCWSTR, # lpCurrentDirectory
+ LPSTARTUPINFO, # lpStartupInfo
+ LPPROCESS_INFORMATION # lpProcessInformation
+ )
+
+CreateProcessFlags = ((1, "lpApplicationName", None),
+ (1, "lpCommandLine"),
+ (1, "lpProcessAttributes", None),
+ (1, "lpThreadAttributes", None),
+ (1, "bInheritHandles", True),
+ (1, "dwCreationFlags", 0),
+ (1, "lpEnvironment", None),
+ (1, "lpCurrentDirectory", None),
+ (1, "lpStartupInfo"),
+ (2, "lpProcessInformation"))
+
+def ErrCheckCreateProcess(result, func, args):
+ ErrCheckBool(result, func, args)
+ # return a tuple (hProcess, hThread, dwProcessID, dwThreadID)
+ pi = args[9]
+ return AutoHANDLE(pi.hProcess), AutoHANDLE(pi.hThread), pi.dwProcessID, pi.dwThreadID
+
+CreateProcess = CreateProcessProto(("CreateProcessW", windll.kernel32),
+ CreateProcessFlags)
+CreateProcess.errcheck = ErrCheckCreateProcess
+
+CREATE_BREAKAWAY_FROM_JOB = 0x01000000
+CREATE_DEFAULT_ERROR_MODE = 0x04000000
+CREATE_NEW_CONSOLE = 0x00000010
+CREATE_NEW_PROCESS_GROUP = 0x00000200
+CREATE_NO_WINDOW = 0x08000000
+CREATE_SUSPENDED = 0x00000004
+CREATE_UNICODE_ENVIRONMENT = 0x00000400
+DEBUG_ONLY_THIS_PROCESS = 0x00000002
+DEBUG_PROCESS = 0x00000001
+DETACHED_PROCESS = 0x00000008
+
+# CreateJobObject()
+
+CreateJobObjectProto = WINFUNCTYPE(HANDLE, # Return type
+ LPVOID, # lpJobAttributes
+ LPCWSTR # lpName
+ )
+
+CreateJobObjectFlags = ((1, "lpJobAttributes", None),
+ (1, "lpName", None))
+
+CreateJobObject = CreateJobObjectProto(("CreateJobObjectW", windll.kernel32),
+ CreateJobObjectFlags)
+CreateJobObject.errcheck = ErrCheckHandle
+
+# AssignProcessToJobObject()
+
+AssignProcessToJobObjectProto = WINFUNCTYPE(BOOL, # Return type
+ HANDLE, # hJob
+ HANDLE # hProcess
+ )
+AssignProcessToJobObjectFlags = ((1, "hJob"),
+ (1, "hProcess"))
+AssignProcessToJobObject = AssignProcessToJobObjectProto(
+ ("AssignProcessToJobObject", windll.kernel32),
+ AssignProcessToJobObjectFlags)
+AssignProcessToJobObject.errcheck = ErrCheckBool
+
+# ResumeThread()
+
+def ErrCheckResumeThread(result, func, args):
+ if result == -1:
+ raise WinError()
+
+ return args
+
+ResumeThreadProto = WINFUNCTYPE(DWORD, # Return type
+ HANDLE # hThread
+ )
+ResumeThreadFlags = ((1, "hThread"),)
+ResumeThread = ResumeThreadProto(("ResumeThread", windll.kernel32),
+ ResumeThreadFlags)
+ResumeThread.errcheck = ErrCheckResumeThread
+
+# TerminateJobObject()
+
+TerminateJobObjectProto = WINFUNCTYPE(BOOL, # Return type
+ HANDLE, # hJob
+ UINT # uExitCode
+ )
+TerminateJobObjectFlags = ((1, "hJob"),
+ (1, "uExitCode", 127))
+TerminateJobObject = TerminateJobObjectProto(
+ ("TerminateJobObject", windll.kernel32),
+ TerminateJobObjectFlags)
+TerminateJobObject.errcheck = ErrCheckBool
+
+# WaitForSingleObject()
+
+WaitForSingleObjectProto = WINFUNCTYPE(DWORD, # Return type
+ HANDLE, # hHandle
+ DWORD, # dwMilliseconds
+ )
+WaitForSingleObjectFlags = ((1, "hHandle"),
+ (1, "dwMilliseconds", -1))
+WaitForSingleObject = WaitForSingleObjectProto(
+ ("WaitForSingleObject", windll.kernel32),
+ WaitForSingleObjectFlags)
+
+INFINITE = -1
+WAIT_TIMEOUT = 0x0102
+WAIT_OBJECT_0 = 0x0
+WAIT_ABANDONED = 0x0080
+
+# GetExitCodeProcess()
+
+GetExitCodeProcessProto = WINFUNCTYPE(BOOL, # Return type
+ HANDLE, # hProcess
+ LPDWORD, # lpExitCode
+ )
+GetExitCodeProcessFlags = ((1, "hProcess"),
+ (2, "lpExitCode"))
+GetExitCodeProcess = GetExitCodeProcessProto(
+ ("GetExitCodeProcess", windll.kernel32),
+ GetExitCodeProcessFlags)
+GetExitCodeProcess.errcheck = ErrCheckBool
diff --git a/demo/x509/ca.py b/demo/x509/ca.py
new file mode 100644
index 0000000..446998b
--- /dev/null
+++ b/demo/x509/ca.py
@@ -0,0 +1,104 @@
+#!/usr/bin/env python
+
+"""
+How to create a CA certificate with Python.
+
+WARNING: This sample only demonstrates how to use the objects and methods,
+ not how to create a safe and correct certificate.
+
+Copyright (c) 2004 Open Source Applications Foundation.
+Author: Heikki Toivonen
+"""
+
+from M2Crypto import RSA, X509, EVP, m2, Rand, Err
+
+# XXX Do I actually need more keys?
+# XXX Check return values from functions
+
+def generateRSAKey():
+ return RSA.gen_key(2048, m2.RSA_F4)
+
+def makePKey(key):
+ pkey = EVP.PKey()
+ pkey.assign_rsa(key)
+ return pkey
+
+def makeRequest(pkey):
+ req = X509.Request()
+ req.set_version(2)
+ req.set_pubkey(pkey)
+ name = X509.X509_Name()
+ name.CN = 'My CA, Inc.'
+ req.set_subject_name(name)
+ ext1 = X509.new_extension('subjectAltName', 'DNS:foobar.example.com')
+ ext2 = X509.new_extension('nsComment', 'Hello there')
+ extstack = X509.X509_Extension_Stack()
+ extstack.push(ext1)
+ extstack.push(ext2)
+
+ assert(extstack[1].get_name() == 'nsComment')
+
+ req.add_extensions(extstack)
+ req.sign(pkey, 'sha1')
+ return req
+
+def makeCert(req, caPkey):
+ pkey = req.get_pubkey()
+ #woop = makePKey(generateRSAKey())
+ #if not req.verify(woop.pkey):
+ if not req.verify(pkey):
+ # XXX What error object should I use?
+ raise ValueError, 'Error verifying request'
+ sub = req.get_subject()
+ # If this were a real certificate request, you would display
+ # all the relevant data from the request and ask a human operator
+ # if you were sure. Now we just create the certificate blindly based
+ # on the request.
+ cert = X509.X509()
+ # We know we are making CA cert now...
+ # Serial defaults to 0.
+ cert.set_serial_number(1)
+ cert.set_version(2)
+ cert.set_subject(sub)
+ issuer = X509.X509_Name()
+ issuer.CN = 'The Issuer Monkey'
+ issuer.O = 'The Organization Otherwise Known as My CA, Inc.'
+ cert.set_issuer(issuer)
+ cert.set_pubkey(pkey)
+ notBefore = m2.x509_get_not_before(cert.x509)
+ notAfter = m2.x509_get_not_after(cert.x509)
+ m2.x509_gmtime_adj(notBefore, 0)
+ days = 30
+ m2.x509_gmtime_adj(notAfter, 60*60*24*days)
+ cert.add_ext(
+ X509.new_extension('subjectAltName', 'DNS:foobar.example.com'))
+ ext = X509.new_extension('nsComment', 'M2Crypto generated certificate')
+ ext.set_critical(0)# Defaults to non-critical, but we can also set it
+ cert.add_ext(ext)
+ cert.sign(caPkey, 'sha1')
+
+ assert(cert.get_ext('subjectAltName').get_name() == 'subjectAltName')
+ assert(cert.get_ext_at(0).get_name() == 'subjectAltName')
+ assert(cert.get_ext_at(0).get_value() == 'DNS:foobar.example.com')
+
+ return cert
+
+def ca():
+ key = generateRSAKey()
+ pkey = makePKey(key)
+ req = makeRequest(pkey)
+ cert = makeCert(req, pkey)
+ return (cert, pkey)
+
+if __name__ == '__main__':
+ Rand.load_file('../randpool.dat', -1)
+ rsa = generateRSAKey()
+ pkey = makePKey(rsa)
+ req = makeRequest(pkey)
+ print req.as_text()
+ cert = makeCert(req, pkey)
+ print cert.as_text()
+ print cert.as_pem()
+ cert.save_pem('my_ca_cert.pem')
+ rsa.save_key('my_key.pem', 'aes_256_cbc')
+ Rand.save_file('../randpool.dat')
diff --git a/demo/x509/certdata2pem.py b/demo/x509/certdata2pem.py
new file mode 100755
index 0000000..9fd6d32
--- /dev/null
+++ b/demo/x509/certdata2pem.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+"""
+Small utility to convert the Mozilla-format certificates
+(/mozilla/security/nss/lib/ckfw/builtins/certdata.txt in the Mozilla CVS)
+into PEM. Got the idea from http://curl.haxx.se/docs/parse-certs.txt.
+
+Copyright (c) 2007 Open Source Applications Foundation.
+"""
+
+import array
+from M2Crypto import X509
+
+counter = 0
+value = None
+name = None
+
+out = open('cacert.pem', 'wb')
+
+for line in open('certdata.txt'):
+ line = line.strip()
+ if line.startswith('CKA_LABEL'):
+ assert value is None
+
+ label_encoding, name, dummy = line.split('"')
+ label, encoding = label_encoding.split()
+
+ assert encoding == 'UTF8'
+
+ elif line == 'CKA_VALUE MULTILINE_OCTAL':
+ assert name is not None
+
+ value = array.array('c')
+
+ elif value is not None and line == 'END':
+ assert name is not None
+
+ print 'Writing ' + name
+ x509 = X509.load_cert_string(value.tostring(), X509.FORMAT_DER)
+ if not x509.verify():
+ print ' Skipping ' + name + ' since it does not verify'
+ name = None
+ value = None
+ continue
+ counter += 1
+
+ out.write(name + '\n' + '=' * len(name) + '\n\n')
+ out.write('SHA1 Fingerprint=' + x509.get_fingerprint('sha1') + '\n')
+ out.write(x509.as_text())
+ out.write(x509.as_pem())
+ out.write('\n')
+
+ name = None
+ value = None
+
+ elif value is not None:
+ assert name is not None
+
+ for number in line.split('\\'):
+ if not number:
+ continue
+
+ value.append(chr(int(number, 8)))
+
+print 'Wrote %d certificates' % counter
diff --git a/demo/x509/client2.pem b/demo/x509/client2.pem
new file mode 100644
index 0000000..166919a
--- /dev/null
+++ b/demo/x509/client2.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDDjCCAnegAwIBAgIBAzANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tMB4XDTAwMTEyMDEzMDMwNVoXDTAyMTEyMDEzMDMwNVowWzEL
+MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRowGAYDVQQDExFNMkNyeXB0
+byBDbGllbnQgMjEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20wXDANBgkq
+hkiG9w0BAQEFAANLADBIAkEAn9qneRYTKPokme3obiJa2NTz1Z2kcF3NHVh60Qod
+/TV/q4olPrZdFR2TDWt63Lgnygcsgf3u9pnhcEGk6IvntwIDAQABo4IBBDCCAQAw
+CQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2Vy
+dGlmaWNhdGUwHQYDVR0OBBYEFDKWFe6VWMhtRTE3/78+hAnSGxmvMIGlBgNVHSME
+gZ0wgZqAFPuHI2nrnDqTFeXFvylRT/7tKDgBoX+kfTB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tggEAMA0GCSqGSIb3DQEBBAUAA4GBABpE9xt1Hlq2dQZUXHuX
+HI57vc2mlWnhhM0wnNhsNZFwfXRHCZOo/JJBhEIT3Rgyz0ErrbOr1SN96HNDKXOD
+z6bh4NxB5DZ9sRPKEBj66zDsWJVMlom+Lkeal+GkVy36vpAyP1r+cTXyc9M2Gw/o
+FBMinMHH/BXvF5GJ+UleheZe
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBOgIBAAJBAJ/ap3kWEyj6JJnt6G4iWtjU89WdpHBdzR1YetEKHf01f6uKJT62
+XRUdkw1rety4J8oHLIH97vaZ4XBBpOiL57cCAwEAAQJANXfspprUo9MvpPEn2pbR
+Lk/kk2IcW510e0laI0uwBj50djfHqvsU5ccuVLrxowngLGrFmM3G4lnMknR2NvH8
+0QIhAMsK0AwStUNM/KyvIMikHHBOE9PrK7ARgKvlKl+0ieWPAiEAyYwonIVAtr1f
+M8vmrc6TM2YxzSq4+jyYktaaNhYw11kCIA5pmhMBUPSSBm2LkNwtKgeewzGLw/If
+i+6nubZJbnBpAiEAvJQvy4PCsTkvQr+d7zJB+O2920IGId1gxMOXNtQ8jsECIGvn
+Uz54oonshmTg+Kj2DxnUKQEzFAmQLbtFslp1m47v
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBFTCBwAIBADBbMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xGjAY
+BgNVBAMTEU0yQ3J5cHRvIENsaWVudCAyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBv
+c3QxLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCf2qd5FhMo+iSZ7ehuIlrY
+1PPVnaRwXc0dWHrRCh39NX+riiU+tl0VHZMNa3rcuCfKByyB/e72meFwQaToi+e3
+AgMBAAGgADANBgkqhkiG9w0BAQQFAANBAHI5KXfL6kIRoNjR8G9/uKiPUt4uVBKF
+ecGp87M5t2a92Z0KpWOMXSHZ0LLQKqwWzALvWcPPIj6S8F6ENdwpfMk=
+-----END CERTIFICATE REQUEST-----
diff --git a/demo/x509/demo1.py b/demo/x509/demo1.py
new file mode 100644
index 0000000..f0aa282
--- /dev/null
+++ b/demo/x509/demo1.py
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+
+"""X.509 certificate manipulation and such.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved."""
+
+import os
+
+from M2Crypto import X509
+from M2Crypto.EVP import MessageDigest
+
+def demo1():
+ print 'Test 1: As DER...'
+ cert1 = X509.load_cert('server.pem')
+ der1 = cert1.as_der()
+ dgst1 = MessageDigest('sha1')
+ dgst1.update(der1)
+ print 'Using M2Crypto:\n', `dgst1.final()`, '\n'
+
+ cert2 = os.popen('openssl x509 -inform pem -outform der -in server.pem')
+ der2 = cert2.read()
+ dgst2 = MessageDigest('sha1')
+ dgst2.update(der2)
+ print 'Openssl command line:\n', `dgst2.final()`, '\n'
+
+
+def demo2():
+ print 'Test 2: As text...'
+ cert = X509.load_cert('client2.pem')
+ print 'version ', cert.get_version()
+ print 'serial# ', cert.get_serial_number()
+ print 'not before ', cert.get_not_before()
+ print 'not after ', cert.get_not_after()
+ issuer = cert.get_issuer()
+ #print 'issuer ', issuer
+ print 'issuer.C ', `issuer.C`
+ print 'issuer.SP ', `issuer.SP`
+ print 'issuer.L ', `issuer.L`
+ print 'issuer.O ', `issuer.O`
+ print 'issuer.OU ', `issuer.OU`
+ print 'issuer.CN ', `issuer.CN`
+ print 'issuer.Email', `issuer.Email`
+ print 'subject ', cert.get_subject()
+ #print cert.as_text(), '\n'
+
+def demo3():
+ cert = X509.load_cert('server.pem')
+ while 1:
+ x = cert.get_subject()
+
+if __name__ == "__main__":
+ #demo1()
+ demo2()
+ #demo3()
diff --git a/demo/x509/proxy_destroy.py b/demo/x509/proxy_destroy.py
new file mode 100644
index 0000000..c79bd2f
--- /dev/null
+++ b/demo/x509/proxy_destroy.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+############################################################################
+# Matt Rodriguez, LBNL MKRodriguez@lbl.gov
+############################################################################
+"""
+Script that destroys a proxy certificate file by overwriting its contents
+before the file is removed
+"""
+
+import proxylib
+import optparse, os
+
+USAGEHELP = "proxy_destroy.py file1 file2 Destroys files listed"
+JUNK = "LalalAlalaLalalALalalAlalaLalalALalalAlalaLalalALalalAlalaLalalA"
+
+def scrub_file(filename):
+ """
+ Overwrite the file with junk, before removing it
+ """
+ s = os.stat(filename)
+ proxy_file = file(filename, "w")
+ size = s.st_size
+ while size > 64:
+ proxy_file.write(JUNK)
+ size -= 64
+
+ proxy_file.flush()
+ proxy_file.close()
+ os.remove(filename)
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.set_usage(USAGEHELP)
+ opts, args = parser.parse_args()
+ if len(args) is 0:
+ proxy_file = proxylib.get_proxy_filename()
+ scrub_file(proxy_file)
+
+ for proxy_file in args:
+ scrub_file(proxy_file)
+
+if __name__ == "__main__": main()
diff --git a/demo/x509/proxy_info.py b/demo/x509/proxy_info.py
new file mode 100644
index 0000000..ce9cdc3
--- /dev/null
+++ b/demo/x509/proxy_info.py
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+############################################################################
+# Matt Rodriguez, LBNL MKRodriguez@lbl.gov
+############################################################################
+"""
+script that displays information about a proxy certificate
+"""
+
+
+import proxylib
+import time, datetime, calendar
+import sys, optparse
+
+FILEHELP = "Location of the proxy."
+
+def print_info(proxy_cert):
+ """
+ Print information about the proxy cert
+ """
+ cert = proxy_cert.getcert()
+ print "Subject: ", cert.get_subject().as_text()
+ print "Issuer: ", cert.get_issuer().as_text()
+ pubkey = cert.get_pubkey()
+ size = pubkey.size()
+ print "Strength: ", size * 8
+ after = cert.get_not_after()
+ after_tuple = time.strptime(str(after),"%b %d %H:%M:%S %Y %Z")
+ expires = calendar.timegm(after_tuple)
+ now = datetime.timedelta(seconds=time.time())
+ expires = datetime.timedelta(seconds=expires)
+ td = expires - now
+ if td.days < 0:
+ print "Time left: Proxy has expired."
+ else:
+ hours = td.seconds / 3600
+ hours += td.days * 24
+ minutes = (td.seconds % 3600) / 60
+ seconds = (td.seconds % 3600) % 60
+ print "Time left: %d:%d:%d" % (hours, minutes, seconds)
+ fraction = round((float(td.seconds) / float(3600 * 24)), 1)
+ print "Days left: ", str(td.days) + str(fraction)[1:]
+
+
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option("-f", "--file", dest="filename", help=FILEHELP)
+ (opts, args) = parser.parse_args()
+ filename = opts.filename
+ if filename is None:
+ proxyfile = proxylib.get_proxy_filename()
+ else:
+ proxyfile = filename
+ proxy_cert = proxylib.Proxy()
+ try:
+ proxy_cert.read(proxyfile)
+ except IOError:
+ print "The file: " + proxyfile + " does not exist."
+ sys.exit(0)
+ print_info(proxy_cert)
+
+if __name__ == "__main__": main()
diff --git a/demo/x509/proxy_init.py b/demo/x509/proxy_init.py
new file mode 100644
index 0000000..1ca5cbb
--- /dev/null
+++ b/demo/x509/proxy_init.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+############################################################################
+# Matt Rodriguez, LBNL MKRodriguez@lbl.gov
+############################################################################
+"""
+script that generates a proxy certificate
+"""
+
+import proxylib
+import optparse
+import sys
+
+OUTHELP = "Location of the new proxy cert."
+CERTHELP = "Location of user certificate."
+KEYHELP = "Location of the user key."
+VALIDHELP = "h:m Proxy certificate is valid for h hours and m minutes."
+FULLPROXY = "Creates a limited proxy"
+def main():
+ parser = optparse.OptionParser()
+ parser.add_option('-o', '--output', dest='output', help=OUTHELP)
+ parser.add_option('-c', '--cert' , dest='cert', help=CERTHELP)
+ parser.add_option('-k', '--key', dest='key', help=KEYHELP)
+ parser.add_option('-v', '--valid', dest='valid', help=VALIDHELP)
+ parser.add_option('-l', '--limited', action="store_true",
+ default=False, dest='limited', help=VALIDHELP)
+ (opts, args) = parser.parse_args()
+ kw = {}
+ kw['cert'] = opts.cert
+ kw['key'] = opts.key
+ if opts.valid is None:
+ valid_tuple = (12, 0)
+ else:
+ valid = opts.valid.split(':')
+ valid_tuple = tuple(map(int, valid))
+ kw['valid'] = valid_tuple
+ kw['full'] = not opts.limited
+ try:
+ proxy_factory = proxylib.ProxyFactory(kw)
+ except IOError:
+ print "Can't find usercert or userkey. Use the -c or -k arguments"
+ sys.exit(0)
+ proxy_factory.generate()
+ proxy_cert = proxy_factory.getproxy()
+ if opts.output is None:
+ proxy_cert.write(proxylib.get_proxy_filename())
+ else:
+ proxy_cert.write(opts.output)
+
+if __name__ == "__main__": main()
diff --git a/demo/x509/proxylib.py b/demo/x509/proxylib.py
new file mode 100644
index 0000000..e0f3395
--- /dev/null
+++ b/demo/x509/proxylib.py
@@ -0,0 +1,327 @@
+############################################################################
+# Matt Rodriguez, LBNL
+#Copyright (c) 2003, The Regents of the University of California,
+#through Lawrence Berkeley National Laboratory
+#(subject to receipt of any required approvals from the U.S. Dept. of Energy).
+#All rights reserved.
+############################################################################
+"""
+API to generated proxy certificates
+"""
+import os, sys
+import struct
+import re
+import time, calendar, datetime
+
+import_regex = re.compile(r"\s*libssl.so.0.9.8\s*")
+errstr = "You must have the openssl 0.9.8 libraries in your LD_LIBRARY_PATH"""
+
+try:
+ from M2Crypto import BIO, X509, RSA, EVP, ASN1
+except ImportError, ex:
+ if import_regex.match(str(ex)):
+ print errstr
+ sys.exit(-1)
+ else:
+ raise ex
+
+MBSTRING_FLAG = 0x1000
+MBSTRING_ASC = MBSTRING_FLAG | 1
+KEY_USAGE_VALUE = "Digital Signature, Key Encipherment, Data Encipherment"
+PCI_VALUE_FULL = "critical, language:Inherit all"
+PCI_VALUE_LIMITED = "critical, language:1.3.6.1.4.1.3536.1.1.1.9"
+
+def create_write_file(fname, perm=0600):
+ """
+ Creates a file to write to while avoiding a possible race condition.
+ This is essential for writing out the proxy file. Need to make sure
+ there is no pre-existing file.
+ """
+ if os.path.exists(fname):
+ os.remove(fname)
+ # Make sure the file doesn't exist. Will throw an exception if
+ # it does. This would only happen if the code is attacked.
+ fd = os.open(fname, os.O_CREAT|os.O_EXCL|os.O_WRONLY, perm)
+ f = os.fdopen(fd, 'w')
+ return f
+
+
+class ProxyFactoryException(Exception):
+ """
+ Base class for exceptions in the ProxyFactory class
+ """
+
+class Proxy:
+ """
+ class that holds proxy certificate information,
+ consisting of an issuer cert a user cert and
+ a key for the user cert
+ """
+ def __init__(self):
+ self._key = None
+ self._cert = None
+ self._issuer = None
+
+ def read(self, proxypath=None):
+ """
+ reads in a proxy certificate information
+ """
+ if proxypath is None:
+ proxypath = get_proxy_filename()
+
+ proxyfile = open(proxypath)
+ bio = BIO.File(proxyfile)
+ self._cert = X509.load_cert_bio(bio)
+ self._key = RSA.load_key_bio(bio)
+ self._issuer = X509.load_cert_bio(bio)
+
+ def getcert(self):
+ """
+ Returns a X509 instance
+ """
+ return self._cert
+
+ def getkey(self):
+ """
+ Returns a RSA instance
+ """
+ return self._key
+
+ def getissuer(self):
+ """
+ Returns a X509 instance
+ """
+ return self._issuer
+
+ def setcert(self, cert):
+ """
+ Sets the user cert should be a X509 instance
+ """
+ self._cert = cert
+
+ def setkey(self, key):
+ """
+ Sets the user key should be a RSA instance
+ """
+ self._key = key
+
+ def setissuer(self, issuer):
+ """
+ Sets the issuer cert should be a X509 instance
+ """
+ self._issuer = issuer
+
+ def write(self, proxypath=None):
+ """
+ Writes the proxy information to a file
+ """
+ proxyfile = create_write_file(proxypath)
+ bio = BIO.File(proxyfile)
+ bio.write(self._cert.as_pem())
+ self._key.save_key_bio(bio, cipher=None)
+ bio.write(self._issuer.as_pem())
+ bio.close()
+ os.chmod(proxypath, 0600)
+
+
+class ProxyFactory:
+ """
+ Creates proxies
+ """
+ def __init__(self, kw={'cert':None,'key':None,'valid':(12,0),'full':True}):
+
+ self._usercert = get_usercert(kw['cert'])
+ self._userkey = get_userkey(kw['key'])
+ self._proxycert = None
+ self._proxykey = None
+ self._valid = kw['valid']
+ self._full = kw['full']
+
+ def generate(self):
+ """
+ generates a new proxy like grid-proxy-init
+ """
+ if not self._check_valid():
+ raise ProxyFactoryException("The issuer cert is expired")
+ if self._proxycert is None:
+ self._proxycert = X509.X509()
+ key = EVP.PKey()
+ self._proxykey = RSA.gen_key(512, 65537)
+ key.assign_rsa(self._proxykey, capture=0)
+ self._proxycert.set_pubkey(key)
+ self._proxycert.set_version(2)
+ self._set_times()
+ issuer_name = self._usercert.get_subject()
+ self._proxycert.set_issuer_name(issuer_name)
+ serial_number = self._make_serial_number(self._proxycert)
+ self._proxycert.set_serial_number(serial_number)
+ self._set_subject()
+ sign_pk = EVP.PKey()
+ sign_pk.assign_rsa(self._userkey)
+ self._add_extensions()
+ self._proxycert.sign(sign_pk, 'md5')
+
+ def set_proxycert(self, proxycert):
+ """
+ This method is useful if you don't
+ want to pay the costs associated with
+ generating a new key pair.
+ """
+ self._proxycert = proxycert
+
+ def getproxy(self):
+ """
+ Return a proxy instance
+ """
+ proxy = Proxy()
+ proxy.setissuer(self._usercert)
+ proxy.setcert(self._proxycert)
+ proxy.setkey(self._proxykey)
+ return proxy
+
+ def _set_subject(self):
+ """
+ Internal method that sets the subject name
+ """
+ subject_name = X509.X509_Name()
+ serial_number = self._make_serial_number(self._proxycert)
+ issuer_name = self._usercert.get_subject()
+ issuer_name_txt = issuer_name.as_text()
+ seq = issuer_name_txt.split(",")
+ for entry in seq:
+ name_component = entry.split("=")
+ subject_name.add_entry_by_txt(field=name_component[0].strip(),
+ type=MBSTRING_ASC,
+ entry=name_component[1],len=-1,
+ loc=-1, set=0)
+
+
+ subject_name.add_entry_by_txt(field="CN",
+ type=MBSTRING_ASC,
+ entry=str(serial_number),
+ len=-1, loc=-1, set=0)
+
+ self._proxycert.set_subject_name(subject_name)
+
+ def _set_times(self):
+ """
+ Internal function that sets the time on the proxy
+ certificate
+ """
+ not_before = ASN1.ASN1_UTCTIME()
+ not_after = ASN1.ASN1_UTCTIME()
+ not_before.set_time(int(time.time()))
+ offset = (self._valid[0] * 3600) + (self._valid[1] * 60)
+ not_after.set_time(int(time.time()) + offset )
+ self._proxycert.set_not_before(not_before)
+ self._proxycert.set_not_after(not_after)
+
+ def _make_serial_number(self, cert):
+ """
+ Lifted from the globus code
+ """
+ message_digest = EVP.MessageDigest('sha1')
+ pubkey = cert.get_pubkey()
+ der_encoding = pubkey.as_der()
+ message_digest.update(der_encoding)
+ digest = message_digest.final()
+ digest_tuple = struct.unpack('BBBB', digest[:4])
+ sub_hash = long(digest_tuple[0] + (digest_tuple[1] + ( digest_tuple[2] +
+ ( digest_tuple[3] >> 1) * 256 ) * 256) * 256)
+ return sub_hash
+
+ def _add_extensions(self):
+ """
+ Internal method that adds the extensions to the certificate
+ """
+ key_usage_ext = X509.new_extension("keyUsage", KEY_USAGE_VALUE, 1)
+ self._proxycert.add_ext(key_usage_ext)
+ if self._full:
+ pci_ext = X509.new_extension("proxyCertInfo",
+ PCI_VALUE_FULL, 1, 0)
+ else:
+ pci_ext = X509.new_extension("proxyCertInfo",
+ PCI_VALUE_LIMITED, 1, 0)
+ self._proxycert.add_ext(pci_ext)
+
+ def _check_valid(self):
+ """
+ Internal method that ensures the issuer cert has
+ valid, not_before and not_after fields
+ """
+ before_time = self._usercert.get_not_before()
+ after_time = self._usercert.get_not_after()
+ before_tuple = time.strptime(str(before_time), "%b %d %H:%M:%S %Y %Z")
+ after_tuple = time.strptime(str(after_time), "%b %d %H:%M:%S %Y %Z")
+ starts = datetime.timedelta(seconds=calendar.timegm(before_tuple))
+ expires = datetime.timedelta(seconds=calendar.timegm(after_tuple))
+ now = datetime.timedelta(seconds=time.time())
+ time_delta = expires - now
+ #cert has expired
+ if time_delta.days < 0:
+ return False
+ #cert is not yet valid, not likely but should still return False
+ time_delta = now - starts
+ if time_delta.days < 0:
+ return False
+
+ return True
+
+#Utility Functions
+def get_proxy_filename():
+ """
+ function that returns the default proxy path
+ which is /tmp/x509up_uuid
+ """
+ if os.name == 'posix':
+ proxy_filename = "x509up_u" + (str(os.getuid()))
+ proxypath = os.path.join("/tmp", proxy_filename)
+ elif os.name == 'nt':
+ username = os.getenv("USERNAME")
+ if username is None:
+ raise RuntimeError("""USERNAME is not set in environment. Can't
+ determine proxy file location""")
+
+ proxy_filename = "x509up_u" + username
+ drive = os.path.splitdrive(os.getcwd())[0]
+ proxydir = drive + os.sep + "temp"
+ proxypath = os.path.join(proxydir, proxy_filename)
+ else:
+ except_string = """get_proxy_filename is not supported on this platform
+ Try explicitly specifying the location of the
+ proxyfile"""
+ raise RuntimeError(except_string)
+ return proxypath
+
+def get_usercert(certfile=None):
+ """
+ function that returns a X509 instance which
+ is the user cert that is expected to be a ~/.globus/usercert.pem
+
+ A check is performed to ensure the certificate has valid
+ before and after times.
+ """
+ if certfile is None:
+ certfile = open(os.path.join(os.getenv("HOME"),
+ ".globus","usercert.pem"))
+ else:
+ certfile = open(certfile)
+ bio = BIO.File(certfile)
+ cert = X509.load_cert_bio(bio)
+ return cert
+
+def get_userkey(keyfile=None):
+ """
+ function that returns a X509 instance which
+ is the user cert that is expected to be a ~/.globus/userkey.pem
+ """
+ if keyfile is None:
+ keyfile = open(os.path.join(os.getenv("HOME"),
+ ".globus","userkey.pem"))
+ else:
+ keyfile = open(keyfile)
+ bio = BIO.File(keyfile)
+ key = RSA.load_key_bio(bio)
+ return key
+
+
diff --git a/demo/x509/server-expired.pem b/demo/x509/server-expired.pem
new file mode 100644
index 0000000..80ef9dc
--- /dev/null
+++ b/demo/x509/server-expired.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDBjCCAm+gAwIBAgIBATANBgkqhkiG9w0BAQQFADB7MQswCQYDVQQGEwJTRzER
+MA8GA1UEChMITTJDcnlwdG8xFDASBgNVBAsTC00yQ3J5cHRvIENBMSQwIgYDVQQD
+ExtNMkNyeXB0byBDZXJ0aWZpY2F0ZSBNYXN0ZXIxHTAbBgkqhkiG9w0BCQEWDm5n
+cHNAcG9zdDEuY29tMB4XDTAwMDkxMDA5NTEzMFoXDTAyMDkxMDA5NTEzMFowUzEL
+MAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhv
+c3QxHTAbBgkqhkiG9w0BCQEWDm5ncHNAcG9zdDEuY29tMFwwDQYJKoZIhvcNAQEB
+BQADSwAwSAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAaOCAQQwggEAMAkGA1UdEwQC
+MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl
+MB0GA1UdDgQWBBTPhIKSvnsmYsBVNWjj0m3M2z0qVTCBpQYDVR0jBIGdMIGagBT7
+hyNp65w6kxXlxb8pUU/+7Sg4AaF/pH0wezELMAkGA1UEBhMCU0cxETAPBgNVBAoT
+CE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlw
+dG8gQ2VydGlmaWNhdGUgTWFzdGVyMR0wGwYJKoZIhvcNAQkBFg5uZ3BzQHBvc3Qx
+LmNvbYIBADANBgkqhkiG9w0BAQQFAAOBgQA7/CqT6PoHycTdhEStWNZde7M/2Yc6
+BoJuVwnW8YxGO8Sn6UJ4FeffZNcYZddSDKosw8LtPOeWoK3JINjAk5jiPQ2cww++
+7QGG/g5NDjxFZNDJP1dGiLAxPW6JXwov4v0FmdzfLOZ01jDcgQQZqEpYlgpuI5JE
+WUQ9Ho4EzbYCOQ==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIIBPAIBAAJBAKy+e3dulvXzV7zoTZWc5TzgApr8DmeQHTYC8ydfzH7EECe4R1Xh
+5kwIzOuuFfn178FBiS84gngaNcrFi0Z5fAkCAwEAAQJBAIqm/bz4NA1H++Vx5Ewx
+OcKp3w19QSaZAwlGRtsUxrP7436QjnREM3Bm8ygU11BjkPVmtrKm6AayQfCHqJoT
+ZIECIQDW0BoMoL0HOYM/mrTLhaykYAVqgIeJsPjvkEhTFXWBuQIhAM3deFAvWNu4
+nklUQ37XsCT2c9tmNt1LAT+slG2JOTTRAiAuXDtC/m3NYVwyHfFm+zKHRzHkClk2
+HjubeEgjpj32AQIhAJqMGTaZVOwevTXvvHwNEH+vRWsAYU/gbx+OQB+7VOcBAiEA
+oolb6NMg/R3enNPvS1O4UU1H8wpaF77L4yiSWlE0p4w=
+-----END RSA PRIVATE KEY-----
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBDTCBuAIBADBTMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xEjAQ
+BgNVBAMTCWxvY2FsaG9zdDEdMBsGCSqGSIb3DQEJARYObmdwc0Bwb3N0MS5jb20w
+XDANBgkqhkiG9w0BAQEFAANLADBIAkEArL57d26W9fNXvOhNlZzlPOACmvwOZ5Ad
+NgLzJ1/MfsQQJ7hHVeHmTAjM664V+fXvwUGJLziCeBo1ysWLRnl8CQIDAQABoAAw
+DQYJKoZIhvcNAQEEBQADQQA7uqbrNTjVWpF6By5ZNPvhZ4YdFgkeXFVWi5ao/TaP
+Vq4BG021fJ9nlHRtr4rotpgHDX1rr+iWeHKsx4+5DRSy
+-----END CERTIFICATE REQUEST-----
diff --git a/demo/x509/server.pem b/demo/x509/server.pem
new file mode 100644
index 0000000..1ee9282
--- /dev/null
+++ b/demo/x509/server.pem
@@ -0,0 +1,36 @@
+-----BEGIN CERTIFICATE-----
+MIIDYjCCAsugAwIBAgIBBDANBgkqhkiG9w0BAQQFADCBgDELMAkGA1UEBhMCU0cx
+ETAPBgNVBAoTCE0yQ3J5cHRvMRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UE
+AxMbTTJDcnlwdG8gQ2VydGlmaWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNu
+Z3BzQG5ldG1lbWV0aWMuY29tMB4XDTAzMDYyMjEzMzAxNFoXDTA0MDYyMTEzMzAx
+NFowXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwls
+b2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5leGFtcGxlLmRv
+bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA37aKGZtFicl8xXXTLJ8/JD7c
+kd3t/teCX9i61lpaDQCKBoVrrursIvZihemMKI9g/u/+BLqt5g8mBdgUdYz0txc8
+KEbV2hj+wwOX4H3XwD0Y+DysXiNHq7/tFdmzSVHoLxpY4zYzXbxQ/p049wvIyPRp
+/y3omcnx/TEUhkn+JmkCAwEAAaOCAQwwggEIMAkGA1UdEwQCMAAwLAYJYIZIAYb4
+QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBTB
+H/mUYlww24mJlSxEtGdlwojO9zCBrQYDVR0jBIGlMIGigBTr+pwHMS1CmF9cuGI4
+du3YqoHAwqGBhqSBgzCBgDELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+MRQwEgYDVQQLEwtNMkNyeXB0byBDQTEkMCIGA1UEAxMbTTJDcnlwdG8gQ2VydGlm
+aWNhdGUgTWFzdGVyMSIwIAYJKoZIhvcNAQkBFhNuZ3BzQG5ldG1lbWV0aWMuY29t
+ggEAMA0GCSqGSIb3DQEBBAUAA4GBAAvl6v0s3eFeGP4iAcrfysuK7jzFKhjDYuOy
+lVS3u33bZNLnMpM6OSEM9yPh4WpFCVHf+nYwC71pk4ilsLVXjKxymm2lNGcxLVuW
+iydFz4Ly9nmN7Ja9ygYT39dGAFP/wN7ELTpsbul8VfmqhNg9y81d8i/A1tK3AGA8
+0QkPQNdP
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDftooZm0WJyXzFddMsnz8kPtyR3e3+14Jf2LrWWloNAIoGhWuu
+6uwi9mKF6Ywoj2D+7/4Euq3mDyYF2BR1jPS3FzwoRtXaGP7DA5fgfdfAPRj4PKxe
+I0erv+0V2bNJUegvGljjNjNdvFD+nTj3C8jI9Gn/LeiZyfH9MRSGSf4maQIDAQAB
+AoGAHpeVtv62uarl9xKvuBBm0AwQmZnhq9HIsFaw5hMg8Vo7hbzFBvx1IirTOkC/
+u+QvfW1QLVFh6m3z4ySzV4fZBtcd6F9SbSrZ0xsxIUB2NOSa1RGgiaP61bJnMMM1
+xM3O9iwM5GZc3Gqy08QOCpDl0772VJ+9Gz3FA88mrc6rHQkCQQDz6RIatFjT28n8
+1vy0nHwwZz2oXTpe/pyZPwoKj8zVsmrKhKwOw7l8ArxjP8zoHOE7AQBCXYDMNoFp
+IAF0yuqrAkEA6s0wMEdPpQeb0XHAfccyJQoeULxHdVFoz1wWmGSOm4YmQtR8/QJx
+luEgfpeRkzxBKt5Ls3MEkheIOw7xV24zOwJAMz+DaE0AZPNHS3K4ghJnHZxzng6I
+lzEUIjbWm0V/ml70hTy/EhMZw+6nOotLOHHo+QbK0SboSwAgzL/Gzo1cJQJANqpS
+38qadleRJXAQWrg3qnvyluVe1aeAeVZ9RDmVIgxXeBO0jcs12uTLBe4PzHGo0mwy
+v7K1i7XC180gzzQu5QJBAOxITT9RoWSSozPvnirHd37sn+RsrNYkV07NAa80M20Z
+DkBPHeMVkNgigrQ6L6vWmbRDGQbGcMplAxnI5ppKCoU=
+-----END RSA PRIVATE KEY-----
diff --git a/demo/x509/x509auth.py b/demo/x509/x509auth.py
new file mode 100644
index 0000000..a1f6e4b
--- /dev/null
+++ b/demo/x509/x509auth.py
@@ -0,0 +1,675 @@
+#!/usr/bin/env python2
+#
+# vim: ts=4 sw=4 nowrap
+#
+# ChannelHandler
+# ReceiveChannel
+# SendChannel
+#
+# SocketDispatcher
+# Loop
+# Poll
+# ReadEvent
+# WriteEvent
+#
+import sys, re, time, thread, os
+import getopt
+import exceptions
+import select
+import string
+import socket
+import StringIO
+import traceback
+
+
+from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, ENOTCONN, ESHUTDOWN, EINTR, EISCONN
+
+STDOUT = sys.stdout
+
+IDstdin = 0
+IDstdout = 1
+IDstderr = 2
+
+
+
+
+class ExitNow (exceptions.Exception):
+ pass
+
+
+class AuthError(exceptions.Exception):
+ def __init__ (self, msg):
+ self.msg = msg
+ def __str__ (self):
+ return repr( self.msg )
+
+#----------------------------------------------------------------
+#
+# Class Security Certification
+#
+#----------------------------------------------------------------
+import M2Crypto
+import random
+import base64
+import sha
+
+class CertHandler:
+
+ def __init__ ( self ):
+ self.Nonce = None
+ self.CurrentObj = {}
+ #self.ServerName = socket.gethostbyaddr(socket.gethostname())[2][0]
+ self.ServerName = 'AuthInstance'
+
+ self.ObjNames = { 'C' : 'countryName', 'ST' : 'stateOrProvinceName', 'L' : 'localityName', 'O' : 'organizationName', 'OU' : 'organizationalUnitName', 'CN' : 'commonName', 'email' : 'emailAddress' }
+ self.ObjMap = {}
+
+ self.Params = { 'Digest' : 'sha1',
+ 'Version' : 1,
+ 'Serial' : 1,
+ 'NotBefore' : ( 60 * 60 * 24 * 365 ),
+ 'NotAfter' : ( 60 * 60 * 24 * 365 * 5 ),
+ 'Issuer' : { 'countryName' : 'US', 'stateOrProvinceName' : 'florida', 'localityName' : 'tampas', 'organizationName' : 'watersprings', 'organizationalUnitName' : 'security', 'commonName' : 'Certificate Authority', 'emailAddress' : 'admin@security' },
+ 'Subject' : { 'countryName' : 'US', 'stateOrProvinceName' : 'florida', 'localityName' : 'miami', 'organizationName' : 'watersprings', 'organizationalUnitName' : 'security', 'commonName' : 'Certificate Authority', 'emailAddress' : 'admin@security' }
+ }
+ self.KeyEnv = { 'RsaPubKey' : [ '-----BEGIN PUBLIC KEY-----', '-----END PUBLIC KEY-----' ],
+ 'RsaPKey' : [ '-----BEGIN RSA PRIVATE KEY-----', '-----END RSA PRIVATE KEY-----' ],
+ 'X509Cert' : [ '-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----' ]
+ }
+ self.CertContainer ()
+
+ self.ObjFromContainer ( ObjName='CA' )
+ self.ObjFromContainer ( ObjName=self.ServerName )
+ self.ServerObj = self.ObjMap[ self.ServerName ]
+
+
+
+
+
+ def CertContainer (self):
+ self.PemMap = { 'CA' : { 'Subject' : {'organizationalUnitName': 'security', 'organizationName': 'watersprings', 'commonName': 'Certificate Authority', 'stateOrProvinceName': 'florida', 'countryName': 'US', 'emailAddress': 'admin@security', 'localityName': 'miami'},
+ 'RsaPKey' : ['MIICXQIBAAKBgQDmAl+4+XdF34D3kBN58An29mA8/D+NUHVJW+XeE96uDJ9mw8f1', 'xguVYgfpMaiVihW/qDWZRu/NhWOfheKBVNstx5OcqIjY10vBvGAG17CQZhcon8eN', 'Kufg7XzON7e5WXXD8qyklhuesHtTEGGpZ1FfA+n+D/0JF3YfTBDeYyY2VQIDAQAB', 'AoGBAI53L/Uxx7fmzUoJ2pZvoKxwRIHhuDd+e3c5zbJ1WjsyJFWRtLw9tBUOCFpf', 'YM1nHzt8I97RulzxXxiC5B45ghu3S0s+B06oEOUxNLbjsai08DKBRFqM+IZIx11r', 'IM/tZsTdJg1KtKojRu63NDtOzR6a7ggTeMge5CDKpXVWpvVtAkEA+QF/q2NnsdmL', 'ak6ALl8QwMbvwujJcjLwvecHQJmB0jO9KF60Hh4VY8mTRIMZ/r9Wf+REsQcZtmhG', 'WRr12si5qwJBAOx4R0Wd/inoXOpvKheIoKgTg01FnLhT8uiLY59CuZRr6AcTELjC', 'Kvk6LyfhspYBkUwWAEwKxJ3kMeqXG+k8z/8CQQCy+GDKzqe5LKMHxWRb7/galuG9', 'NZOUgQiHdYXA6JRmgMl0Op07CGRXVIqEs7X7Y4rIYUj99ByG/muRn88VcTABAkBQ', 'Z6V0WoBtp4DQhfP+BIr8G4Zt49miI4lY4OyC3qFTgk1m+miZKgyKqeoW2Xtr3iSV', 'hnWbZZ3tQgZnCfKHoBHpAkAmf2OvfhLxaW1PwdjBdm9tFGVbzkLFDqdqww2aHRUx', 'sXonHyVG2EDm37qW7nzmAqUgQCueMhHREZQYceDrtLLO'],
+ 'X509Cert' : ['MIICpzCCAhCgAwIBAQIBATANBgkqhkiG9w0BAQUFADCBmTERMA8GA1UECxMIc2Vj', 'dXJpdHkxFTATBgNVBAoTDHdhdGVyc3ByaW5nczEeMBwGA1UEAxMVQ2VydGlmaWNh', 'dGUgQXV0aG9yaXR5MRAwDgYDVQQIEwdmbG9yaWRhMQswCQYDVQQGEwJVUzEdMBsG', 'CSqGSIb3DQEJARYOYWRtaW5Ac2VjdXJpdHkxDzANBgNVBAcTBnRhbXBhczAeFw0w', 'MzAzMzExMDQ2MDVaFw0wOTAzMjkxMDQ2MDVaMIGYMREwDwYDVQQLEwhzZWN1cml0', 'eTEVMBMGA1UEChMMd2F0ZXJzcHJpbmdzMR4wHAYDVQQDExVDZXJ0aWZpY2F0ZSBB', 'dXRob3JpdHkxEDAOBgNVBAgTB2Zsb3JpZGExCzAJBgNVBAYTAlVTMR0wGwYJKoZI', 'hvcNAQkBFg5hZG1pbkBzZWN1cml0eTEOMAwGA1UEBxMFbWlhbWkwgZ8wDQYJKoZI', 'hvcNAQEBBQADgY0AMIGJAoGBAOYCX7j5d0XfgPeQE3nwCfb2YDz8P41QdUlb5d4T', '3q4Mn2bDx/XGC5ViB+kxqJWKFb+oNZlG782FY5+F4oFU2y3Hk5yoiNjXS8G8YAbX', 'sJBmFyifx40q5+DtfM43t7lZdcPyrKSWG56we1MQYalnUV8D6f4P/QkXdh9MEN5j', 'JjZVAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAK7f4YodUnT7Ygp7BWBBDHSq/r+tY', 'H69ly3W23U5VupaIglNiNQoMqnZpVVfcuIYltajrux5TSH4gPbenCg163Ua8RvF6', 'E2JElccprbKiCf9tf8l6Rxpsall4EF+CazP56DiUD1NfGLhWp9V2ga9SoynEo1P1', 'eztMBfk01atBJ/s=']
+ },
+ 'AuthInstance' : { 'Subject' : {'commonName': 'AuthInstance', 'organizationalUnitName': 'security', 'emailAddress': 'ioclient@AuthInstance'},
+ 'RsaPKey' : ['MIICXQIBAAKBgQCwfB6CoOQTJTd6D4ua1G/H9hwqpdVUMMjG3O8Y93vYGesZdwtT', '1iEQX/6TWACBxa7jOC8hHUHe2lsPu7imHv8dDiD59Rzets7BM88HsJTemYrxSv5G', 'uh8FloB1KEtSHeCZSlDT/tzSX4M0JfVPmtx+0FsyDOVZ6jXjRIyIKgqDlwIDAQAB', 'AoGAYc8YFatXW6j3mwU8iL2NidPC/nvTxAoZa+UL+dlG4JhUrFNGitsUjf+1ljFi', 'bomBiFod/Is7c2euqgSOrDpnheYlogv2QpnP80YUpiv9OruaB9I1zqJ7QM7PrkrH', 'm1C36DzyzVY+4DMvTV29do4Mf6CKT8xf6hXlLK/NbqwO9NkCQQDYwxwCTWxrkX08', '+0c5KaTYxfqCByxOqoiKl97p6wHxNtlzdLeFoSZD0n3Q1c2v0DIXhcBPRPPaZBWC', 'yTayMkRzAkEA0G6I5mHQVNIx18Xmc75urC0QWrum9cj5VcyRvl3KCzB2kQoXkx6v', 'y0JN6YS37rSp8vmvIFNO/oHWSuEJlFYfTQJAajWv07D8Hvj61JaLH4c4Lr9TL8M0', 'Apesr7wajaOJIBgwFFJsWh3MEg9hdqJMVok9AimXQUAX/DpuD9dn5Yib4QJBAIdt', 'Kno2V7ylDkmahk/yDcrFRPkPMD5GpOrAjnnYSqzWglNe8U5gA+zXWfQ+jZwFut7q', 'qIUiXBM1nVzttuGwy4kCQQC3MHppypSWoFqd+TaxK3MX/HoZqaoRELXdeiniOt3Z', 'gFMJ4m6D9lL4segWDoDpequjDYxv2cl+wS1+qDOyeG3J'],
+ 'X509Cert' : ['MIIBxDCCAS2gAwIBAQIBATANBgkqhkiG9w0BAQUFADAAMB4XDTAzMDMzMTEwNTE1', 'N1oXDTA5MDMyOTEwNTE1N1owUDEkMCIGCSqGSIb3DQEJARYVaW9jbGllbnRAQXV0', 'aEluc3RhbmNlMREwDwYDVQQLEwhzZWN1cml0eTEVMBMGA1UEAxMMQXV0aEluc3Rh', 'bmNlMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwfB6CoOQTJTd6D4ua1G/H', '9hwqpdVUMMjG3O8Y93vYGesZdwtT1iEQX/6TWACBxa7jOC8hHUHe2lsPu7imHv8d', 'DiD59Rzets7BM88HsJTemYrxSv5Guh8FloB1KEtSHeCZSlDT/tzSX4M0JfVPmtx+', '0FsyDOVZ6jXjRIyIKgqDlwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFsXewlXnKpH', 'uSwxavmLPUqRk7o1D5E3ByTddBe3BY5NqEXk7Y2SJFtumiuUY5/sWB/aO8Xbqj/b', '/7Cwg9+bc9QqxeeIe/YvtFOmv1ELh2BC1Nof7zSa5rLa/+gPYCoogS4mLRMuUfRk', 'tVHhpoxL1B+UXp4jNeKeTgquOjpUiyBR']
+ },
+ '192.168.1.20' : { 'Subject' : {'commonName': '192.168.1.20', 'organizationalUnitName': 'security', 'emailAddress': 'ioclient@192.168.1.20'},
+ 'RsaPKey' : ['MIICWwIBAAKBgQDD285BGY+FHhfpRvcqupN8X2lPwUNq4G7k5kit5cyuQLfN0+eQ', 'I+VFZdtfJhCZC54dEIvNgA4I7563pRUD0S9rmN6kh/M0GgrKZjYNO+CvvG2dts26', 'MGK0eUQaSsvDf9phEA+0mSv9dsUrdyBTJBn4mXvApekYHt+mNLfCVLkM1QIDAQAB', 'AoGAMqcFB3cJ0/59Zpowz/8ip3axcKvluJ1EcLRRtY+JyML6BiQ4beGqqLD38/qP', 'LlV/1bpyvXnRp2P5IztxXORbo77IzDVzl62YesQATnecSCMLTaeOusy2EZZsjE0k', 'V2cR1rZvzyJPY+Fi8X54hiB+5IcKkPRX9LVw7+yBbBh4sKECQQD0Yi0/DGa3IetR', '9F+/jgN/VIcTd5KwMBW3Bw/+Bh78ZlZGaucpRiR1IQuD7sLTnhNS6RMJUxv10jnS', 'BGW9pjX5AkEAzSslOGFyJ5Aoy48rgC2kKwq6nFKJ/PmY92cnm0nqmwb2npbOtDxz', 'sPUdb7oYmUU/nVCJh3yb+KJIw2g9XxnUvQJAG8ybNwPTH1vlZ+Izjhe6gB5+axF8', 'BzzBC5vrDstldPKzN7lraD+JYCWNKMndMbNWoWTP/IyOrqzmVOSZKjShCQJAbzuE', 'C2QxaqeqpmnxkKWuCrPfZl8NdryvpPolK/jQG8qTrHlgibD4nCjYE7nWGkrD6Xs/', 'hNgXC56YSnDaTRQJFQJAD5GFACv9QgcMZhy1hza0yGDMSQ0WR8/y3CJhi3DPOuAf', 'MetGM1kLQR8bDFrl7yEs+Nufk8QTsE5ngZ7dGFgmuA=='],
+ 'X509Cert' : ['MIIBxDCCAS2gAwIBAQIBATANBgkqhkiG9w0BAQUFADAAMB4XDTAzMDMzMTEwNTMw', 'NVoXDTA5MDMyOTEwNTMwNVowUDEkMCIGCSqGSIb3DQEJARYVaW9jbGllbnRAMTky', 'LjE2OC4xLjIwMREwDwYDVQQLEwhzZWN1cml0eTEVMBMGA1UEAxMMMTkyLjE2OC4x', 'LjIwMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDD285BGY+FHhfpRvcqupN8', 'X2lPwUNq4G7k5kit5cyuQLfN0+eQI+VFZdtfJhCZC54dEIvNgA4I7563pRUD0S9r', 'mN6kh/M0GgrKZjYNO+CvvG2dts26MGK0eUQaSsvDf9phEA+0mSv9dsUrdyBTJBn4', 'mXvApekYHt+mNLfCVLkM1QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAA5pWiXJOHCJ', 'P6kHcBITJuwv94zZ0dbHby1ljUfG/8z3Fmr8JRUcTuTtgVYH9M0O9QujIR8YLWSV', '0GeD3nkLRn0+ezam0CW0dF/Ph5vNXFP4a0DSEVv7T0G21VFmbUV3xrVeaXARFuLa', 'AtqRoSyBMajd3g0WNXDCgGEH7LvzJ5EP']
+ },
+ '192.168.1.26' : { 'Subject' : {'commonName': '192.168.1.26', 'organizationalUnitName': 'security', 'emailAddress': 'ioclient@192.168.1.26'},
+ 'RsaPKey' : ['MIICXQIBAAKBgQC5ILRHC3wFoqG9Egb96N3iGEnVrgvQikHyXYc/jFMUgB79rVJp', 'hY1MziGkSjSyc3RFMshkjHlMlARMPCNtomIikqAQaO4Eke2SYWyaOBoTdkeOy+yZ', 't/POpoGp3nRmKGed6NNcdMd5BO01GiatUb7X/Se3Yyvmj5UcEmv/hZQGFwIDAQAB', 'AoGBALdR5FMp0zE9X437iQLsErQuOwcmpzplfnJDHYfXK/nz+TxY4m/tuQNiZ7vp', 'Y4+Gdo+Dfx7aX89uD2dycd7B2wwTziBGIEjhusD8gtralVjhBDjCowSOkezWTeY+', '2h40NB4e1uypOZb0PXWvAL/l9xN7NBGioq9zmShT5c+FFO8RAkEA4k3QSaT1ScGI', '5II5JolvPnv6yS+0dCQTn1SC2ABWbH75NDUHMGAdNIf1sqhaLSQnY9GuXhb8XqX6', 'UUhoypUHzwJBANFrqnuEuTNKR0HVD31/2trPYLfZL6/9RUsR4mlvxPb0tX+T5LVL', '5he43zbura/lZqNxt0ZVeD03LanPN7bvZzkCQQCMAToIJa6+x6YKQOpchhA1pvwb', 'NZE9fQhKvT0JpwPQsak4/EmLSxsmYarGsdLANKrN3W4ztaLCZ4r6eIKkOhkPAkBz', 'ke4wYitucbRnUTRONuvJSx599x6JCcVey0zekO7qtlsfP7e8kVk2iDCu+QLjCj8d', 'Pdk9uFc1uSi7CH8ftniJAkBLNYF0kfGC+CaTuyfnIwiBZ/tjmm4UvHfwtlaZHJYc', 'QIjimBxVA7mujrv3xIBTiDMdxUhq9YIaKIEdlveaTwPK'],
+ 'X509Cert' : ['MIIBxDCCAS2gAwIBAQIBATANBgkqhkiG9w0BAQUFADAAMB4XDTAzMDMzMTEwNTQx', 'NloXDTA5MDMyOTEwNTQxNlowUDEkMCIGCSqGSIb3DQEJARYVaW9jbGllbnRAMTky', 'LjE2OC4xLjI2MREwDwYDVQQLEwhzZWN1cml0eTEVMBMGA1UEAxMMMTkyLjE2OC4x', 'LjI2MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC5ILRHC3wFoqG9Egb96N3i', 'GEnVrgvQikHyXYc/jFMUgB79rVJphY1MziGkSjSyc3RFMshkjHlMlARMPCNtomIi', 'kqAQaO4Eke2SYWyaOBoTdkeOy+yZt/POpoGp3nRmKGed6NNcdMd5BO01GiatUb7X', '/Se3Yyvmj5UcEmv/hZQGFwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAMPd5SXzpwZ+', '40SdOv/PeQ5cjieDm6QjndWE/T8nG2I5h6YRWbZPohsCClQjrTyZCMXwcUiCimuJ', 'BaMigI/YqP5THVv58Gu8DpoVZppz7uhUNS5hsuV9lxZUh1bRkUtL6n0qSTEdM34I', 'NJBJKGlf0skULg9BT4LJYTPGWJ0KosUl']
+ }
+ }
+
+ def CreateObj ( self, ObjName='CA' ):
+ self.ObjMap[ObjName] = { 'RsaPKey' : None,
+ 'PsaPubKey' : None,
+ 'EvpPKey' : None,
+ 'EvpPubKey' : None,
+ 'X509Req' : None,
+ 'X509Cert' : None,
+ }
+ self.CurrentObj = self.ObjMap[ObjName]
+
+ def ObjFromContainer (self, ObjName='CA' ):
+ if not self.ObjMap.has_key( ObjName ):
+ self.CreateObj ( ObjName=ObjName )
+ self.PKeyFromPemRepr ( ObjName=ObjName )
+ self.CertFromPemRepr ( ObjName=ObjName )
+
+ def PKeyFromPemRepr ( self, ObjName=None, PemPKey=None ):
+ def callback (): return ''
+ if self.PemMap.has_key( ObjName ):
+ UsedPemPKey = self.KeyEnv['RsaPKey'][0] + '\n' + string.join( self.PemMap[ObjName]['RsaPKey'], '\n' ) + '\n' + self.KeyEnv['RsaPKey'][1] + '\n'
+ else:
+ if not PemPKey:
+ raise AuthError( 'no such Object "%s" in container - abort!' % ObjName )
+ else:
+ UsedPemPKey = PemPKey
+ self.CurrentObj['RsaPKey'] = M2Crypto.RSA.load_key_string( UsedPemPKey, callback )
+ self.CurrentObj['EvpPKey'] = M2Crypto.EVP.PKey ( md='sha1' )
+ self.CurrentObj['EvpPKey'].assign_rsa ( self.CurrentObj['RsaPKey'] )
+ self.CurrentObj['RsaPubKey'] = M2Crypto.RSA.new_pub_key( self.CurrentObj['RsaPKey'].pub () )
+
+ def CertFromPemRepr ( self, ObjName=None, PemCert=None ):
+ if self.PemMap.has_key( ObjName ):
+ UsedPemCert = self.KeyEnv['X509Cert'][0] + '\n' + string.join( self.PemMap[ObjName]['X509Cert'], '\n' ) + '\n' + self.KeyEnv['X509Cert'][1] + '\n'
+ else:
+ UsedPemCert = PemCert
+ self.CurrentObj['X509Cert'] = M2Crypto.X509.load_cert_string( PemCert )
+ self.CurrentObj['EvpPubKey'] = self.CurrentObj['X509Cert'].get_pubkey ()
+ #self.CurrentObj['RsaPubKey'] = M2Crypto.RSA.rsa_from_pkey( self.CurrentObj['EvpPubKey'] )
+
+ def ObjNameFromPemCert ( self, PemCert=None ):
+ """
+ generate objmap structure and fill it with values from PemCert
+ return ObjName string
+ """
+ X509Cert = M2Crypto.X509.load_cert_string( PemCert )
+ Subject = X509Cert.get_subject ()
+ SubjectTxt = Subject.print_ex ()
+ SubjectTxtList = re.split('[\n\r]', SubjectTxt )
+ SubjectMap = {}
+ for Entry in SubjectTxtList:
+ ( Key, Value ) = re.split('=', Entry)
+ if self.ObjNames.has_key( Key ):
+ SubjectMap[ self.ObjNames[Key] ] = Value
+ else:
+ SubjectMap[ Key ] = Value
+ if not SubjectMap.has_key( 'commonName' ):
+ return False
+ ObjName = SubjectMap['commonName']
+ if not self.ObjMap.has_key( ObjName ):
+ self.CreateObj( ObjName=ObjName )
+ self.CurrentObj = self.ObjMap[ObjName]
+ self.CurrentObj['X509Cert'] = X509Cert
+ self.CurrentObj['EvpPubKey'] = self.CurrentObj['X509Cert'].get_pubkey ()
+ self.CurrentObj['RsaPubKey'] = M2Crypto.RSA.rsa_from_pkey( self.CurrentObj['EvpPubKey'] )
+ else:
+ self.CurrentObj = self.ObjMap[ ObjName ]
+ return ObjName
+
+
+
+ def ServerCert ( self ):
+ self.ObjFromContainer( ObjName='X509Auth' )
+
+
+ def CreatePKey ( self ):
+ def PassPhraseFkt (): return ''
+ RsaKeyParams = { 'KeyLength' : 1024,
+ 'PubExponent' : 0x10001, # -> 65537
+ 'keygen_callback' : PassPhraseFkt
+ }
+ self.CurrentObj['RsaPKey'] = M2Crypto.RSA.gen_key( RsaKeyParams['KeyLength'], RsaKeyParams['PubExponent'], RsaKeyParams['keygen_callback'] )
+ self.CurrentObj['EvpPKey'] = M2Crypto.EVP.PKey ( md=self.Params['Digest'] )
+ self.CurrentObj['EvpPKey'].assign_rsa ( self.CurrentObj['RsaPKey'] )
+ #print self.EvpPKey
+
+
+ def CreateCert ( self, SignEvpPKey=None ):
+ """
+ generate new x509 certificate
+ SignEvpKey pkey to sign x509 certification, if None this x509 cert will be self signed
+ """
+ self.CurrentObj = self.ObjMap['CA']
+ self.CreatePKey ()
+ X509Cert = M2Crypto.X509.X509 ()
+ X509Cert.set_version ( self.Params['Version'] )
+ X509Cert.set_serial_number ( self.Params['Serial'] )
+ X509Cert.set_not_before ( int( time.time() - self.Params['NotBefore'] )) # 1 year in the past
+ X509Cert.set_not_after ( int( time.time() + self.Params['NotAfter'] )) # 5 years in the future
+ X509Cert.set_issuer ( self.Params['Issuer'] )
+ X509Cert.set_subject ( self.Params['Subject'] )
+ X509Cert.set_pubkey ( self.CurrentObj['EvpPKey'] )
+ if SignEvpPKey:
+ X509Cert.sign ( SignEvpPKey, self.Params['Digest'] )
+ else:
+ X509Cert.sign ( self.CurrentObj['EvpPKey'], self.Params['Digest'] )
+ self.CurrentObj['X509Cert'] = X509Cert
+ self.DumpOutInternalPemRepr( ObjName='CA' )
+
+
+ def CreateObjCert (self, ObjName):
+ """
+ generate Obj with new PKey and Request, signed by 'CA'
+ ObjName the primary key to identify key-pair
+ """
+ # new obj
+ if not self.ObjMap.has_key( ObjName ):
+ self.ObjMap[ObjName] = {}
+ self.CurrentObj = self.ObjMap[ObjName]
+ if not self.CurrentObj.has_key( 'Subject' ):
+ self.CurrentObj['Subject'] = { 'organizationalUnitName' : 'security', 'commonName' : ObjName, 'emailAddress' : 'ioclient@' + ObjName }
+ # new pkey
+ self.CreatePKey ()
+ # new request
+ self.CreateReq ( SignEvpPKey=self.ObjMap['CA']['EvpPKey'] )
+ # new certification
+ if not self.Req2Cert ( SignEvpPKey=self.ObjMap['CA']['EvpPKey'] ):
+ print "300 error occured while verifying - abort!"
+ # shipout x509 certification
+ self.DumpOutInternalPemRepr( ObjName=ObjName )
+
+
+ def CreateReq ( self, SignEvpPKey=None ):
+ X509Req = M2Crypto.X509.Request ()
+ if self.Params['Version']:
+ X509Req.set_version ( self.Params['Version'] )
+ X509Req.set_subject ( self.CurrentObj['Subject'] )
+ X509Req.set_pubkey ( self.CurrentObj['EvpPKey'] )
+ if SignEvpPKey:
+ X509Req.sign ( SignEvpPKey, self.Params['Digest'] )
+ else:
+ X509Req.sign ( self.CurrentObj['EvpPKey'], self.Params['Digest'] )
+ self.CurrentObj['X509Req'] = X509Req
+
+
+ def Req2Cert ( self, SignEvpPKey=None ):
+ X509Cert = M2Crypto.X509.X509 ()
+ Version = self.CurrentObj['X509Req'].get_version ()
+ X509Cert.set_version ( Version )
+ X509Cert.set_serial ( self.Params['Serial'] )
+ X509Cert.set_not_before ( int( time.time() - self.Params['NotBefore'] )) # 1 year in the past
+ X509Cert.set_not_after ( int( time.time() + self.Params['NotAfter'] )) # 5 years in the future
+ Issuer = self.ObjMap['CA']['X509Cert'].get_issuer ()
+ X509Cert.set_issuer_name ( Issuer )
+ X509Name_Subject = self.CurrentObj['X509Req'].get_subject ()
+ X509Cert.set_subject_name ( X509Name_Subject )
+ PKey = self.CurrentObj['X509Req'].get_pubkey ()
+ EvpPKey = M2Crypto.EVP.PKey( PKey )
+ X509Cert.set_pubkey ( EvpPKey )
+ if SignEvpPKey:
+ X509Cert.sign ( SignEvpPKey, self.Params['Digest'] )
+ else:
+ X509Cert.sign ( self.CurrentObj['EvpPKey'], self.Params['Digest'] )
+ self.CurrentObj['X509Cert'] = X509Cert
+ if self.VerifyCert ( SignEvpPKey ):
+ return True
+ else:
+ return False
+
+
+
+
+ #--------------------------------
+ # CertHandler Verifying
+ #--------------------------------
+ def ExtractPublicKeyFromCert ( self ):
+ self.CurrentObj['EvpPubKey'] = self.CurrentObj['X509Cert'].get_pubkey ()
+
+ def VerifyCert ( self, EvpPKey ):
+ if dir(EvpPKey).count('_ptr'):
+ Result = self.CurrentObj['X509Cert'].verify ( EvpPKey._ptr() )
+ else:
+ Result = self.CurrentObj['X509Cert'].verify ( EvpPKey )
+ if Result:
+ return True
+ return False
+
+
+
+ #--------------------------------
+ # CertHandler DumpOut
+ #--------------------------------
+ def DumpOutInternalPemRepr( self, ObjName='unknown', File='PyReprPem.txt' ):
+ if File:
+ open( File, 'w').write("\t\t\t\t'%s' : { " % ( ObjName ))
+ else:
+ sys.stdout.write("\t\t\t\t'%s' : { " % ( ObjName ))
+ self.ShowX509CertSubject ( File )
+ self.RsaPKey2PemRepr ( File, Cipher=None ) # unprotectd pkey representation
+ self.X509Cert2PemRepr ( File )
+ if File:
+ open( File, 'a').write("\t\t\t\t\t }\n")
+ else:
+ sys.stdout.write("\t\t\t\t\t }\n")
+
+ def ShowX509CertIssuer ( self, File=None ):
+ IssuerName = self.CurrentObj['X509Cert'].get_issuer ()
+ print IssuerName.print_ex ()
+
+ def ShowX509CertSubject ( self, File=None ):
+ Subject = self.CurrentObj['X509Cert'].get_subject ()
+ SubjectTxt = Subject.print_ex ()
+ SubjectTxtList = re.split('[\n\r]', SubjectTxt )
+ SubjectMap = {}
+ for Entry in SubjectTxtList:
+ ( Key, Value ) = re.split('=', Entry)
+ if self.ObjNames.has_key( Key ):
+ SubjectMap[ self.ObjNames[Key] ] = Value
+ else:
+ SubjectMap[ Key ] = Value
+ if File:
+ open( File, 'a').write("'Subject' : %s,\n" % ( repr( SubjectMap ) ))
+ else:
+ sys.stdout.write("Subject: %s\n" % ( repr( SubjectMap ) ))
+
+ def RsaPKey2PemRepr ( self, File=None, Cipher=None ):
+ """
+ converting pkey to PEM representation
+ Cipher if set to None, the pkey will be unprotected!!!!! possible other value: 'des_ede3_cbc'
+ """
+ PemRsaPKey = self.CurrentObj['RsaPKey'].repr_key_pem ( cipher=Cipher )
+ PemRsaPKeyList = re.split('[\n\r]', PemRsaPKey)
+ if File:
+ open( File, 'a').write("\t\t\t\t\t\t\t\t'RsaPKey' : %s,\n" % ( repr(PemRsaPKeyList[1:-2]) ))
+ else:
+ sys.stdout.write("\t\t\t\t\t\t\t\t'RsaPKey' : %s,\n" % ( repr(PemRsaPKeyList[1:-2]) ))
+
+ def X509Cert2PemRepr ( self, File=None ):
+ PemCert = self.CurrentObj['X509Cert'].repr_cert_pem ()
+ #print PemCert
+ PemCertList = re.split('[\n\r]', PemCert)
+ if File:
+ open( File, 'a').write("\t\t\t\t\t\t\t\t'X509Cert' : %s\n" % ( repr(PemCertList[1:-2]) ))
+ else:
+ sys.stdout.write("\t\t\t\t\t\t\t\t'X509Cert' : %s\n" % ( repr(PemCertList[1:-2]) ))
+
+
+
+ #--------------------------------
+ # CertHandler encryption / decryption
+ #--------------------------------
+ def CreateNonce ( self ):
+ """
+ creating some randomised data
+ return new Nonce string
+ """
+ random.seed ()
+ RawNonce = "%s_%f_%f" % ( os.getpid(), time.time(), random.random() )
+ sha1=M2Crypto.EVP.MessageDigest('sha1')
+ sha1.update( RawNonce )
+ NonceDecrypted = sha1.digest()
+ return NonceDecrypted
+
+ def NonceEncryptPrivate ( self, NonceDecrypted, RsaPKey=None ):
+ """
+ creating private encrypted string from NonceDecrypted
+ """
+ padding = M2Crypto.RSA.pkcs1_padding
+ if not RsaPKey:
+ UsedRsaPKey = self.ServerObj['RsaPKey']
+ else:
+ UsedRsaPKey = RsaPKey
+ NoncePrivEncrypted = UsedRsaPKey.private_encrypt ( NonceDecrypted, padding )
+ return NoncePrivEncrypted
+
+ def NonceEncryptPublic ( self, NonceDecrypted, RsaPubKey=None ):
+ """
+ creating public encrypted string from NonceDecrypted
+ """
+ padding = M2Crypto.RSA.pkcs1_padding
+ if not RsaPubKey:
+ UsedRsaPubKey = self.ServerObj['RsaPubKey']
+ else:
+ UsedRsaPubKey = RsaPubKey
+ NoncePubEncrypted = UsedRsaPubKey.public_encrypt ( NonceDecrypted, padding )
+ return NoncePubEncrypted
+
+ def NonceDecryptPublic ( self, NoncePrivEncrypted, RsaPubKey=None ):
+ """
+ creating decrypted string from NoncePrivEncrypted
+ """
+ padding = M2Crypto.RSA.pkcs1_padding
+ if not RsaPubKey:
+ UsedRsaPubKey = self.ServerObj['RsaPubKey']
+ else:
+ UsedRsaPubKey = RsaPubKey
+ try:
+ NonceDecrypted = UsedRsaPubKey.public_decrypt ( NoncePrivEncrypted, padding )
+ except:
+ raise AuthError('decrypting of public key failed - abort!')
+ return NonceDecrypted
+
+ def NonceDecryptPrivate ( self, NoncePubEncrypted, RsaPKey=None ):
+ padding = M2Crypto.RSA.pkcs1_padding
+ if not RsaPKey:
+ UsedRsaPKey = self.ServerObj['RsaPKey']
+ else:
+ UsedRsaPKey = RsaPKey
+ NonceDecrypted = UsedRsaPKey.private_decrypt ( NoncePubEncrypted, padding )
+ return NonceDecrypted
+
+ def NonceVerify ( self, DecryptedNonce=None ):
+ if self.CurrentObj['Nonce']['Decrypted'] == DecryptNonce:
+ return True
+ return False
+
+ #--------------------------------
+ # CertHandler authentication request
+ #--------------------------------
+ def ClientInit ( self, ObjName=None ):
+ """
+ generating AuthString
+ Nonce messagedigest 'sha1', encrypted with own instance private key
+ Cert own instance X509 cert, PEM encoded
+ any linefeed charaters stripped out of the base64 code
+ return generated Nonce and AuthString
+ """
+ if ObjName:
+ if self.PemMap.has_key( ObjName ):
+ UsedObjName = ObjName
+ else:
+ UsedObjName = self.ServerName
+ else:
+ UsedObjName = self.ServerName
+
+ NonceDecrypted = self.CreateNonce ()
+ NoncePrivEncrypted = re.sub('\012', '', base64.encodestring( self.NonceEncryptPrivate ( NonceDecrypted, RsaPKey=self.ServerObj['RsaPKey'] )) )
+ PemCert = re.sub('\012', '', base64.encodestring( self.KeyEnv['X509Cert'][0] + '\n' + string.join( self.PemMap[UsedObjName]['X509Cert'], '\n' ) + '\n' + self.KeyEnv['X509Cert'][1] + '\n' ))
+ InitString = re.sub('\012', '', base64.encodestring('%s:%s' % ( NoncePrivEncrypted, PemCert )))
+ return ( NonceDecrypted, InitString )
+
+
+ def ClientInitVerify ( self, InitString ):
+ """
+ return decrypted Nonce from AuthString and ObjName from AuthString X509 Cert
+ """
+ try:
+ PemBaseString = base64.decodestring( InitString )
+ except base64.binascii.Error, msg:
+ raise base64.binascii.Error( msg )
+ try:
+ ( Base64Nonce, Base64Cert ) = re.split(':', PemBaseString )
+ except:
+ raise AuthError( 'cannot split PemBaseString into parts - abort!' )
+ try:
+ NoncePrivEncrypted = base64.decodestring( Base64Nonce )
+ except base64.binascii.Error, msg:
+ raise base64.binascii.Error( msg )
+ try:
+ PemCert = base64.decodestring( Base64Cert )
+ except base64.binascii.Error, msg:
+ raise base64.binascii.Error( msg )
+ try:
+ X509Cert = M2Crypto.X509.load_cert_string( PemCert )
+ except:
+ raise AuthError( 'cannot extract X509 cert from PEM representation - abort!' )
+ EvpPKey = self.ObjMap['CA']['EvpPKey']
+ if dir(EvpPKey).count('_ptr'):
+ Result = X509Cert.verify ( EvpPKey._ptr() )
+ else:
+ Result = X509Cert.verify ( EvpPKey )
+ if Result != 1:
+ raise AuthError( 'verification of X509 cert with Certification Authority "CA" failed with code %d - abort!' % ( Result ))
+ ClientObjName = self.ObjNameFromPemCert( PemCert=PemCert )
+ try:
+ NonceDecrypted = self.NonceDecryptPublic( NoncePrivEncrypted, RsaPubKey=self.CurrentObj['RsaPubKey'] )
+ except:
+ raise AuthError( 'wrong public key for encoding nonce - abort!' )
+
+ return ( NonceDecrypted, ClientObjName )
+
+
+
+
+ def ServerInit ( self, ClientObjName, ClientNonce ):
+ """
+ NonceServer new Nonce from server encrypted with client publickey and base64 encoded
+ NonceBounce the authrequest nonce encrypted with server privatekey and base64 encoded
+ PemServerCert server X509 certification PEM encoded and base64 encoded
+ """
+ if not self.ObjMap.has_key( ClientObjName ):
+ if not self.PemMap.has_key( ClientObjName ):
+ raise AuthError( 'cannot find ClientObjName - abort!' )
+ else:
+ self.ObjFromContainer( ObjName=ClientObjName )
+ else:
+ self.CurrentObj = self.ObjMap[ClientObjName]
+
+ NonceDecrypted = self.CreateNonce ()
+ NonceServer = re.sub('\012', '', base64.encodestring( self.NonceEncryptPublic ( NonceDecrypted, RsaPubKey=self.CurrentObj['RsaPubKey'] )) )
+ NonceBounce = re.sub('\012', '', base64.encodestring( self.NonceEncryptPublic ( ClientNonce, RsaPubKey=self.CurrentObj['RsaPubKey'] )) )
+ PemServerCert = re.sub('\012', '', base64.encodestring( self.KeyEnv['X509Cert'][0] + '\n' + string.join( self.PemMap[self.ServerName]['X509Cert'], '\n' ) + '\n' + self.KeyEnv['X509Cert'][1] + '\n' ) )
+
+ InitString = re.sub('\012', '', base64.encodestring('%s:%s:%s' % ( NonceServer, NonceBounce, PemServerCert )) )
+ return ( NonceDecrypted, InitString )
+
+
+ def ServerInitVerify ( self, InitString, ObjName=None ):
+ NonceDecrypted = ''
+ ObjName = ''
+ try:
+ PemBaseString = base64.decodestring( InitString )
+ except:
+ return False
+
+ ( NonceServer, NonceBounce, ServerCert ) = re.split(':', PemBaseString )
+ NoncePubServer = base64.decodestring( NonceServer ) # NonceServer
+ NoncePubBounce = base64.decodestring( NonceBounce ) # NonceBounce
+ PemServerCert = base64.decodestring( ServerCert ) # PemServerCert
+
+ try:
+ X509Cert = M2Crypto.X509.load_cert_string( PemServerCert )
+ except:
+ return False
+
+ # verify X509 cert
+ EvpPKey = self.ObjMap['CA']['EvpPKey']
+ if dir(EvpPKey).count('_ptr'):
+ Result = X509Cert.verify ( EvpPKey._ptr() )
+ else:
+ Result = X509Cert.verify ( EvpPKey )
+ if not Result:
+ return False
+
+ # verify Nonce from Server encrypted with my own publickey
+ try:
+ NonceDecrypted = self.NonceDecryptPrivate( NoncePubServer, RsaPKey=self.ServerObj['RsaPKey'] )
+ except:
+ return False
+
+ ServerObjName = self.ObjNameFromPemCert( PemCert=PemServerCert )
+
+ # verify Nonce bounced from Server encrypted with server privatekey
+ try:
+ NonceBounceDecrypted = self.NonceDecryptPrivate( NoncePubBounce, RsaPKey=self.CurrentObj['RsaPKey'] )
+ except:
+ return False
+
+ return ( NonceDecrypted, NonceBounceDecrypted, ServerObjName )
+
+
+
+ def ReplyInit ( self, ReplyObjName, ReplyBounce ):
+ NonceDecrypted = self.CreateNonce ()
+ NoncePubInit = re.sub('\012', '', base64.encodestring( self.NonceEncryptPublic ( NonceDecrypted, RsaPubKey=self.ObjMap[ ReplyObjName ]['RsaPubKey'] )) )
+ NoncePubBounce = re.sub('\012', '', base64.encodestring( self.NonceEncryptPublic ( ReplyBounce, RsaPubKey=self.ObjMap[ ReplyObjName ]['RsaPubKey'] )) )
+ ReplyString = re.sub('\012', '', base64.encodestring('%s:%s' % ( NoncePubInit, NoncePubBounce )) )
+ return ( NonceDecrypted, ReplyString )
+
+
+ def ReplyVerify ( self, ReplyString ):
+ try:
+ PemBaseString = base64.decodestring( ReplyString )
+ except base64.binascii.Error, msg:
+ raise base64.binascii.Error( msg )
+ ( NoncePubInit, NoncePubBounce ) = re.split(':', PemBaseString )
+
+ try:
+ NoncePubInit = base64.decodestring( NoncePubInit ) # new Nonce from Remote, encrypted with own publickey
+ except base64.binascii.Error, msg:
+ raise base64.binascii.Error( msg )
+ try:
+ NoncePubBounce = base64.decodestring( NoncePubBounce ) # bounced Nonce from Remote, encrypted with Remote privatekey
+ except base64.binascii.Error, msg:
+ raise base64.binascii.Error( msg )
+
+ # verify Nonce from Remote encrypted with my own publickey
+ try:
+ NonceRemote = self.NonceDecryptPrivate( NoncePubInit, RsaPKey=self.ServerObj['RsaPKey'] )
+ except:
+ raise AuthError( 'cannot encode nonce with own private key - abort!' )
+
+ # verify Nonce bounced from Remote encrypted with Remote privatekey
+ try:
+ NonceBounced = self.NonceDecryptPrivate( NoncePubBounce, RsaPKey=self.ServerObj['RsaPKey'] )
+ except:
+ raise AuthError( 'wrong public key for encoding nonce - abort!' )
+
+ return ( NonceRemote, NonceBounced )
+
+
+
+
+ #-------------------------------------------------------------------------------------------
+ # TEST
+ #-------------------------------------------------------------------------------------------
+ def CreateCAForContainer ( self ):
+ self.CreateCert ()
+
+ def CreateForContainer ( self, ObjName ):
+ """
+ create new pkey pair and x509 cert for specified objname
+ result will be written in file "PyReprPem.txt"
+ """
+ self.ObjFromContainer ( ObjName='AuthInstance' )
+ self.CreateObjCert ( ObjName=ObjName )
+
+
+ def Test ( self ):
+ #self.CreateCert ()
+ #self.ExtractPublicKeyFromCert ()
+ #self.VerifyCert ()
+
+ ( ClientInitNonce, ClientInitString ) = self.ClientInit ()
+ ( ClientSendNonce, ClientObjName ) = self.ClientInitVerify ( InitString=ClientInitString )
+ ( ServerInitNonce, ServerInitString ) = self.ServerInit ( ClientObjName=ClientObjName, ClientNonce=ClientSendNonce )
+ ( ServerSendNonce, ClientBounceNonce, ServerObjName ) = self.ServerInitVerify ( InitString=ServerInitString )
+ if ClientInitNonce == ClientBounceNonce :
+ print '100 Test Nonce bounced True'
+ else:
+ print '100 Test Nonce bounced False - abort!'
+
+ ( ReplyInitNonce, ReplyInitString ) = self.ReplyInit ( ReplyObjName=ClientObjName, ReplyBounce=ServerSendNonce )
+ ( ReplySendNonce, NonceBounced ) = self.ReplyVerify ( ReplyString=ReplyInitString )
+ if ServerInitNonce == NonceBounced :
+ print '100 Test Nonce bounced True'
+ else:
+ print '100 Test Nonce bounced False - abort!'
+
+ ( Reply2InitNonce, Reply2InitString ) = self.ReplyInit ( ReplyObjName=ClientObjName, ReplyBounce=ServerSendNonce )
+ ( Reply2SendNonce, Nonce2Bounced ) = self.ReplyVerify ( ReplyString=Reply2InitString )
+ if ServerInitNonce == Nonce2Bounced :
+ print '100 Test Nonce bounced True'
+ else:
+ print '100 Test Nonce bounced False - abort!'
+
+ #self.NonceEncryptPrivate ()
+ #self.NonceDecryptPublic ()
+ #self.NonceVerify ()
+
+
+#-----------------------------------------------------------------------------------------------
+# MAIN
+#
+# x509auth.py --ca
+# will create a file "PyReprPem.txt" in the current directory
+# append the contents of the file to the CertContainer in this script
+#
+# x509auth.py --cert <ObjName>
+# creates a file "PyReprPem.txt" in the current directory
+# append the contents of the file to the CertContainer in this script
+#
+# x509auth.py --test
+# running authentification tests with bounced nonce
+#
+#-----------------------------------------------------------------------------------------------
+if __name__ == '__main__':
+ run = CertHandler ()
+
+ if len( sys.argv ) > 1:
+ if sys.argv[1] == '--test':
+ run.Test ()
+ elif sys.argv[1] == '--ca':
+ run.CreateCert()
+ elif sys.argv[1] == '--cert':
+ run.CreateForContainer( sys.argv[2] )
+
+ sys.exit( 0 )
+
diff --git a/doc/ZServerSSL-HOWTO.html b/doc/ZServerSSL-HOWTO.html
new file mode 100644
index 0000000..78c15a9
--- /dev/null
+++ b/doc/ZServerSSL-HOWTO.html
@@ -0,0 +1,271 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+<base href="http://localhost:9080/home/m2/zserverssl-011-howto/" />
+
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<meta name="generator" content="Docutils 0.2.8: http://docutils.sourceforge.net/" />
+<title>ZServerSSL HOWTO</title>
+<meta name="author" content="Ng Pheng Siong" />
+<meta name="date" content="2003-06-22" />
+<link rel="stylesheet" href="default.css" type="text/css" />
+</head>
+<body>
+<div class="document" id="zserverssl-howto">
+<h1 class="title">ZServerSSL HOWTO</h1>
+<table class="docinfo" frame="void" rules="none">
+<col class="docinfo-name" />
+<col class="docinfo-content" />
+<tbody valign="top">
+<tr><th class="docinfo-name">Author:</th>
+<td>Ng Pheng Siong</td></tr>
+<tr class="field"><th class="docinfo-name">Id:</th><td class="field-body">ZServerSSL-HOWTO,v 1.1 2003/06/22 17:40:13 ngps Exp</td>
+</tr>
+<tr><th class="docinfo-name">Date:</th>
+<td>2003-06-22</td></tr>
+<tr class="field"><th class="docinfo-name">Web-Site:</th><td class="field-body"><a class="reference" href="http://chandlerproject.org/Projects/MeTooCrypto">http://chandlerproject.org/Projects/MeTooCrypto</a></td>
+</tr>
+</tbody>
+</table>
+<div class="contents topic" id="contents">
+<p class="topic-title"><a name="contents">Contents</a></p>
+<ul class="simple">
+<li><a class="reference" href="#introduction" id="id2" name="id2">Introduction</a></li>
+<li><a class="reference" href="#preparation" id="id3" name="id3">Preparation</a></li>
+<li><a class="reference" href="#installation" id="id4" name="id4">Installation</a></li>
+<li><a class="reference" href="#testing" id="id5" name="id5">Testing</a><ul>
+<li><a class="reference" href="#https" id="id6" name="id6">HTTPS</a></li>
+<li><a class="reference" href="#webdav-over-https" id="id7" name="id7">WebDAV-over-HTTPS</a></li>
+<li><a class="reference" href="#webdav-source-over-https" id="id8" name="id8">WebDAV-Source-over-HTTPS</a></li>
+<li><a class="reference" href="#python-with-m2crypto" id="id9" name="id9">Python with M2Crypto</a><ul>
+<li><a class="reference" href="#id1" id="id10" name="id10">HTTPS</a></li>
+<li><a class="reference" href="#xmlrpc-over-https" id="id11" name="id11">XMLRPC-over-HTTPS</a></li>
+</ul>
+</li>
+</ul>
+</li>
+<li><a class="reference" href="#conclusion" id="id12" name="id12">Conclusion</a></li>
+</ul>
+</div>
+<div class="section" id="introduction">
+<h1><a class="toc-backref" href="#id2" name="introduction">Introduction</a></h1>
+<p>ZServerSSL adds to Zope's ZServer the following:</p>
+<ul class="simple">
+<li>HTTPS server</li>
+<li>WebDAV-source-over-HTTPS server</li>
+</ul>
+<p>With the HTTPS server, ZServerSSL also provides WebDAV-over-HTTPS
+and XMLRPC-over-HTTPS access to Zope.</p>
+<p>These instructions apply to both Un*x and Windows installations of
+Zope 2.6.1. To avoid cluttering the presentation, Windows pathnames
+are shown in Un*x fashion.</p>
+</div>
+<div class="section" id="preparation">
+<h1><a class="toc-backref" href="#id3" name="preparation">Preparation</a></h1>
+<ol class="arabic simple">
+<li>Download M2Crypto 0.11, contained in the file <tt class="literal"><span class="pre">m2crypto-0.11.zip</span></tt>.</li>
+<li>Unpack <tt class="literal"><span class="pre">m2crypto-0.11.zip</span></tt>. This will create a directory
+<tt class="literal"><span class="pre">m2crypto-0.11</span></tt>. Henceforth, we refer to this directory as <tt class="literal"><span class="pre">$M2</span></tt>.</li>
+<li>Install M2Crypto per the instructions in <tt class="literal"><span class="pre">$M2/INSTALL</span></tt>.</li>
+</ol>
+<p>The ZServerSSL distribution is in <tt class="literal"><span class="pre">$M2/demo/Zope</span></tt>. We shall refer to
+this directory as <tt class="literal"><span class="pre">$ZSSL</span></tt>.</p>
+</div>
+<div class="section" id="installation">
+<h1><a class="toc-backref" href="#id4" name="installation">Installation</a></h1>
+<p>Below, we refer to your Zope top-level directory as <tt class="literal"><span class="pre">$ZOPE</span></tt>.</p>
+<ol class="arabic">
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/z2s.py</span></tt> into <tt class="literal"><span class="pre">$ZOPE</span></tt>.</p>
+</li>
+<li><p class="first">Depending on your operating system, modify <tt class="literal"><span class="pre">$ZOPE/start</span></tt> or
+<tt class="literal"><span class="pre">$ZOPE/start.bat</span></tt> to invoke <tt class="literal"><span class="pre">$ZOPE/z2s.py</span></tt>, instead of
+<tt class="literal"><span class="pre">$ZOPE/z2.py</span></tt>. The files <tt class="literal"><span class="pre">$ZSSL/starts</span></tt> and
+<tt class="literal"><span class="pre">$ZSSL/starts.bat</span></tt> serve as examples.</p>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/dh1024.pem</span></tt> into <tt class="literal"><span class="pre">$ZOPE</span></tt>. This file contains
+Diffie-Hellman parameters for use by the SSL protocol.</p>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/randpool.dat</span></tt> into <tt class="literal"><span class="pre">$ZOPE</span></tt>. This file contains seed
+material for the OpenSSL PRNG. Alternatively, create
+<tt class="literal"><span class="pre">$ZOPE/randpool.dat</span></tt> thusly:</p>
+<pre class="literal-block">
+$ dd if=/dev/urandom of=randpool.dat bs=1024 count=1
+</pre>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/ca.pem</span></tt> to <tt class="literal"><span class="pre">$ZOPE</span></tt>. This file contains an example
+Certification Authority (CA) certificate. For information on
+operating your own CA, see
+<a class="reference" href="http://svn.osafoundation.org/m2crypto/trunk/doc/howto.ca.html">howto.ca.html</a> or one of numerous
+similar documents available on the web.</p>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/server.pem</span></tt> to <tt class="literal"><span class="pre">$ZOPE</span></tt>. This file contains an RSA
+key pair and its X.509v3 certificate issued by the above CA. You
+may also create your own key/certificate bundle.</p>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/ZServer/HTTPS_Server.py</span></tt> to <tt class="literal"><span class="pre">$ZOPE/ZServer</span></tt>.</p>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/ZServer/__init__.py</span></tt> to <tt class="literal"><span class="pre">$ZOPE/ZServer</span></tt>. This
+overwrites the existing <tt class="literal"><span class="pre">$ZOPE/ZServer/__init__.py</span></tt>. Alternatively,
+apply the following patch to <tt class="literal"><span class="pre">$ZOPE/ZServer/__init__.py</span></tt>:</p>
+<pre class="literal-block">
+--- __init__.py.org Sat Jun 21 23:20:41 2003
++++ __init__.py Tue Jan 7 23:30:53 2003
+&#64;&#64; -84,6 +84,7 &#64;&#64;
+ import asyncore
+ from medusa import resolver, logger
+ from HTTPServer import zhttp_server, zhttp_handler
++from HTTPS_Server import zhttps_server, zhttps_handler
+ from PCGIServer import PCGIServer
+ from FCGIServer import FCGIServer
+ from FTPServer import FTPServer
+</pre>
+</li>
+<li><p class="first">Copy <tt class="literal"><span class="pre">$ZSSL/ZServer/medusa/https_server.py</span></tt> to
+<tt class="literal"><span class="pre">$ZOPE/ZServer/medusa</span></tt>.</p>
+</li>
+<li><p class="first">Stop Zope, if it is running.</p>
+</li>
+<li><p class="first">Start Zope with ZServerSSL thusly:</p>
+<pre class="literal-block">
+./starts -X -f 9021 -w 9080 -W 9081 -y 9443 -Y 9444
+</pre>
+<p>This starts the following:</p>
+<ul class="simple">
+<li>an FTP server on port 9021</li>
+<li>a HTTP server on port 9080</li>
+<li>a WebDAV-source server on port 9081</li>
+<li>a HTTPS server on port 9443</li>
+<li>a WebDAV-source-over-HTTPS server on port 9444</li>
+</ul>
+</li>
+</ol>
+</div>
+<div class="section" id="testing">
+<h1><a class="toc-backref" href="#id5" name="testing">Testing</a></h1>
+<p>Below, we assume your Zope server is running on <tt class="literal"><span class="pre">localhost</span></tt>.</p>
+<div class="section" id="https">
+<h2><a class="toc-backref" href="#id6" name="https">HTTPS</a></h2>
+<p>This testing is done with Mozilla 1.1 on FreeBSD.</p>
+<ol class="arabic simple">
+<li>With a browser, connect to <a class="reference" href="https://localhost:9443/">https://localhost:9443/</a>. Browse
+around. Check out your browser's HTTPS informational screens.</li>
+<li>Connect to <a class="reference" href="https://localhost:9443/manage">https://localhost:9443/manage</a>. Verify that you can
+access Zope's management functionality.</li>
+</ol>
+</div>
+<div class="section" id="webdav-over-https">
+<h2><a class="toc-backref" href="#id7" name="webdav-over-https">WebDAV-over-HTTPS</a></h2>
+<p>This testing is done with Cadaver 0.21.0 on FreeBSD.</p>
+<pre class="literal-block">
+$ cadaver https://localhost:9443/
+WARNING: Untrusted server certificate presented:
+Issued to: M2Crypto, SG
+Issued by: M2Crypto, SG
+Do you wish to accept the certificate? (y/n) y
+dav:/&gt; ls
+Listing collection `/': succeeded.
+Coll: Channels 0 Jun 19 00:04
+Coll: Control_Panel 0 Jun 6 00:13
+Coll: Examples 0 Jun 6 00:12
+Coll: catalog 0 Jun 12 11:53
+Coll: ngps 0 Jun 16 15:34
+Coll: portal 0 Jun 21 15:21
+Coll: skunk 0 Jun 18 21:18
+Coll: temp_folder 0 Jun 22 17:57
+Coll: zope 0 Jun 20 15:27
+ acl_users 0 Dec 30 1998
+ browser_id_manager 0 Jun 6 00:12
+ default.css 3037 Jun 21 16:38
+ error_log 0 Jun 6 00:12
+ index_html 313 Jun 12 13:36
+ portal0 0 Jun 21 15:21
+ session_data_manager 0 Jun 6 00:12
+ standard_error_message 1365 Jan 21 2001
+ standard_html_footer 50 Jun 12 12:30
+ standard_html_header 80 Jan 21 2001
+ standard_template.pt 282 Jun 6 00:12
+ zsyncer 0 Jun 17 15:28
+dav:/&gt; quit
+Connection to `localhost' closed.
+$
+</pre>
+</div>
+<div class="section" id="webdav-source-over-https">
+<h2><a class="toc-backref" href="#id8" name="webdav-source-over-https">WebDAV-Source-over-HTTPS</a></h2>
+<p>This testing is done with Mozilla 1.1 on FreeBSD.</p>
+<ol class="arabic simple">
+<li>Open the Mozilla Composer window.</li>
+<li>Click &quot;File&quot;, &quot;Open Web Location&quot;. A dialog box appears.</li>
+<li>Enter <tt class="literal"><span class="pre">https://localhost:9444/index_html</span></tt> for the URL.</li>
+<li>Select &quot;Open in new Composer window.&quot;</li>
+<li>Click &quot;Open&quot;. A new Composer window will open with <tt class="literal"><span class="pre">index_html</span></tt>
+loaded.</li>
+</ol>
+</div>
+<div class="section" id="python-with-m2crypto">
+<h2><a class="toc-backref" href="#id9" name="python-with-m2crypto">Python with M2Crypto</a></h2>
+<p>This testing is done with M2Crypto 0.11 and Python 2.2.2 on FreeBSD.</p>
+<div class="section" id="id1">
+<h3><a class="toc-backref" href="#id10" name="id1">HTTPS</a></h3>
+<pre class="doctest-block">
+&gt;&gt;&gt; from M2Crypto import Rand, SSL, m2urllib
+&gt;&gt;&gt; url = m2urllib.FancyURLopener()
+&gt;&gt;&gt; url.addheader('Connection', 'close')
+&gt;&gt;&gt; u = url.open('https://127.0.0.1:9443/')
+send: 'GET / HTTP/1.1\r\nHost: 127.0.0.1:9443\r\nAccept-Encoding: identity\r\nUser-agent: Python-urllib/1.15\r\nConnection: close\r\n\r\n'
+reply: 'HTTP/1.1 200 OK\r\n'
+header: Server: ZServerSSL/0.11
+header: Date: Sun, 22 Jun 2003 13:42:34 GMT
+header: Connection: close
+header: Content-Type: text/html
+header: Etag:
+header: Content-Length: 535
+&gt;&gt;&gt; while 1:
+... data = u.read()
+... if not data: break
+... print data
+...
+</pre>
+<pre class="literal-block">
+&lt;html&gt;&lt;head&gt;
+&lt;base href=&quot;https://127.0.0.1:9443/&quot; /&gt;
+&lt;title&gt;Zope&lt;/title&gt;&lt;/head&gt;&lt;body bgcolor=&quot;#FFFFFF&quot;&gt;
+
+&lt;h1&gt;NgPS Desktop Portal&lt;/h1&gt;
+
+&amp;nbsp;&amp;nbsp;So many hacks.&lt;br&gt;
+&amp;nbsp;&amp;nbsp;So little time.&lt;br&gt;
+
+&lt;h2&gt;Link Farm&lt;/h2&gt;
+&lt;ul&gt;
+&lt;li&gt;&lt;a href=&quot;http://localhost:8080/portal&quot;&gt;Portal&lt;/a&gt;&lt;/li&gt;
+&lt;li&gt;&lt;a href=&quot;http://localhost/&quot;&gt;Local Apache Home Page&lt;/a&gt;&lt;/li&gt;
+&lt;/ul&gt;
+
+&lt;hr&gt;&lt;a href=&quot;http://www.zope.org/Credits&quot; target=&quot;_top&quot;&gt;&lt;img src=&quot;https://127.0.0.1:9443/p_/ZopeButton&quot; width=&quot;115&quot; height=&quot;50&quot; border=&quot;0&quot; alt=&quot;Powered by Zope&quot; /&gt;&lt;/a&gt;&lt;/body&gt;&lt;/html&gt;
+</pre>
+<pre class="doctest-block">
+&gt;&gt;&gt; u.close()
+&gt;&gt;&gt;
+</pre>
+</div>
+<div class="section" id="xmlrpc-over-https">
+<h3><a class="toc-backref" href="#id11" name="xmlrpc-over-https">XMLRPC-over-HTTPS</a></h3>
+<pre class="doctest-block">
+&gt;&gt;&gt; from M2Crypto.m2xmlrpclib import Server, SSL_Transport
+&gt;&gt;&gt; zs = Server('https://127.0.0.1:9443/', SSL_Transport())
+&gt;&gt;&gt; print zs.propertyMap()
+[{'type': 'string', 'id': 'title', 'mode': 'w'}]
+&gt;&gt;&gt;
+</pre>
+</div>
+</div>
+</div>
+<div class="section" id="conclusion">
+<h1><a class="toc-backref" href="#id12" name="conclusion">Conclusion</a></h1>
+<p>Well, it works! ;-)</p>
+</div>
+</div>
+</body>
+</html>
diff --git a/doc/howto.ca.html b/doc/howto.ca.html
new file mode 100644
index 0000000..74142d5
--- /dev/null
+++ b/doc/howto.ca.html
@@ -0,0 +1,891 @@
+<HTML
+><HEAD
+><TITLE
+>HOWTO: Creating your own CA with OpenSSL</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.64
+"></HEAD
+><BODY
+CLASS="ARTICLE"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="ARTICLE"
+><DIV
+CLASS="TITLEPAGE"
+><H1
+CLASS="TITLE"
+><A
+NAME="AEN2"
+>HOWTO: Creating your own CA with OpenSSL</A
+></H1
+><H3
+CLASS="AUTHOR"
+><A
+NAME="AEN4"
+>Pheng Siong Ng</A
+></H3
+><DIV
+CLASS="AFFILIATION"
+><DIV
+CLASS="ADDRESS"
+><P
+CLASS="ADDRESS"
+>ngps@post1.com</P
+></DIV
+></DIV
+><P
+CLASS="COPYRIGHT"
+>Copyright &copy; 2000, 2001 by Ng Pheng Siong.</P
+><DIV
+CLASS="REVHISTORY"
+><TABLE
+WIDTH="100%"
+BORDER="0"
+><TR
+><TH
+ALIGN="LEFT"
+VALIGN="TOP"
+COLSPAN="3"
+><B
+>Revision History</B
+></TH
+></TR
+><TR
+><TD
+ALIGN="LEFT"
+>Revision $Revision: 1.1 $</TD
+><TD
+ALIGN="LEFT"
+>$Date: 2003/06/22 16:41:18 $</TD
+><TD
+ALIGN="LEFT"
+></TD
+></TR
+><TR
+><TD
+ALIGN="LEFT"
+COLSPAN="3"
+></TD
+></TR
+></TABLE
+></DIV
+><HR></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="INTRODUCTION"
+>Introduction</A
+></H1
+><P
+>This is a HOWTO on creating your own <I
+CLASS="EMPHASIS"
+>certification
+ authority</I
+> (<I
+CLASS="EMPHASIS"
+>CA</I
+>) with OpenSSL.
+ </P
+><P
+>I last created a CA about a year ago, when I began work on <A
+HREF="http://chandlerproject.org/Projects/MeTooCrypto"
+TARGET="_top"
+>M2Crypto</A
+> and needed
+ certificates for the SSL bits. I accepted the tools' default settings
+ then, e.g., certificate validity of 365 days; this meant that my
+ certificates, including my CA's certificate, have now expired.
+ </P
+><P
+>Since I am using these certificates for M2Crypto's demonstration
+ programs (and I have forgotten the passphrase to the CA's private key),
+ I decided to discard the old CA and start afresh. I also decided to
+ document the process, hence this HOWTO.
+ </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="PROCEDURE"
+>The Procedure</A
+></H1
+><P
+>I use <TT
+CLASS="FILENAME"
+>CA.pl</TT
+>, a Perl program written by
+ Steve Hanson and bundled with OpenSSL.
+ </P
+><P
+>The following are the steps to create a CA:
+ </P
+><DIV
+CLASS="PROCEDURE"
+><OL
+TYPE="1"
+><LI
+><P
+>Choose a directory to do your CA work. All commands are executed
+ within this directory. Let's call the directory <TT
+CLASS="FILENAME"
+>demo</TT
+>.
+ </P
+></LI
+><LI
+><P
+>Copy <TT
+CLASS="FILENAME"
+>CA.pl</TT
+> and <TT
+CLASS="FILENAME"
+>openssl.cnf</TT
+>
+ into <TT
+CLASS="FILENAME"
+>demo</TT
+>.
+ </P
+></LI
+><LI
+><P
+>Apply the following patch to <TT
+CLASS="FILENAME"
+>CA.pl</TT
+>, which
+ allows it to generate a CA certificate with a validity period of 1095 days,
+ i.e., 3 years:
+ </P
+><PRE
+CLASS="PROGRAMLISTING"
+> --- CA.pl.org Sat Mar 31 12:40:13 2001
+ +++ CA.pl Sat Mar 31 12:41:15 2001
+ @@ -97,7 +97,7 @@
+ } else {
+ print "Making CA certificate ...\n";
+ system ("$REQ -new -x509 -keyout " .
+ - "${CATOP}/private/$CAKEY -out ${CATOP}/$CACERT $DAYS");
+ + "${CATOP}/private/$CAKEY -out ${CATOP}/$CACERT -days 1095");
+ $RET=$?;
+ }
+ }
+ </PRE
+></LI
+><LI
+><P
+>Create a new CA like this:
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>./CA.pl -newca
+ </B
+></TT
+>
+ A certificate filename (or enter to create) <TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;enter&gt;</I
+></TT
+></B
+></TT
+>
+
+ Making CA certificate ...
+ Using configuration from openssl.cnf
+ Generating a 1024 bit RSA private key
+ ............++++++
+ ......................++++++
+ writing new private key to './demoCA/private/cakey.pem'
+ Enter PEM pass phrase: <TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;secret passphrase here&gt;</I
+></TT
+></B
+></TT
+>
+ Verifying password - Enter PEM pass phrase: <TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;secret passphrase again&gt;</I
+></TT
+></B
+></TT
+>
+ -----
+ You are about to be asked to enter information that will be incorporated
+ into your certificate request.
+ What you are about to enter is what is called a Distinguished Name or a DN.
+ There are quite a few fields but you can leave some blank
+ For some fields there will be a default value,
+ If you enter '.', the field will be left blank.
+ -----
+ Country Name (2 letter code) [AU]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>SG</I
+></TT
+></B
+></TT
+>
+ State or Province Name (full name) [Some-State]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+ Locality Name (eg, city) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>.
+ Organization Name (eg, company) [Internet Widgits Pty Ltd]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>DemoCA</I
+></TT
+></B
+></TT
+>
+ Organizational Unit Name (eg, section) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+ Common Name (eg, YOUR name) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>DemoCA Certificate Master</I
+></TT
+></B
+></TT
+>
+ Email Address []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>certmaster@democa.dom</I
+></TT
+></B
+></TT
+>
+ </PRE
+><P
+>This creates a new CA in the directory <TT
+CLASS="FILENAME"
+>demoCA</TT
+>.
+ The CA's self-signed certificate is in
+ <TT
+CLASS="FILENAME"
+>demoCA/cacert.pem</TT
+> and its RSA key pair is in
+ <TT
+CLASS="FILENAME"
+>demoCA/private/cakey.pem</TT
+>.
+ </P
+><P
+><TT
+CLASS="FILENAME"
+>demoCA/private/cakey.pem</TT
+> looks like this:
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>cat demoCA/private/cakey.pem
+ </B
+></TT
+>
+ -----BEGIN RSA PRIVATE KEY-----
+ Proc-Type: 4,ENCRYPTED
+ DEK-Info: DES-EDE3-CBC,19973A9DBBB601BA
+
+ eOq9WFScNiI4/UWEUaSnGTKpJv2JYuMD3HwQox2Q3Cd4zGqVjJ6gF3exa5126cKf
+ X/bMVnwbPpuFZPiAIvaLyCjT6pYeXTBbSzs7/GQnvEOv+nYnDUFWi0Qm92qLk0uy
+ pFi/M1aWheN3vir2ZlAw+DW0bOOZhj8tC7Co7lMYb0YE271b6/YRPZCwQ3GXAHUJ
+ +aMYxlUDrK45aCUa/1CZDzTgk7h9cDgx2QJSIvYMYytCfI3zsuZMJS8/4OXLL0bI
+ lKmAc1dwB3DqGJt5XK4WJesiNfdxeCNEgAcYtEAgYZTPIApU+kTgTCIxJl2nMW7j
+ ax+Q1z7g+4MpgG20WD633D4z4dTlDdz+dnLi0rvuvxiwt+dUhrqiML1tyi+Z6EBH
+ jU4/cLBWev3rYfrlp4x8J9mDte0YKOk3t0wQOHqRetTsIfdtjnFp/Hu3qDmTCWjD
+ z/g7PPoO/bg/B877J9WBPbL/1hXXFYo88M+2aGlPOgDcFdiOqbLb2DCscohMbbVr
+ A4mgiy2kwWfIE73qiyV7yyG8FlRvr1iib+jbT3LTGf743utYAAs7HNGuOUObhoyt
+ jYvBD7ACn35P5YX7KTqvqErwdijxYCaNBCnvmRtmYSaNw9Kv1UJTxc5Vx7YLwIPk
+ E9KyBgKI7vPOjWBZ27+zOvNycmv1ciNtpALAw4bWtXnhCDVTHaVDy34OkheMzNCg
+ 2cjcBFzOkMIjcI03KbTQXOFIQGlsTWXGzkNf/zBQ+KksT1MCj+zBXSCvlDASMckg
+ kef21pGgUqPF14gKGfWX3sV4bjc1vbrRwq6zlG3nMuYqR5MtJJY9eQ==
+ -----END RSA PRIVATE KEY-----
+ </PRE
+></LI
+><LI
+><P
+>Next, generate a certificate request.
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>./CA.pl -newreq
+ </B
+></TT
+>
+ Using configuration from openssl.cnf
+ Generating a 1024 bit RSA private key
+ ..........++++++
+ ..............++++++
+ writing new private key to 'newreq.pem'
+ Enter PEM pass phrase: <TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;another secret passphrase here&gt;</I
+></TT
+></B
+></TT
+>
+ Verifying password - Enter PEM pass phrase: <TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;another secret passphrase again&gt;</I
+></TT
+></B
+></TT
+>
+ -----
+ You are about to be asked to enter information that will be incorporated
+ into your certificate request.
+ What you are about to enter is what is called a Distinguished Name or a DN.
+ There are quite a few fields but you can leave some blank
+ For some fields there will be a default value,
+ If you enter '.', the field will be left blank.
+ -----
+ Country Name (2 letter code) [AU]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>SG</I
+></TT
+></B
+></TT
+>
+ State or Province Name (full name) [Some-State]:.<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+ Locality Name (eg, city) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+ Organization Name (eg, company) [Internet Widgits Pty Ltd]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>M2Crypto</I
+></TT
+></B
+></TT
+>
+ Organizational Unit Name (eg, section) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+ Common Name (eg, YOUR name) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>localhost</I
+></TT
+></B
+></TT
+>
+ Email Address []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>admin@server.example.dom</I
+></TT
+></B
+></TT
+>
+
+ Please enter the following 'extra' attributes
+ to be sent with your certificate request
+ A challenge password []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;enter&gt;</I
+></TT
+></B
+></TT
+>
+ An optional company name []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;enter&gt;</I
+></TT
+></B
+></TT
+>
+ Request (and private key) is in newreq.pem
+ </PRE
+><P
+>The certificate request and private key in <TT
+CLASS="FILENAME"
+>newreq.pem</TT
+> looks like this:
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>cat newreq.pem
+ </B
+></TT
+>
+ -----BEGIN RSA PRIVATE KEY-----
+ Proc-Type: 4,ENCRYPTED
+ DEK-Info: DES-EDE3-CBC,41B2874DF3D02DD4
+
+ mg611EoVkLEooSTv+qTM0Ddmm/M1jE/Jy5RD/sc3LSMhuGu9xc26OgsTJmkQuIAh
+ J/B4lAw8G59VTG6DykeEtrG0rUBx4bggc7PKbFuiN423YjJODWcHvVgnPOzXMQt+
+ lY4tPl5+217MRHyx2NsWGrpkQNdu3GeSPOVMl3jeQiaXupONbwQ7rj42+X/VtAJP
+ W4D1NNwu8aGCPyShsEXHc/fI1WDpphYWke97pOjIZVQESFZOPty5HjIYZux4U+td
+ W81xODtq2ecJXc8fn2Wpa9y5VD1LT7oJksOuL1+Z04OVaeUe4x0swM17HlBm2kVt
+ fe/C/L6kN27MwZhE331VjtTjSGl4/gknqQDbLOtqT06f3OISsDJETm2itllyhgzv
+ C6Fi3N03rGFmKectijC+tws5k+P+HRG6sai33usk8xPokJqA+HYSWPz1XVlpRmv4
+ kdjQOdST7ovU62mOTgf3ARcduPPwuzTfxOlYONe5NioO1APVHBrInQwcpLkpOTQR
+ vI4roIN+b75/nihUWGUJn/nbbBa2Yl0N5Gs1Tyiy9Z+CcRT2TfWKBBFlEUIFl7Mb
+ J9fTV3DI+k+akbR4il1NkQ8EcSmCr3WpA0I9n0EHI7ZVpVaHxc0sqaPFl8YGdFHq
+ 1Qk53C/w6+qPpDzT3yKFmG2LZytAAM1czvb6RbNRJJP2ZrpBwn/h99sUTo/yPfxY
+ nueYmFJDm0uVNtG0icXGNUfSfnjKNTtHPAgyKGetRIC3kgJz/bo2w7EI6iEjBAzK
+ l5TRm4x6ZJxwuXXMiJCehMMd8TC8ybwWO4AO19B3ebFFeTVsUgxSGA==
+ -----END RSA PRIVATE KEY-----
+ -----BEGIN CERTIFICATE REQUEST-----
+ MIIBnTCCAQYCAQAwXTELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRIw
+ EAYDVQQDEwlsb2NhbGhvc3QxJzAlBgkqhkiG9w0BCQEWGGFkbWluQHNlcnZlci5l
+ eGFtcGxlLmRvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAr1nYY1Qrll1r
+ uB/FqlCRrr5nvupdIN+3wF7q915tvEQoc74bnu6b8IbbGRMhzdzmvQ4SzFfVEAuM
+ MuTHeybPq5th7YDrTNizKKxOBnqE2KYuX9X22A1Kh49soJJFg6kPb9MUgiZBiMlv
+ tb7K3CHfgw5WagWnLl8Lb+ccvKZZl+8CAwEAAaAAMA0GCSqGSIb3DQEBBAUAA4GB
+ AHpoRp5YS55CZpy+wdigQEwjL/wSluvo+WjtpvP0YoBMJu4VMKeZi405R7o8oEwi
+ PdlrrliKNknFmHKIaCKTLRcU59ScA6ADEIWUzqmUzP5Cs6jrSRo3NKfg1bd09D1K
+ 9rsQkRc9Urv9mRBIsredGnYECNeRaK5R1yzpOowninXC
+ -----END CERTIFICATE REQUEST-----
+ </PRE
+><P
+>Decoding the certificate request gives the following:
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>openssl req -text -noout &#60; newreq.pem
+ </B
+></TT
+>
+ Using configuration from /usr/local/pkg/openssl/openssl.cnf
+ Certificate Request:
+ Data:
+ Version: 0 (0x0)
+ Subject: C=SG, O=M2Crypto, CN=localhost/Email=admin@server.example.dom
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:af:59:d8:63:54:2b:96:5d:6b:b8:1f:c5:aa:50:
+ 91:ae:be:67:be:ea:5d:20:df:b7:c0:5e:ea:f7:5e:
+ 6d:bc:44:28:73:be:1b:9e:ee:9b:f0:86:db:19:13:
+ 21:cd:dc:e6:bd:0e:12:cc:57:d5:10:0b:8c:32:e4:
+ c7:7b:26:cf:ab:9b:61:ed:80:eb:4c:d8:b3:28:ac:
+ 4e:06:7a:84:d8:a6:2e:5f:d5:f6:d8:0d:4a:87:8f:
+ 6c:a0:92:45:83:a9:0f:6f:d3:14:82:26:41:88:c9:
+ 6f:b5:be:ca:dc:21:df:83:0e:56:6a:05:a7:2e:5f:
+ 0b:6f:e7:1c:bc:a6:59:97:ef
+ Exponent: 65537 (0x10001)
+ Attributes:
+ a0:00
+ Signature Algorithm: md5WithRSAEncryption
+ 7a:68:46:9e:58:4b:9e:42:66:9c:be:c1:d8:a0:40:4c:23:2f:
+ fc:12:96:eb:e8:f9:68:ed:a6:f3:f4:62:80:4c:26:ee:15:30:
+ a7:99:8b:8d:39:47:ba:3c:a0:4c:22:3d:d9:6b:ae:58:8a:36:
+ 49:c5:98:72:88:68:22:93:2d:17:14:e7:d4:9c:03:a0:03:10:
+ 85:94:ce:a9:94:cc:fe:42:b3:a8:eb:49:1a:37:34:a7:e0:d5:
+ b7:74:f4:3d:4a:f6:bb:10:91:17:3d:52:bb:fd:99:10:48:b2:
+ b7:9d:1a:76:04:08:d7:91:68:ae:51:d7:2c:e9:3a:8c:27:8a:
+ 75:c2
+ </PRE
+></LI
+><LI
+><P
+>Now, sign the certificate request:
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>./CA.pl -sign
+ </B
+></TT
+>
+ Using configuration from openssl.cnf
+ Enter PEM pass phrase: <TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;CA's passphrase&gt;</I
+></TT
+></B
+></TT
+>
+ Check that the request matches the signature
+ Signature ok
+ The Subjects Distinguished Name is as follows
+ countryName :PRINTABLE:'SG'
+ organizationName :PRINTABLE:'M2Crypto'
+ commonName :PRINTABLE:'localhost'
+ emailAddress :IA5STRING:'admin@server.example.dom'
+ Certificate is to be certified until Mar 31 02:57:30 2002 GMT (365 days)
+ Sign the certificate? [y/n]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>y</I
+></TT
+></B
+></TT
+>
+
+
+ 1 out of 1 certificate requests certified, commit? [y/n]<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>y</I
+></TT
+></B
+></TT
+>
+ Write out database with 1 new entries
+ Data Base Updated
+ Signed certificate is in newcert.pem
+ </PRE
+><P
+><TT
+CLASS="FILENAME"
+>newcert.pem</TT
+> looks like this:
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>cat newcert.pem
+ </B
+></TT
+>
+ Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: md5WithRSAEncryption
+ Issuer: C=SG, O=DemoCA, CN=DemoCA Certificate Master/Email=certmaster@democa.dom
+ Validity
+ Not Before: Mar 31 02:57:30 2001 GMT
+ Not After : Mar 31 02:57:30 2002 GMT
+ Subject: C=SG, O=M2Crypto, CN=localhost/Email=admin@server.example.dom
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:af:59:d8:63:54:2b:96:5d:6b:b8:1f:c5:aa:50:
+ 91:ae:be:67:be:ea:5d:20:df:b7:c0:5e:ea:f7:5e:
+ 6d:bc:44:28:73:be:1b:9e:ee:9b:f0:86:db:19:13:
+ 21:cd:dc:e6:bd:0e:12:cc:57:d5:10:0b:8c:32:e4:
+ c7:7b:26:cf:ab:9b:61:ed:80:eb:4c:d8:b3:28:ac:
+ 4e:06:7a:84:d8:a6:2e:5f:d5:f6:d8:0d:4a:87:8f:
+ 6c:a0:92:45:83:a9:0f:6f:d3:14:82:26:41:88:c9:
+ 6f:b5:be:ca:dc:21:df:83:0e:56:6a:05:a7:2e:5f:
+ 0b:6f:e7:1c:bc:a6:59:97:ef
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 1 (0x1)
+ Signature Algorithm: md5WithRSAEncryption
+ Issuer: C=SG, O=DemoCA, CN=DemoCA Certificate Master/Email=certmaster@democa.dom
+ Validity
+ Not Before: Mar 31 02:57:30 2001 GMT
+ Not After : Mar 31 02:57:30 2002 GMT
+ Subject: C=SG, O=M2Crypto, CN=localhost/Email=admin@server.example.dom
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:af:59:d8:63:54:2b:96:5d:6b:b8:1f:c5:aa:50:
+ 91:ae:be:67:be:ea:5d:20:df:b7:c0:5e:ea:f7:5e:
+ 6d:bc:44:28:73:be:1b:9e:ee:9b:f0:86:db:19:13:
+ 21:cd:dc:e6:bd:0e:12:cc:57:d5:10:0b:8c:32:e4:
+ c7:7b:26:cf:ab:9b:61:ed:80:eb:4c:d8:b3:28:ac:
+ 4e:06:7a:84:d8:a6:2e:5f:d5:f6:d8:0d:4a:87:8f:
+ 6c:a0:92:45:83:a9:0f:6f:d3:14:82:26:41:88:c9:
+ 6f:b5:be:ca:dc:21:df:83:0e:56:6a:05:a7:2e:5f:
+ 0b:6f:e7:1c:bc:a6:59:97:ef
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ B3:D6:89:88:2F:B1:15:40:EC:0A:C0:30:35:3A:B7:DA:72:73:1B:4D
+ X509v3 Authority Key Identifier:
+ keyid:F9:6A:A6:34:97:6B:BC:BB:5A:17:0D:19:FC:62:21:0B:00:B5:0E:29
+ DirName:/C=SG/O=DemoCA/CN=DemoCA Certificate Master/Email=certmaster@democa.dom
+ serial:00
+
+ Signature Algorithm: md5WithRSAEncryption
+ </PRE
+></LI
+><LI
+><P
+>In certain situations, e.g., where your certificate and
+ private key are to be used in an unattended SSL server, you may wish to
+ not encrypt the private key, i.e., leave the key in the clear. This
+ decision should be governed by your site's security policy and threat
+ model, of course.
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>openssl rsa &#60; newkey.pem &#62; newkey2.pem
+ </B
+></TT
+>
+ read RSA key
+ Enter PEM pass phrase:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;secret passphrase here&gt;</I
+></TT
+></B
+></TT
+>
+ writing RSA key
+ </PRE
+><P
+><TT
+CLASS="FILENAME"
+>newkey2.pem</TT
+> looks like this:
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>cat newkey2.pem
+ </B
+></TT
+>
+ -----BEGIN RSA PRIVATE KEY-----
+ MIICXgIBAAKBgQCvWdhjVCuWXWu4H8WqUJGuvme+6l0g37fAXur3Xm28RChzvhue
+ 7pvwhtsZEyHN3Oa9DhLMV9UQC4wy5Md7Js+rm2HtgOtM2LMorE4GeoTYpi5f1fbY
+ DUqHj2ygkkWDqQ9v0xSCJkGIyW+1vsrcId+DDlZqBacuXwtv5xy8plmX7wIDAQAB
+ AoGAbAkU8w3W1Qu15Hle1bJSL7GMReoreqeblOBmMAZz4by0l6sXZXJpjWXo86f/
+ +dASMYTMPC4ZTYtv06N07AFbjL+kDfqDMTfzQkYMHp1LAq1Ihbq1rHWSBH5n3ekq
+ KiY8JKpv8DR5Po1iKaXJFuDByGDENJwYbSRSpSK3P+vkWWECQQDkEUE/ZPqqqZkQ
+ 2iWRPAsCbEID8SAraQl3DdCLYs/GgARfmmj4yUHEwkys9Jo1H8k4BdxugmaUwNi5
+ YQ/CVzrXAkEAxNO80ArbGxPUmr11GHG/bGBYj1DUBkHZSc7dgxZdtUCLGNxQnNsg
+ Iwq3n6j1sUzS3UW6abQ8bivYNOUcMKJAqQJBANQxFaLU4b/NQaODQ3aoBZpAfP9L
+ 5eFdvbet+7zjt2r5CpikgkwOfAmDuXEltx/8LevY0CllW+nErx9zJgVrwUsCQQCu
+ 76H5JiznPBDSF2FjgHWqVVdgyW4owY3mU739LHvNBLicN/RN9VPy0Suy8/CqzKT9
+ lWPBXzf2k3FuUdNkRlFBAkEAmpXoybuiFR2S5Bma/ax96lVs0/VihhfC1zZP/X/F
+ Br77+h9dIul+2DnyOl50zu0Sdzst1/7ay4JSDHyiBCMGSQ==
+ -----END RSA PRIVATE KEY-----
+ </PRE
+></LI
+></OL
+></DIV
+><P
+>That's it! The certificate, <TT
+CLASS="FILENAME"
+>newcert.pem</TT
+>, and
+ the private key - <TT
+CLASS="FILENAME"
+>newkey.pem</TT
+> (encrypted) or
+ <TT
+CLASS="FILENAME"
+>newkey2.pem</TT
+> (unencrypted) - are now ready to be used.
+ You may wish to rename the files to more intuitive names.
+ </P
+><P
+>You should also keep the CA's certificate <TT
+CLASS="FILENAME"
+>demo/cacert.pem
+ </TT
+> handy for use when developing and deploying SSL or S/MIME
+ applications.
+ </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="CONCLUSION"
+>Conclusion</A
+></H1
+><P
+>We've walked through the basic steps in the creation of a CA and
+ certificates using the tools that come with OpenSSL. We did not cover more
+ advanced topics such as constraining a certificate to be SSL-only or
+ S/MIME-only.
+ </P
+><P
+>There exist several HOWTOs similar to this one on the net. This one
+ is written specifically to facilitate discussions in my other HOWTOs
+ on developing SSL and S/MIME applications in
+ <A
+HREF="http://www.python.org"
+TARGET="_top"
+>Python</A
+> using
+ <A
+HREF="http://chandlerproject.org/Projects/MeTooCrypto"
+TARGET="_top"
+>M2Crypto</A
+>.
+ </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="ID-KLUDGE"
+></A
+></H1
+><P
+> <TT
+CLASS="LITERAL"
+>$Id:howto.ca.html 583 2007-10-01 19:23:12Z heikki $</TT
+>
+ </P
+></DIV
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/doc/howto.smime.html b/doc/howto.smime.html
new file mode 100644
index 0000000..21bc5cb
--- /dev/null
+++ b/doc/howto.smime.html
@@ -0,0 +1,1570 @@
+<HTML
+><HEAD
+><TITLE
+>HOWTO: Programming S/MIME in Python with M2Crypto</TITLE
+><META
+NAME="GENERATOR"
+CONTENT="Modular DocBook HTML Stylesheet Version 1.64
+"></HEAD
+><BODY
+CLASS="ARTICLE"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+><DIV
+CLASS="ARTICLE"
+><DIV
+CLASS="TITLEPAGE"
+><H1
+CLASS="TITLE"
+><A
+NAME="AEN2"
+>HOWTO: Programming S/MIME in Python with M2Crypto</A
+></H1
+><H3
+CLASS="AUTHOR"
+><A
+NAME="AEN4"
+>Pheng Siong Ng</A
+></H3
+><DIV
+CLASS="AFFILIATION"
+><DIV
+CLASS="ADDRESS"
+><P
+CLASS="ADDRESS"
+>ngps@post1.com</P
+></DIV
+></DIV
+><P
+CLASS="COPYRIGHT"
+>Copyright &copy; 2000, 2001 by Ng Pheng Siong.</P
+><DIV
+CLASS="REVHISTORY"
+><TABLE
+WIDTH="100%"
+BORDER="0"
+><TR
+><TH
+ALIGN="LEFT"
+VALIGN="TOP"
+COLSPAN="3"
+><B
+>Revision History</B
+></TH
+></TR
+><TR
+><TD
+ALIGN="LEFT"
+>Revision $Id: howto.smime.html 646 2008-10-29 03:12:56Z heikki $</TD
+><TD
+ALIGN="LEFT"
+></TD
+><TD
+ALIGN="LEFT"
+></TD
+></TR
+><TR
+><TD
+ALIGN="LEFT"
+COLSPAN="3"
+></TD
+></TR
+></TABLE
+></DIV
+><HR></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="INTRODUCTION"
+>Introduction</A
+></H1
+><P
+><A
+HREF="http://chandlerproject.org/Projects/MeTooCrypto"
+TARGET="_top"
+>M2Crypto</A
+>
+ is a <A
+HREF="http://www.python.org"
+TARGET="_top"
+>Python</A
+>
+ interface to <A
+HREF="http://www.openssl.org"
+TARGET="_top"
+>OpenSSL</A
+>. It makes
+ available to the Python programmer SSL functionality to implement clients
+ and servers, S/MIME v2, RSA, DSA, DH, symmetric ciphers, message digests and
+ HMACs.
+ </P
+><P
+>This document demonstrates programming S/MIME with M2Crypto.
+ </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="SMIME"
+>S/MIME</A
+></H1
+><P
+>S/MIME - Secure Multipurpose Internet Mail Extensions
+ [<SPAN
+CLASS="CITATION"
+>RFC 2311, RFC 2312</SPAN
+>] - provides a
+ consistent way to send and receive secure MIME data. Based on the popular
+ Internet MIME standard, S/MIME provides the following cryptographic security
+ services for electronic messaging applications -
+ <I
+CLASS="EMPHASIS"
+>authentication</I
+>, <I
+CLASS="EMPHASIS"
+>message integrity </I
+>
+ and <I
+CLASS="EMPHASIS"
+>non-repudiation of origin </I
+> (using <I
+CLASS="EMPHASIS"
+>digital
+ signatures</I
+>), and <I
+CLASS="EMPHASIS"
+>privacy </I
+> and <I
+CLASS="EMPHASIS"
+>data
+ security</I
+> (using <I
+CLASS="EMPHASIS"
+>encryption</I
+>).
+ </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="KEYS-AND-CERTIFICATES"
+>Keys and Certificates</A
+></H1
+><P
+>To create an S/MIME-signed message, you need an RSA key pair
+ (this consists of a public key and a private key) and an X.509
+ certificate of said public key.
+ </P
+><P
+>To create an S/MIME-encrypted message, you need an X.509
+ certificate for each recipient.
+ </P
+><P
+>To create an S/MIME-signed <I
+CLASS="EMPHASIS"
+>and</I
+> -encrypted
+ message, first create a signed message, then encrypt the signed
+ message with the recipients' certificates.
+ </P
+><P
+>You may generate key pairs and obtain certificates by using a
+ commercial <I
+CLASS="EMPHASIS"
+>certification authority</I
+> service.
+ </P
+><P
+>You can also do so using freely-available software. For many
+ purposes, e.g., automated S/MIME messaging by system administration
+ processes, this approach is cheap and effective.
+ </P
+><P
+>We now work through using OpenSSL to generate key pairs and
+ certificates. This assumes you have OpenSSL installed properly on your
+ system.
+ </P
+><P
+>First, we generate an X.509 certificate to be used for signing:
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>openssl req -newkey rsa:1024 -nodes -x509 -days 365 -out signer.pem
+ </B
+></TT
+>
+ Using configuration from /usr/local/pkg/openssl/openssl.cnf
+ Generating a 1024 bit RSA private key
+ ..++++++
+ ....................++++++
+ writing new private key to 'privkey.pem'
+ -----
+ You are about to be asked to enter information that will be incorporated
+ into your certificate request.
+ What you are about to enter is what is called a Distinguished Name or a DN.
+ There are quite a few fields but you can leave some blank
+ For some fields there will be a default value,
+ If you enter '.', the field will be left blank.
+ -----
+ Country Name (2 letter code) [AU]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>SG</I
+></TT
+></B
+></TT
+>
+ State or Province Name (full name) [Some-State]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+ Locality Name (eg, city) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+ Organization Name (eg, company) [Internet Widgits Pty Ltd]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>M2Crypto</I
+></TT
+></B
+></TT
+>
+ Organizational Unit Name (eg, section) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+ Common Name (eg, YOUR name) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>S/MIME Sender</I
+></TT
+></B
+></TT
+>
+ Email Address []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>sender@example.dom</I
+></TT
+></B
+></TT
+>
+ </PRE
+><P
+>This generates a 1024-bit RSA key pair, unencrypted, into
+ <TT
+CLASS="FILENAME"
+>privkey.pem</TT
+>; it also generates a self-signed X.509
+ certificate for the public key into <TT
+CLASS="FILENAME"
+>signer.pem</TT
+>. The
+ certificate is valid for 365 days, i.e., a year.
+ </P
+><P
+>Let's rename <TT
+CLASS="FILENAME"
+>privkey.pem</TT
+> so that we know it is
+ a companion of <TT
+CLASS="FILENAME"
+>signer.pem</TT
+>'s:
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>mv privkey.pem signer_key.pem</B
+></TT
+>
+ </PRE
+><P
+>To verify the content of <TT
+CLASS="FILENAME"
+>signer.pem</TT
+>, execute the
+ following:
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>openssl x509 -noout -text -in signer.pem
+ </B
+></TT
+>
+ Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number: 0 (0x0)
+ Signature Algorithm: md5WithRSAEncryption
+ Issuer: C=SG, O=M2Crypto, CN=S/MIME Sender/Email=sender@example.dom
+ Validity
+ Not Before: Mar 24 12:56:16 2001 GMT
+ Not After : Mar 24 12:56:16 2002 GMT
+ Subject: C=SG, O=M2Crypto, CN=S/MIME Sender/Email=sender@example.dom
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:a9:d6:e2:b5:11:3b:ae:3c:e2:17:31:70:e1:6e:
+ 01:f4:19:6d:bd:2a:42:36:2b:37:34:e2:83:1d:0d:
+ 11:2e:b4:99:44:db:10:67:be:97:5f:5b:1a:26:33:
+ 46:23:2f:95:04:7a:35:da:9d:f9:26:88:39:9e:17:
+ cd:3e:eb:a8:19:8d:a8:2a:f1:43:da:55:a9:2e:2c:
+ 65:ed:04:71:42:ce:73:53:b8:ea:7e:c7:f0:23:c6:
+ 63:c5:5e:68:96:64:a7:b4:2a:94:26:76:eb:79:ea:
+ e3:4e:aa:82:09:4f:44:87:4a:12:62:b5:d7:1f:ca:
+ f2:ce:d5:ba:7e:1f:48:fd:b9
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ 29:FB:38:B6:BF:E2:40:BB:FF:D5:71:D7:D5:C4:F0:83:1A:2B:C7:99
+ X509v3 Authority Key Identifier:
+ keyid:29:FB:38:B6:BF:E2:40:BB:FF:D5:71:D7:D5:C4:F0:83:1A:2B:C7:99
+ DirName:/C=SG/O=M2Crypto/CN=S/MIME Sender/Email=sender@example.dom
+ serial:00
+
+ X509v3 Basic Constraints:
+ CA:TRUE
+ Signature Algorithm: md5WithRSAEncryption
+ 68:c8:6b:1b:fa:7c:9a:39:35:76:18:15:c9:fd:89:97:62:db:
+ 7a:b0:2d:13:dd:97:e8:1b:7a:9f:22:27:83:24:9d:2e:56:ec:
+ 97:89:3c:ef:16:55:80:5a:18:7c:22:d0:f6:bb:e3:a4:e8:59:
+ 30:ff:99:5a:93:3e:ea:bc:ee:7f:8d:d6:7d:37:8c:ac:3d:74:
+ 80:ce:7a:99:ba:27:b9:2a:a3:71:fa:a5:25:ba:47:17:df:07:
+ 56:96:36:fd:60:b9:6c:96:06:e8:e3:7b:9f:4b:6a:95:71:a8:
+ 34:fc:fc:b5:88:8b:c4:3f:1e:24:f6:52:47:b2:7d:44:67:d9:
+ 83:e8
+ </PRE
+><P
+>Next, we generate a self-signed X.509 certificate for the
+ recipient. Note that <TT
+CLASS="FILENAME"
+>privkey.pem</TT
+> will be
+ recreated.
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>openssl req -newkey rsa:1024 -nodes -x509 -days 365 -out recipient.pem
+ </B
+></TT
+>
+ Using configuration from /usr/local/pkg/openssl/openssl.cnf
+ Generating a 1024 bit RSA private key
+ .....................................++++++
+ .................++++++
+ writing new private key to 'privkey.pem'
+ -----
+ You are about to be asked to enter information that will be incorporated
+ into your certificate request.
+ What you are about to enter is what is called a Distinguished Name or a DN.
+ There are quite a few fields but you can leave some blank
+ For some fields there will be a default value,
+ If you enter '.', the field will be left blank.
+ -----
+ Country Name (2 letter code) [AU]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>SG</I
+></TT
+></B
+></TT
+>
+ State or Province Name (full name) [Some-State]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+ Locality Name (eg, city) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+ Organization Name (eg, company) [Internet Widgits Pty Ltd]:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>M2Crypto</I
+></TT
+></B
+></TT
+>
+ Organizational Unit Name (eg, section) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>.</I
+></TT
+></B
+></TT
+>
+ Common Name (eg, YOUR name) []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>S/MIME Recipient</I
+></TT
+></B
+></TT
+>
+ Email Address []:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>recipient@example.dom</I
+></TT
+></B
+></TT
+>
+ </PRE
+><P
+>Again, rename <TT
+CLASS="FILENAME"
+>privkey.pem</TT
+>:
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>mv privkey.pem recipient_key.pem</B
+></TT
+>
+ </PRE
+><P
+>In the examples to follow, S/MIME Sender,
+ <TT
+CLASS="EMAIL"
+>&#60;<A
+HREF="mailto:sender@example.dom"
+>sender@example.dom</A
+>&#62;</TT
+>, shall be the sender of S/MIME messages,
+ while S/MIME Recipient, <TT
+CLASS="EMAIL"
+>&#60;<A
+HREF="mailto:recipient@example.dom"
+>recipient@example.dom</A
+>&#62;</TT
+>, shall be the
+ recipient of S/MIME messages.
+ </P
+><P
+>Armed with the key pairs and certificates, we are now ready to begin
+ programming S/MIME in Python.
+ </P
+><DIV
+CLASS="NOTE"
+><BLOCKQUOTE
+CLASS="NOTE"
+><P
+><B
+>Note: </B
+>The private keys generated above are
+ <I
+CLASS="EMPHASIS"
+>not passphrase-protected</I
+>, i.e., they are
+ <I
+CLASS="EMPHASIS"
+>in the clear</I
+>. Anyone who has access to such a key can
+ generate S/MIME-signed messages with it, and decrypt S/MIME messages
+ encrypted to it's corresponding public key.
+ </P
+><P
+>We may passphrase-protect the keys, if we so choose. M2Crypto will
+ prompt the user for the passphrase when such a key is being loaded.
+ </P
+></BLOCKQUOTE
+></DIV
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="M2CRYPTO-SMIME"
+>M2Crypto.SMIME</A
+></H1
+><P
+>The Python programmer accesses M2Crypto's S/MIME functionality
+ through class <TT
+CLASS="CLASSNAME"
+>SMIME</TT
+> in the module
+ <TT
+CLASS="CLASSNAME"
+>M2Crypto.SMIME</TT
+>. Typically, an <TT
+CLASS="CLASSNAME"
+>SMIME</TT
+>
+ object is instantiated; the object is then set up for the intended
+ operation: sign, encrypt, decrypt or verify; finally, the operation is invoked on
+ the object.
+ </P
+><P
+><TT
+CLASS="CLASSNAME"
+>M2Crypto.SMIME</TT
+> makes extensive use of
+ <TT
+CLASS="CLASSNAME"
+>M2Crypto.BIO</TT
+>: <TT
+CLASS="CLASSNAME"
+>M2Crypto.BIO</TT
+>
+ is a Python abstraction of the <TT
+CLASS="CLASSNAME"
+>BIO</TT
+> abstraction in
+ OpenSSL. A commonly used <TT
+CLASS="CLASSNAME"
+>BIO</TT
+> abstraction in M2Crypto is
+ <TT
+CLASS="CLASSNAME"
+>M2Crypto.BIO.MemoryBuffer</TT
+>, which implements a
+ memory-based file-like object, similar to Python's own
+ <TT
+CLASS="CLASSNAME"
+>StringIO</TT
+>.
+ </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="SIGN"
+>Sign</A
+></H1
+><P
+>The following code demonstrates how to generate an S/MIME-signed
+ message. <TT
+CLASS="FILENAME"
+>randpool.dat</TT
+> contains random data which is
+ used to seed OpenSSL's pseudo-random number generator via M2Crypto.
+ </P
+><PRE
+CLASS="PROGRAMLISTING"
+> from M2Crypto import BIO, Rand, SMIME
+
+ def makebuf(text):
+ return BIO.MemoryBuffer(text)
+
+ # Make a MemoryBuffer of the message.
+ buf = makebuf('a sign of our times')
+
+ # Seed the PRNG.
+ Rand.load_file('randpool.dat', -1)
+
+ # Instantiate an SMIME object; set it up; sign the buffer.
+ s = SMIME.SMIME()
+ s.load_key('signer_key.pem', 'signer.pem')
+ p7 = s.sign(buf)
+ </PRE
+><P
+><TT
+CLASS="VARNAME"
+>p7</TT
+> now contains a <I
+CLASS="EMPHASIS"
+>PKCS #7 signature
+ blob</I
+> wrapped in an <TT
+CLASS="CLASSNAME"
+>M2Crypto.SMIME.PKCS7</TT
+>
+ object. Note that <TT
+CLASS="VARNAME"
+>buf</TT
+> has been consumed by
+ <TT
+CLASS="FUNCTION"
+>sign()</TT
+> and has to be recreated if it is to be used
+ again.
+ </P
+><P
+>We may now send the signed message via SMTP. In these examples, we
+ shall not do so; instead, we'll render the S/MIME output in
+ mail-friendly format, and pretend that our messages are sent and
+ received correctly.
+ </P
+><PRE
+CLASS="PROGRAMLISTING"
+> # Recreate buf.
+ buf = makebuf('a sign of our times')
+
+ # Output p7 in mail-friendly format.
+ out = BIO.MemoryBuffer()
+ out.write('From: sender@example.dom\n')
+ out.write('To: recipient@example.dom\n')
+ out.write('Subject: M2Crypto S/MIME testing\n')
+ s.write(out, p7, buf)
+
+ print out.read()
+
+ # Save the PRNG's state.
+ Rand.save_file('randpool.dat')
+ </PRE
+><P
+>Here's the output:
+ </P
+><PRE
+CLASS="SCREEN"
+> From: sender@example.dom
+ To: recipient@example.dom
+ Subject: M2Crypto S/MIME testing
+ MIME-Version: 1.0
+ Content-Type: multipart/signed ; protocol="application/x-pkcs7-signature" ; micalg=sha1 ; boundary="----3C93156FC7B4EBF49FE9C7DB7F503087"
+
+ This is an S/MIME signed message
+
+ ------3C93156FC7B4EBF49FE9C7DB7F503087
+ a sign of our times
+ ------3C93156FC7B4EBF49FE9C7DB7F503087
+ Content-Type: application/x-pkcs7-signature; name="smime.p7s"
+ Content-Transfer-Encoding: base64
+ Content-Disposition: attachment; filename="smime.p7s"
+
+ MIIE8AYJKoZIhvcNAQcCoIIE4TCCBN0CAQExCzAJBgUrDgMCGgUAMCIGCSqGSIb3
+ DQEHAaAVBBNhIHNpZ24gb2Ygb3VyIHRpbWVzoIIC5zCCAuMwggJMoAMCAQICAQAw
+ DQYJKoZIhvcNAQEEBQAwWzELMAkGA1UEBhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRv
+ MRYwFAYDVQQDEw1TL01JTUUgU2VuZGVyMSEwHwYJKoZIhvcNAQkBFhJzZW5kZXJA
+ ZXhhbXBsZS5kb20wHhcNMDEwMzMxMTE0MDMzWhcNMDIwMzMxMTE0MDMzWjBbMQsw
+ CQYDVQQGEwJTRzERMA8GA1UEChMITTJDcnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBT
+ ZW5kZXIxITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmRvbTCBnzANBgkq
+ hkiG9w0BAQEFAAOBjQAwgYkCgYEA5c5Tj1CHTSOxa1q2q0FYiwMWYHptJpJcvtZm
+ UwrgU5sHrA8OnCM0cDXEj0KPf3cfNjHffB8HWMzI4UEgNmFXQNsxoGZ+iqwxLlNj
+ y9Mh7eFW/Bjq5hNXbouSlQ0rWBRkoxV64y+t6lQehb32WfYXQbKFxFJSXzSxOx3R
+ 8YhSPd0CAwEAAaOBtjCBszAdBgNVHQ4EFgQUXOyolL1t4jaBwZFRM7MS8nBLzUow
+ gYMGA1UdIwR8MHqAFFzsqJS9beI2gcGRUTOzEvJwS81KoV+kXTBbMQswCQYDVQQG
+ EwJTRzERMA8GA1UEChMITTJDcnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBTZW5kZXIx
+ ITAfBgkqhkiG9w0BCQEWEnNlbmRlckBleGFtcGxlLmRvbYIBADAMBgNVHRMEBTAD
+ AQH/MA0GCSqGSIb3DQEBBAUAA4GBAHo3DrCHR86fSTVAvfiXdSswWqKtCEhUHRdC
+ TLFGl4hDk2GyZxaFuqZwiURz/H7nMicymI2wkz8H/wyHFg8G3BIehURpj2v/ZWXY
+ eovbgS7EZALVVkDj4hNl/IIHWd6Gtv1UODf7URbxtl3hQ9/eTWITrefT1heuPnar
+ 8czydsOLMYIBujCCAbYCAQEwYDBbMQswCQYDVQQGEwJTRzERMA8GA1UEChMITTJD
+ cnlwdG8xFjAUBgNVBAMTDVMvTUlNRSBTZW5kZXIxITAfBgkqhkiG9w0BCQEWEnNl
+ bmRlckBleGFtcGxlLmRvbQIBADAJBgUrDgMCGgUAoIGxMBgGCSqGSIb3DQEJAzEL
+ BgkqhkiG9w0BBwEwHAYJKoZIhvcNAQkFMQ8XDTAxMDMzMTExNDUwMlowIwYJKoZI
+ hvcNAQkEMRYEFOoeRUd8ExIYXfQq8BTFuKWrSP3iMFIGCSqGSIb3DQEJDzFFMEMw
+ CgYIKoZIhvcNAwcwDgYIKoZIhvcNAwICAgCAMA0GCCqGSIb3DQMCAgFAMAcGBSsO
+ AwIHMA0GCCqGSIb3DQMCAgEoMA0GCSqGSIb3DQEBAQUABIGAQpU8hFUtLCF6hO2t
+ ec9EYJ/Imqqiiw+BxWxkUUVT81Vbjwdn9JST6+sztM5JRP2ZW+b4txEjZriYC8f3
+ kv95YMTGbIsuWkJ93GrbvqoJ/CxO23r9WWRnZEm/1EZN9ZmlrYqzBTxnNRmP3Dhj
+ cW8kzZwH+2/2zz2G7x1HxRWH95A=
+
+ ------3C93156FC7B4EBF49FE9C7DB7F503087--
+ </PRE
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="VERIFY"
+>Verify</A
+></H1
+><P
+>Assume the above output has been saved into <TT
+CLASS="FILENAME"
+>sign.p7</TT
+>.
+ Let's now verify the signature:
+ </P
+><PRE
+CLASS="PROGRAMLISTING"
+> from M2Crypto import SMIME, X509
+
+ # Instantiate an SMIME object.
+ s = SMIME.SMIME()
+
+ # Load the signer's cert.
+ x509 = X509.load_cert('signer.pem')
+ sk = X509.X509_Stack()
+ sk.push(x509)
+ s.set_x509_stack(sk)
+
+ # Load the signer's CA cert. In this case, because the signer's
+ # cert is self-signed, it is the signer's cert itself.
+ st = X509.X509_Store()
+ st.load_info('signer.pem')
+ s.set_x509_store(st)
+
+ # Load the data, verify it.
+ p7, data = SMIME.smime_load_pkcs7('sign.p7')
+ v = s.verify(p7)
+ print v
+ print data
+ print data.read()
+ </PRE
+><P
+>Here's the output of the above program:
+ </P
+><PRE
+CLASS="SCREEN"
+> a sign of our times
+ &lt;M2Crypto.BIO.BIO instance at 0x822012c&gt;
+ a sign of our times
+ </PRE
+><P
+>Suppose, instead of loading <TT
+CLASS="FILENAME"
+>signer.pem</TT
+> above,
+ we load <TT
+CLASS="FILENAME"
+>recipient.pem</TT
+>. That is, we do a global
+ substitution of <TT
+CLASS="LITERAL"
+>recipient.pem</TT
+> for
+ <TT
+CLASS="LITERAL"
+>signer.pem</TT
+> in the above program. Here's the modified
+ program's output:
+ </P
+><PRE
+CLASS="SCREEN"
+> Traceback (most recent call last):
+ File "./verify.py", line 22, in ?
+ v = s.verify(p7)
+ File "/usr/local/home/ngps/prog/m2/M2Crypto/SMIME.py", line 205, in verify
+ raise SMIME_Error, Err.get_error()
+ M2Crypto.SMIME.SMIME_Error: 312:error:21075075:PKCS7 routines:PKCS7_verify:certificate verify error:pk7_smime.c:213:Verify error:self signed certificate
+ </PRE
+><P
+>As displayed, the error is generated by line 213 of OpenSSL's
+ <TT
+CLASS="FILENAME"
+>pk7_smime.c</TT
+> (as of OpenSSL 0.9.6); if you are a
+ C programmer, you may wish to look up the C source to explore OpenSSL's
+ S/MIME implementation and understand why the error message is worded thus.
+ </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="ENCRYPT"
+>Encrypt</A
+></H1
+><P
+>We now demonstrate how to generate an S/MIME-encrypted message:
+ </P
+><PRE
+CLASS="PROGRAMLISTING"
+> from M2Crypto import BIO, Rand, SMIME, X509
+
+ def makebuf(text):
+ return BIO.MemoryBuffer(text)
+
+ # Make a MemoryBuffer of the message.
+ buf = makebuf('a sign of our times')
+
+ # Seed the PRNG.
+ Rand.load_file('randpool.dat', -1)
+
+ # Instantiate an SMIME object.
+ s = SMIME.SMIME()
+
+ # Load target cert to encrypt to.
+ x509 = X509.load_cert('recipient.pem')
+ sk = X509.X509_Stack()
+ sk.push(x509)
+ s.set_x509_stack(sk)
+
+ # Set cipher: 3-key triple-DES in CBC mode.
+ s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+
+ # Encrypt the buffer.
+ p7 = s.encrypt(buf)
+
+ # Output p7 in mail-friendly format.
+ out = BIO.MemoryBuffer()
+ out.write('From: sender@example.dom\n')
+ out.write('To: recipient@example.dom\n')
+ out.write('Subject: M2Crypto S/MIME testing\n')
+ s.write(out, p7)
+
+ print out.read()
+
+ # Save the PRNG's state.
+ Rand.save_file('randpool.dat')
+ </PRE
+><P
+>Here's the output of the above program:
+ </P
+><PRE
+CLASS="SCREEN"
+> From: sender@example.dom
+ To: recipient@example.dom
+ Subject: M2Crypto S/MIME testing
+ MIME-Version: 1.0
+ Content-Disposition: attachment; filename="smime.p7m"
+ Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+ Content-Transfer-Encoding: base64
+
+ MIIBVwYJKoZIhvcNAQcDoIIBSDCCAUQCAQAxggEAMIH9AgEAMGYwYTELMAkGA1UE
+ BhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRkwFwYDVQQDExBTL01JTUUgUmVjaXBp
+ ZW50MSQwIgYJKoZIhvcNAQkBFhVyZWNpcGllbnRAZXhhbXBsZS5kb20CAQAwDQYJ
+ KoZIhvcNAQEBBQAEgYCBaXZ+qjpBEZwdP7gjfzfAtQitESyMwo3i+LBOw6sSDir6
+ FlNDPCnkrTvqDX3Rt6X6vBtTCYOm+qiN7ujPkOU61cN7h8dvHR8YW9+0IPY80/W0
+ lZ/HihSRgwTNd7LnxUUcPx8YV1id0dlmP0Hz+Lg+mHf6rqaR//JcYhX9vW4XvjA7
+ BgkqhkiG9w0BBwEwFAYIKoZIhvcNAwcECMN+qya6ADywgBgHr9Jkhwn5Gsdu7BwX
+ nIQfYTYcdL9I5Sk=
+ </PRE
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="DECRYPT"
+>Decrypt</A
+></H1
+><P
+>Assume the above output has been saved into <TT
+CLASS="FILENAME"
+>encrypt.p7</TT
+>.
+ Decrypt the message thusly:
+ </P
+><PRE
+CLASS="PROGRAMLISTING"
+> from M2Crypto import BIO, SMIME, X509
+
+ # Instantiate an SMIME object.
+ s = SMIME.SMIME()
+
+ # Load private key and cert.
+ s.load_key('recipient_key.pem', 'recipient.pem')
+
+ # Load the encrypted data.
+ p7, data = SMIME.smime_load_pkcs7('encrypt.p7')
+
+ # Decrypt p7.
+ out = s.decrypt(p7)
+
+ print out
+ </PRE
+><P
+>Here's the output:
+ </P
+><PRE
+CLASS="SCREEN"
+> a sign of our times
+ </PRE
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="SIGN-AND-ENCRYPT"
+>Sign and Encrypt</A
+></H1
+><P
+>Here's how to generate an S/MIME-signed/encrypted message:
+ </P
+><PRE
+CLASS="PROGRAMLISTING"
+> from M2Crypto import BIO, Rand, SMIME, X509
+
+ def makebuf(text):
+ return BIO.MemoryBuffer(text)
+
+ # Make a MemoryBuffer of the message.
+ buf = makebuf('a sign of our times')
+
+ # Seed the PRNG.
+ Rand.load_file('randpool.dat', -1)
+
+ # Instantiate an SMIME object.
+ s = SMIME.SMIME()
+
+ # Load signer's key and cert. Sign the buffer.
+ s.load_key('signer_key.pem', 'signer.pem')
+ p7 = s.sign(buf)
+
+ # Load target cert to encrypt the signed message to.
+ x509 = X509.load_cert('recipient.pem')
+ sk = X509.X509_Stack()
+ sk.push(x509)
+ s.set_x509_stack(sk)
+
+ # Set cipher: 3-key triple-DES in CBC mode.
+ s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+
+ # Create a temporary buffer.
+ tmp = BIO.MemoryBuffer()
+
+ # Write the signed message into the temporary buffer.
+ s.write(tmp, p7, buf)
+
+ # Encrypt the temporary buffer.
+ p7 = s.encrypt(tmp)
+
+ # Output p7 in mail-friendly format.
+ out = BIO.MemoryBuffer()
+ out.write('From: sender@example.dom\n')
+ out.write('To: recipient@example.dom\n')
+ out.write('Subject: M2Crypto S/MIME testing\n')
+ s.write(out, p7)
+
+ print out.read()
+
+ # Save the PRNG's state.
+ Rand.save_file('randpool.dat')
+ </PRE
+><P
+>Here's the output of the above program:
+ </P
+><PRE
+CLASS="SCREEN"
+> From: sender@example.dom
+ To: recipient@example.dom
+ Subject: M2Crypto S/MIME testing
+ MIME-Version: 1.0
+ Content-Disposition: attachment; filename="smime.p7m"
+ Content-Type: application/x-pkcs7-mime; name="smime.p7m"
+ Content-Transfer-Encoding: base64
+
+ MIIIwwYJKoZIhvcNAQcDoIIItDCCCLACAQAxggEAMIH9AgEAMGYwYTELMAkGA1UE
+ BhMCU0cxETAPBgNVBAoTCE0yQ3J5cHRvMRkwFwYDVQQDExBTL01JTUUgUmVjaXBp
+ ZW50MSQwIgYJKoZIhvcNAQkBFhVyZWNpcGllbnRAZXhhbXBsZS5kb20CAQAwDQYJ
+ KoZIhvcNAQEBBQAEgYBlZlGupFphwhsGtIAPvDExN61qisz3oem88xoXkUW0SzoR
+ B9zJFFAuQTWzdNJgrKKYikhWjDojaAc/PFl1K5dYxRgtZLB36ULJD/v/yWmxnjz8
+ TvtK+Wbal2P/MH2pZ4LVERXa/snTElhCawUlwtiFz/JvY5CiF/dcwd+AwFQq4jCC
+ B6UGCSqGSIb3DQEHATAUBggqhkiG9w0DBwQIRF525UfwszaAggeA85RmX6AXQMxb
+ eBDz/LJeCgc3RqU1UwIsbKMquIs1S46Ebbm5nP75izPnujOkJ2hv+LNzqOWADmOl
+ +CnGEq1qxTyduIgUDA2nBgCL/gVyVy+/XC9dtImUUTxtxLgYtB0ujkBNsOaENOlM
+ fv4SGM3jkR+K/xlYG6HHzZGbfYyNGj2Y7yMZ1rL1m8SnRNmkCysKGTrudeNf6wT9
+ J6wO9DzLTioz3ZnVr3LjsSKIb4tIp4ugqNJaLuW7m3FtZ3MAgxN68hBbJs8TZ8tL
+ V/0jwUqS+grcgZEb9ymfcedxahtDUfHjRkpDpsxZzVVGkSBNcbQu92oByQVnRQ8m
+ wrYLp3/eawM5AvuV7HNpTT5ZR+1t8luishHN9899IMP2Vyg0Ub67FqFypYmM2cm2
+ sjAI4KpfvT00XFNvgLuYwYEKs9syGTO7hiHNQKcF44F5LYv6nTFwmFQB11dAtY9V
+ ull4D2CLDx9OvyNyKwdEZB5dyV0r/uKIdkhST60V2Q9KegpzgFpoZtSKM/HPYSVH
+ 1Bc9f3Q/GqZCvNZZCMx8UvRjQR8dRWDSmPJ0VXG1+wJ+fCmSPP3AuQ1/VsgPRqx2
+ 56VrpGPpGut40hV8xQFbWIZ2whwWLKPFAHj8B79ZtFUzUrU6Z2rNpvv8inHc/+S/
+ b6GR5s8/gucRblvd7n3OFNX5UJmPmcw9zWbu/1Dr9DY8l0nAQh21y5FGSS8B1wdE
+ oD2M3Lp7JbwjQbRtnDhImqul2S4yu+m+wDD1aR2K4k3GAI7KKgOBWT0+BDClcn8A
+ 4Ju6/YUbj33YlMPJgnGijLnolFy0hNW7TmWqR+8tSI3wO5eNKg4qwBnarqc3vgCV
+ quVxINAXyGQCO9lzdw6hudk8/+BlweGdqhONaIWbK5z1L/SfQo6LC9MTsj7FJydq
+ bc+kEbfZS8aSq7uc9axW6Ti0eAPJ8EVHtwhSBgZQRweKFBXs6HbbhMIdc4N0M7Oq
+ UiFXaF6s4n2uihVP6TqXtHEjTpZoC7pC+HCYiuKXUJtaqtXBOh+y3KLvHk09YL6D
+ XmTDg+UTiFsh4jKKm/BhdelbR5JbpJcj5AId76Mfr8+F/1g9ePOvsWHpQr/oIQTo
+ xEkaxCmzEgP0b6caMWfMUQrbVGxBBNcqKc/ir9fGGOPHATzzq/xLcQYvK1tZhd/D
+ ah/gpMPndsyvVCEuFPluWyDiM0VkwHgC2/3pJIYFHaxK64IutmPsy393rHMEB4kN
+ AHau6kWK+yL9qEVH1pP2zvswQ12P7gjt3T/G3bGsmvlXkEfztfjkXo6XnjcBNf5y
+ G+974AKLcjnk1gzIgarz+lAMY57Gkw4oNDMrTqVQ2OJQlvOSbllPXzH+aAiavB8W
+ ZPECLLwHxD4B1AuaiAArgKl935u/TOB+yQOR8JgGsUzROyJqHJ/SC51HkebgCkL1
+ aggtjgPlIBEXLZAlhpWLZ9lAQyrQpvCVJYwaOvfMmvRav4NAFNoZ2/Q7S4Tn1z+U
+ XX+f+GD58P4MPMhU5IKnz4yH4nlHnAiTEvcs85TZUAXze9g/uBOwZITeGtyLi52S
+ aETIr4v7SgXMepX7ThQ1Pv/jddsK/u4j2F34u0XktwCP+UrbfkE2mocdXvdzxbmd
+ tZSznK2qwgVSsPOs9MhUaepbnjmNBFFBrULhrUtSglM/VX/rWNiyh0aw4XYyHhIt
+ 9ZNlfEjKjJ67VEMBxBJ/ieUCouRGCxPYD1j65VT7oB3ZiyPu2F2nlUIcYNqPg1Sd
+ QBCrdaOXdJ0uLwyTAUeVE+wMbgscLvWsfZcCCJHAvw9NHFMUcnrdWxAYMVETNUOn
+ uryVAK7VfOldaz6z3NOSOi6nonNeHpR/sipBa4ik5xCRLT9e0S2QJgRvO9GyfAqz
+ 3DIzHtxIGePFzTiUYUTxS3i2gnMX2PEe3ChTLlYWD3jNeAKz0iOzpDphIF2xHLLQ
+ 1tCAqBmq/vUzALyDFFdFuTIqQZys4z/u4Dmyq9uXs421eN3v2hkVHvDy8uT2Ot29
+ lg4Q5YezR1EjaW//9guL1BXbcKrTEdtxeNqtem7SpZOMTSwD2lhB8z65GrX90Cyt
+ EMmaRSGYEdf5h1afL1SmKOMskbqxe1D2jG/vsXC7XX7xO/ioy0BdiJcYN1JiMOHJ
+ EOzFol5I20YkiV6j+cenfQFwc/NkaSxEkR8AUHJSbvUmRQRl6r0nnsFpZdR1w7pv
+ wkaT+eOpZynO4mY/ZtF6MpXJsixi6L4ZYXEbS6yHf+XGFfB0okILylmwv2bf6+Mq
+ nqXlmGj3Jwq7X9/+2BDqvfpFFX5lSmItKZAobLdssjFR6roJxOqRsGia2aZ+0+U5
+ VhgdITtnElgtHBaeZU5rHDswgdeLVBP+rGWnKxpJ+pLtNNi25sPYRcWFL6Erd25u
+ eXiY8GEIr+u7rqBWpc9HR34sAPRs3ubbCUleT748keCbx247ImBtiDctZxcc1O86
+ +0QjHP6HUT7FSo/FmT7a120S3Gd2jixGh06l/9ij5Z6mJa7Rm7TTbSjup/XISnOT
+ MKWcbI1nfVOhCv3xDq2eLae+s0oVoc041ceRazqFM2TL/Z6UXRME
+ </PRE
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="DECRYPT-AND-VERIFY"
+>Decrypt and Verify</A
+></H1
+><P
+>Suppose the above output has been saved into
+ <TT
+CLASS="FILENAME"
+>se.p7</TT
+>. The following demonstrates how to decrypt and
+ verify it:
+ </P
+><PRE
+CLASS="PROGRAMLISTING"
+> from M2Crypto import BIO, SMIME, X509
+
+ # Instantiate an SMIME object.
+ s = SMIME.SMIME()
+
+ # Load private key and cert.
+ s.load_key('recipient_key.pem', 'recipient.pem')
+
+ # Load the signed/encrypted data.
+ p7, data = SMIME.smime_load_pkcs7('se.p7')
+
+ # After the above step, 'data' == None.
+ # Decrypt p7. 'out' now contains a PKCS #7 signed blob.
+ out = s.decrypt(p7)
+
+ # Load the signer's cert.
+ x509 = X509.load_cert('signer.pem')
+ sk = X509.X509_Stack()
+ sk.push(x509)
+ s.set_x509_stack(sk)
+
+ # Load the signer's CA cert. In this case, because the signer's
+ # cert is self-signed, it is the signer's cert itself.
+ st = X509.X509_Store()
+ st.load_info('signer.pem')
+ s.set_x509_store(st)
+
+ # Recall 'out' contains a PKCS #7 blob.
+ # Transform 'out'; verify the resulting PKCS #7 blob.
+ p7_bio = BIO.MemoryBuffer(out)
+ p7, data = SMIME.smime_load_pkcs7_bio(p7_bio)
+ v = s.verify(p7)
+
+ print v
+ </PRE
+><P
+>The output is as follows:
+ </P
+><PRE
+CLASS="SCREEN"
+> a sign of our times
+ </PRE
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="SMTP"
+>Sending S/MIME messages via SMTP</A
+></H1
+><P
+>In the above examples, we've assumed that our S/MIME messages
+ are sent and received automagically. The following is a Python function that
+ generates S/MIME-signed/encrypted messages and sends them via SMTP:
+ </P
+><PRE
+CLASS="PROGRAMLISTING"
+> from M2Crypto import BIO, SMIME, X509
+ import smtplib, string, sys
+
+ def sendsmime(from_addr, to_addrs, subject, msg, from_key, from_cert=None, to_certs=None, smtpd='localhost'):
+
+ msg_bio = BIO.MemoryBuffer(msg)
+ sign = from_key
+ encrypt = to_certs
+
+ s = SMIME.SMIME()
+ if sign:
+ s.load_key(from_key, from_cert)
+ p7 = s.sign(msg_bio, flags=SMIME.PKCS7_TEXT)
+ msg_bio = BIO.MemoryBuffer(msg) # Recreate coz sign() has consumed it.
+
+ if encrypt:
+ sk = X509.X509_Stack()
+ for x in to_certs:
+ sk.push(X509.load_cert(x))
+ s.set_x509_stack(sk)
+ s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+ tmp_bio = BIO.MemoryBuffer()
+ if sign:
+ s.write(tmp_bio, p7)
+ else:
+ tmp_bio.write(msg)
+ p7 = s.encrypt(tmp_bio)
+
+ out = BIO.MemoryBuffer()
+ out.write('From: %s\r\n' % from_addr)
+ out.write('To: %s\r\n' % string.join(to_addrs, ", "))
+ out.write('Subject: %s\r\n' % subject)
+ if encrypt:
+ s.write(out, p7)
+ else:
+ if sign:
+ s.write(out, p7, msg_bio, SMIME.PKCS7_TEXT)
+ else:
+ out.write('\r\n')
+ out.write(msg)
+ out.close()
+
+ smtp = smtplib.SMTP()
+ smtp.connect(smtpd)
+ smtp.sendmail(from_addr, to_addrs, out.read())
+ smtp.quit()
+ </PRE
+><P
+>This function sends plain, S/MIME-signed, S/MIME-encrypted,
+ and S/MIME-signed/encrypted messages, depending on the parameters
+ <TT
+CLASS="PARAMETER"
+><I
+>from_key</I
+></TT
+> and <TT
+CLASS="PARAMETER"
+><I
+>to_certs</I
+></TT
+>. The
+ function's output interoperates with Netscape Messenger.
+ </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="VERIFYING-ORIGIN"
+>Verifying origin of S/MIME messages</A
+></H1
+><P
+>In our examples above that decrypt or verify messages, we skipped
+ a step: verifying that the <TT
+CLASS="LITERAL"
+>from</TT
+> address of the message
+ matches the <TT
+CLASS="LITERAL"
+>email address</TT
+> attribute in the sender's
+ certificate.
+ </P
+><P
+>The premise of current X.509 certification practice is that the
+ CA is supposed to verify your identity, and to issue a certificate with
+ <TT
+CLASS="LITERAL"
+>email address</TT
+> that matches your actual mail address.
+ (Verisign's March 2001 failure in identity verification resulting in
+ Microsoft certificates being issued to spoofers notwithstanding.)
+ </P
+><P
+>If you run your own CA, your certification practice is up to you, of
+ course, and it would probably be part of your security policy.
+ </P
+><P
+>Whether your S/MIME messaging application needs to verify the
+ <TT
+CLASS="LITERAL"
+>from</TT
+> addresses of S/MIME messages depends on your
+ security policy and your system's threat model, as always.
+ </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="NETSCAPE-MESSENGER"
+>Interoperating with Netscape Messenger</A
+></H1
+><P
+>Suppose S/MIME Recipient uses Netscape Messenger. To enable Messenger
+ to handle S/MIME messages from S/MIME Sender, S/MIME Recipient needs
+ to configure Messenger with his private key and certificate, as well as S/MIME
+ Sender's certificate.
+ </P
+><DIV
+CLASS="NOTE"
+><BLOCKQUOTE
+CLASS="NOTE"
+><P
+><B
+>Note: </B
+>Configuring Messenger's POP or IMAP settings so that it retrieves
+ mail correctly is beyond the scope of this HOWTO.
+ </P
+></BLOCKQUOTE
+></DIV
+><P
+>The following steps demonstrate how to import S/MIME Recipient's
+ private key and certificate for Messenger:
+ </P
+><DIV
+CLASS="PROCEDURE"
+><OL
+TYPE="1"
+><LI
+><P
+>Transform S/MIME Recipient's private key and certificate into
+ <I
+CLASS="EMPHASIS"
+>PKCS #12</I
+> format.
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>openssl pkcs12 -export -in recipient.pem -inkey recipient_key.pem -name "S/MIME Recipient" -out recipient.p12
+ </B
+></TT
+>
+ Enter Export Password:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;enter&gt;</I
+></TT
+></B
+></TT
+>
+ Verifying password - Enter Export Password:<TT
+CLASS="USERINPUT"
+><B
+><TT
+CLASS="REPLACEABLE"
+><I
+>&lt;enter&gt;</I
+></TT
+></B
+></TT
+>
+ </PRE
+></LI
+><LI
+><P
+>Start Messenger.
+ </P
+></LI
+><LI
+><P
+>Click on the (open) "lock" icon at the bottom left corner of
+ Messenger's window. This brings up the "Security Info" dialog box.
+ </P
+></LI
+><LI
+><P
+>Click on "Yours" under "Certificates".
+ </P
+></LI
+><LI
+><P
+>Select "Import a certificate", then pick
+ <TT
+CLASS="FILENAME"
+>recipient.p12</TT
+> from the ensuing file selection dialog
+ box.
+ </P
+></LI
+></OL
+></DIV
+><P
+>Next, you need to import <TT
+CLASS="FILENAME"
+>signer.pem</TT
+> as a CA
+ certificate, so that Messenger will mark messages signed by S/MIME Sender as
+ "trusted":
+ </P
+><DIV
+CLASS="PROCEDURE"
+><OL
+TYPE="1"
+><LI
+><P
+>Create a DER encoding of <TT
+CLASS="FILENAME"
+>signer.pem</TT
+>.
+ </P
+><PRE
+CLASS="SCREEN"
+> <TT
+CLASS="USERINPUT"
+><B
+>openssl x509 -inform pem -outform der -in signer.pem -out signer.der
+ </B
+></TT
+>
+ </PRE
+></LI
+><LI
+><P
+>Install <TT
+CLASS="FILENAME"
+>signer.der</TT
+> into Messenger as MIME type
+ <TT
+CLASS="LITERAL"
+>application/x-x509-ca-cert</TT
+>. You do this by downloading
+ <TT
+CLASS="FILENAME"
+>signer.der</TT
+> via Navigator from a HTTP or HTTPS server,
+ with the correct MIME type mapping. (You may use
+ <TT
+CLASS="FILENAME"
+>demo/ssl/https_srv.py</TT
+>, bundled with M2Crypto, for
+ this purpose.) Follow the series of dialog boxes to accept
+ <TT
+CLASS="FILENAME"
+>signer.der</TT
+> as a CA for certifying email users.
+ </P
+></LI
+></OL
+></DIV
+><P
+>S/MIME Recipient is now able to decrypt and read S/MIME Sender's
+ messages with Messenger. Messenger will indicate that S/MIME Sender's
+ messages are signed, encrypted, or encrypted <I
+CLASS="EMPHASIS"
+>and</I
+>
+ signed, as the case may be, via the "stamp" icon on the message window's
+ top right corner.
+ </P
+><P
+>Clicking on the "stamp" icon brings you to the Security Info dialog
+ box. Messenger informs you that the message is, say, encrypted with
+ 168-bit DES-EDE3-CBC and that it is digitally signed by the private key
+ corresponding to the public key contained in the certificate
+ <TT
+CLASS="FILENAME"
+>signer.pem</TT
+>.
+ </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="MICROSOFT-OUTLOOK"
+>Interoperating with Microsoft Outlook</A
+></H1
+><P
+>I do not know how to do this, as I do not use Outlook. (Nor do I use
+ Netscape Messenger, actually. I use Mutt, top dog of MUAs. ;-) Information on
+ how to configure Outlook with keys and certificates so that it handles
+ S/MIME mail is gratefully accepted.
+ </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="ZSMIME"
+>ZSmime</A
+></H1
+><P
+>ZSmime is a <A
+HREF="http://www.zope.org"
+TARGET="_top"
+>Zope</A
+>
+ <I
+CLASS="EMPHASIS"
+>product</I
+> that enables Zope to generate
+ S/MIME-signed/encrypted messages. ZSmime demonstrates how to invoke
+ M2Crypto in a web application server extension.
+ </P
+><P
+>ZSmime has its own <A
+HREF="http://sandbox.rulemaker.net/ngps/zope/zsmime/howto.html"
+TARGET="_top"
+>HOWTO</A
+>
+ explaining its usage. (That HOWTO has some overlap in content with
+ this document.)
+ </P
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="RESOURCES"
+>Resources</A
+></H1
+><P
+></P
+><UL
+><LI
+STYLE="list-style-type: opencircle"
+><P
+>IETF S/MIME Working Group -
+ <A
+HREF="http://www.imc.org/ietf-smime"
+TARGET="_top"
+> http://www.imc.org/ietf-smime</A
+>
+ </P
+></LI
+><LI
+STYLE="list-style-type: opencircle"
+><P
+>S/MIME and OpenPGP -
+ <A
+HREF="http://www.imc.org/smime-pgpmime.html"
+TARGET="_top"
+> http://www.imc.org/smime-pgpmime.html</A
+>
+ </P
+></LI
+><LI
+STYLE="list-style-type: opencircle"
+><P
+>S/MIME Freeware Library -
+ <A
+HREF="http://www.getronicsgov.com/hot/sfl_home.htm"
+TARGET="_top"
+> http://www.getronicsgov.com/hot/sfl_home.htm</A
+>
+ </P
+></LI
+><LI
+STYLE="list-style-type: opencircle"
+><P
+>Mozilla Network Security Services -
+ <A
+HREF="http://www.mozilla.org/projects/security/pkg/nss"
+TARGET="_top"
+> http://www.mozilla.org/projects/security/pkg/nss</A
+>
+ </P
+></LI
+><LI
+STYLE="list-style-type: opencircle"
+><P
+>S/MIME Cracking Screen Saver -
+ <A
+HREF="http://www.counterpane.com/smime.html"
+TARGET="_top"
+> http://www.counterpane.com/smime.html</A
+>
+ </P
+></LI
+></UL
+></DIV
+><DIV
+CLASS="SECT1"
+><HR><H1
+CLASS="SECT1"
+><A
+NAME="ID-KLUDGE"
+></A
+></H1
+><P
+> <TT
+CLASS="LITERAL"
+>$Id:howto.smime.html 583 2007-10-01 19:23:12Z heikki $</TT
+>
+ </P
+></DIV
+></DIV
+></BODY
+></HTML
+> \ No newline at end of file
diff --git a/doc/howto.ssl.html b/doc/howto.ssl.html
new file mode 100644
index 0000000..340f264
--- /dev/null
+++ b/doc/howto.ssl.html
@@ -0,0 +1,206 @@
+<HTML
+><HEAD
+><TITLE
+>HOWTO: Programming SSL in Python with M2Crypto</TITLE
+>
+
+</HEAD>
+<BODY
+CLASS="ARTICLE"
+BGCOLOR="#FFFFFF"
+TEXT="#000000"
+LINK="#0000FF"
+VLINK="#840084"
+ALINK="#0000FF"
+>
+
+<DIV
+CLASS="TITLEPAGE"
+><H1
+CLASS="TITLE"
+><A
+NAME="AEN2"
+>HOWTO: Programming SSL in Python with M2Crypto</A
+></H1
+>
+<P>
+Ng Pheng Siong (ngps@netmemetic.com) and Heikki Toivonen (heikki@osafoundation.org)
+</P
+><P
+CLASS="COPYRIGHT"
+>Copyright &copy; 2001, 2002 by Ng Pheng Siong.</P
+>
+<P
+CLASS="COPYRIGHT"
+>Portions Copyright &copy; 2006 by Open Source Applications Foundation.</P
+>
+</DIV>
+
+<DIV
+CLASS="SECT1"
+><H1
+CLASS="SECT1"
+><A
+NAME="INTRODUCTION"
+>Introduction</A
+></H1
+><P
+><A
+HREF="http://chandlerproject.org/Projects/MeTooCrypto"
+TARGET="_top"
+>M2Crypto</A
+>
+ is a <A
+HREF="http://www.python.org"
+TARGET="_top"
+>Python</A
+>
+ interface to <A
+HREF="http://www.openssl.org"
+TARGET="_top"
+>OpenSSL</A
+>. It makes
+ available to the Python programmer SSL functionality to implement clients
+ and servers, S/MIME v2, RSA, DSA, DH, symmetric ciphers, message digests and
+ HMACs.
+ </P
+><P
+>This document demonstrates programming HTTPS with M2Crypto.
+ </P
+></DIV
+>
+
+
+<DIV
+CLASS="SECT1"
+><H1
+CLASS="SECT1"
+><A
+NAME="history"
+>A bit of history</A
+></H1
+>
+
+<p> M2Crypto was created during the time of Python 1.5, which features
+ a module httplib providing client-side HTTP functionality. M2Crypto sports
+ a httpslib based on httplib.
+ </p>
+
+ <p>
+ Beginning with version 2.0, Python's socket module provided
+ (rudimentary) SSL support. Also in the same version, httplib was
+ enhanced with class HTTPConnection, which is more sophisticated than
+ the old class HTTP, and HTTPSConnection, which does HTTPS.
+ </p>
+
+ <p>
+ Subsequently, M2Crypto.httpslib grew a compatible (but not identical)
+ class HTTPSConnection.
+ </p>
+
+ <p>
+ The primary interface difference between the two HTTPSConnection
+ classes is that M2Crypto's version accepts an M2Crypto.SSL.Context
+ instance as a parameter, whereas Python 2.x's SSL support does not
+ permit Pythonic control of the SSL context.
+ </p>
+
+ <p> Within the implementations, Python's
+ <tt>HTTPSConnection</tt> employs a
+ <tt>FakeSocket</tt> object, which collects all input from
+ the SSL connection before returning it to the application as a
+ <tt>StringIO</tt> buffer, whereas M2Crypto's
+ <tt>HTTPSConnection</tt> uses a buffering
+ <tt>M2Crypto.BIO.IOBuffer</tt> object that works over the
+ underlying M2Crypto.SSL.Connection directly. </p>
+
+ <p>Since then M2Crypto has gained a Twisted wrapper that allows securing
+ Twisted SSL connections with M2Crypto.</p>
+</DIV
+>
+
+
+<DIV CLASS="SECT1" id="secure" name="secure">
+<H1 CLASS="SECT1">Secure SSL</H1>
+
+<p>It is recommended that you read the book Network Security with OpenSSL by John Viega, Matt Messier and Pravir Chandra,
+ISBN 059600270X.</p>
+
+<p>Using M2Crypto does not automatically make an SSL connection secure. There are various steps that need to be made
+before we can make that claim. Let's see how a simple client can establish a secure connection:</p>
+
+<pre>
+ctx = SSL.Context()
+ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, depth=9)
+if ctx.load_verify_locations('ca.pem') != 1: raise Exception('No CA certs')
+s = SSL.Connection(ctx)
+s.connect(server_address)
+# Normal protocol (for example HTTP) commands follow
+</pre>
+
+<p>The first line creates an SSL context. The defaults allow any SSL version (except SSL version 2 which has known
+weaknesses) and sets the allowed ciphers to secure ones.</p>
+
+<p>The second line tells M2Crypto to perform certificate validation. The flags shown above are typical for clients,
+and requires the server to send a certificate. The depth parameter tells how long certificate chains are allowed -
+9 is pretty common default, although probably too long in practice.</p>
+
+<p>The third line loads the allowed root (certificate authority or CA) certificates.
+Most Linux distributions come with CA certificates in suitable format. You
+could also download the <a href="http://mxr.mozilla.org/seamonkey/source//security/nss/lib/ckfw/builtins/certdata.txt?raw=1">certdata.txt</a>
+file from the <a href="http://www.mozilla.org/projects/security/pki/nss/">NSS</a>
+project and convert it
+with the little M2Crypto utility script <a href="http://svn.osafoundation.org/m2crypto/trunk/demo/x509/certdata2pem.py">demo/x509/certdata2pem.py</a>.</p>
+
+<p>The fourth line creates an SSL connection object with the secure context.</p>
+
+<p>The fifth line connects to the server. During this time we perform the last security step: just after connection, but before
+exchanging any data, we compare the commonName (or subjectAltName DNS field) field in the certificate the server returned to the
+server address we tried to connect to. This happens automatically with SSL.Connection and the Twisted wrapper class, and anything
+that uses those. In all other cases you must do the check manually. It is recommended you call the SSL.Checker to do the actual check.</p>
+
+<p>SSL servers are different in that they typically do not require the client to send a certificate, so there is usually no certificate
+checking. Also, it is typically useless to perform host name checking.</p>
+
+</DIV>
+
+<DIV CLASS="SECT1">
+<H1 CLASS="SECT1">Code Samples</H1>
+
+<p>The best samples of how to use the various SSL objects are in the tests directory, and the test_ssl.py file specifically.
+There are additional samples in the demo directory, but they are not quaranteed to be up to date.</p>
+
+<p>NOTE: The tests and demos
+may not be secure as is. Use the information above on how to make them secure.</p>
+</DIV>
+
+<DIV
+CLASS="SECT1"
+><H1
+CLASS="SECT1"
+><A
+NAME="SSLDUMP"
+>ssldump</A
+></H1
+>
+<P>ssldump "is an SSLv3/TLS network protocol analyser. It identifies
+ TCP connections on the chosen network interface and attempts to interpret
+ them as SSLv3/TLS traffic. When it identifies SSLv3/TLS traffic, it
+ decodes the records and displays them in a textual form to stdout. If
+ provided with the appropriate keying material, it will also decrypt the
+ connections and display the application data traffic.
+ </P>
+
+ <P>
+ If linked with OpenSSL, ssldump can display certificates in decoded form
+ and decrypt traffic (provided that it has the appropriate keying
+ material)."
+ </P>
+
+ <P>ssldump is written by Eric Rescorla.
+ </P>
+</DIV
+>
+
+</BODY>
+</HTML>
diff --git a/epydoc.conf b/epydoc.conf
new file mode 100644
index 0000000..2afa556
--- /dev/null
+++ b/epydoc.conf
@@ -0,0 +1,29 @@
+# Copyright 2009 Heikki Toivonen. All rights reserved.
+# epydoc --no-private -v --config=epydoc.conf
+[epydoc]
+
+name = M2Crypto
+url = http://chandlerproject.org/Projects/MeTooCrypto
+
+output = html
+target = doc/api
+graph = all
+sourcecode = no
+private = no
+
+modules = ./M2Crypto
+
+# Private
+exclude-introspect = M2Crypto.__m2crypto
+
+# Variable shadows module, which causes the module to double in the doc
+# with second instance showing '. Exclude so we only get one (with ').
+exclude = M2Crypto.PGP.PublicKey
+exclude = M2Crypto.PGP.PublicKeyRing
+exclude = M2Crypto.PGP.packet
+exclude = M2Crypto.SSL.Cipher
+exclude = M2Crypto.SSL.Connection
+exclude = M2Crypto.SSL.Context
+exclude = M2Crypto.SSL.SSLServer
+exclude = M2Crypto.SSL.ssl_dispatcher
+exclude = M2Crypto.SSL.timeout
diff --git a/fedora_setup.sh b/fedora_setup.sh
new file mode 100755
index 0000000..76d1f59
--- /dev/null
+++ b/fedora_setup.sh
@@ -0,0 +1,15 @@
+#!/bin/sh
+# This script is meant to work around the differences on Fedora Core-based
+# distributions (Redhat, CentOS, ...) compared to other common Linux
+# distributions.
+#
+# Usage: ./fedora_setup.sh [setup.py options]
+#
+
+arch=`uname -m`
+for i in SWIG/_{ec,evp}.i; do
+ sed -i -e "s/opensslconf\./opensslconf-${arch}\./" "$i"
+done
+
+SWIG_FEATURES=-cpperraswarn python setup.py $*
+
diff --git a/pack.py b/pack.py
new file mode 100644
index 0000000..0005026
--- /dev/null
+++ b/pack.py
@@ -0,0 +1,37 @@
+#!/usr/bin/env python
+
+# Clean up M2Crypto source base.
+
+import glob, os, os.path, sys
+
+def zap(arg, dirname, names):
+ for f in glob.glob(dirname + arg):
+ try:
+ os.remove(f)
+ except:
+ pass
+
+if __name__ == "__main__":
+ start = sys.argv[1]
+
+ os.path.walk(start, zap, "/*.pyc")
+
+ if os.name == 'nt':
+ zap_m2 = ("__m2cryptoc.pyd","_m2crypto.py")
+ elif os.name == 'posix':
+ zap_m2 = ("__m2crypto.so","_m2crypto.py")
+ for x in zap_m2:
+ try:
+ os.remove("%s/M2Crypto/%s" % (start, x))
+ except:
+ pass
+
+ zap_swig = ("_m2crypto_wrap*", "_m2crypto.c", "_m2crypto.py", "vc60.pdb")
+ for x in zap_swig:
+ for z in glob.glob("%s/SWIG/%s" % (start, x)):
+ try:
+ os.remove(z)
+ except:
+ pass
+
+
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..861a9f5
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,5 @@
+[egg_info]
+tag_build =
+tag_date = 0
+tag_svn_revision = 0
+
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..e7c49eb
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python
+
+"""
+Distutils/setuptools installer for M2Crypto.
+
+Copyright (c) 1999-2004, Ng Pheng Siong. All rights reserved.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2004-2007 OSAF. All Rights Reserved.
+
+Copyright 2008-2011 Heikki Toivonen. All rights reserved.
+"""
+
+import os, sys
+try:
+ from setuptools import setup
+ from setuptools.command import build_ext
+except ImportError:
+ from distutils.core import setup
+ from distutils.command import build_ext
+
+from distutils.core import Extension
+
+
+class _M2CryptoBuildExt(build_ext.build_ext):
+ '''Specialization of build_ext to enable swig_opts to inherit any
+ include_dirs settings made at the command line or in a setup.cfg file'''
+ user_options = build_ext.build_ext.user_options + \
+ [('openssl=', 'o', 'Prefix for openssl installation location')]
+
+ def initialize_options(self):
+ '''Overload to enable custom openssl settings to be picked up'''
+
+ build_ext.build_ext.initialize_options(self)
+
+ # openssl is the attribute corresponding to openssl directory prefix
+ # command line option
+ if os.name == 'nt':
+ self.libraries = ['ssleay32', 'libeay32']
+ self.openssl = 'c:\\pkg'
+ else:
+ self.libraries = ['ssl', 'crypto']
+ self.openssl = '/usr'
+
+
+ def finalize_options(self):
+ '''Overloaded build_ext implementation to append custom openssl
+ include file and library linking options'''
+
+ build_ext.build_ext.finalize_options(self)
+
+ opensslIncludeDir = os.path.join(self.openssl, 'include')
+ opensslLibraryDir = os.path.join(self.openssl, 'lib')
+
+ self.swig_opts = ['-I%s' % i for i in self.include_dirs + \
+ [opensslIncludeDir]]
+ self.swig_opts.append('-includeall')
+ #self.swig_opts.append('-D__i386__') # Uncomment for early OpenSSL 0.9.7 versions, or on Fedora Core if build fails
+ #self.swig_opts.append('-DOPENSSL_NO_EC') # Try uncommenting if you can't build with EC disabled
+
+ self.include_dirs += [os.path.join(self.openssl, opensslIncludeDir),
+ os.path.join(os.getcwd(), 'SWIG')]
+
+ if sys.platform == 'cygwin':
+ # Cygwin SHOULD work (there's code in distutils), but
+ # if one first starts a Windows command prompt, then bash,
+ # the distutils code does not seem to work. If you start
+ # Cygwin directly, then it would work even without this change.
+ # Someday distutils will be fixed and this won't be needed.
+ self.library_dirs += [os.path.join(self.openssl, 'bin')]
+
+ self.library_dirs += [os.path.join(self.openssl, opensslLibraryDir)]
+
+
+if sys.version_info < (2,4):
+
+ # This copy of swig_sources is from Python 2.2.
+
+ def swig_sources (self, sources):
+
+ """Walk the list of source files in 'sources', looking for SWIG
+ interface (.i) files. Run SWIG on all that are found, and
+ return a modified 'sources' list with SWIG source files replaced
+ by the generated C (or C++) files.
+ """
+
+ new_sources = []
+ swig_sources = []
+ swig_targets = {}
+
+ # XXX this drops generated C/C++ files into the source tree, which
+ # is fine for developers who want to distribute the generated
+ # source -- but there should be an option to put SWIG output in
+ # the temp dir.
+
+ if self.swig_cpp:
+ target_ext = '.cpp'
+ else:
+ target_ext = '.c'
+
+ for source in sources:
+ (base, ext) = os.path.splitext(source)
+ if ext == ".i": # SWIG interface file
+ new_sources.append(base + target_ext)
+ swig_sources.append(source)
+ swig_targets[source] = new_sources[-1]
+ else:
+ new_sources.append(source)
+
+ if not swig_sources:
+ return new_sources
+
+ swig = self.find_swig()
+ swig_cmd = [swig, "-python", "-ISWIG"]
+ if self.swig_cpp:
+ swig_cmd.append("-c++")
+
+ swig_cmd += self.swig_opts
+
+ for source in swig_sources:
+ target = swig_targets[source]
+ self.announce("swigging %s to %s" % (source, target))
+ self.spawn(swig_cmd + ["-o", target, source])
+
+ return new_sources
+
+ build_ext.build_ext.swig_sources = swig_sources
+
+
+m2crypto = Extension(name = 'M2Crypto.__m2crypto',
+ sources = ['SWIG/_m2crypto.i'],
+ extra_compile_args = ['-DTHREADING'],
+ #extra_link_args = ['-Wl,-search_paths_first'], # Uncomment to build Universal Mac binaries
+ )
+
+setup(name = 'M2Crypto',
+ version = '0.21.1',
+ description = 'M2Crypto: A Python crypto and SSL toolkit',
+ long_description = '''\
+M2Crypto is the most complete Python wrapper for OpenSSL featuring RSA, DSA,
+DH, EC, HMACs, message digests, symmetric ciphers (including AES); SSL
+functionality to implement clients and servers; HTTPS extensions to Python's
+httplib, urllib, and xmlrpclib; unforgeable HMAC'ing AuthCookies for web
+session management; FTP/TLS client and server; S/MIME; ZServerSSL: A HTTPS
+server for Zope and ZSmime: An S/MIME messenger for Zope. M2Crypto can also be
+used to provide SSL for Twisted.''',
+ license = 'BSD-style license',
+ platforms = ['any'],
+ author = 'Ng Pheng Siong',
+ author_email = 'ngps at sandbox rulemaker net',
+ maintainer = 'Heikki Toivonen',
+ maintainer_email = 'heikki@osafoundation.org',
+ url = 'http://chandlerproject.org/Projects/MeTooCrypto',
+ packages = ['M2Crypto', 'M2Crypto.SSL', 'M2Crypto.PGP'],
+ classifiers = [
+ 'Development Status :: 5 - Production/Stable',
+ 'Intended Audience :: Developers',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: C',
+ 'Programming Language :: Python',
+ 'Topic :: Security :: Cryptography',
+ 'Topic :: Software Development :: Libraries :: Python Modules',
+ ],
+
+ ext_modules = [m2crypto],
+ test_suite='tests.alltests.suite',
+ cmdclass = {'build_ext': _M2CryptoBuildExt}
+ )
diff --git a/tests/README b/tests/README
new file mode 100644
index 0000000..28eac67
--- /dev/null
+++ b/tests/README
@@ -0,0 +1,62 @@
+This directory contains unit tests for M2Crypto.
+
+To run all tests, make sure you have installed setuptools and then issue the
+following command from the M2Crypto root directory:
+
+python setup.py test
+
+To run tests in a single file, for example test_ssl.py, do this:
+
+python setup.py test --test-suite=tests.test_ssl
+
+
+Look also in the demo directory for other samples.
+
+
+To create new test certificates:
+
+mkdir certs
+cd certs
+
+Making the CA. You may want to use a locally edited openssl.cnf to
+make sure that X509v3 Basic Constraints CA:TRUE gets set (by default
+it may be false). By default duration may only be just one year; should
+set this for at least 3 years.
+
+CA.sh -newca
+cp demoCA/cacert.pem ../ca.pem
+
+Making the server certificate and private key. make sure commonName
+field is localhost.
+
+CA.sh -newreq
+CA.sh -signreq
+cp newcert.pem ../server.pem
+openssl rsa <newkey.pem >>../server.pem
+
+Making the x509 certificate and key.
+
+CA.sh -newreq
+CA.sh -signreq
+cp newcert.pem ../x509.pem
+openssl rsa <newkey.pem >>../x509.pem
+openssl x509 -in ../x509.pem -out ../x509.der -outform DER
+
+Making the signer certificate. Make sure the email address is
+signer@example.com.
+
+CA.sh -newreq
+CA.sh -signreq
+cp newcert.pem ../signer.pem
+openssl rsa <newkey.pem >../signer_key.pem
+
+Making the recipient certificate. Make sure the email address is
+recipient@example.com.
+
+CA.sh -newreq
+CA.sh -signreq
+cp newcert.pem ../recipient.pem
+openssl rsa <newkey.pem >../recipient_key.pem
+
+
+Finally run the tests and edit for new values.
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/__init__.py
diff --git a/tests/alltests.py b/tests/alltests.py
new file mode 100644
index 0000000..b35e58f
--- /dev/null
+++ b/tests/alltests.py
@@ -0,0 +1,100 @@
+#!/usr/bin/env python
+
+def suite():
+ from M2Crypto import m2
+ import os
+ import unittest
+
+ def my_import(name):
+ # See http://docs.python.org/lib/built-in-funcs.html#l2h-6
+ components = name.split('.')
+ try:
+ # python setup.py test
+ mod = __import__(name)
+ for comp in components[1:]:
+ mod = getattr(mod, comp)
+ except ImportError:
+ # python tests/alltests.py
+ mod = __import__(components[1])
+ return mod
+
+ modules_to_test = [
+ 'tests.test_asn1',
+ 'tests.test_bio',
+ 'tests.test_bio_membuf',
+ 'tests.test_bio_file',
+ 'tests.test_bio_iobuf',
+ 'tests.test_bio_ssl',
+ 'tests.test_bn',
+ 'tests.test_authcookie',
+ 'tests.test_dh',
+ 'tests.test_dsa',
+ 'tests.test_engine',
+ 'tests.test_evp',
+ 'tests.test_obj',
+ 'tests.test_pgp',
+ 'tests.test_rand',
+ 'tests.test_rc4',
+ 'tests.test_rsa',
+ 'tests.test_smime',
+ 'tests.test_ssl_offline',
+ 'tests.test_threading',
+ 'tests.test_x509']
+ if os.name == 'posix':
+ modules_to_test.append('tests.test_ssl')
+ elif os.name == 'nt':
+ modules_to_test.append('tests.test_ssl_win')
+ if m2.OPENSSL_VERSION_NUMBER >= 0x90800F and m2.OPENSSL_NO_EC == 0:
+ modules_to_test.append('tests.test_ecdh')
+ modules_to_test.append('tests.test_ecdsa')
+ modules_to_test.append('tests.test_ec_curves')
+ alltests = unittest.TestSuite()
+ for module in map(my_import, modules_to_test):
+ alltests.addTest(module.suite())
+ return alltests
+
+
+def dump_garbage():
+ import gc
+ print '\nGarbage:'
+ gc.collect()
+ if len(gc.garbage):
+
+ print '\nLeaked objects:'
+ for x in gc.garbage:
+ s = str(x)
+ if len(s) > 77: s = s[:73]+'...'
+ print type(x), '\n ', s
+
+ print 'There were %d leaks.' % len(gc.garbage)
+ else:
+ print 'Python garbage collector did not detect any leaks.'
+ print 'However, it is still possible there are leaks in the C code.'
+
+
+def runall(report_leaks=0):
+ report_leaks = report_leaks
+
+ if report_leaks:
+ import gc
+ gc.enable()
+ gc.set_debug(gc.DEBUG_LEAK & ~gc.DEBUG_SAVEALL)
+
+ import os, unittest
+ from M2Crypto import Rand
+
+ try:
+ Rand.load_file('tests/randpool.dat', -1)
+ unittest.TextTestRunner(verbosity=2).run(suite())
+ Rand.save_file('tests/randpool.dat')
+ finally:
+ if os.name == 'posix':
+ from test_ssl import zap_servers
+ zap_servers()
+
+ if report_leaks:
+ dump_garbage()
+
+
+if __name__ == '__main__':
+ runall(0)
diff --git a/tests/ca.pem b/tests/ca.pem
new file mode 100644
index 0000000..5450a68
--- /dev/null
+++ b/tests/ca.pem
@@ -0,0 +1,62 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ d1:b6:bf:af:06:17:8c:bd
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=California, O=M2Crypto, CN=Heikki Toivonen
+ Validity
+ Not Before: Jul 28 04:30:50 2009 GMT
+ Not After : Jul 27 04:30:50 2012 GMT
+ Subject: C=US, ST=California, O=M2Crypto, CN=Heikki Toivonen
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:c8:9b:59:18:c2:bf:21:68:dc:d4:62:30:1f:43:
+ 29:52:85:8d:36:fc:20:7f:11:1b:c6:f3:e6:c2:7a:
+ d0:17:0e:6e:78:43:21:e9:e2:df:9f:31:87:e8:7a:
+ 37:88:1f:a4:56:a1:e9:cb:13:7b:1b:c0:28:cf:5a:
+ db:a3:e7:50:6c:c6:55:76:e3:61:e8:73:4b:c2:8c:
+ ee:1c:29:c1:ee:2d:fd:e2:30:34:69:06:ea:d0:af:
+ bd:c5:db:86:70:92:26:0a:33:1b:70:a9:e7:6e:a4:
+ 2e:ee:4a:8a:f3:b2:6c:c9:97:28:39:28:28:3f:c5:
+ 90:4d:4e:83:0a:0e:cd:98:93
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Subject Key Identifier:
+ AD:64:45:74:8F:83:C7:2C:D5:D7:A0:85:91:10:40:9A:9C:96:CF:EE
+ X509v3 Authority Key Identifier:
+ keyid:AD:64:45:74:8F:83:C7:2C:D5:D7:A0:85:91:10:40:9A:9C:96:CF:EE
+ DirName:/C=US/ST=California/O=M2Crypto/CN=Heikki Toivonen
+ serial:D1:B6:BF:AF:06:17:8C:BD
+
+ X509v3 Basic Constraints:
+ CA:TRUE
+ Signature Algorithm: sha1WithRSAEncryption
+ c8:11:af:7d:6d:fb:1c:82:0d:c0:e7:41:f4:b2:a5:b0:69:6d:
+ 18:e3:04:aa:49:e6:4a:69:6d:c3:e3:8b:ab:d1:18:ac:72:ef:
+ 48:9e:49:c7:57:75:2d:00:1e:08:9f:c3:dc:ca:5f:91:38:0d:
+ ac:f8:1f:cc:fc:f7:c2:5b:ce:d7:0c:cf:b2:fe:c9:a9:ce:b8:
+ 07:45:17:1c:cf:b3:07:f9:1f:69:6a:94:03:be:62:62:9c:af:
+ a2:24:25:2d:1f:63:0a:91:6b:bb:e3:6c:ec:20:de:80:d3:04:
+ b4:5e:42:1f:27:bc:1f:79:98:18:ba:fb:8a:34:24:a9:40:1e:
+ b9:7b
+-----BEGIN CERTIFICATE-----
+MIICzjCCAjegAwIBAgIJANG2v68GF4y9MA0GCSqGSIb3DQEBBQUAME8xCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQKEwhNMkNyeXB0bzEY
+MBYGA1UEAxMPSGVpa2tpIFRvaXZvbmVuMB4XDTA5MDcyODA0MzA1MFoXDTEyMDcy
+NzA0MzA1MFowTzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAP
+BgNVBAoTCE0yQ3J5cHRvMRgwFgYDVQQDEw9IZWlra2kgVG9pdm9uZW4wgZ8wDQYJ
+KoZIhvcNAQEBBQADgY0AMIGJAoGBAMibWRjCvyFo3NRiMB9DKVKFjTb8IH8RG8bz
+5sJ60BcObnhDIeni358xh+h6N4gfpFah6csTexvAKM9a26PnUGzGVXbjYehzS8KM
+7hwpwe4t/eIwNGkG6tCvvcXbhnCSJgozG3Cp526kLu5KivOybMmXKDkoKD/FkE1O
+gwoOzZiTAgMBAAGjgbEwga4wHQYDVR0OBBYEFK1kRXSPg8cs1deghZEQQJqcls/u
+MH8GA1UdIwR4MHaAFK1kRXSPg8cs1deghZEQQJqcls/uoVOkUTBPMQswCQYDVQQG
+EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTERMA8GA1UEChMITTJDcnlwdG8xGDAW
+BgNVBAMTD0hlaWtraSBUb2l2b25lboIJANG2v68GF4y9MAwGA1UdEwQFMAMBAf8w
+DQYJKoZIhvcNAQEFBQADgYEAyBGvfW37HIINwOdB9LKlsGltGOMEqknmSmltw+OL
+q9EYrHLvSJ5Jx1d1LQAeCJ/D3MpfkTgNrPgfzPz3wlvO1wzPsv7Jqc64B0UXHM+z
+B/kfaWqUA75iYpyvoiQlLR9jCpFru+Ns7CDegNMEtF5CHye8H3mYGLr7ijQkqUAe
+uXs=
+-----END CERTIFICATE-----
diff --git a/tests/der_encoded_seq.b64 b/tests/der_encoded_seq.b64
new file mode 100644
index 0000000..ece83d7
--- /dev/null
+++ b/tests/der_encoded_seq.b64
@@ -0,0 +1,17 @@
+MIIDwTCCA70wggKloAMCAQICAg5QMA0GCSqGSIb3DQEBBQUAMGkxEzARBgoJkiaJk/IsZAEZFgNv
+cmcxGDAWBgoJkiaJk/IsZAEZFghET0VHcmlkczEgMB4GA1UECxMXQ2VydGlmaWNhdGUgQXV0aG9y
+aXRpZXMxFjAUBgNVBAMTDURPRUdyaWRzIENBIDEwHhcNMDUwMTEyMDA1MTAyWhcNMDYwMTEyMDA1
+MTAyWjBhMRMwEQYKCZImiZPyLGQBGRMDb3JnMRgwFgYKCZImiZPyLGQBGRMIZG9lZ3JpZHMxETAP
+BgNVBAsTCFNlcnZpY2VzMR0wGwYDVQQDExRob3N0L2Jvc3Nob2cubGJsLmdvdjCCASIwDQYJKoZI
+hvcNAQEBBQADggEPADCCAQoCggEBAJ+M2ICaiIkr1YcPzXlOSA/DTzduZPq+ALwbmU+nHatL8wXr
+8SmwMq/aIG9egGnJzsTlofkSA5D7HAX3nLKkU0A+k0Eig2QzZ5iPjrtAr2m9/b/EsF2I58yOcEDT
+EZuHQWCg19YE29n3UwqXB6pgpuMcFfFqLz6NydN9i0T7Md8OGwYpNzK6TsOCtyF+nin5mZleDyXF
+BtkXeIDHmq8sYmsELv1fbAwYzpAZqvz3k0umXSMwnyq+z6WpawybNuLrhRzqMrMZYzq0Db/hS4xr
+TeXOFuRQ/IcgWt0rLmOIRYKrVfLujFcAQnaPDW8llK2kgi6OrPlTo8e8JVEcz5d7bKUCAwEAAaN3
+MHUwEQYJYIZIAYb4QgEBBAQDAgbAMA4GA1UdDwEB/wQEAwIE8DAfBgNVHSMEGDAWgBTKGR0Sjm6k
+OF1C1DEOCNvZjRcNXTAvBgNVHREEKDAmgg9ib3NzaG9nLmxibC5nb3aBE01LUm9kcmlndWV6QGxi
+bC5nb3YwDQYJKoZIhvcNAQEFBQADggEBAKZkj+amwHCSdcMC05NBv1Vck46tGEsAEf9WiNvf3B1N
+O+B0d+egcbXME+LgrxWvOIf4k2DKvORt/oz5BavEbmD1+/NUseBEA01bzjXXgmWwW48CK5cb6Qam
+wq1uMa/asUgyLfpVkgA9ThwHIr2/g4HEIqUZLaqotLS5+EzZKhOuWh9OJh+AR6WCxOjTQw1xCJd0
+T/sSlHdtlH47xBkDL5DKe0pn9CgGY+ihehcRpzXMB8tKgxzsE8NuB49IUrVC+wIgzEXEeaSPFenL
+XdWxAKaFWRXyPHrsrtsenxnRkM6y8qxWN6UV5UqpvdE/cy+5QORbzWtfVPYqtNVZoO+N3rc= \ No newline at end of file
diff --git a/tests/dhparams.pem b/tests/dhparams.pem
new file mode 100644
index 0000000..edb320a
--- /dev/null
+++ b/tests/dhparams.pem
@@ -0,0 +1,5 @@
+-----BEGIN DH PARAMETERS-----
+MIGHAoGBAIgAcu3gpJeO8aS6N+sTMa655BMzBNlK66q62VH9RqmHwFJSjjCs3ZsF
+aVsTkO3Mt0gULn1drXsK6Lc6pA8s0eQN8ggyEPVr6ND0jN2jr2qc1XlqD7jxzdxe
+igB66pLvrWvAulrGxg4QMsQjqcpwZZ2ndRpYSmErIi4M1r19nTgjAgEC
+-----END DH PARAMETERS-----
diff --git a/tests/dsa.param.pem b/tests/dsa.param.pem
new file mode 100644
index 0000000..6300c4f
--- /dev/null
+++ b/tests/dsa.param.pem
@@ -0,0 +1,9 @@
+-----BEGIN DSA PARAMETERS-----
+MIIBHgKBgQDqYu3smvs8KHBx4XX8otDHQUdtCyWKIRC52eQUSYeWejvdaqm11rIq
+QMFBJDnvicM4avyOIU2d/ZOfLzN7aXFw3Ep67amsLpj82N+n4lASUJwKdOJyzyLG
+9IS41RRek8B3lRAs/zqk1U6P5EaE/uIG4+avYmkSDpB4kmnRGhTYeQIVALKQpdTy
+uat9aoq9mFm8dVA2VxAhAoGAausAkdN4Hj6S5nfPMjJTnwV4u7hVY6b6eAZTJmxs
+ykr3XlKqM3PS0hKhjatp7f+mRNFxYvGrWPLAwnPOIp/iNn7lM7U41ceUj1O3KrD/
+Cg4LQ/bADvY48eytdjMLZDpL3Jwootfs4i8WIb0RnIxg8nClhkY2K2/+4voft+Xi
+DlA=
+-----END DSA PARAMETERS-----
diff --git a/tests/dsa.priv.pem b/tests/dsa.priv.pem
new file mode 100644
index 0000000..cd5578c
--- /dev/null
+++ b/tests/dsa.priv.pem
@@ -0,0 +1,9 @@
+-----BEGIN PRIVATE KEY-----
+MIIBSgIBADCCASsGByqGSM44BAEwggEeAoGBAOpi7eya+zwocHHhdfyi0MdBR20L
+JYohELnZ5BRJh5Z6O91qqbXWsipAwUEkOe+Jwzhq/I4hTZ39k58vM3tpcXDcSnrt
+qawumPzY36fiUBJQnAp04nLPIsb0hLjVFF6TwHeVECz/OqTVTo/kRoT+4gbj5q9i
+aRIOkHiSadEaFNh5AhUAspCl1PK5q31qir2YWbx1UDZXECECgYBq6wCR03gePpLm
+d88yMlOfBXi7uFVjpvp4BlMmbGzKSvdeUqozc9LSEqGNq2nt/6ZE0XFi8atY8sDC
+c84in+I2fuUztTjVx5SPU7cqsP8KDgtD9sAO9jjx7K12MwtkOkvcnCii1+ziLxYh
+vRGcjGDycKWGRjYrb/7i+h+35eIOUAQWAhQ/vK8oLKHdEu7W77fbZ3+jQvvt6Q==
+-----END PRIVATE KEY-----
diff --git a/tests/dsa.pub.pem b/tests/dsa.pub.pem
new file mode 100644
index 0000000..34c8d45
--- /dev/null
+++ b/tests/dsa.pub.pem
@@ -0,0 +1,12 @@
+-----BEGIN PUBLIC KEY-----
+MIIBtzCCASsGByqGSM44BAEwggEeAoGBAOpi7eya+zwocHHhdfyi0MdBR20LJYoh
+ELnZ5BRJh5Z6O91qqbXWsipAwUEkOe+Jwzhq/I4hTZ39k58vM3tpcXDcSnrtqawu
+mPzY36fiUBJQnAp04nLPIsb0hLjVFF6TwHeVECz/OqTVTo/kRoT+4gbj5q9iaRIO
+kHiSadEaFNh5AhUAspCl1PK5q31qir2YWbx1UDZXECECgYBq6wCR03gePpLmd88y
+MlOfBXi7uFVjpvp4BlMmbGzKSvdeUqozc9LSEqGNq2nt/6ZE0XFi8atY8sDCc84i
+n+I2fuUztTjVx5SPU7cqsP8KDgtD9sAO9jjx7K12MwtkOkvcnCii1+ziLxYhvRGc
+jGDycKWGRjYrb/7i+h+35eIOUAOBhQACgYEAmYO4Qss7pYq3vAEj5qj35A1gsOeC
+rGxvu8w9Dj8jACuz0IHOTq+vFbnB6p30rdloMU7Ci4NHPWqOVmWCFcXxkFgJMJld
+sBbkdbGnhPI80sWJGouUYofHFG4pK0QoMeDZKgg7OgwH8EnmQM+W9KDZYJRSuU4+
+6F6hQkmkwAKAXkU=
+-----END PUBLIC KEY-----
diff --git a/tests/ec.priv.pem b/tests/ec.priv.pem
new file mode 100644
index 0000000..2147d67
--- /dev/null
+++ b/tests/ec.priv.pem
@@ -0,0 +1,5 @@
+-----BEGIN EC PRIVATE KEY-----
+MG0CAQEEHXXhxMbflWHSfCjfxsqHTsIR+BVbREI6JFYGaUs0oAcGBSuBBAAaoUAD
+PgAEAdJXSN/xnRiDqc4wSiYbWB7LGabs71Y9zzIE1ZbzAcvb7uxtoyUxrmRQC8xD
+EO2qZX16mtpmgoNz3EeT
+-----END EC PRIVATE KEY-----
diff --git a/tests/ec.pub.pem b/tests/ec.pub.pem
new file mode 100644
index 0000000..4a08b59
--- /dev/null
+++ b/tests/ec.pub.pem
@@ -0,0 +1,4 @@
+-----BEGIN PUBLIC KEY-----
+MFIwEAYHKoZIzj0CAQYFK4EEABoDPgAEAdJXSN/xnRiDqc4wSiYbWB7LGabs71Y9
+zzIE1ZbzAcvb7uxtoyUxrmRQC8xDEO2qZX16mtpmgoNz3EeT
+-----END PUBLIC KEY-----
diff --git a/tests/fips.py b/tests/fips.py
new file mode 100644
index 0000000..32eb75b
--- /dev/null
+++ b/tests/fips.py
@@ -0,0 +1,8 @@
+try:
+ f = open('/proc/sys/crypto/fips_enabled')
+ try:
+ fips_mode = int(f.read())
+ finally:
+ f.close()
+except Exception, e:
+ fips_mode = 0
diff --git a/tests/long_serial_cert.pem b/tests/long_serial_cert.pem
new file mode 100644
index 0000000..20ccac4
--- /dev/null
+++ b/tests/long_serial_cert.pem
@@ -0,0 +1,17 @@
+-----BEGIN CERTIFICATE-----
+MIICsDCCAhmgAwIBAgIJAPR7mEmILgX6MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMDkwNjE5MTkzNjIyWhcNMDkwNzE5MTkzNjIyWjBF
+MQswCQYDVQQGEwJVUzETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
+gQDRb/jlXLidgXQGsOLoFbh4JAvC+BgufR7jn93KPybI0oo8VXFUqr2eFuLDcPiE
+gpWIMrLwq9f0US/M/yXQdsH08L2xe+aaiNl+j+o4VsPhXfnvFvAtFRs+JqCR3VfI
+vVePwov31+/28PmF1kOxr9SmSzvSPnN3SqSC0GDAmhWNYwIDAQABo4GnMIGkMB0G
+A1UdDgQWBBR3SQBG5X/vH18obsb2aaBxhU/+HjB1BgNVHSMEbjBsgBR3SQBG5X/v
+H18obsb2aaBxhU/+HqFJpEcwRTELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUt
+U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAPR7mEmI
+LgX6MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAoyvUPgjkwPAgiqiU
+4cD1tZdcxIzkk22U02cTjJtAPnIYwqwwDpFltApvRbp8MNiPqXbBt0tchj/Ovu4D
+JiDTMaizVOZ+XmiC68uNTZ4nvEwHvVdKmudNVTZDdFr/a6BErAeTknlMCihN3v6M
+POx8a1iz8Y/wJ8YA74vMPORKlKc=
+-----END CERTIFICATE-----
diff --git a/tests/pubring.pgp b/tests/pubring.pgp
new file mode 100644
index 0000000..fbe4863
--- /dev/null
+++ b/tests/pubring.pgp
Binary files differ
diff --git a/tests/recipient.pem b/tests/recipient.pem
new file mode 100644
index 0000000..d766266
--- /dev/null
+++ b/tests/recipient.pem
@@ -0,0 +1,61 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ d1:b6:bf:af:06:17:8c:c1
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=California, O=M2Crypto, CN=Heikki Toivonen
+ Validity
+ Not Before: Jul 28 04:39:19 2009 GMT
+ Not After : Jul 26 04:39:19 2019 GMT
+ Subject: C=US, ST=California, O=M2Crypto, CN=Recipient/emailAddress=recipient@example.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:c2:21:a3:4f:64:59:9c:21:39:21:d2:3c:e7:0a:
+ 60:72:c8:39:b3:c3:27:4a:6d:56:8f:a0:5d:1b:c6:
+ e4:3e:26:61:09:a9:ae:04:83:69:3f:9d:2b:12:7e:
+ d4:f7:8e:d0:6e:a9:8c:9b:d1:bf:17:0c:bd:d0:73:
+ 99:02:6e:7e:cb:7a:80:2d:cf:b1:29:c0:30:36:3f:
+ 68:12:3e:4e:bf:f9:8b:3d:1d:56:af:24:94:ae:d5:
+ 59:b4:00:50:0c:c0:2b:59:c3:99:b3:8a:19:f1:86:
+ 14:bd:ee:e9:c4:f1:d7:6a:0c:e9:67:8a:94:9a:2d:
+ 2d:60:25:22:c6:72:68:c2:0d
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ 11:CB:60:AC:55:85:52:84:C5:C8:20:5A:50:13:D0:89:C7:7A:B7:81
+ X509v3 Authority Key Identifier:
+ keyid:AD:64:45:74:8F:83:C7:2C:D5:D7:A0:85:91:10:40:9A:9C:96:CF:EE
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 87:56:17:6d:ba:3b:a6:c4:22:af:20:f1:a0:e5:9d:27:c4:50:
+ bd:79:eb:d2:84:e5:9a:00:5f:5d:5a:c3:34:58:77:f5:a9:00:
+ f9:76:e9:2d:89:b4:3f:9d:e3:cf:15:0c:64:1b:0a:03:db:e4:
+ 6f:2b:ff:1c:82:89:1a:0f:7e:83:58:0f:e6:da:af:26:97:49:
+ 4a:59:d7:61:3f:4b:ed:1d:5b:51:00:3b:83:96:c7:1e:3d:84:
+ f4:91:1f:70:69:12:b9:a7:2c:5b:1b:05:cd:74:90:2b:a0:ba:
+ e7:70:cd:6b:7d:ac:be:d7:92:50:e9:f5:c0:42:29:04:ef:8f:
+ a1:68
+-----BEGIN CERTIFICATE-----
+MIICtzCCAiCgAwIBAgIJANG2v68GF4zBMA0GCSqGSIb3DQEBBQUAME8xCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQKEwhNMkNyeXB0bzEY
+MBYGA1UEAxMPSGVpa2tpIFRvaXZvbmVuMB4XDTA5MDcyODA0MzkxOVoXDTE5MDcy
+NjA0MzkxOVowbzELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAP
+BgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlSZWNpcGllbnQxJDAiBgkqhkiG9w0B
+CQEWFXJlY2lwaWVudEBleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
+gYkCgYEAwiGjT2RZnCE5IdI85wpgcsg5s8MnSm1Wj6BdG8bkPiZhCamuBINpP50r
+En7U947QbqmMm9G/Fwy90HOZAm5+y3qALc+xKcAwNj9oEj5Ov/mLPR1WrySUrtVZ
+tABQDMArWcOZs4oZ8YYUve7pxPHXagzpZ4qUmi0tYCUixnJowg0CAwEAAaN7MHkw
+CQYDVR0TBAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2Vy
+dGlmaWNhdGUwHQYDVR0OBBYEFBHLYKxVhVKExcggWlAT0InHereBMB8GA1UdIwQY
+MBaAFK1kRXSPg8cs1deghZEQQJqcls/uMA0GCSqGSIb3DQEBBQUAA4GBAIdWF226
+O6bEIq8g8aDlnSfEUL1569KE5ZoAX11awzRYd/WpAPl26S2JtD+d488VDGQbCgPb
+5G8r/xyCiRoPfoNYD+baryaXSUpZ12E/S+0dW1EAO4OWxx49hPSRH3BpErmnLFsb
+Bc10kCuguudwzWt9rL7XklDp9cBCKQTvj6Fo
+-----END CERTIFICATE-----
diff --git a/tests/recipient_key.pem b/tests/recipient_key.pem
new file mode 100644
index 0000000..5bf0f70
--- /dev/null
+++ b/tests/recipient_key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDCIaNPZFmcITkh0jznCmByyDmzwydKbVaPoF0bxuQ+JmEJqa4E
+g2k/nSsSftT3jtBuqYyb0b8XDL3Qc5kCbn7LeoAtz7EpwDA2P2gSPk6/+Ys9HVav
+JJSu1Vm0AFAMwCtZw5mzihnxhhS97unE8ddqDOlnipSaLS1gJSLGcmjCDQIDAQAB
+AoGAZlrJ+kAUpyc1Mkng5ogoFhzPn6ITg0Bm1U9eCBkzmjkuDKQ0JhkLUwkQ/q10
+qBnad55ZjoZmVEbZhaCNWiTcIIy0nKAMWNKRcg3vTgrnbmbjco1HECDStfJKogZl
+7egoIImHnU1f/IeKQDUYUfs/INonmnnZ1d2jrU7QsdTz84ECQQDzhT0UwP8S1oma
+0IBgeUOt5ptZs7nFdZnbIKCd+ADra6NiQznokCHe5K0WZHqPKvN9asKx1u0h+97H
+Wmk6Fw7RAkEAzBR1+mTRSrlJT8/NTCsIDPtCK/+OhmGbNy1pfsOWq1lN58Za5HV7
+fmtaH2No+MP+DlfNigsg557GzAYl2ZumfQJAHQj33W+dehuGUKUniVksDqH+R9W8
+AqUg8RWU0QDu6yLsWhz13JrCzxao5JCaZFOUsJF4IUglAfZL+6z1+u0g4QJAH5aL
+LFaujoJfdpsTi9adSGUbuPO1e9dfzwqYaaaci6knBdkN+I62rrqvGGyqstajXFT6
+24MddLx+yNWqxiPxgQJBAKF8YiR4eLqLSnq4ftqCqVCC1XbA2H9b7G5RBWi00WFq
+3Nx+B/wjLzbqsMamTCIDUCEW+MzFx6otCxduDZRMKH8=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/rsa.priv.pem b/tests/rsa.priv.pem
new file mode 100644
index 0000000..0b8b163
--- /dev/null
+++ b/tests/rsa.priv.pem
@@ -0,0 +1,16 @@
+-----BEGIN PRIVATE KEY-----
+MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAM1lIRXaaLVgzlvW
+F2S6OMFJsfG+coZLx9qzmNb2gK6qjyGa71HeaLvFmQFv60dPjpuaGPs2uhL88hcN
+JAChGiD8LxNpVW0EEw+RRH6/CBlDGuKjkSaPz8zzpEhSZq/yGb0F4zaau1HINnwo
+rYPyRXWyRUzfpEB/7mx8/FUD24knAgMBAAECgYAaInsSP8dBBP9c+iHh5DwihBEL
+VJNX+T6F2oJhH96B2xv5R7CZ9zXWZq8wWqBSY5IexH3XQUBt+BeJzVc+aUFcpKLM
+D1O3OZ8NwC9HGIY0sLeX+uawYdFAPJfF8BZ8x3LMxWA8jdJM+4/P3C3jh2EvyzLT
+HQ1rXBPrLkH45xJQSQJBAPPfSiObRvbeJvkgE0z5SqdbQjTGxeAX5qt1e2LtTwdU
+gAxpYnYPz9CFCcIpa0j9UejlGninXasQvhpdwytBLk0CQQDXm/2kKh9BuWzemIu/
+QcLSgc7VrGgZnGQ27fp3bXDSOV3Bev9VywLQ5VDBJcRSkMTC0V/+iHZbMl9EpwHN
+8ZdDAkBJHtAZ8PrMFjvVQnrG/5AUsdYeAONfl4sAKc9/D+w8JGfoUMjG4WLMALe2
+UbjrP5kJnXfcaUI6gmCdgzN7iqWZAkAvJbpKOrfJDH4lEuCEOyIaHC6ZhPDioNM9
+O77ofLMOFWNOGtJY9WKxQWPuSI7sqyGLpHNEWpzfBl3UylxXp3u3AkEAzCzGUMfM
+0qw/TFlVLzCHhrkP13SrJohdD693w9nuhYM2u27R32qJlF1OvN9NxEV7ZoOSGJxi
+CGTjWcXUGgYQgQ==
+-----END PRIVATE KEY-----
diff --git a/tests/rsa.priv2.pem b/tests/rsa.priv2.pem
new file mode 100644
index 0000000..013ab5e
--- /dev/null
+++ b/tests/rsa.priv2.pem
@@ -0,0 +1,17 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIICxjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQIq+j6kBSOkTkCAggA
+MBQGCCqGSIb3DQMHBAin1qbPaI3dAQSCAoAwb9BTWY6+o9GAZk9ZUJHAHL0Yb7C/
+Hkm8Kh+YBqIEHbTzzSzIO3pFFnLrLLSVbWuYX3bBJRDSUfmV9JaZu0YYJ/TzBtb5
+epgD+sZ83E11NM0L3rJTI9GOUm8b9U15N94X+gnQj0JSK8Ex0dJpJ3rwHPd1zAOe
+0SjXViOCCuHeu4Mnz3P9B42FR5C/53GLkqtSZCsznSBsbPGZ/mb6eEGjgYtxFm15
+17Px7ezDjjr5knBozYua3OehCfI6lN1W+yyTvHGF4lpWkm7Pj24uHHh6yagFQuvB
+RgE8eFLLPLBBa3kHWTn6hAPL4pfPIaPiDtX69IshSv2LVcbUPp6pTkji7mo3EFpN
+Jigd3msMCf6w5Wh4I2k8Hb6eSkfsModIru05xq0fuTYi1nTh2l/M3FEGeOuBmpbD
+AYzpT6J1+373rshkdqmv1C/REsnnrACGwbM7JN6K3sKnJZesI3iiHY5tnumypyv3
+f7wMaRcIq0QOi/WUIKzU0B4f9WxgjDuFwWyYlEBl2IYZ8wxD0P2s968puc7RRwrc
+11Tn0a993122gBAHaa24iAW2ig2hGktLtxY1EvY6Sfd/migu2iVA6bwdVz68kKBj
+tYfJQEoMGJhR+NqSDYvgJYgoNljOIf6Wq++L9/zqgtYkiL7xRLqSvths2NWaxGmc
+RvjWFeq2sTiVXFn36jzO9YfJ4BFqgt5UoBRSw8jYQwm+W5TUhgWGQxQTTrCUs/36
+5oQXOwpRol+ivO/VtMdDShg6sKHEjQ/FhHqNpPccVLg/g81HbJyfmEmeqYu6rtOd
+xBe9lVFW+86wObsYl1WCHYUQuBUlPv+uEDLqC92/6zLdCtDYYRYvdLF8
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/tests/rsa.pub.pem b/tests/rsa.pub.pem
new file mode 100644
index 0000000..e4f13e0
--- /dev/null
+++ b/tests/rsa.pub.pem
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDNZSEV2mi1YM5b1hdkujjBSbHx
+vnKGS8fas5jW9oCuqo8hmu9R3mi7xZkBb+tHT46bmhj7NroS/PIXDSQAoRog/C8T
+aVVtBBMPkUR+vwgZQxrio5Emj8/M86RIUmav8hm9BeM2mrtRyDZ8KK2D8kV1skVM
+36RAf+5sfPxVA9uJJwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/tests/server.pem b/tests/server.pem
new file mode 100644
index 0000000..825abc6
--- /dev/null
+++ b/tests/server.pem
@@ -0,0 +1,75 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ d1:b6:bf:af:06:17:8c:be
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=California, O=M2Crypto, CN=Heikki Toivonen
+ Validity
+ Not Before: Jul 28 04:31:41 2009 GMT
+ Not After : Jul 26 04:31:41 2019 GMT
+ Subject: C=US, ST=California, O=M2Crypto, CN=localhost
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:d4:99:6f:33:3f:e6:ac:0a:34:d8:0e:45:97:f3:
+ 2b:6a:50:2a:84:30:0a:52:9c:15:30:9f:05:29:3a:
+ 21:f4:c1:c3:01:9e:2f:55:56:4e:35:ac:f1:16:1e:
+ 26:8d:b5:26:b7:99:78:92:ea:1c:74:46:ab:41:12:
+ ef:cc:53:62:cc:59:5c:9e:c4:86:df:d9:25:35:55:
+ 05:4b:16:ff:d9:90:e3:f4:51:b4:b4:fa:c5:98:4b:
+ 60:f0:60:7f:14:4e:1e:dd:61:9b:22:a2:9c:21:17:
+ 43:a3:cb:07:80:f5:75:59:9c:55:1c:fe:e0:66:d4:
+ 70:77:5e:13:06:0c:05:c7:1f
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ 04:05:3D:6A:A7:E8:D7:52:BD:2F:C4:52:30:7C:2C:BD:D3:81:46:C6
+ X509v3 Authority Key Identifier:
+ keyid:AD:64:45:74:8F:83:C7:2C:D5:D7:A0:85:91:10:40:9A:9C:96:CF:EE
+
+ Signature Algorithm: sha1WithRSAEncryption
+ ac:2b:ad:86:36:96:5c:fb:34:2c:02:ca:d9:5f:a7:8e:b6:58:
+ 24:1d:27:b6:8e:81:aa:69:0e:60:26:64:2e:72:a1:ff:d8:ba:
+ bb:7e:5d:46:c7:07:2d:a8:c8:4c:df:1e:ba:c8:bc:21:5b:f2:
+ b3:01:4c:d6:3b:10:fd:49:70:e6:83:01:f3:24:e2:a9:97:d7:
+ c3:9c:5b:2d:d7:64:2b:e5:e2:0e:3e:d9:8c:e6:93:86:39:32:
+ 50:43:5f:36:4a:3b:b0:05:e7:65:a3:b3:ef:50:56:7f:7e:dc:
+ f0:65:83:ac:42:7e:97:a0:c0:7e:63:c6:c8:c6:35:d3:60:d1:
+ 4f:51
+-----BEGIN CERTIFICATE-----
+MIICkTCCAfqgAwIBAgIJANG2v68GF4y+MA0GCSqGSIb3DQEBBQUAME8xCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQKEwhNMkNyeXB0bzEY
+MBYGA1UEAxMPSGVpa2tpIFRvaXZvbmVuMB4XDTA5MDcyODA0MzE0MVoXDTE5MDcy
+NjA0MzE0MVowSTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAP
+BgNVBAoTCE0yQ3J5cHRvMRIwEAYDVQQDEwlsb2NhbGhvc3QwgZ8wDQYJKoZIhvcN
+AQEBBQADgY0AMIGJAoGBANSZbzM/5qwKNNgORZfzK2pQKoQwClKcFTCfBSk6IfTB
+wwGeL1VWTjWs8RYeJo21JreZeJLqHHRGq0ES78xTYsxZXJ7Eht/ZJTVVBUsW/9mQ
+4/RRtLT6xZhLYPBgfxROHt1hmyKinCEXQ6PLB4D1dVmcVRz+4GbUcHdeEwYMBccf
+AgMBAAGjezB5MAkGA1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2Vu
+ZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBQEBT1qp+jXUr0vxFIwfCy904FG
+xjAfBgNVHSMEGDAWgBStZEV0j4PHLNXXoIWREECanJbP7jANBgkqhkiG9w0BAQUF
+AAOBgQCsK62GNpZc+zQsAsrZX6eOtlgkHSe2joGqaQ5gJmQucqH/2Lq7fl1Gxwct
+qMhM3x66yLwhW/KzAUzWOxD9SXDmgwHzJOKpl9fDnFst12Qr5eIOPtmM5pOGOTJQ
+Q182SjuwBedlo7PvUFZ/ftzwZYOsQn6XoMB+Y8bIxjXTYNFPUQ==
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDUmW8zP+asCjTYDkWX8ytqUCqEMApSnBUwnwUpOiH0wcMBni9V
+Vk41rPEWHiaNtSa3mXiS6hx0RqtBEu/MU2LMWVyexIbf2SU1VQVLFv/ZkOP0UbS0
++sWYS2DwYH8UTh7dYZsiopwhF0OjyweA9XVZnFUc/uBm1HB3XhMGDAXHHwIDAQAB
+AoGBALBHrSm8kYMTT2/anZ/5tIUJhcdnohePbg6LvJbLqf4tb4l25V6IGn9tL9Yc
+F/GmRD02VwDSd9d+BWAG2Kj+d0rfdCLfKY9O8PVVm0DF6grLZ7ugItYqUHRDYOdV
+MOVOQrx+mCIzHtoEtQ6HLqmqt2rIX731L1TA7OLNm3XHyISJAkEA/mgNNNg0e23G
+64z83yxxwPEnBrnKd1+xjH9QJ0Z9SJJuF4sNXRIFA4YUNvv2MNe3gMS4Hg9w78HL
+PwcEzLnO9QJBANXuWAZGV58CdkM2w7H9+ukxMbQeLSnmgjpdddo31qqbfgFAYZMK
+LppRqyosj+a2qQ6vua0ndstTImSi7KPmCUMCQQDbwr5Fu836ISYIK830aswIw0fX
+A37mB3+zwfZXNwjaO8NmCvQMRZiXJqcnqBdOsckOLuBs9yGzuk/7rfBzeL5RAkA2
+uBcly7o/vsZ3HLvjfB5ApUecVZehvwcSXLN3VI8A5nLNaSVMEe+nozoPuIQ6NAB7
+9DCe/JgjG6mRaibzKTS3AkEAjTl5MTKkYR78+2u3NRU/ypa1iKCicSvI/Ryw7p/z
+Q8XmVA0CmNRvltf9gA1gJ04ZijBPtl+s09uppaCw9L3vuA==
+-----END RSA PRIVATE KEY-----
diff --git a/tests/signer.pem b/tests/signer.pem
new file mode 100644
index 0000000..816f5fa
--- /dev/null
+++ b/tests/signer.pem
@@ -0,0 +1,61 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ d1:b6:bf:af:06:17:8c:c0
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=California, O=M2Crypto, CN=Heikki Toivonen
+ Validity
+ Not Before: Jul 28 04:37:25 2009 GMT
+ Not After : Jul 26 04:37:25 2019 GMT
+ Subject: C=US, ST=California, O=M2Crypto, CN=Signer/emailAddress=signer@example.com
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:c3:9c:76:f3:21:aa:10:19:9f:77:e3:82:1d:9d:
+ c3:4a:da:bc:c3:83:71:d1:89:78:8b:82:a4:b9:c5:
+ 70:bb:e3:00:bf:49:b8:99:96:67:0b:bf:fe:72:cb:
+ d9:b6:63:85:f4:fb:86:55:32:22:1e:6e:ce:fd:88:
+ 5c:75:9d:77:3c:92:17:c5:b2:70:04:59:02:33:ef:
+ be:33:26:f1:e4:72:41:45:72:f1:bf:c4:21:b1:fe:
+ de:92:b9:f3:25:3e:1a:15:4b:26:47:29:cc:38:7f:
+ 58:3b:ae:b7:c5:69:e7:48:81:b6:55:61:45:c3:3f:
+ b6:9d:06:e5:17:41:f6:f2:e9
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ 22:CA:29:B7:D7:39:B4:BF:35:F9:36:5E:EE:2B:E4:17:4E:F9:6E:EE
+ X509v3 Authority Key Identifier:
+ keyid:AD:64:45:74:8F:83:C7:2C:D5:D7:A0:85:91:10:40:9A:9C:96:CF:EE
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 5f:a0:da:6b:37:b4:bb:25:34:a7:ed:f3:f7:2e:f2:85:aa:91:
+ 01:8f:c3:80:e5:44:87:df:9e:64:5e:5f:3e:5c:7f:c1:07:12:
+ 2a:46:cc:bb:9f:a4:a5:c8:3f:84:9a:a4:9e:d5:26:33:af:b4:
+ 5f:eb:8e:7d:81:65:f6:44:18:78:89:17:74:fb:07:dc:04:65:
+ fa:15:0c:b2:f3:e7:e7:af:1f:d9:02:c4:c4:44:b7:95:91:47:
+ fe:c0:2a:e1:7a:ae:dd:5f:f8:a9:fa:bb:dd:89:2d:0b:05:b6:
+ ce:ba:12:37:7f:97:4c:48:a9:fb:d4:b7:a5:d1:61:f6:85:ea:
+ 30:8c
+-----BEGIN CERTIFICATE-----
+MIICsTCCAhqgAwIBAgIJANG2v68GF4zAMA0GCSqGSIb3DQEBBQUAME8xCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQKEwhNMkNyeXB0bzEY
+MBYGA1UEAxMPSGVpa2tpIFRvaXZvbmVuMB4XDTA5MDcyODA0MzcyNVoXDTE5MDcy
+NjA0MzcyNVowaTELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAP
+BgNVBAoTCE0yQ3J5cHRvMQ8wDQYDVQQDEwZTaWduZXIxITAfBgkqhkiG9w0BCQEW
+EnNpZ25lckBleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA
+w5x28yGqEBmfd+OCHZ3DStq8w4Nx0Yl4i4KkucVwu+MAv0m4mZZnC7/+csvZtmOF
+9PuGVTIiHm7O/YhcdZ13PJIXxbJwBFkCM+++Mybx5HJBRXLxv8Qhsf7ekrnzJT4a
+FUsmRynMOH9YO663xWnnSIG2VWFFwz+2nQblF0H28ukCAwEAAaN7MHkwCQYDVR0T
+BAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNh
+dGUwHQYDVR0OBBYEFCLKKbfXObS/Nfk2Xu4r5BdO+W7uMB8GA1UdIwQYMBaAFK1k
+RXSPg8cs1deghZEQQJqcls/uMA0GCSqGSIb3DQEBBQUAA4GBAF+g2ms3tLslNKft
+8/cu8oWqkQGPw4DlRIffnmReXz5cf8EHEipGzLufpKXIP4SapJ7VJjOvtF/rjn2B
+ZfZEGHiJF3T7B9wEZfoVDLLz5+evH9kCxMREt5WRR/7AKuF6rt1f+Kn6u92JLQsF
+ts66Ejd/l0xIqfvUt6XRYfaF6jCM
+-----END CERTIFICATE-----
diff --git a/tests/signer_key.pem b/tests/signer_key.pem
new file mode 100644
index 0000000..39abda1
--- /dev/null
+++ b/tests/signer_key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQDDnHbzIaoQGZ9344IdncNK2rzDg3HRiXiLgqS5xXC74wC/SbiZ
+lmcLv/5yy9m2Y4X0+4ZVMiIebs79iFx1nXc8khfFsnAEWQIz774zJvHkckFFcvG/
+xCGx/t6SufMlPhoVSyZHKcw4f1g7rrfFaedIgbZVYUXDP7adBuUXQfby6QIDAQAB
+AoGAZL24JQ85XoFTt5Lb+BS/91Uf0jFn9Nov0um9nE8q+Bi40ctN3wuulkaS7Nw/
+i8dFvh2r2USwfavjvn7z3z7xoMG8V2c1ZFJCI2CKjocuWVkGwNnIsbO7/BOG03nu
+vir/i7TXN0YbN8zMhfuFC9APmR8bdmMa2KgHXzQcLuAmI4ECQQDhDIkC97l6rMKG
+QWbYrbc7GoMZNwCsPb/fasUknGmtPmq+s818i335u1yyhAk5pwKV7HF+WyZ76S2A
+P1bZf9+FAkEA3oN98qoklVmWSK0qV+CKHjZHSqtt32q2eu6+eAO5fVZOWHwXhS/B
+MkTtfKJbIDTLyUnwhKyht/hXOniVqHE5FQJAf99VgoArvc6oAQzsWTXrpQOddhhQ
+o426lkHenrzZNvz+PjmACsJf5CRXuX9Ylo+U4ockvb0hEssddX+H47HK2QJBAIYr
+aV1SJH79pvWpnLeiSAYRmok2tyiZMvELVkQNkuI1kUYfhRslAWxrTXvyddoEm8CC
+2glWAqlokEhMf4kyxEUCQCIQbV+XFoEqkECchik34PPmcPi2ends32dv/sW+AKjQ
+pxKpWbxVB4sEOPZzpmujP0LLxvCY4HOUJDlhENGQ8MM=
+-----END RSA PRIVATE KEY-----
diff --git a/tests/test_asn1.py b/tests/test_asn1.py
new file mode 100644
index 0000000..30ef759
--- /dev/null
+++ b/tests/test_asn1.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.ASN1.
+
+Copyright (c) 2005 Open Source Applications Foundation. All rights reserved."""
+
+import unittest, time, datetime
+from M2Crypto import ASN1, m2
+
+class ASN1TestCase(unittest.TestCase):
+
+ def test_Integer(self):
+ pass # XXX Dunno how to test
+
+ def test_BitSTring(self):
+ pass # XXX Dunno how to test
+
+ def test_String(self):
+ asn1ptr = m2.asn1_string_new()
+ text = 'hello there'
+ # In RFC2253 format:
+ # #040B68656C6C6F207468657265
+ # h e l l o t h e r e
+ m2.asn1_string_set(asn1ptr, text)
+ a = ASN1.ASN1_String(asn1ptr, 1)
+ assert a.as_text() == 'hello there', a.as_text()
+ assert a.as_text(flags=m2.ASN1_STRFLGS_RFC2253) == '#040B68656C6C6F207468657265', a.as_text(flags=m2.ASN1_STRFLGS_RFC2253)
+ self.assertEqual(a.as_text(), str(a))
+
+ def test_Object(self):
+ pass # XXX Dunno how to test
+
+ def test_UTCTIME(self):
+ asn1 = ASN1.ASN1_UTCTIME()
+ assert str(asn1) == 'Bad time value'
+
+ format = '%b %d %H:%M:%S %Y GMT'
+ utcformat = '%y%m%d%H%M%SZ'
+
+ s = '990807053011Z'
+ asn1.set_string(s)
+ #assert str(asn1) == 'Aug 7 05:30:11 1999 GMT'
+ t1 = time.strptime(str(asn1), format)
+ t2 = time.strptime(s, utcformat)
+ self.assertEqual(t1, t2)
+
+ asn1.set_time(500)
+ #assert str(asn1) == 'Jan 1 00:08:20 1970 GMT'
+ t1 = time.strftime(format, time.strptime(str(asn1), format))
+ t2 = time.strftime(format, time.gmtime(500))
+ self.assertEqual(t1, t2)
+
+ t = long(time.time()) + time.timezone
+ asn1.set_time(t)
+ t1 = time.strftime(format, time.strptime(str(asn1), format))
+ t2 = time.strftime(format, time.gmtime(t))
+ self.assertEqual(t1, t2)
+
+ def test_UTCTIME_datetime(self):
+ asn1 = ASN1.ASN1_UTCTIME()
+ # Test get_datetime and set_datetime
+ t = time.time()
+ dt = datetime.datetime.fromtimestamp(int(t))
+ udt = dt.replace(tzinfo=ASN1.LocalTimezone()).astimezone(ASN1.UTC)
+ asn1.set_time(int(t))
+ t1 = str(asn1)
+ asn1.set_datetime(dt)
+ t2 = str(asn1)
+ self.assertEqual(t1, t2)
+ self.assertEqual(str(udt), str(asn1.get_datetime()))
+
+ dt = dt.replace(tzinfo=ASN1.LocalTimezone())
+ asn1.set_datetime(dt)
+ t2 = str(asn1)
+ self.assertEqual(t1, t2)
+ self.assertEqual(str(udt), str(asn1.get_datetime()))
+
+ dt = dt.astimezone(ASN1.UTC)
+ asn1.set_datetime(dt)
+ t2 = str(asn1)
+ self.assertEqual(t1, t2)
+ self.assertEqual(str(udt), str(asn1.get_datetime()))
+
+
+def suite():
+ return unittest.makeSuite(ASN1TestCase)
+
+
+if __name__ == '__main__':
+ unittest.TextTestRunner().run(suite())
diff --git a/tests/test_authcookie.py b/tests/test_authcookie.py
new file mode 100644
index 0000000..9ce0eb8
--- /dev/null
+++ b/tests/test_authcookie.py
@@ -0,0 +1,147 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.AuthCookie.
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+import Cookie, binascii, time, unittest, sys
+from M2Crypto.AuthCookie import AuthCookie, AuthCookieJar, mix, unmix, unmix3
+from M2Crypto import Rand, EVP
+
+class AuthCookieTestCase(unittest.TestCase):
+
+ _format = 'Set-Cookie: _M2AUTH_="exp=%s&data=%s&digest=%s"'
+ if sys.version_info < (2,5):
+ _format += ';'
+ _token = '_M2AUTH_'
+
+ def setUp(self):
+ self.data = 'cogitoergosum'
+ self.exp = time.time() + 3600
+ self.jar = AuthCookieJar()
+
+ def tearDown(self):
+ pass
+
+ def test_mix_unmix(self):
+ dough = mix(self.exp, self.data)
+ exp, data = unmix(dough)
+ self.failUnlessEqual(data, self.data)
+ self.failUnlessEqual(exp, self.exp)
+
+ def test_make_cookie(self):
+ c = self.jar.makeCookie(self.exp, self.data)
+ self.failUnless(isinstance(c, AuthCookie))
+ self.failUnlessEqual(c.expiry(), self.exp)
+ self.failUnlessEqual(c.data(), self.data)
+ # Peek inside the cookie jar...
+ key = self.jar._key
+ mac = binascii.b2a_base64(EVP.hmac(key, mix(self.exp, self.data), 'sha1'))[:-1]
+ self.failUnlessEqual(c.mac(), mac)
+ # Ok, stop peeking now.
+ cookie_str = self._format % (repr(self.exp), self.data, mac)
+ self.failUnlessEqual(c.output(), cookie_str)
+
+ def test_expired(self):
+ t = self.exp - 7200
+ c = self.jar.makeCookie(t, self.data)
+ self.failUnless(c.isExpired())
+
+ def test_not_expired(self):
+ c = self.jar.makeCookie(self.exp, self.data)
+ self.failIf(c.isExpired())
+
+ def test_is_valid(self):
+ c = self.jar.makeCookie(self.exp, self.data)
+ self.failUnless(self.jar.isGoodCookie(c))
+
+ def test_is_invalid_expired(self):
+ t = self.exp - 7200
+ c = self.jar.makeCookie(t, self.data)
+ self.failIf(self.jar.isGoodCookie(c))
+
+ def test_is_invalid_changed_exp(self):
+ c = self.jar.makeCookie(self.exp, self.data)
+ c._expiry = 'this is bad'
+ self.failIf(self.jar.isGoodCookie(c))
+
+ def test_is_invalid_changed_data(self):
+ c = self.jar.makeCookie(self.exp, self.data)
+ c._data = 'this is bad'
+ self.failIf(self.jar.isGoodCookie(c))
+
+ def test_is_invalid_changed_mac(self):
+ c = self.jar.makeCookie(self.exp, self.data)
+ c._mac = 'this is bad'
+ self.failIf(self.jar.isGoodCookie(c))
+
+ def test_mix_unmix3(self):
+ c = self.jar.makeCookie(self.exp, self.data)
+ s = Cookie.SmartCookie()
+ s.load(c.output())
+ exp, data, digest = unmix3(s[self._token].value)
+ self.failUnlessEqual(data, self.data)
+ self.failUnlessEqual(float(exp), self.exp)
+ key = self.jar._key # Peeking...
+ mac = binascii.b2a_base64(EVP.hmac(key, mix(self.exp, self.data), 'sha1'))[:-1]
+ self.failUnlessEqual(digest, mac)
+
+ def test_cookie_str(self):
+ c = self.jar.makeCookie(self.exp, self.data)
+ self.failUnless(self.jar.isGoodCookieString(c.output()))
+
+ def test_cookie_str2(self):
+ c = self.jar.makeCookie(self.exp, self.data)
+ s = Cookie.SmartCookie()
+ s.load(c.output())
+ self.failUnless(self.jar.isGoodCookieString(s.output()))
+
+ def test_cookie_str_expired(self):
+ t = self.exp - 7200
+ c = self.jar.makeCookie(t, self.data)
+ s = Cookie.SmartCookie()
+ s.load(c.output())
+ self.failIf(self.jar.isGoodCookieString(s.output()))
+
+ def test_cookie_str_arbitrary_change(self):
+ c = self.jar.makeCookie(self.exp, self.data)
+ cout = c.output()
+ str = cout[:32] + 'this is bad' + cout[32:]
+ s = Cookie.SmartCookie()
+ s.load(str)
+ self.failIf(self.jar.isGoodCookieString(s.output()))
+
+ def test_cookie_str_changed_exp(self):
+ c = self.jar.makeCookie(self.exp, self.data)
+ cout = c.output()
+ str = cout[:26] + '2' + cout[27:]
+ s = Cookie.SmartCookie()
+ s.load(str)
+ self.failIf(self.jar.isGoodCookieString(s.output()))
+
+ def test_cookie_str_changed_data(self):
+ c = self.jar.makeCookie(self.exp, self.data)
+ cout = c.output()
+ str = cout[:36] + 'X' + cout[37:]
+ s = Cookie.SmartCookie()
+ s.load(str)
+ self.failIf(self.jar.isGoodCookieString(s.output()))
+
+ def test_cookie_str_changed_mac(self):
+ c = self.jar.makeCookie(self.exp, self.data)
+ cout = c.output()
+ str = cout[:76] + 'X' + cout[77:]
+ s = Cookie.SmartCookie()
+ s.load(str)
+ self.failIf(self.jar.isGoodCookieString(s.output()))
+
+
+def suite():
+ return unittest.makeSuite(AuthCookieTestCase)
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+
diff --git a/tests/test_bio.py b/tests/test_bio.py
new file mode 100644
index 0000000..1d7b0c3
--- /dev/null
+++ b/tests/test_bio.py
@@ -0,0 +1,76 @@
+#!/usr/bin/env python
+
+"""
+Unit tests for M2Crypto.BIO.
+
+Copyright (c) 1999-2003 Ng Pheng Siong. All rights reserved.
+
+Copyright (c) 2006 Open Source Applications Foundation
+Author: Heikki Toivonen
+"""
+
+import unittest
+from M2Crypto import BIO, Rand
+
+from fips import fips_mode
+
+class CipherStreamTestCase(unittest.TestCase):
+ def try_algo(self, algo):
+ enc = 1
+ dec = 0
+ data = '123456789012345678901234'
+ # Encrypt.
+ mem = BIO.MemoryBuffer()
+ cf = BIO.CipherStream(mem)
+ cf.set_cipher(algo, 'key', 'iv', 1)
+ cf.write(data)
+ cf.flush()
+ cf.write_close()
+ cf.close()
+ xxx = mem.read()
+
+ # Decrypt.
+ mem = BIO.MemoryBuffer(xxx)
+ cf = BIO.CipherStream(mem)
+ cf.set_cipher(algo, 'key', 'iv', 0)
+ cf.write_close()
+ data2 = cf.read()
+ cf.close()
+ assert not cf.readable()
+
+ self.assertRaises(IOError, cf.read)
+ self.assertRaises(IOError, cf.readline)
+ self.assertRaises(IOError, cf.readlines)
+
+ assert data == data2, '%s algorithm cipher test failed' % algo
+
+ def test_ciphers(self):
+ ciphers=[
+ 'des_ede_ecb', 'des_ede_cbc', 'des_ede_cfb', 'des_ede_ofb',
+ 'des_ede3_ecb', 'des_ede3_cbc', 'des_ede3_cfb', 'des_ede3_ofb',
+ 'aes_128_ecb', 'aes_128_cbc', 'aes_128_cfb', 'aes_128_ofb',
+ 'aes_192_ecb', 'aes_192_cbc', 'aes_192_cfb', 'aes_192_ofb',
+ 'aes_256_ecb', 'aes_256_cbc', 'aes_256_cfb', 'aes_256_ofb']
+ nonfips_ciphers=['bf_ecb', 'bf_cbc', 'bf_cfb', 'bf_ofb',
+ #'idea_ecb', 'idea_cbc', 'idea_cfb', 'idea_ofb',
+ 'cast5_ecb', 'cast5_cbc', 'cast5_cfb', 'cast5_ofb',
+ #'rc5_ecb', 'rc5_cbc', 'rc5_cfb', 'rc5_ofb',
+ 'des_ecb', 'des_cbc', 'des_cfb', 'des_ofb',
+ 'rc4', 'rc2_40_cbc']
+ if not fips_mode: # Forbidden ciphers
+ ciphers += nonfips_ciphers
+ for i in ciphers:
+ self.try_algo(i)
+
+ self.assertRaises(ValueError, self.try_algo, 'nosuchalgo4567')
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(CipherStreamTestCase))
+ return suite
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+
diff --git a/tests/test_bio_file.py b/tests/test_bio_file.py
new file mode 100644
index 0000000..e118386
--- /dev/null
+++ b/tests/test_bio_file.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.BIO.File.
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+import unittest
+import M2Crypto
+from M2Crypto.BIO import File, openfile
+import os, sys
+
+class FileTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.data = 'abcdef' * 64
+ if sys.platform != 'win32':
+ self.fname = os.tmpnam()
+ else:
+ import tempfile
+ self.fname = tempfile.mktemp()
+
+ def tearDown(self):
+ try:
+ os.unlink(self.fname)
+ except OSError:
+ pass
+
+ def test_openfile_rb(self):
+ # First create the file using Python's open().
+ f = open(self.fname, 'wb')
+ f.write(self.data)
+ f.close()
+ # Now open the file using M2Crypto.BIO.openfile().
+ f = openfile(self.fname, 'rb')
+ data = f.read(len(self.data))
+ assert data == self.data
+
+ def test_openfile_wb(self):
+ # First create the file using M2Crypto.BIO.openfile().
+ f = openfile(self.fname, 'wb')
+ f.write(self.data)
+ f.close()
+ # Now open the file using Python's open().
+ f = open(self.fname, 'rb')
+ data = f.read(len(self.data))
+ assert data == self.data
+
+ def test_closed(self):
+ f = openfile(self.fname, 'wb')
+ f.write(self.data)
+ f.close()
+ self.assertRaises(IOError, f.write, self.data)
+
+ def test_use_pyfile(self):
+ # First create the file.
+ f = open(self.fname, 'wb')
+ f2 = File(f)
+ f2.write(self.data)
+ f2.close()
+ # Now read the file.
+ f = open(self.fname, 'rb')
+ data = f.read(len(self.data))
+ assert data == self.data
+
+
+def suite():
+ # Python 2.2 warns that os.tmpnam() is unsafe.
+ try:
+ import warnings
+ warnings.filterwarnings('ignore')
+ except ImportError:
+ pass
+ return unittest.makeSuite(FileTestCase)
+
+
+if __name__ == '__main__':
+ unittest.TextTestRunner().run(suite())
+
diff --git a/tests/test_bio_iobuf.py b/tests/test_bio_iobuf.py
new file mode 100644
index 0000000..358e5df
--- /dev/null
+++ b/tests/test_bio_iobuf.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.BIO.IOBuffer.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+from cStringIO import StringIO
+
+import unittest
+import M2Crypto
+from M2Crypto.BIO import IOBuffer, MemoryBuffer
+
+class IOBufferTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self._data = 'abcdef\n'
+ self.data = self._data * 1024
+
+ def tearDown(self):
+ pass
+
+ def test_init_empty(self):
+ mb = MemoryBuffer()
+ io = IOBuffer(mb)
+ out = io.read()
+ assert out == ''
+
+ def test_init_something(self):
+ mb = MemoryBuffer(self.data)
+ io = IOBuffer(mb)
+ out = io.read(len(self.data))
+ assert out == self.data
+
+ def test_read_less_than(self):
+ chunk = len(self.data) - 7
+ mb = MemoryBuffer(self.data)
+ io = IOBuffer(mb)
+ out = io.read(chunk)
+ assert out == self.data[:chunk]
+
+ def test_read_more_than(self):
+ chunk = len(self.data) + 8
+ mb = MemoryBuffer(self.data)
+ io = IOBuffer(mb)
+ out = io.read(chunk)
+ assert out == self.data
+
+ def test_readline(self):
+ buf = StringIO()
+ mb = MemoryBuffer(self.data)
+ io = IOBuffer(mb)
+ while 1:
+ out = io.readline()
+ if not out:
+ break
+ buf.write(out)
+ assert out == self._data
+ assert buf.getvalue() == self.data
+
+ def test_readlines(self):
+ buf = StringIO()
+ mb = MemoryBuffer(self.data)
+ io = IOBuffer(mb)
+ lines = io.readlines()
+ for line in lines:
+ assert line == self._data
+ buf.write(line)
+ assert buf.getvalue() == self.data
+
+ def test_closed(self):
+ mb = MemoryBuffer(self.data)
+ io = IOBuffer(mb)
+ io.close()
+ self.assertRaises(IOError, io.write, self.data)
+ assert not io.readable() and not io.writeable()
+
+ def test_read_only(self):
+ mb = MemoryBuffer(self.data)
+ io = IOBuffer(mb, mode='r')
+ self.assertRaises(IOError, io.write, self.data)
+ assert not io.writeable()
+
+
+def suite():
+ return unittest.makeSuite(IOBufferTestCase)
+
+
+if __name__ == '__main__':
+ unittest.TextTestRunner().run(suite())
+
diff --git a/tests/test_bio_membuf.py b/tests/test_bio_membuf.py
new file mode 100644
index 0000000..7d719fd
--- /dev/null
+++ b/tests/test_bio_membuf.py
@@ -0,0 +1,64 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.BIO.MemoryBuffer.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+import unittest
+import M2Crypto
+from M2Crypto.BIO import MemoryBuffer
+
+class MemoryBufferTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.data = 'abcdef' * 64
+
+ def tearDown(self):
+ pass
+
+ def test_init_empty(self):
+ mb = MemoryBuffer()
+ assert len(mb) == 0
+ out = mb.read()
+ assert out is None
+
+ def test_init_something(self):
+ mb = MemoryBuffer(self.data)
+ assert len(mb) == len(self.data)
+ out = mb.read()
+ assert out == self.data
+
+ def test_read_less_than(self):
+ chunk = len(self.data) - 7
+ mb = MemoryBuffer(self.data)
+ out = mb.read(chunk)
+ assert out == self.data[:chunk] and len(mb) == (len(self.data) - chunk)
+
+ def test_read_more_than(self):
+ chunk = len(self.data) + 8
+ mb = MemoryBuffer(self.data)
+ out = mb.read(chunk)
+ assert out == self.data and len(mb) == 0
+
+ def test_write_close(self):
+ mb = MemoryBuffer(self.data)
+ assert mb.writeable()
+ mb.write_close()
+ assert mb.readable()
+ self.assertRaises(IOError, mb.write, self.data)
+ assert not mb.writeable()
+
+ def test_closed(self):
+ mb = MemoryBuffer(self.data)
+ mb.close()
+ self.assertRaises(IOError, mb.write, self.data)
+ assert mb.readable() and not mb.writeable()
+
+
+def suite():
+ return unittest.makeSuite(MemoryBufferTestCase)
+
+
+if __name__ == '__main__':
+ unittest.TextTestRunner().run(suite())
+
diff --git a/tests/test_bio_ssl.py b/tests/test_bio_ssl.py
new file mode 100644
index 0000000..0dee9da
--- /dev/null
+++ b/tests/test_bio_ssl.py
@@ -0,0 +1,150 @@
+#!/usr/bin/env python
+"""Unit tests for M2Crypto.BIO.File.
+
+Copyright (c) 1999-2002 Ng Pheng Siong. All rights reserved."""
+
+import unittest, threading, sys, socket
+
+from M2Crypto import BIO
+from M2Crypto import SSL
+from M2Crypto import Err
+from M2Crypto import Rand
+from M2Crypto import threading as m2threading
+
+from test_ssl import srv_host, srv_port
+
+class HandshakeClient(threading.Thread):
+
+ def __init__(self, host, port):
+ threading.Thread.__init__(self)
+ self.host = host
+ self.port = port
+
+ def run(self):
+ ctx = SSL.Context()
+ ctx.load_cert_chain("tests/server.pem")
+ conn = SSL.Connection(ctx)
+ cipher_list = conn.get_cipher_list()
+ sslbio = BIO.SSLBio()
+ readbio = BIO.MemoryBuffer()
+ writebio = BIO.MemoryBuffer()
+ sslbio.set_ssl(conn)
+ conn.set_bio(readbio, writebio)
+ conn.set_connect_state()
+ sock = socket.socket()
+ sock.connect((self.host, self.port))
+
+ handshake_complete = False
+ while not handshake_complete:
+ ret = sslbio.do_handshake()
+ if ret <= 0:
+ if not sslbio.should_retry() or not sslbio.should_read():
+ err_string = Err.get_error()
+ print err_string
+ sys.exit("unrecoverable error in handshake - client")
+ else:
+ output_token = writebio.read()
+ if output_token is not None:
+ sock.sendall(output_token)
+ else:
+ input_token = sock.recv(1024)
+ readbio.write(input_token)
+ else:
+ handshake_complete = True
+
+ sock.close()
+
+
+class SSLTestCase(unittest.TestCase):
+
+ def setUp(self):
+ self.sslbio = BIO.SSLBio()
+
+ def test_pass(self): # XXX leaks 64/24 bytes
+ pass
+
+ def test_set_ssl(self): # XXX leaks 64/1312 bytes
+ ctx = SSL.Context()
+ conn = SSL.Connection(ctx)
+ self.sslbio.set_ssl(conn)
+
+ def test_do_handshake_fail(self): # XXX leaks 64/42066 bytes
+ ctx = SSL.Context()
+ conn = SSL.Connection(ctx)
+ conn.set_connect_state()
+ self.sslbio.set_ssl(conn)
+ ret = self.sslbio.do_handshake()
+ assert ret == 0
+
+ def test_should_retry_fail(self): # XXX leaks 64/1312 bytes
+ ctx = SSL.Context()
+ conn = SSL.Connection(ctx)
+ self.sslbio.set_ssl(conn)
+ ret = self.sslbio.do_handshake()
+ assert ret == -1
+ ret = self.sslbio.should_retry()
+ assert ret == 0
+
+ def test_should_write_fail(self): # XXX leaks 64/1312 bytes
+ ctx = SSL.Context()
+ conn = SSL.Connection(ctx)
+ self.sslbio.set_ssl(conn)
+ ret = self.sslbio.do_handshake()
+ assert ret == -1
+ ret = self.sslbio.should_write()
+ assert ret == 0
+
+ def test_should_read_fail(self): # XXX leaks 64/1312 bytes
+ ctx = SSL.Context()
+ conn = SSL.Connection(ctx)
+ self.sslbio.set_ssl(conn)
+ ret = self.sslbio.do_handshake()
+ assert ret == -1
+ ret = self.sslbio.should_read()
+ assert ret == 0
+
+ def test_do_handshake_succeed(self): # XXX leaks 196/26586 bytes
+ ctx = SSL.Context()
+ ctx.load_cert_chain("tests/server.pem")
+ conn = SSL.Connection(ctx)
+ self.sslbio.set_ssl(conn)
+ readbio = BIO.MemoryBuffer()
+ writebio = BIO.MemoryBuffer()
+ conn.set_bio(readbio, writebio)
+ conn.set_accept_state()
+ handshake_complete = False
+ sock = socket.socket()
+ sock.bind((srv_host, srv_port))
+ sock.listen(5)
+ handshake_client = HandshakeClient(srv_host, srv_port)
+ handshake_client.start()
+ new_sock, addr = sock.accept()
+ while not handshake_complete:
+ input_token = new_sock.recv(1024)
+ readbio.write(input_token)
+
+ ret = self.sslbio.do_handshake()
+ if ret <= 0:
+ if not self.sslbio.should_retry() or not self.sslbio.should_read():
+ sys.exit("unrecoverable error in handshake - server")
+ else:
+ handshake_complete = True
+
+ output_token = writebio.read()
+ if output_token is not None:
+ new_sock.sendall(output_token)
+
+ handshake_client.join()
+ sock.close()
+ new_sock.close()
+
+def suite():
+ return unittest.makeSuite(SSLTestCase)
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ m2threading.init()
+ unittest.TextTestRunner().run(suite())
+ m2threading.cleanup()
+ Rand.save_file('randpool.dat')
diff --git a/tests/test_bn.py b/tests/test_bn.py
new file mode 100755
index 0000000..62c83fe
--- /dev/null
+++ b/tests/test_bn.py
@@ -0,0 +1,78 @@
+#!/usr/bin/env python
+
+"""
+Unit tests for M2Crypto.BN.
+
+Copyright (c) 2005 Open Source Applications Foundation. All rights reserved.
+"""
+
+import unittest, re
+from M2Crypto import BN, Rand
+
+loops = 16
+
+class BNTestCase(unittest.TestCase):
+
+ def test_rand(self):
+ # defaults
+ for x in range(loops):
+ r8 = BN.rand(8)
+
+ # top
+ for x in range(loops):
+ r8 = BN.rand(8, top=0)
+ assert r8 & 128
+ for x in range(loops):
+ r8 = BN.rand(8, top=1)
+ assert r8 & 192
+
+ # bottom
+ for x in range(loops):
+ r8 = BN.rand(8, bottom=1)
+ assert r8 % 2 == 1
+
+ # make sure we can get big numbers and work with them
+ for x in range(loops):
+ r8 = BN.rand(8, top=0)
+ r16 = BN.rand(16, top=0)
+ r32 = BN.rand(32, top=0)
+ r64 = BN.rand(64, top=0)
+ r128 = BN.rand(128, top=0)
+ r256 = BN.rand(256, top=0)
+ r512 = BN.rand(512, top=0)
+ assert r8 < r16 < r32 < r64 < r128 < r256 < r512 < (r512 + 1)
+
+
+ def test_rand_range(self):
+ # small range
+ for x in range(loops):
+ r = BN.rand_range(1)
+ assert r == 0
+
+ for x in range(loops):
+ r = BN.rand_range(4)
+ assert 0 <= r < 4
+
+ # large range
+ r512 = BN.rand(512, top=0)
+ for x in range(loops):
+ r = BN.rand_range(r512)
+ assert 0 <= r < r512
+
+
+ def test_randfname(self):
+ m = re.compile('^[a-zA-Z0-9]{8}$')
+ for x in range(loops):
+ r = BN.randfname(8)
+ assert m.match(r)
+
+
+def suite():
+ return unittest.makeSuite(BNTestCase)
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+
diff --git a/tests/test_dh.py b/tests/test_dh.py
new file mode 100644
index 0000000..0e66c89
--- /dev/null
+++ b/tests/test_dh.py
@@ -0,0 +1,65 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.DH.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+import unittest
+from M2Crypto import DH, BIO, Rand, m2
+
+class DHTestCase(unittest.TestCase):
+
+ params = 'tests/dhparam.pem'
+
+ def genparam_callback(self, *args):
+ pass
+
+ def genparam_callback2(self):
+ pass
+
+ def test_init_junk(self):
+ self.assertRaises(TypeError, DH.DH, 'junk')
+
+ def test_gen_params(self):
+ a = DH.gen_params(1024, 2, self.genparam_callback)
+ assert a.check_params() == 0
+
+ def test_gen_params_bad_cb(self):
+ a = DH.gen_params(1024, 2, self.genparam_callback2)
+ assert a.check_params() == 0
+
+ def test_print_params(self):
+ a = DH.gen_params(1024, 2, self.genparam_callback)
+ bio = BIO.MemoryBuffer()
+ a.print_params(bio)
+ params = bio.read()
+ assert params.find('(1024 bit)')
+ assert params.find('generator: 2 (0x2)')
+
+ def test_load_params(self):
+ a = DH.load_params('tests/dhparams.pem')
+ assert a.check_params() == 0
+
+ def test_compute_key(self):
+ a = DH.load_params('tests/dhparams.pem')
+ b = DH.set_params(a.p, a.g)
+ a.gen_key()
+ b.gen_key()
+ ak = a.compute_key(b.pub)
+ bk = b.compute_key(a.pub)
+ assert ak == bk
+ self.assertEqual(len(a), 128)
+
+ self.assertRaises(DH.DHError, setattr, a, 'p', 1)
+ self.assertRaises(DH.DHError, setattr, a, 'priv', 1)
+
+
+def suite():
+ return unittest.makeSuite(DHTestCase)
+
+
+if __name__=='__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+
diff --git a/tests/test_dsa.py b/tests/test_dsa.py
new file mode 100644
index 0000000..7823f50
--- /dev/null
+++ b/tests/test_dsa.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.DSA.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+import unittest
+import sha
+from M2Crypto import DSA, BIO, Rand, m2
+
+class DSATestCase(unittest.TestCase):
+
+ errkey = 'tests/rsa.priv.pem'
+ privkey = 'tests/dsa.priv.pem'
+ pubkey = 'tests/dsa.pub.pem'
+ param = 'tests/dsa.param.pem'
+
+ data = sha.sha('Can you spell subliminal channel?').digest()
+ different_data = sha.sha('I can spell.').digest()
+
+ def callback(self, *args):
+ pass
+
+ def test_loadkey_junk(self):
+ self.assertRaises(DSA.DSAError, DSA.load_key, self.errkey)
+
+ def test_loadkey(self):
+ dsa = DSA.load_key(self.privkey)
+ assert len(dsa) == 1024
+ self.assertRaises(AttributeError, getattr, dsa, 'foobar')
+ for k in ('p', 'q', 'g', 'priv', 'pub'):
+ self.assertRaises(DSA.DSAError, setattr, dsa, k, 1)
+
+ def test_loadparam(self):
+ self.assertRaises(DSA.DSAError, DSA.load_key, self.param)
+ dsa = DSA.load_params(self.param)
+ assert not dsa.check_key()
+ assert len(dsa) == 1024
+
+ def test_sign(self):
+ dsa = DSA.load_key(self.privkey)
+ assert dsa.check_key()
+ r, s = dsa.sign(self.data)
+ assert dsa.verify(self.data, r, s)
+ assert not dsa.verify(self.data, s, r)
+
+ def test_sign_asn1(self):
+ dsa = DSA.load_key(self.privkey)
+ blob = dsa.sign_asn1(self.data)
+ assert dsa.verify_asn1(self.data, blob)
+
+ def test_sign_with_params_only(self):
+ dsa = DSA.load_params(self.param)
+ self.assertRaises(AssertionError, dsa.sign, self.data)
+ self.assertRaises(AssertionError, dsa.sign_asn1, self.data)
+
+ def test_pub_verify(self):
+ dsa = DSA.load_key(self.privkey)
+ r, s = dsa.sign(self.data)
+ dsapub = DSA.load_pub_key(self.pubkey)
+ assert dsapub.check_key()
+ assert dsapub.verify(self.data, r, s)
+ self.assertRaises(DSA.DSAError, dsapub.sign)
+
+ def test_verify_fail(self):
+ dsa = DSA.load_key(self.privkey)
+ r, s = dsa.sign(self.data)
+ assert not dsa.verify(self.different_data, r, s)
+
+ def test_verify_fail2(self):
+ dsa = DSA.load_key(self.privkey)
+ r,s = dsa.sign(self.data)
+ dsa2 = DSA.load_params(self.param)
+ assert not dsa2.check_key()
+ self.assertRaises(AssertionError, dsa2.verify, self.data, r, s)
+
+ def test_genparam_setparam_genkey(self):
+ dsa = DSA.gen_params(1024, self.callback)
+ assert len(dsa) == 1024
+ p = dsa.p
+ q = dsa.q
+ g = dsa.g
+ dsa2 = DSA.set_params(p,q,g)
+ assert not dsa2.check_key()
+ dsa2.gen_key()
+ assert dsa2.check_key()
+ r,s = dsa2.sign(self.data)
+ assert dsa2.verify(self.data, r, s)
+
+def suite():
+ return unittest.makeSuite(DSATestCase)
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+
diff --git a/tests/test_ec_curves.py b/tests/test_ec_curves.py
new file mode 100644
index 0000000..d01f863
--- /dev/null
+++ b/tests/test_ec_curves.py
@@ -0,0 +1,159 @@
+#!/usr/bin/env python
+# XXX memory leaks
+"""
+ Unit tests for M2Crypto.EC, the curves
+
+ There are several ways one could unittest elliptical curves
+ but we are going to only validate that we are using the
+ OpenSSL curve and that it works with ECDSA. We will assume
+ OpenSSL has validated the curves themselves.
+
+ Also, some curves are shorter than a SHA-1 digest of 160
+ bits. To keep the testing simple, we will take advantage
+ of ECDSA's ability to sign any digest length and create a
+ digset string of only 48 bits. Remember we are testing our
+ ability to access the curve, not ECDSA itself.
+
+ Copyright (c) 2006 Larry Bugbee. All rights reserved.
+
+"""
+
+import unittest
+#import sha
+from M2Crypto import EC, Rand
+from test_ecdsa import ECDSATestCase as ECDSATest
+
+
+curves = [
+ ('secp112r1', 112),
+ ('secp112r2', 112),
+ ('secp128r1', 128),
+ ('secp128r2', 128),
+ ('secp160k1', 160),
+ ('secp160r1', 160),
+ ('secp160r2', 160),
+ ('secp192k1', 192),
+ ('secp224k1', 224),
+ ('secp224r1', 224),
+ ('secp256k1', 256),
+ ('secp384r1', 384),
+ ('secp521r1', 521),
+
+ ('sect113r1', 113),
+ ('sect113r2', 113),
+ ('sect131r1', 131),
+ ('sect131r2', 131),
+ ('sect163k1', 163),
+ ('sect163r1', 163),
+ ('sect163r2', 163),
+ ('sect193r1', 193),
+ ('sect193r2', 193),
+ ('sect233k1', 233),
+ ('sect233r1', 233),
+ ('sect239k1', 239),
+ ('sect283k1', 283),
+ ('sect283r1', 283),
+ ('sect409k1', 409),
+ ('sect409r1', 409),
+ ('sect571k1', 571),
+ ('sect571r1', 571),
+
+ ('X9_62_prime192v1', 192),
+ ('X9_62_prime192v2', 192),
+ ('X9_62_prime192v3', 192),
+ ('X9_62_prime239v1', 239),
+ ('X9_62_prime239v2', 239),
+ ('X9_62_prime239v3', 239),
+ ('X9_62_prime256v1', 256),
+
+ ('X9_62_c2pnb163v1', 163),
+ ('X9_62_c2pnb163v2', 163),
+ ('X9_62_c2pnb163v3', 163),
+ ('X9_62_c2pnb176v1', 176),
+ ('X9_62_c2tnb191v1', 191),
+ ('X9_62_c2tnb191v2', 191),
+ ('X9_62_c2tnb191v3', 191),
+ ('X9_62_c2pnb208w1', 208),
+ ('X9_62_c2tnb239v1', 239),
+ ('X9_62_c2tnb239v2', 239),
+ ('X9_62_c2tnb239v3', 239),
+ ('X9_62_c2pnb272w1', 272),
+ ('X9_62_c2pnb304w1', 304),
+ ('X9_62_c2tnb359v1', 359),
+ ('X9_62_c2pnb368w1', 368),
+ ('X9_62_c2tnb431r1', 431),
+
+ ('wap_wsg_idm_ecid_wtls1', 113),
+ ('wap_wsg_idm_ecid_wtls3', 163),
+ ('wap_wsg_idm_ecid_wtls4', 113),
+ ('wap_wsg_idm_ecid_wtls5', 163),
+ ('wap_wsg_idm_ecid_wtls6', 112),
+ ('wap_wsg_idm_ecid_wtls7', 160),
+ ('wap_wsg_idm_ecid_wtls8', 112),
+ ('wap_wsg_idm_ecid_wtls9', 160),
+ ('wap_wsg_idm_ecid_wtls10', 233),
+ ('wap_wsg_idm_ecid_wtls11', 233),
+ ('wap_wsg_idm_ecid_wtls12', 224),
+]
+
+# The following two curves, according to OpenSSL, have a
+# "Questionable extension field!" and are not supported by
+# the OpenSSL inverse function. ECError: no inverse.
+# As such they cannot be used for signing. They might,
+# however, be usable for encryption but that has not
+# been tested. Until thir usefulness can be established,
+# they are not supported at this time.
+#curves2 = [
+# ('ipsec3', 155),
+# ('ipsec4', 185),
+#]
+
+class ECCurveTests(unittest.TestCase):
+ #data = sha.sha('Kilroy was here!').digest() # 160 bits
+ data = "digest" # keep short (48 bits) so lesser curves
+ # will work... ECDSA requires curve be
+ # equal or longer than digest
+
+ def genkey(self, curveName, curveLen):
+ curve = getattr(EC, 'NID_'+curveName)
+ ec = EC.gen_params(curve)
+ assert len(ec) == curveLen
+ ec.gen_key()
+ assert ec.check_key(), 'check_key() failure for "%s"' % curveName
+ return ec
+
+# def check_ec_curves_genkey(self):
+# for curveName, curveLen in curves2:
+# self.genkey(curveName, curveLen)
+#
+# self.assertRaises(AttributeError, self.genkey,
+# 'nosuchcurve', 1)
+
+ def sign_verify_ecdsa(self, curveName, curveLen):
+ ec = self.genkey(curveName, curveLen)
+ r, s = ec.sign_dsa(self.data)
+ assert ec.verify_dsa(self.data, r, s)
+ assert not ec.verify_dsa(self.data, s, r)
+
+ def test_ec_curves_ECDSA(self):
+ for curveName, curveLen in curves:
+ self.sign_verify_ecdsa(curveName, curveLen)
+
+ self.assertRaises(AttributeError, self.sign_verify_ecdsa,
+ 'nosuchcurve', 1)
+
+# for curveName, curveLen in curves2:
+# self.assertRaises(EC.ECError, self.sign_verify_ecdsa,
+# curveName, curveLen)
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(ECCurveTests))
+ return suite
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+
diff --git a/tests/test_ecdh.py b/tests/test_ecdh.py
new file mode 100644
index 0000000..29581dc
--- /dev/null
+++ b/tests/test_ecdh.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.EC, ECDH part.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved.
+Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam. All rights reserved.
+"""
+
+
+import unittest
+from M2Crypto import EC, BIO, Rand, m2
+import sys
+
+class ECDHTestCase(unittest.TestCase):
+
+ privkey = 'tests/ec.priv.pem'
+
+ def test_init_junk(self):
+ self.assertRaises(TypeError, EC.EC, 'junk')
+
+ def test_compute_key(self):
+ a = EC.load_key(self.privkey)
+ b = EC.gen_params(EC.NID_sect233k1)
+ b.gen_key()
+ ak = a.compute_dh_key(b.pub())
+ bk = b.compute_dh_key(a.pub())
+ assert ak == bk
+
+ def test_pubkey_from_der(self):
+ a = EC.gen_params(EC.NID_sect233k1)
+ a.gen_key()
+ b = EC.gen_params(EC.NID_sect233k1)
+ b.gen_key()
+ a_pub_der = a.pub().get_der()
+ a_pub = EC.pub_key_from_der(a_pub_der)
+ ak = a.compute_dh_key(b.pub())
+ bk = b.compute_dh_key(a_pub)
+ assert ak == bk
+
+
+def suite():
+ return unittest.makeSuite(ECDHTestCase)
+
+
+if __name__=='__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+
diff --git a/tests/test_ecdsa.py b/tests/test_ecdsa.py
new file mode 100644
index 0000000..74d9a82
--- /dev/null
+++ b/tests/test_ecdsa.py
@@ -0,0 +1,75 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.EC, ECDSA part.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved.
+Portions copyright (c) 2005-2006 Vrije Universiteit Amsterdam. All rights reserved.
+"""
+
+import unittest
+import sha
+from M2Crypto import EC, BIO, Rand, m2
+
+class ECDSATestCase(unittest.TestCase):
+
+ errkey = 'tests/rsa.priv.pem'
+ privkey = 'tests/ec.priv.pem'
+ pubkey = 'tests/ec.pub.pem'
+
+ data = sha.sha('Can you spell subliminal channel?').digest()
+
+ def callback(self, *args):
+ pass
+
+ def callback2(self):
+ pass
+
+ def test_loadkey_junk(self):
+ self.assertRaises(ValueError, EC.load_key, self.errkey)
+
+ def test_loadkey(self):
+ ec = EC.load_key(self.privkey)
+ assert len(ec) == 233
+
+ def test_loadpubkey(self):
+ # XXX more work needed
+ ec = EC.load_pub_key(self.pubkey)
+ assert len(ec) == 233
+ self.assertRaises(EC.ECError, EC.load_pub_key, self.errkey)
+
+ def _test_sign_dsa(self):
+ ec = EC.gen_params(EC.NID_sect233k1)
+ # ec.gen_key()
+ self.assertRaises(EC.ECError, ec.sign_dsa, self.data)
+ ec = EC.load_key(self.privkey)
+ r, s = ec.sign_dsa(self.data)
+ assert ec.verify_dsa(self.data, r, s)
+ assert not ec.verify_dsa(self.data, s, r)
+
+ def test_sign_dsa_asn1(self):
+ ec = EC.load_key(self.privkey)
+ blob = ec.sign_dsa_asn1(self.data)
+ assert ec.verify_dsa_asn1(self.data, blob)
+ self.assertRaises(EC.ECError, ec.verify_dsa_asn1, blob, self.data)
+
+ def test_verify_dsa(self):
+ ec = EC.load_key(self.privkey)
+ r, s = ec.sign_dsa(self.data)
+ ec2 = EC.load_pub_key(self.pubkey)
+ assert ec2.verify_dsa(self.data, r, s)
+ assert not ec2.verify_dsa(self.data, s, r)
+
+ def test_genparam(self):
+ ec = EC.gen_params(EC.NID_sect233k1)
+ assert len(ec) == 233
+
+
+def suite():
+ return unittest.makeSuite(ECDSATestCase)
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+
diff --git a/tests/test_engine.py b/tests/test_engine.py
new file mode 100644
index 0000000..91c2aa8
--- /dev/null
+++ b/tests/test_engine.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.Engine."""
+
+import unittest
+from M2Crypto import Engine, m2
+
+class EngineTestCase(unittest.TestCase):
+
+ privkey = 'tests/rsa.priv.pem'
+ bad_id = '1bea1edfeb97'
+
+ def tearDown(self):
+ Engine.cleanup()
+
+ def test_by_id_junk(self):
+ self.assertRaises(ValueError, Engine.Engine, self.bad_id)
+ self.assertRaises(ValueError, Engine.Engine)
+
+ def test_by_id_openssl(self):
+ Engine.load_openssl()
+ e = Engine.Engine('openssl')
+ self.assertEqual(e.get_name(), 'Software engine support')
+ self.assertEqual(e.get_id(), 'openssl')
+
+ def test_by_id_dynamic(self):
+ Engine.load_dynamic()
+ Engine.Engine('dynamic')
+
+ def test_load_private(self):
+ Engine.load_openssl()
+ e = Engine.Engine('openssl')
+ e.set_default()
+ e.load_private_key(self.privkey)
+
+ def test_load_certificate(self):
+ Engine.load_openssl()
+ e = Engine.Engine('openssl')
+ e.set_default()
+ self.assertRaises(Engine.EngineError, e.load_certificate, '/dev/null')
+
+def suite():
+ return unittest.makeSuite(EngineTestCase)
+
+
+if __name__ == '__main__':
+ unittest.TextTestRunner().run(suite())
+
diff --git a/tests/test_evp.py b/tests/test_evp.py
new file mode 100644
index 0000000..ba09092
--- /dev/null
+++ b/tests/test_evp.py
@@ -0,0 +1,471 @@
+#!/usr/bin/env python
+
+"""
+Unit tests for M2Crypto.EVP.
+
+Copyright (c) 2004-2007 Open Source Applications Foundation
+Author: Heikki Toivonen
+"""
+
+import unittest
+import cStringIO, sha
+from binascii import hexlify, unhexlify
+from M2Crypto import EVP, RSA, util, Rand, m2, BIO
+from M2Crypto.util import h2b
+
+from fips import fips_mode
+
+class EVPTestCase(unittest.TestCase):
+ def _gen_callback(self, *args):
+ pass
+
+ def _pass_callback(self, *args):
+ return 'foobar'
+
+ def _assign_rsa(self):
+ rsa = RSA.gen_key(1024, 3, callback=self._gen_callback)
+ pkey = EVP.PKey()
+ pkey.assign_rsa(rsa, capture=0) # capture=1 should cause crash
+ return rsa
+
+ def test_assign(self):
+ rsa = self._assign_rsa()
+ rsa.check_key()
+
+ def test_pem(self):
+ rsa = RSA.gen_key(1024, 3, callback=self._gen_callback)
+ pkey = EVP.PKey()
+ pkey.assign_rsa(rsa)
+ assert pkey.as_pem(callback=self._pass_callback) != pkey.as_pem(cipher=None)
+ self.assertRaises(ValueError, pkey.as_pem, cipher='noXX$$%%suchcipher',
+ callback=self._pass_callback)
+
+ def test_as_der(self):
+ """
+ Test DER encoding the PKey instance after assigning
+ a RSA key to it.
+ """
+ rsa = RSA.gen_key(1024, 3, callback=self._gen_callback)
+ pkey = EVP.PKey()
+ pkey.assign_rsa(rsa)
+ der_blob = pkey.as_der()
+ #A quick but not thorough sanity check
+ assert len(der_blob) == 160
+
+
+ def test_MessageDigest(self):
+ self.assertRaises(ValueError, EVP.MessageDigest, 'sha513')
+ md = EVP.MessageDigest('sha1')
+ assert md.update('Hello') == 1
+ assert util.octx_to_num(md.final()) == 1415821221623963719413415453263690387336440359920
+
+ def test_as_der_capture_key(self):
+ """
+ Test DER encoding the PKey instance after assigning
+ a RSA key to it. Have the PKey instance capture the RSA key.
+ """
+ rsa = RSA.gen_key(1024, 3, callback=self._gen_callback)
+ pkey = EVP.PKey()
+ pkey.assign_rsa(rsa, 1)
+ der_blob = pkey.as_der()
+ #A quick but not thorough sanity check
+ assert len(der_blob) == 160
+
+ def test_size(self):
+ rsa = RSA.gen_key(1024, 3, callback=self._gen_callback)
+ pkey = EVP.PKey()
+ pkey.assign_rsa(rsa)
+ size = pkey.size()
+ assert size == 128
+
+ def test_hmac(self):
+ assert util.octx_to_num(EVP.hmac('key', 'data')) == 92800611269186718152770431077867383126636491933, util.octx_to_num(EVP.hmac('key', 'data'))
+ if not fips_mode: # Disabled algorithms
+ assert util.octx_to_num(EVP.hmac('key', 'data', algo='md5')) == 209168838103121722341657216703105225176, util.octx_to_num(EVP.hmac('key', 'data', algo='md5'))
+ assert util.octx_to_num(EVP.hmac('key', 'data', algo='ripemd160')) == 1176807136224664126629105846386432860355826868536, util.octx_to_num(EVP.hmac('key', 'data', algo='ripemd160'))
+
+ if m2.OPENSSL_VERSION_NUMBER >= 0x90800F:
+ assert util.octx_to_num(EVP.hmac('key', 'data', algo='sha224')) == 2660082265842109788381286338540662430962855478412025487066970872635, util.octx_to_num(EVP.hmac('key', 'data', algo='sha224'))
+ assert util.octx_to_num(EVP.hmac('key', 'data', algo='sha256')) == 36273358097036101702192658888336808701031275731906771612800928188662823394256, util.octx_to_num(EVP.hmac('key', 'data', algo='sha256'))
+ assert util.octx_to_num(EVP.hmac('key', 'data', algo='sha384')) == 30471069101236165765942696708481556386452105164815350204559050657318908408184002707969468421951222432574647369766282, util.octx_to_num(EVP.hmac('key', 'data', algo='sha384'))
+ assert util.octx_to_num(EVP.hmac('key', 'data', algo='sha512')) == 3160730054100700080556942280820129108466291087966635156623014063982211353635774277148932854680195471287740489442390820077884317620321797003323909388868696, util.octx_to_num(EVP.hmac('key', 'data', algo='sha512'))
+
+ self.assertRaises(ValueError, EVP.hmac, 'key', 'data', algo='sha513')
+
+
+ def test_get_rsa(self):
+ """
+ Testing retrieving the RSA key from the PKey instance.
+ """
+ rsa = RSA.gen_key(512, 3, callback=self._gen_callback)
+ assert isinstance(rsa, RSA.RSA)
+ pkey = EVP.PKey()
+ pkey.assign_rsa(rsa)
+ rsa2 = pkey.get_rsa()
+ assert isinstance(rsa2, RSA.RSA_pub)
+ assert rsa.e == rsa2.e
+ assert rsa.n == rsa2.n
+ pem = rsa.as_pem(callback=self._pass_callback)
+ pem2 = rsa2.as_pem()
+ assert pem
+ assert pem2
+ assert pem != pem2
+
+ message = "This is the message string"
+ digest = sha.sha(message).digest()
+ assert rsa.sign(digest) == rsa2.sign(digest)
+
+ rsa3 = RSA.gen_key(1024, 3, callback=self._gen_callback)
+ assert rsa.sign(digest) != rsa3.sign(digest)
+
+ def test_get_rsa_fail(self):
+ """
+ Testing trying to retrieve the RSA key from the PKey instance
+ when it is not holding a RSA Key. Should raise a ValueError.
+ """
+ pkey = EVP.PKey()
+ self.assertRaises(ValueError, pkey.get_rsa)
+
+ def test_get_modulus(self):
+ pkey = EVP.PKey()
+ self.assertRaises(ValueError, pkey.get_modulus)
+
+ rsa = RSA.gen_key(512, 3, callback=self._gen_callback)
+ pkey.assign_rsa(rsa)
+ mod = pkey.get_modulus()
+ assert len(mod) > 0, mod
+ assert len(mod.strip('0123456789ABCDEF')) == 0
+
+ def test_verify_final(self):
+ from M2Crypto import X509
+ pkey = EVP.load_key('tests/signer_key.pem')
+ pkey.sign_init()
+ pkey.sign_update('test message')
+ sig = pkey.sign_final()
+
+ # OK
+ x509 = X509.load_cert('tests/signer.pem')
+ pubkey = x509.get_pubkey()
+ pubkey.verify_init()
+ pubkey.verify_update('test message')
+ assert pubkey.verify_final(sig) == 1
+
+ # wrong cert
+ x509 = X509.load_cert('tests/x509.pem')
+ pubkey = x509.get_pubkey()
+ pubkey.verify_init()
+ pubkey.verify_update('test message')
+ assert pubkey.verify_final(sig) == 0
+
+ # wrong message
+ x509 = X509.load_cert('tests/signer.pem')
+ pubkey = x509.get_pubkey()
+ pubkey.verify_init()
+ pubkey.verify_update('test message not')
+ assert pubkey.verify_final(sig) == 0
+
+ def test_load_bad(self):
+ self.assertRaises(BIO.BIOError, EVP.load_key,
+ 'thisdoesnotexist-dfgh56789')
+ self.assertRaises(EVP.EVPError, EVP.load_key,
+ 'tests/signer.pem') # not a key
+ self.assertRaises(EVP.EVPError, EVP.load_key_bio,
+ BIO.MemoryBuffer('no a key'))
+
+ def test_pad(self):
+ self.assertEqual(util.pkcs5_pad('Hello World'),
+ 'Hello World\x05\x05\x05\x05\x05')
+ self.assertEqual(util.pkcs7_pad('Hello World', 15),
+ 'Hello World\x04\x04\x04\x04')
+ self.assertRaises(ValueError, util.pkcs7_pad, 'Hello', 256)
+
+
+class CipherTestCase(unittest.TestCase):
+ def cipher_filter(self, cipher, inf, outf):
+ while 1:
+ buf=inf.read()
+ if not buf:
+ break
+ outf.write(cipher.update(buf))
+ outf.write(cipher.final())
+ return outf.getvalue()
+
+ def try_algo(self, algo):
+ enc = 1
+ dec = 0
+ otxt='against stupidity the gods themselves contend in vain'
+
+ k=EVP.Cipher(algo, 'goethe','12345678', enc, 1, 'sha1', 'saltsalt', 5)
+ pbuf=cStringIO.StringIO(otxt)
+ cbuf=cStringIO.StringIO()
+ ctxt=self.cipher_filter(k, pbuf, cbuf)
+ pbuf.close()
+ cbuf.close()
+
+ j=EVP.Cipher(algo, 'goethe','12345678', dec, 1, 'sha1', 'saltsalt', 5)
+ pbuf=cStringIO.StringIO()
+ cbuf=cStringIO.StringIO(ctxt)
+ ptxt=self.cipher_filter(j, cbuf, pbuf)
+ pbuf.close()
+ cbuf.close()
+
+ assert otxt == ptxt, '%s algorithm cipher test failed' % algo
+
+ def test_ciphers(self):
+ ciphers=[
+ 'des_ede_ecb', 'des_ede_cbc', 'des_ede_cfb', 'des_ede_ofb',
+ 'des_ede3_ecb', 'des_ede3_cbc', 'des_ede3_cfb', 'des_ede3_ofb',
+ 'aes_128_ecb', 'aes_128_cbc', 'aes_128_cfb', 'aes_128_ofb',
+ 'aes_192_ecb', 'aes_192_cbc', 'aes_192_cfb', 'aes_192_ofb',
+ 'aes_256_ecb', 'aes_256_cbc', 'aes_256_cfb', 'aes_256_ofb']
+ nonfips_ciphers=['bf_ecb', 'bf_cbc', 'bf_cfb', 'bf_ofb',
+ #'idea_ecb', 'idea_cbc', 'idea_cfb', 'idea_ofb',
+ 'cast5_ecb', 'cast5_cbc', 'cast5_cfb', 'cast5_ofb',
+ #'rc5_ecb', 'rc5_cbc', 'rc5_cfb', 'rc5_ofb',
+ 'des_ecb', 'des_cbc', 'des_cfb', 'des_ofb',
+ 'rc4', 'rc2_40_cbc']
+ if not fips_mode: # Disabled algorithms
+ ciphers += nonfips_ciphers
+ for i in ciphers:
+ self.try_algo(i)
+
+ # idea might not be compiled in
+ ciphers=['idea_ecb', 'idea_cbc', 'idea_cfb', 'idea_ofb']
+ try:
+ for i in ciphers:
+ self.try_algo(i)
+ except ValueError, e:
+ if str(e) != "('unknown cipher', 'idea_ecb')":
+ raise
+
+ # rc5 might not be compiled in
+ ciphers=['rc5_ecb', 'rc5_cbc', 'rc5_cfb', 'rc5_ofb']
+ try:
+ for i in ciphers:
+ self.try_algo(i)
+ except ValueError, e:
+ if str(e) != "('unknown cipher', 'rc5_ecb')":
+ raise
+
+ self.assertRaises(ValueError, self.try_algo, 'nosuchalgo4567')
+
+ def test_AES(self):
+ enc = 1
+ dec = 0
+ tests = [
+ # test vectors from rfc 3602
+ #Case #1: Encrypting 16 bytes (1 block) using AES-CBC with 128-bit key
+ {
+ 'KEY': '06a9214036b8a15b512e03d534120006',
+ 'IV': '3dafba429d9eb430b422da802c9fac41',
+ 'PT': 'Single block msg',
+ 'CT': 'e353779c1079aeb82708942dbe77181a',
+ },
+
+ #Case #2: Encrypting 32 bytes (2 blocks) using AES-CBC with 128-bit key
+ {
+ 'KEY': 'c286696d887c9aa0611bbb3e2025a45a',
+ 'IV': '562e17996d093d28ddb3ba695a2e6f58',
+ 'PT': unhexlify('000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f'),
+ 'CT': 'd296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1',
+ },
+
+ #Case #3: Encrypting 48 bytes (3 blocks) using AES-CBC with 128-bit key
+ {
+ 'KEY': '6c3ea0477630ce21a2ce334aa746c2cd',
+ 'IV': 'c782dc4c098c66cbd9cd27d825682c81',
+ 'PT': 'This is a 48-byte message (exactly 3 AES blocks)',
+ 'CT': 'd0a02b3836451753d493665d33f0e8862dea54cdb293abc7506939276772f8d5021c19216bad525c8579695d83ba2684',
+ },
+ ]
+
+ # Test with padding
+ for test in tests:
+ # encrypt
+ k=EVP.Cipher(alg='aes_128_cbc', key=unhexlify(test['KEY']), iv=unhexlify(test['IV']), op=enc)
+ pbuf=cStringIO.StringIO(test['PT'])
+ cbuf=cStringIO.StringIO()
+ ciphertext = hexlify(self.cipher_filter(k, pbuf, cbuf))
+ cipherpadding = ciphertext[len(test['PT']) * 2:]
+ ciphertext = ciphertext[:len(test['PT']) * 2] # Remove the padding from the end
+ pbuf.close()
+ cbuf.close()
+ self.assertEqual(ciphertext, test['CT'])
+
+ # decrypt
+ j=EVP.Cipher(alg='aes_128_cbc', key=unhexlify(test['KEY']), iv=unhexlify(test['IV']), op=dec)
+ pbuf=cStringIO.StringIO()
+ cbuf=cStringIO.StringIO(unhexlify(test['CT'] + cipherpadding))
+ plaintext=self.cipher_filter(j, cbuf, pbuf)
+ pbuf.close()
+ cbuf.close()
+ self.assertEqual(plaintext, test['PT'])
+
+ # Test without padding
+ for test in tests:
+ # encrypt
+ k=EVP.Cipher(alg='aes_128_cbc', key=unhexlify(test['KEY']), iv=unhexlify(test['IV']), op=enc, padding=False)
+ pbuf=cStringIO.StringIO(test['PT'])
+ cbuf=cStringIO.StringIO()
+ ciphertext = hexlify(self.cipher_filter(k, pbuf, cbuf))
+ pbuf.close()
+ cbuf.close()
+ self.assertEqual(ciphertext, test['CT'])
+
+ # decrypt
+ j=EVP.Cipher(alg='aes_128_cbc', key=unhexlify(test['KEY']), iv=unhexlify(test['IV']), op=dec, padding=False)
+ pbuf=cStringIO.StringIO()
+ cbuf=cStringIO.StringIO(unhexlify(test['CT']))
+ plaintext=self.cipher_filter(j, cbuf, pbuf)
+ pbuf.close()
+ cbuf.close()
+ self.assertEqual(plaintext, test['PT'])
+
+
+ def test_raises(self):
+ def _cipherFilter(cipher, inf, outf):
+ while 1:
+ buf = inf.read()
+ if not buf:
+ break
+ outf.write(cipher.update(buf))
+ outf.write(cipher.final())
+ return outf.getvalue()
+
+ def decrypt(ciphertext, key, iv, alg='aes_256_cbc'):
+ cipher = EVP.Cipher(alg=alg, key=key, iv=iv, op=0)
+ pbuf = cStringIO.StringIO()
+ cbuf = cStringIO.StringIO(ciphertext)
+ plaintext = _cipherFilter(cipher, cbuf, pbuf)
+ pbuf.close()
+ cbuf.close()
+ return plaintext
+
+ self.assertRaises(EVP.EVPError, decrypt,
+ unhexlify('941d3647a642fab26d9f99a195098b91252c652d07235b9db35758c401627711724637648e45cad0f1121751a1240a4134998cfdf3c4a95c72de2a2444de3f9e40d881d7f205630b0d8ce142fdaebd8d7fbab2aea3dc47f5f29a0e9b55aae59222671d8e2877e1fb5cd8ef1c427027e0'),
+ unhexlify('5f2cc54067f779f74d3cf1f78c735aec404c8c3a4aaaa02eb1946f595ea4cddb'),
+ unhexlify('0001efa4bd154ee415b9413a421cedf04359fff945a30e7c115465b1c780a85b65c0e45c'))
+
+ self.assertRaises(EVP.EVPError, decrypt,
+ unhexlify('a78a510416c1a6f1b48077cc9eeb4287dcf8c5d3179ef80136c18876d774570d'),
+ unhexlify('5cd148eeaf680d4ff933aed83009cad4110162f53ef89fd44fad09611b0524d4'),
+ unhexlify(''))
+
+
+class PBKDF2TestCase(unittest.TestCase):
+ def test_rfc3211_test_vectors(self):
+ from binascii import hexlify, unhexlify
+
+ password = 'password'
+ salt = unhexlify('12 34 56 78 78 56 34 12'.replace(' ', ''))
+ iter = 5
+ keylen = 8
+ ret = EVP.pbkdf2(password, salt, iter, keylen)
+ self.assertEqual(hexlify(ret), 'D1 DA A7 86 15 F2 87 E6'.replace(' ', '').lower())
+
+ password = 'All n-entities must communicate with other n-entities via n-1 entiteeheehees'
+ salt = unhexlify('12 34 56 78 78 56 34 12'.replace(' ', ''))
+ iter = 500
+ keylen = 16
+ ret = EVP.pbkdf2(password, salt, iter, keylen)
+ self.assertEqual(hexlify(ret), '6A 89 70 BF 68 C9 2C AE A8 4A 8D F2 85 10 85 86'.replace(' ', '').lower())
+
+
+class HMACTestCase(unittest.TestCase):
+ data1=['', 'More text test vectors to stuff up EBCDIC machines :-)', \
+ h2b("e9139d1e6ee064ef8cf514fc7dc83e86")]
+
+ data2=[h2b('0b'*16), "Hi There", \
+ h2b("9294727a3638bb1c13f48ef8158bfc9d")]
+
+ data3=['Jefe', "what do ya want for nothing?", \
+ h2b("750c783e6ab0b503eaa86e310a5db738")]
+
+ data4=[h2b('aa'*16), h2b('dd'*50), \
+ h2b("0x56be34521d144c88dbb8c733f0e8b3f6")]
+
+ data=[data1, data2, data3, data4]
+
+ def test_simple(self):
+ algo = 'md5'
+ for d in self.data:
+ h = EVP.HMAC(d[0], algo)
+ h.update(d[1])
+ ret = h.final()
+ self.assertEqual(ret, d[2])
+ self.assertRaises(ValueError, EVP.HMAC, d[0], algo='nosuchalgo')
+
+ def make_chain_HMAC(self, key, start, input, algo='sha1'):
+ chain = []
+ hmac = EVP.HMAC(key, algo)
+ hmac.update(`start`)
+ digest = hmac.final()
+ chain.append((digest, start))
+ for i in input:
+ hmac.reset(digest)
+ hmac.update(`i`)
+ digest = hmac.final()
+ chain.append((digest, i))
+ return chain
+
+ def make_chain_hmac(self, key, start, input, algo='sha1'):
+ from M2Crypto.EVP import hmac
+ chain = []
+ digest = hmac(key, `start`, algo)
+ chain.append((digest, start))
+ for i in input:
+ digest = hmac(digest, `i`, algo)
+ chain.append((digest, i))
+ return chain
+
+ def verify_chain_hmac(self, key, start, chain, algo='sha1'):
+ from M2Crypto.EVP import hmac
+ digest = hmac(key, `start`, algo)
+ c = chain[0]
+ if c[0] != digest or c[1] != start:
+ return 0
+ for d, v in chain[1:]:
+ digest = hmac(digest, `v`, algo)
+ if digest != d:
+ return 0
+ return 1
+
+ def verify_chain_HMAC(self, key, start, chain, algo='sha1'):
+ hmac = EVP.HMAC(key, algo)
+ hmac.update(`start`)
+ digest = hmac.final()
+ c = chain[0]
+ if c[0] != digest or c[1] != start:
+ return 0
+ for d, v in chain[1:]:
+ hmac.reset(digest)
+ hmac.update(`v`)
+ digest = hmac.final()
+ if digest != d:
+ return 0
+ return 1
+
+ def test_complicated(self):
+ make_chain = self.make_chain_hmac
+ verify_chain = self.verify_chain_hmac
+ key = 'numero uno'
+ start = 'zeroth item'
+ input = ['first item', 'go go go', 'fly fly fly']
+ chain = make_chain(key, start, input)
+ self.assertEquals(verify_chain('some key', start, chain), 0)
+ self.assertEquals(verify_chain(key, start, chain), 1)
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(EVPTestCase))
+ suite.addTest(unittest.makeSuite(CipherTestCase))
+ suite.addTest(unittest.makeSuite(PBKDF2TestCase))
+ suite.addTest(unittest.makeSuite(HMACTestCase))
+ return suite
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+
diff --git a/tests/test_obj.py b/tests/test_obj.py
new file mode 100644
index 0000000..9144135
--- /dev/null
+++ b/tests/test_obj.py
@@ -0,0 +1,92 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.m2 obj_* functions.
+"""
+
+import unittest
+from M2Crypto import X509, ASN1, BIO, Rand, m2
+
+"""
+These functions must be cleaned up and moved to some python module
+Taken from CA managment code
+"""
+
+def x509_name2list(name):
+ for i in range(0, name.entry_count()):
+ yield X509.X509_Name_Entry(m2.x509_name_get_entry(name._ptr(), i), _pyfree = 0)
+
+def x509_name_entry2tuple(entry):
+ bio = BIO.MemoryBuffer()
+ m2.asn1_string_print(bio._ptr(), m2.x509_name_entry_get_data(entry._ptr()))
+ return (m2.obj_obj2txt(m2.x509_name_entry_get_object(entry._ptr()), 0), bio.getvalue())
+
+def tuple2x509_name_entry(tup):
+ obj, data = tup
+ _x509_ne = m2.x509_name_entry_create_by_txt(None, obj, ASN1.MBSTRING_ASC, data, len(data))
+ if not _x509_ne:
+ raise ValueError("Invalid object indentifier: %s" % obj)
+ return X509.X509_Name_Entry(_x509_ne, _pyfree = 1) # Prevent memory leaks
+
+class ObjectsTestCase(unittest.TestCase):
+
+ def callback(self, *args):
+ pass
+
+ def test_obj2txt(self):
+ assert m2.obj_obj2txt(m2.obj_txt2obj("commonName", 0), 1) == "2.5.4.3", "2.5.4.3"
+ assert m2.obj_obj2txt(m2.obj_txt2obj("commonName", 0), 0) == "commonName", "commonName"
+
+ def test_nid(self):
+ assert m2.obj_ln2nid("commonName") == m2.obj_txt2nid("2.5.4.3"), "ln2nid and txt2nid mismatch"
+ assert m2.obj_ln2nid("CN") == 0, "ln2nid on sn"
+ assert m2.obj_sn2nid("CN") == m2.obj_ln2nid("commonName"), "ln2nid and sn2nid mismatch"
+ assert m2.obj_sn2nid("CN") == m2.obj_obj2nid(m2.obj_txt2obj("CN", 0)), "obj2nid"
+ assert m2.obj_txt2nid("__unknown") == 0, "__unknown"
+
+ def test_tuple2tuple(self):
+ tup = ("CN", "someCommonName")
+ tup1 = x509_name_entry2tuple(tuple2x509_name_entry(tup))
+ assert tup1[1] == tup[1], tup1 # tup1[0] is 'commonName', not 'CN'
+ assert x509_name_entry2tuple(tuple2x509_name_entry(tup1)) == tup1, tup1
+
+ def test_unknown(self):
+ self.assertRaises(ValueError, tuple2x509_name_entry, ("__unknown", "_"))
+
+ def test_x509_name(self):
+ n = X509.X509_Name()
+ n.C = 'US' # It seems this actually needs to be a real 2 letter country code
+ n.SP = 'State or Province'
+ n.L = 'locality name'
+ n.O = 'orhanization name'
+ n.OU = 'org unit'
+ n.CN = 'common name'
+ n.Email = 'bob@example.com'
+ n.serialNumber = '1234'
+ n.SN = 'surname'
+ n.GN = 'given name'
+
+ n.givenName = 'name given'
+ assert len(n) == 11, len(n)
+
+ tl = map(x509_name_entry2tuple, x509_name2list(n))
+
+ assert len(tl) == len(n), len(tl)
+
+ x509_n = m2.x509_name_new()
+ for o in map(tuple2x509_name_entry, tl):
+ m2.x509_name_add_entry(x509_n, o._ptr(), -1, 0)
+ o._pyfree = 0 # Take care of underlying object
+ n1 = X509.X509_Name(x509_n)
+
+ assert n.as_text() == n1.as_text(), n1.as_text()
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(ObjectsTestCase))
+ return suite
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
diff --git a/tests/test_pgp.py b/tests/test_pgp.py
new file mode 100644
index 0000000..c86d153
--- /dev/null
+++ b/tests/test_pgp.py
@@ -0,0 +1,40 @@
+#!/usr/bin/env python
+
+"""PGP test program.
+
+Copyright (c) 1999 Ng Pheng Siong. All rights reserved."""
+
+import unittest
+from M2Crypto import EVP, PGP
+from cStringIO import StringIO
+
+
+class PGPTestCase(unittest.TestCase):
+
+ def test_simple(self):
+ pkr = PGP.load_pubring('tests/pubring.pgp')
+ daft = pkr['daft']
+ daft_pkt = daft._pubkey_pkt.pack()
+ s1 = EVP.MessageDigest('sha1')
+ s1.update(daft_pkt)
+ s1f = `s1.final()`
+
+ buf = StringIO(daft_pkt)
+ ps = PGP.packet_stream(buf)
+ dift_pkt = ps.read()
+ s2 = EVP.MessageDigest('sha1')
+ s2.update(dift_pkt.pack())
+ s2f = `s2.final()`
+
+ self.assertEqual(s1f, s2f)
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(PGPTestCase))
+ return suite
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
diff --git a/tests/test_rand.py b/tests/test_rand.py
new file mode 100644
index 0000000..3e4d65d
--- /dev/null
+++ b/tests/test_rand.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.Rand.
+
+Copyright (C) 2006 Open Source Applications Foundation (OSAF). All Rights Reserved.
+"""
+
+import unittest
+import os, sys
+from M2Crypto import Rand
+
+class RandTestCase(unittest.TestCase):
+ def test_bytes(self):
+ self.assertRaises(MemoryError, Rand.rand_bytes, -1)
+ assert Rand.rand_bytes(0) == ''
+ assert len(Rand.rand_bytes(1)) == 1
+
+ def test_pseudo_bytes(self):
+ self.assertRaises(MemoryError, Rand.rand_pseudo_bytes, -1)
+ assert Rand.rand_pseudo_bytes(0) == ('', 1)
+ a, b = Rand.rand_pseudo_bytes(1)
+ assert len(a) == 1
+ assert b == 1
+
+ def test_load_save(self):
+ try:
+ os.remove('tests/randpool.dat')
+ except OSError:
+ pass
+ assert Rand.load_file('tests/randpool.dat', -1) == 0
+ assert Rand.save_file('tests/randpool.dat') == 1024
+ assert Rand.load_file('tests/randpool.dat', -1) == 1024
+
+ def test_seed_add(self):
+ if sys.version_info >= (2, 4):
+ assert Rand.rand_seed(os.urandom(1024)) is None
+
+ # XXX Should there be limits on the entropy parameter?
+ assert Rand.rand_add(os.urandom(2), 0.5) is None
+ Rand.rand_add(os.urandom(2), -0.5)
+ Rand.rand_add(os.urandom(2), 5000.0)
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(RandTestCase))
+ return suite
+
+
+if __name__ == '__main__':
+ unittest.TextTestRunner().run(suite())
diff --git a/tests/test_rc4.py b/tests/test_rc4.py
new file mode 100644
index 0000000..5a2d239
--- /dev/null
+++ b/tests/test_rc4.py
@@ -0,0 +1,41 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.RC4.
+
+Copyright (c) 2009 Heikki Toivonen. All rights reserved."""
+
+import unittest
+from binascii import hexlify
+from M2Crypto import RC4
+
+class RC4TestCase(unittest.TestCase):
+
+ def test_vectors(self):
+ """
+ Test with test vectors from Wikipedia: http://en.wikipedia.org/wiki/Rc4
+ """
+ vectors = (('Key', 'Plaintext', 'BBF316E8D940AF0AD3'),
+ ('Wiki', 'pedia', '1021BF0420'),
+ ('Secret', 'Attack at dawn', '45A01F645FC35B383552544B9BF5'))
+
+ rc4 = RC4.RC4()
+ for key, plaintext, ciphertext in vectors:
+ rc4.set_key(key)
+ self.assertEqual(hexlify(rc4.update(plaintext)).upper(), ciphertext)
+
+ self.assertEqual(rc4.final(), '')
+
+ def test_bad(self):
+ rc4 = RC4.RC4('foo')
+ self.assertNotEqual(hexlify(rc4.update('bar')).upper(), '45678')
+
+
+def suite():
+ return unittest.makeSuite(RC4TestCase)
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+
diff --git a/tests/test_rsa.py b/tests/test_rsa.py
new file mode 100644
index 0000000..0bb986b
--- /dev/null
+++ b/tests/test_rsa.py
@@ -0,0 +1,285 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.RSA.
+
+Copyright (c) 2000 Ng Pheng Siong. All rights reserved."""
+
+import unittest
+import sha, md5, os, sys
+from M2Crypto import RSA, BIO, Rand, m2, EVP, X509
+
+class RSATestCase(unittest.TestCase):
+
+ errkey = 'tests/dsa.priv.pem'
+ privkey = 'tests/rsa.priv.pem'
+ privkey2 = 'tests/rsa.priv2.pem'
+ pubkey = 'tests/rsa.pub.pem'
+
+ data = sha.sha('The magic words are squeamish ossifrage.').digest()
+
+ e_padding_ok = ('pkcs1_padding', 'pkcs1_oaep_padding')
+
+ s_padding_ok = ('pkcs1_padding',)
+ s_padding_nok = ('no_padding', 'sslv23_padding', 'pkcs1_oaep_padding')
+
+ def gen_callback(self, *args):
+ pass
+
+ def gen2_callback(self):
+ pass
+
+ def pp_callback(self, *args):
+ # The passphrase for rsa.priv2.pem is 'qwerty'.
+ return 'qwerty'
+
+ def pp2_callback(self, *args):
+ # Misbehaving passphrase callback.
+ pass
+
+ def test_loadkey_junk(self):
+ self.assertRaises(RSA.RSAError, RSA.load_key, self.errkey)
+
+ def test_loadkey_pp(self):
+ rsa = RSA.load_key(self.privkey2, self.pp_callback)
+ assert len(rsa) == 1024
+ assert rsa.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+ assert rsa.check_key() == 1
+
+ def test_loadkey_pp_bad_cb(self):
+ self.assertRaises(RSA.RSAError, RSA.load_key, self.privkey2, self.pp2_callback)
+
+ def test_loadkey(self):
+ rsa = RSA.load_key(self.privkey)
+ assert len(rsa) == 1024
+ assert rsa.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+ self.assertEqual(rsa.n, "\x00\x00\x00\x81\x00\xcde!\x15\xdah\xb5`\xce[\xd6\x17d\xba8\xc1I\xb1\xf1\xber\x86K\xc7\xda\xb3\x98\xd6\xf6\x80\xae\xaa\x8f!\x9a\xefQ\xdeh\xbb\xc5\x99\x01o\xebGO\x8e\x9b\x9a\x18\xfb6\xba\x12\xfc\xf2\x17\r$\x00\xa1\x1a \xfc/\x13iUm\x04\x13\x0f\x91D~\xbf\x08\x19C\x1a\xe2\xa3\x91&\x8f\xcf\xcc\xf3\xa4HRf\xaf\xf2\x19\xbd\x05\xe36\x9a\xbbQ\xc86|(\xad\x83\xf2Eu\xb2EL\xdf\xa4@\x7f\xeel|\xfcU\x03\xdb\x89'")
+ self.assertRaises(AttributeError, getattr, rsa, 'nosuchprop')
+ assert rsa.check_key() == 1
+
+ def test_loadkey_bio(self):
+ keybio = BIO.MemoryBuffer(open(self.privkey).read())
+ rsa = RSA.load_key_bio(keybio)
+ assert len(rsa) == 1024
+ assert rsa.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+ assert rsa.check_key() == 1
+
+ def test_keygen(self):
+ rsa = RSA.gen_key(1024, 65537, self.gen_callback)
+ assert len(rsa) == 1024
+ assert rsa.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+ assert rsa.check_key() == 1
+
+ def test_keygen_bad_cb(self):
+ rsa = RSA.gen_key(1024, 65537, self.gen2_callback)
+ assert len(rsa) == 1024
+ assert rsa.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+ assert rsa.check_key() == 1
+
+ def test_private_encrypt(self):
+ priv = RSA.load_key(self.privkey)
+ # pkcs1_padding
+ for padding in self.s_padding_ok:
+ p = getattr(RSA, padding)
+ ctxt = priv.private_encrypt(self.data, p)
+ ptxt = priv.public_decrypt(ctxt, p)
+ assert ptxt == self.data
+ # The other paddings.
+ for padding in self.s_padding_nok:
+ p = getattr(RSA, padding)
+ self.assertRaises(RSA.RSAError, priv.private_encrypt, self.data, p)
+ # Type-check the data to be encrypted.
+ self.assertRaises(TypeError, priv.private_encrypt, self.gen_callback, RSA.pkcs1_padding)
+
+ def test_public_encrypt(self):
+ priv = RSA.load_key(self.privkey)
+ # pkcs1_padding, pkcs1_oaep_padding
+ for padding in self.e_padding_ok:
+ p = getattr(RSA, padding)
+ ctxt = priv.public_encrypt(self.data, p)
+ ptxt = priv.private_decrypt(ctxt, p)
+ assert ptxt == self.data
+ # sslv23_padding
+ ctxt = priv.public_encrypt(self.data, RSA.sslv23_padding)
+ self.assertRaises(RSA.RSAError, priv.private_decrypt, ctxt, RSA.sslv23_padding)
+ # no_padding
+ self.assertRaises(RSA.RSAError, priv.public_encrypt, self.data, RSA.no_padding)
+ # Type-check the data to be encrypted.
+ self.assertRaises(TypeError, priv.public_encrypt, self.gen_callback, RSA.pkcs1_padding)
+
+ def test_x509_public_encrypt(self):
+ x509 = X509.load_cert("tests/recipient.pem")
+ rsa = x509.get_pubkey().get_rsa()
+ rsa.public_encrypt("data", RSA.pkcs1_padding)
+
+ def test_loadpub(self):
+ rsa = RSA.load_pub_key(self.pubkey)
+ assert len(rsa) == 1024
+ assert rsa.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+ self.assertRaises(RSA.RSAError, setattr, rsa, 'e', '\000\000\000\003\001\000\001')
+ self.assertRaises(RSA.RSAError, rsa.private_encrypt, 1)
+ self.assertRaises(RSA.RSAError, rsa.private_decrypt, 1)
+ assert rsa.check_key()
+
+ def test_loadpub_bad(self):
+ self.assertRaises(RSA.RSAError, RSA.load_pub_key, self.errkey)
+
+ def test_savepub(self):
+ rsa = RSA.load_pub_key(self.pubkey)
+ assert rsa.as_pem() # calls save_key_bio
+ f = 'tests/rsa_test.pub'
+ try:
+ self.assertEquals(rsa.save_key(f), 1)
+ finally:
+ try:
+ os.remove(f)
+ except IOError:
+ pass
+
+ def test_set_bn(self):
+ rsa = RSA.load_pub_key(self.pubkey)
+ assert m2.rsa_set_e(rsa.rsa, '\000\000\000\003\001\000\001') is None
+ self.assertRaises(RSA.RSAError, m2.rsa_set_e, rsa.rsa, '\000\000\000\003\001')
+
+ def test_newpub(self):
+ old = RSA.load_pub_key(self.pubkey)
+ new = RSA.new_pub_key(old.pub())
+ assert new.check_key()
+ assert len(new) == 1024
+ assert new.e == '\000\000\000\003\001\000\001' # aka 65537 aka 0xf4
+
+ def test_sign_and_verify(self):
+ """
+ Testing signing and verifying digests
+ """
+ algos = {'sha1':'',
+ 'ripemd160':'',
+ 'md5':''}
+
+ if m2.OPENSSL_VERSION_NUMBER >= 0x90800F:
+ algos['sha224'] = ''
+ algos['sha256'] = ''
+ algos['sha384'] = ''
+ algos['sha512'] = ''
+
+ message = "This is the message string"
+ digest = sha.sha(message).digest()
+ rsa = RSA.load_key(self.privkey)
+ rsa2 = RSA.load_pub_key(self.pubkey)
+ for algo in algos.keys():
+ signature = rsa.sign(digest, algo)
+ #assert signature == algos[algo], 'mismatched signature with algorithm %s: signature=%s' % (algo, signature)
+ verify = rsa2.verify(digest, signature, algo)
+ assert verify == 1, 'verification failed with algorithm %s' % algo
+
+ if m2.OPENSSL_VERSION_NUMBER >= 0x90708F:
+ def test_sign_and_verify_rsassa_pss(self):
+ """
+ Testing signing and verifying using rsassa_pss
+
+ The maximum size of the salt has to decrease as the
+ size of the digest increases because of the size of
+ our test key limits it.
+ """
+ message = "This is the message string"
+ if sys.version_info < (2, 5):
+ algos = {'sha1': (43, sha.sha(message).digest()),
+ 'md5': (47, md5.md5(message).digest())}
+
+ else:
+ import hashlib
+ algos = {'sha1': 43,
+ 'ripemd160': 43,
+ 'md5': 47}
+
+ if m2.OPENSSL_VERSION_NUMBER >= 0x90800F:
+ algos['sha224'] = 35
+ algos['sha256'] = 31
+ algos['sha384'] = 15
+ algos['sha512'] = 0
+
+ for algo, salt_max in algos.iteritems():
+ h = hashlib.new(algo)
+ h.update(message)
+ digest = h.digest()
+ algos[algo] = (salt_max, digest)
+
+ rsa = RSA.load_key(self.privkey)
+ rsa2 = RSA.load_pub_key(self.pubkey)
+ for algo, (salt_max, digest) in algos.iteritems():
+ for salt_length in range(0, salt_max):
+ signature = rsa.sign_rsassa_pss(digest, algo, salt_length)
+ verify = rsa2.verify_rsassa_pss(digest, signature, algo, salt_length)
+ assert verify == 1, 'verification failed with algorithm %s salt length %d' % (algo, salt_length)
+
+ def test_sign_bad_method(self):
+ """
+ Testing calling sign with an unsupported message digest algorithm
+ """
+ rsa = RSA.load_key(self.privkey)
+ message = "This is the message string"
+ digest = md5.md5(message).digest()
+ self.assertRaises(ValueError, rsa.sign,
+ digest, 'bad_digest_method')
+
+ def test_verify_bad_method(self):
+ """
+ Testing calling verify with an unsupported message digest algorithm
+ """
+ rsa = RSA.load_key(self.privkey)
+ message = "This is the message string"
+ digest = md5.md5(message).digest()
+ signature = rsa.sign(digest, 'sha1')
+ self.assertRaises(ValueError, rsa.verify,
+ digest, signature, 'bad_digest_method')
+
+ def test_verify_mismatched_algo(self):
+ """
+ Testing verify to make sure it fails when we use a different
+ message digest algorithm
+ """
+ rsa = RSA.load_key(self.privkey)
+ message = "This is the message string"
+ digest = sha.sha(message).digest()
+ signature = rsa.sign(digest, 'sha1')
+ rsa2 = RSA.load_pub_key(self.pubkey)
+ self.assertRaises(RSA.RSAError, rsa.verify,
+ digest, signature, 'md5')
+
+ def test_sign_fail(self):
+ """
+ Testing sign to make sure it fails when I give it
+ a bogus digest. Looking at the RSA sign method
+ I discovered that with the digest methods we use
+ it has to be longer than a certain length.
+ """
+ rsa = RSA.load_key(self.privkey)
+ digest = """This string should be long enough to warrant an error in
+ RSA_sign""" * 2
+
+ self.assertRaises(RSA.RSAError, rsa.sign, digest)
+
+ def test_verify_bad_signature(self):
+ """
+ Testing verify to make sure it fails when we use a bad signature
+ """
+ rsa = RSA.load_key(self.privkey)
+ message = "This is the message string"
+ digest = sha.sha(message).digest()
+
+ otherMessage = "Abracadabra"
+ otherDigest = sha.sha(otherMessage).digest()
+ otherSignature = rsa.sign(otherDigest)
+
+ self.assertRaises(RSA.RSAError, rsa.verify,
+ digest, otherSignature)
+
+
+def suite():
+ return unittest.makeSuite(RSATestCase)
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+
diff --git a/tests/test_smime.py b/tests/test_smime.py
new file mode 100644
index 0000000..f18c9db
--- /dev/null
+++ b/tests/test_smime.py
@@ -0,0 +1,262 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.SMIME.
+
+Copyright (C) 2006 Open Source Applications Foundation. All Rights Reserved.
+"""
+
+import unittest
+from M2Crypto import SMIME, BIO, Rand, X509, EVP
+
+class SMIMETestCase(unittest.TestCase):
+ cleartext = 'some text to manipulate'
+
+ def setUp(self):
+ # XXX Ugly, but not sure what would be better
+ self.signed = self.test_sign()
+ self.encrypted = self.test_encrypt()
+
+ def test_load_bad(self):
+ s = SMIME.SMIME()
+ self.assertRaises(EVP.EVPError, s.load_key,
+ 'tests/signer.pem',
+ 'tests/signer.pem')
+
+ self.assertRaises(BIO.BIOError, SMIME.load_pkcs7, 'nosuchfile-dfg456')
+ self.assertRaises(SMIME.PKCS7_Error, SMIME.load_pkcs7, 'tests/signer.pem')
+ self.assertRaises(SMIME.PKCS7_Error, SMIME.load_pkcs7_bio, BIO.MemoryBuffer('no pkcs7'))
+
+ self.assertRaises(SMIME.SMIME_Error, SMIME.smime_load_pkcs7, 'tests/signer.pem')
+ self.assertRaises(SMIME.SMIME_Error, SMIME.smime_load_pkcs7_bio, BIO.MemoryBuffer('no pkcs7'))
+
+ def test_crlf(self):
+ self.assertEqual(SMIME.text_crlf('foobar'), 'Content-Type: text/plain\r\n\r\nfoobar')
+ self.assertEqual(SMIME.text_crlf_bio(BIO.MemoryBuffer('foobar')).read(), 'Content-Type: text/plain\r\n\r\nfoobar')
+
+ def test_sign(self):
+ buf = BIO.MemoryBuffer(self.cleartext)
+ s = SMIME.SMIME()
+ s.load_key('tests/signer_key.pem', 'tests/signer.pem')
+ p7 = s.sign(buf, SMIME.PKCS7_DETACHED)
+ assert len(buf) == 0
+ assert p7.type() == SMIME.PKCS7_SIGNED, p7.type()
+ assert isinstance(p7, SMIME.PKCS7), p7
+ out = BIO.MemoryBuffer()
+ p7.write(out)
+
+ buf = out.read()
+
+ assert buf[:len('-----BEGIN PKCS7-----')] == '-----BEGIN PKCS7-----'
+ buf = buf.strip()
+ assert buf[-len('-----END PKCS7-----'):] == '-----END PKCS7-----', buf[-len('-----END PKCS7-----'):]
+ assert len(buf) > len('-----END PKCS7-----') + len('-----BEGIN PKCS7-----')
+
+ s.write(out, p7, BIO.MemoryBuffer(self.cleartext))
+ return out
+
+ def test_store_load_info(self):
+ st = X509.X509_Store()
+ self.assertRaises(X509.X509Error, st.load_info, 'tests/ca.pem-typoname')
+ self.assertEqual(st.load_info('tests/ca.pem'), 1)
+
+ def test_verify(self):
+ s = SMIME.SMIME()
+
+ x509 = X509.load_cert('tests/signer.pem')
+ sk = X509.X509_Stack()
+ sk.push(x509)
+ s.set_x509_stack(sk)
+
+ st = X509.X509_Store()
+ st.load_info('tests/ca.pem')
+ s.set_x509_store(st)
+
+ p7, data = SMIME.smime_load_pkcs7_bio(self.signed)
+
+ assert isinstance(p7, SMIME.PKCS7), p7
+ v = s.verify(p7, data)
+ assert v == self.cleartext
+
+ t = p7.get0_signers(sk)
+ assert len(t) == 1
+ assert t[0].as_pem() == x509.as_pem(), t[0].as_text()
+
+ def test_verifyBad(self):
+ s = SMIME.SMIME()
+
+ x509 = X509.load_cert('tests/recipient.pem')
+ sk = X509.X509_Stack()
+ sk.push(x509)
+ s.set_x509_stack(sk)
+
+ st = X509.X509_Store()
+ st.load_info('tests/recipient.pem')
+ s.set_x509_store(st)
+
+ p7, data = SMIME.smime_load_pkcs7_bio(self.signed)
+ assert isinstance(p7, SMIME.PKCS7), p7
+ self.assertRaises(SMIME.PKCS7_Error, s.verify, p7) # Bad signer
+
+ def test_encrypt(self):
+ buf = BIO.MemoryBuffer(self.cleartext)
+ s = SMIME.SMIME()
+
+ x509 = X509.load_cert('tests/recipient.pem')
+ sk = X509.X509_Stack()
+ sk.push(x509)
+ s.set_x509_stack(sk)
+
+ self.assertRaises(ValueError, SMIME.Cipher, 'nosuchcipher')
+
+ s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+ p7 = s.encrypt(buf)
+
+ assert len(buf) == 0
+ assert p7.type() == SMIME.PKCS7_ENVELOPED, p7.type()
+ assert isinstance(p7, SMIME.PKCS7), p7
+ out = BIO.MemoryBuffer()
+ p7.write(out)
+
+ buf = out.read()
+
+ assert buf[:len('-----BEGIN PKCS7-----')] == '-----BEGIN PKCS7-----'
+ buf = buf.strip()
+ assert buf[-len('-----END PKCS7-----'):] == '-----END PKCS7-----'
+ assert len(buf) > len('-----END PKCS7-----') + len('-----BEGIN PKCS7-----')
+
+ s.write(out, p7)
+ return out
+
+ def test_decrypt(self):
+ s = SMIME.SMIME()
+
+ s.load_key('tests/recipient_key.pem', 'tests/recipient.pem')
+
+ p7, data = SMIME.smime_load_pkcs7_bio(self.encrypted)
+ assert isinstance(p7, SMIME.PKCS7), p7
+ self.assertRaises(SMIME.SMIME_Error, s.verify, p7) # No signer
+
+ out = s.decrypt(p7)
+ assert out == self.cleartext
+
+ def test_decryptBad(self):
+ s = SMIME.SMIME()
+
+ s.load_key('tests/signer_key.pem', 'tests/signer.pem')
+
+ p7, data = SMIME.smime_load_pkcs7_bio(self.encrypted)
+ assert isinstance(p7, SMIME.PKCS7), p7
+ self.assertRaises(SMIME.SMIME_Error, s.verify, p7) # No signer
+
+ # Cannot decrypt: no recipient matches certificate
+ self.assertRaises(SMIME.PKCS7_Error, s.decrypt, p7)
+
+ def test_signEncryptDecryptVerify(self):
+ # sign
+ buf = BIO.MemoryBuffer(self.cleartext)
+ s = SMIME.SMIME()
+ s.load_key('tests/signer_key.pem', 'tests/signer.pem')
+ p7 = s.sign(buf)
+
+ # encrypt
+ x509 = X509.load_cert('tests/recipient.pem')
+ sk = X509.X509_Stack()
+ sk.push(x509)
+ s.set_x509_stack(sk)
+
+ s.set_cipher(SMIME.Cipher('des_ede3_cbc'))
+
+ tmp = BIO.MemoryBuffer()
+ s.write(tmp, p7)
+
+ p7 = s.encrypt(tmp)
+
+ signedEncrypted = BIO.MemoryBuffer()
+ s.write(signedEncrypted, p7)
+
+ # decrypt
+ s = SMIME.SMIME()
+
+ s.load_key('tests/recipient_key.pem', 'tests/recipient.pem')
+
+ p7, data = SMIME.smime_load_pkcs7_bio(signedEncrypted)
+
+ out = s.decrypt(p7)
+
+ # verify
+ x509 = X509.load_cert('tests/signer.pem')
+ sk = X509.X509_Stack()
+ sk.push(x509)
+ s.set_x509_stack(sk)
+
+ st = X509.X509_Store()
+ st.load_info('tests/ca.pem')
+ s.set_x509_store(st)
+
+ p7_bio = BIO.MemoryBuffer(out)
+ p7, data = SMIME.smime_load_pkcs7_bio(p7_bio)
+ v = s.verify(p7)
+ assert v == self.cleartext
+
+
+class WriteLoadTestCase(unittest.TestCase):
+ def setUp(self):
+ s = SMIME.SMIME()
+ s.load_key('tests/signer_key.pem', 'tests/signer.pem')
+ p7 = s.sign(BIO.MemoryBuffer('some text'))
+ self.filename = 'tests/sig.p7'
+ f = BIO.openfile(self.filename, 'wb')
+ assert p7.write(f) == 1
+ f.close()
+
+ p7 = s.sign(BIO.MemoryBuffer('some text'), SMIME.PKCS7_DETACHED)
+ self.filenameSmime = 'tests/sig.p7s'
+ f = BIO.openfile(self.filenameSmime, 'wb')
+ assert s.write(f, p7, BIO.MemoryBuffer('some text')) == 1
+ f.close()
+
+ def test_write_pkcs7_der(self):
+ buf = BIO.MemoryBuffer()
+ assert SMIME.load_pkcs7(self.filename).write_der(buf) == 1
+ s = buf.read()
+ assert len(s) in (1204, 1243), len(s)
+
+ def test_load_pkcs7(self):
+ assert SMIME.load_pkcs7(self.filename).type() == SMIME.PKCS7_SIGNED
+
+ def test_load_pkcs7_bio(self):
+ f = open(self.filename, 'rb')
+ buf = BIO.MemoryBuffer(f.read())
+ f.close()
+
+ assert SMIME.load_pkcs7_bio(buf).type() == SMIME.PKCS7_SIGNED
+
+ def test_load_smime(self):
+ a, b = SMIME.smime_load_pkcs7(self.filenameSmime)
+ assert isinstance(a, SMIME.PKCS7), a
+ assert isinstance(b, BIO.BIO), b
+ assert a.type() == SMIME.PKCS7_SIGNED
+
+ def test_load_smime_bio(self):
+ f = open(self.filenameSmime, 'rb')
+ buf = BIO.MemoryBuffer(f.read())
+ f.close()
+
+ a, b = SMIME.smime_load_pkcs7_bio(buf)
+ assert isinstance(a, SMIME.PKCS7), a
+ assert isinstance(b, BIO.BIO), b
+ assert a.type() == SMIME.PKCS7_SIGNED
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(SMIMETestCase))
+ suite.addTest(unittest.makeSuite(WriteLoadTestCase))
+ return suite
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+
diff --git a/tests/test_ssl.py b/tests/test_ssl.py
new file mode 100644
index 0000000..701484d
--- /dev/null
+++ b/tests/test_ssl.py
@@ -0,0 +1,1108 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.SSL.
+
+Copyright (c) 2000-2004 Ng Pheng Siong. All rights reserved.
+
+Copyright (c) 2009-2010 Heikki Toivonen. All rights reserved.
+"""
+
+"""
+TODO
+
+Server tests:
+- ???
+
+Others:
+- ssl_dispatcher
+- SSLServer
+- ForkingSSLServer
+- ThreadingSSLServer
+"""
+
+import os, socket, string, sys, tempfile, thread, time, unittest
+from M2Crypto import Rand, SSL, m2, Err
+
+from fips import fips_mode
+
+srv_host = 'localhost'
+srv_port = 64000
+
+def verify_cb_new_function(ok, store):
+ try:
+ assert not ok
+ err = store.get_error()
+ assert err in [m2.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT,
+ m2.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY,
+ m2.X509_V_ERR_CERT_UNTRUSTED,
+ m2.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE]
+ assert store.get_error_depth() == 0
+ app_data = m2.x509_store_ctx_get_app_data(store.ctx)
+ assert app_data
+ x509 = store.get_current_cert()
+ assert x509
+ stack = store.get1_chain()
+ assert len(stack) == 1
+ assert stack[0].as_pem() == x509.as_pem()
+ except AssertionError, e:
+ # If we let exceptions propagate from here the
+ # caller may see strange errors. This is cleaner.
+ return 0
+ return 1
+
+class VerifyCB:
+ def __call__(self, ok, store):
+ return verify_cb_new_function(ok, store)
+
+sleepTime = float(os.getenv('M2CRYPTO_TEST_SSL_SLEEP', 0.5))
+
+def find_openssl():
+ if os.name == 'nt' or sys.platform == 'cygwin':
+ openssl = 'openssl.exe'
+ else:
+ openssl = 'openssl'
+
+ plist = os.environ['PATH'].split(os.pathsep)
+ for p in plist:
+ try:
+ dir = os.listdir(p)
+ if openssl in dir:
+ return True
+ except:
+ pass
+ return False
+
+class BaseSSLClientTestCase(unittest.TestCase):
+
+ openssl_in_path = find_openssl()
+
+ def start_server(self, args):
+ if not self.openssl_in_path:
+ raise Exception('openssl command not in PATH')
+
+ pid = os.fork()
+ if pid == 0:
+ # openssl must be started in the tests directory for it
+ # to find the .pem files
+ os.chdir('tests')
+ try:
+ os.execvp('openssl', args)
+ finally:
+ os.chdir('..')
+
+ else:
+ time.sleep(sleepTime)
+ return pid
+
+ def stop_server(self, pid):
+ os.kill(pid, 1)
+ os.waitpid(pid, 0)
+
+ def http_get(self, s):
+ s.send('GET / HTTP/1.0\n\n')
+ resp = ''
+ while 1:
+ try:
+ r = s.recv(4096)
+ if not r:
+ break
+ except SSL.SSLError: # s_server throws an 'unexpected eof'...
+ break
+ resp = resp + r
+ return resp
+
+ def setUp(self):
+ self.srv_host = srv_host
+ self.srv_port = srv_port
+ self.srv_addr = (srv_host, srv_port)
+ self.srv_url = 'https://%s:%s/' % (srv_host, srv_port)
+ self.args = ['s_server', '-quiet', '-www',
+ #'-cert', 'server.pem', Implicitly using this
+ '-accept', str(self.srv_port)]
+
+ def tearDown(self):
+ global srv_port
+ srv_port = srv_port - 1
+
+
+class PassSSLClientTestCase(BaseSSLClientTestCase):
+
+ def test_pass(self):
+ pass
+
+class HttpslibSSLClientTestCase(BaseSSLClientTestCase):
+
+ def test_HTTPSConnection(self):
+ pid = self.start_server(self.args)
+ try:
+ from M2Crypto import httpslib
+ c = httpslib.HTTPSConnection(srv_host, srv_port)
+ c.request('GET', '/')
+ data = c.getresponse().read()
+ c.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_HTTPSConnection_resume_session(self):
+ pid = self.start_server(self.args)
+ try:
+ from M2Crypto import httpslib
+ ctx = SSL.Context()
+ ctx.load_verify_locations(cafile='tests/ca.pem')
+ ctx.load_cert('tests/x509.pem')
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 1)
+ ctx.set_session_cache_mode(m2.SSL_SESS_CACHE_CLIENT)
+ c = httpslib.HTTPSConnection(srv_host, srv_port, ssl_context=ctx)
+ c.request('GET', '/')
+ ses = c.get_session()
+ t = ses.as_text()
+ data = c.getresponse().read()
+ # Appearently closing connection here screws session; Ali Polatel?
+ # c.close()
+
+ ctx2 = SSL.Context()
+ ctx2.load_verify_locations(cafile='tests/ca.pem')
+ ctx2.load_cert('tests/x509.pem')
+ ctx2.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 1)
+ ctx2.set_session_cache_mode(m2.SSL_SESS_CACHE_CLIENT)
+ c2 = httpslib.HTTPSConnection(srv_host, srv_port, ssl_context=ctx2)
+ c2.set_session(ses)
+ c2.request('GET', '/')
+ ses2 = c2.get_session()
+ t2 = ses2.as_text()
+ data = c2.getresponse().read()
+ c.close()
+ c2.close()
+ assert t == t2, "Sessions did not match"
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_HTTPSConnection_secure_context(self):
+ pid = self.start_server(self.args)
+ try:
+ from M2Crypto import httpslib
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/ca.pem')
+ c = httpslib.HTTPSConnection(srv_host, srv_port, ssl_context=ctx)
+ c.request('GET', '/')
+ data = c.getresponse().read()
+ c.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_HTTPSConnection_secure_context_fail(self):
+ pid = self.start_server(self.args)
+ try:
+ from M2Crypto import httpslib
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/server.pem')
+ c = httpslib.HTTPSConnection(srv_host, srv_port, ssl_context=ctx)
+ self.assertRaises(SSL.SSLError, c.request, 'GET', '/')
+ c.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_HTTPS(self):
+ pid = self.start_server(self.args)
+ try:
+ from M2Crypto import httpslib
+ c = httpslib.HTTPS(srv_host, srv_port)
+ c.putrequest('GET', '/')
+ c.putheader('Accept', 'text/html')
+ c.putheader('Accept', 'text/plain')
+ c.endheaders()
+ err, msg, headers = c.getreply()
+ assert err == 200, err
+ f = c.getfile()
+ data = f.read()
+ c.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_HTTPS_secure_context(self):
+ pid = self.start_server(self.args)
+ try:
+ from M2Crypto import httpslib
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/ca.pem')
+ c = httpslib.HTTPS(srv_host, srv_port, ssl_context=ctx)
+ c.putrequest('GET', '/')
+ c.putheader('Accept', 'text/html')
+ c.putheader('Accept', 'text/plain')
+ c.endheaders()
+ err, msg, headers = c.getreply()
+ assert err == 200, err
+ f = c.getfile()
+ data = f.read()
+ c.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_HTTPS_secure_context_fail(self):
+ pid = self.start_server(self.args)
+ try:
+ from M2Crypto import httpslib
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/server.pem')
+ c = httpslib.HTTPS(srv_host, srv_port, ssl_context=ctx)
+ c.putrequest('GET', '/')
+ c.putheader('Accept', 'text/html')
+ c.putheader('Accept', 'text/plain')
+ self.assertRaises(SSL.SSLError, c.endheaders)
+ c.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_HTTPSConnection_illegalkeywordarg(self):
+ from M2Crypto import httpslib
+ self.assertRaises(ValueError, httpslib.HTTPSConnection, 'example.org',
+ badKeyword=True)
+
+
+class MiscSSLClientTestCase(BaseSSLClientTestCase):
+
+ def test_no_connection(self):
+ ctx = SSL.Context()
+ s = SSL.Connection(ctx)
+
+ def test_server_simple(self):
+ pid = self.start_server(self.args)
+ try:
+ self.assertRaises(ValueError, SSL.Context, 'tlsv5')
+ ctx = SSL.Context()
+ s = SSL.Connection(ctx)
+ s.connect(self.srv_addr)
+ self.assertRaises(ValueError, s.read, 0)
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_server_simple_secure_context(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/ca.pem')
+ s = SSL.Connection(ctx)
+ s.connect(self.srv_addr)
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_server_simple_secure_context_fail(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/server.pem')
+ s = SSL.Connection(ctx)
+ self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_server_simple_timeouts(self):
+ pid = self.start_server(self.args)
+ try:
+ self.assertRaises(ValueError, SSL.Context, 'tlsv5')
+ ctx = SSL.Context()
+ s = SSL.Connection(ctx)
+
+ r = s.get_socket_read_timeout()
+ w = s.get_socket_write_timeout()
+ assert r.sec == 0, r.sec
+ assert r.microsec == 0, r.microsec
+ assert w.sec == 0, w.sec
+ assert w.microsec == 0, w.microsec
+
+ s.set_socket_read_timeout(SSL.timeout())
+ s.set_socket_write_timeout(SSL.timeout(909,9))
+ r = s.get_socket_read_timeout()
+ w = s.get_socket_write_timeout()
+ assert r.sec == 600, r.sec
+ assert r.microsec == 0, r.microsec
+ assert w.sec == 909, w.sec
+ #assert w.microsec == 9, w.microsec XXX 4000
+
+ s.connect(self.srv_addr)
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_tls1_nok(self):
+ if fips_mode: # TLS is required in FIPS mode
+ return
+ self.args.append('-no_tls1')
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context('tlsv1')
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ self.failUnlessEqual(e[0], 'wrong version number')
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_tls1_ok(self):
+ self.args.append('-tls1')
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context('tlsv1')
+ s = SSL.Connection(ctx)
+ s.connect(self.srv_addr)
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_sslv23_no_v2(self):
+ if fips_mode: # TLS is required in FIPS mode
+ return
+ self.args.append('-no_tls1')
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context('sslv23')
+ s = SSL.Connection(ctx)
+ s.connect(self.srv_addr)
+ self.failUnlessEqual(s.get_version(), 'SSLv3')
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_sslv23_no_v2_no_service(self):
+ if fips_mode: # TLS is required in FIPS mode
+ return
+ self.args = self.args + ['-no_tls1', '-no_ssl3']
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context('sslv23')
+ s = SSL.Connection(ctx)
+ self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_sslv23_weak_crypto(self):
+ if fips_mode: # TLS is required in FIPS mode
+ return
+ self.args = self.args + ['-no_tls1', '-no_ssl3']
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context('sslv23', weak_crypto=1)
+ s = SSL.Connection(ctx)
+ if m2.OPENSSL_VERSION_NUMBER < 0x10000000: # SSLv2 ciphers disabled by default in newer OpenSSL
+ s.connect(self.srv_addr)
+ self.failUnlessEqual(s.get_version(), 'SSLv2')
+ else:
+ self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_cipher_mismatch(self):
+ self.args = self.args + ['-cipher', 'AES256-SHA']
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ s = SSL.Connection(ctx)
+ s.set_cipher_list('AES128-SHA')
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ self.failUnlessEqual(e[0], 'sslv3 alert handshake failure')
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_no_such_cipher(self):
+ self.args = self.args + ['-cipher', 'AES128-SHA']
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ s = SSL.Connection(ctx)
+ s.set_cipher_list('EXP-RC2-MD5')
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ self.failUnlessEqual(e[0], 'no ciphers available')
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_no_weak_cipher(self):
+ if fips_mode: # Weak ciphers are prohibited
+ return
+ self.args = self.args + ['-cipher', 'EXP']
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ self.failUnlessEqual(e[0], 'sslv3 alert handshake failure')
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_use_weak_cipher(self):
+ if fips_mode: # Weak ciphers are prohibited
+ return
+ self.args = self.args + ['-cipher', 'EXP']
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context(weak_crypto=1)
+ s = SSL.Connection(ctx)
+ s.connect(self.srv_addr)
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_cipher_ok(self):
+ self.args = self.args + ['-cipher', 'AES128-SHA']
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ s = SSL.Connection(ctx)
+ s.set_cipher_list('AES128-SHA')
+ s.connect(self.srv_addr)
+ data = self.http_get(s)
+
+ assert s.get_cipher().name() == 'AES128-SHA', s.get_cipher().name()
+
+ cipher_stack = s.get_ciphers()
+ assert cipher_stack[0].name() == 'AES128-SHA', cipher_stack[0].name()
+ self.assertRaises(IndexError, cipher_stack.__getitem__, 2)
+ # For some reason there are 2 entries in the stack
+ #assert len(cipher_stack) == 1, len(cipher_stack)
+ assert s.get_cipher_list() == 'AES128-SHA', s.get_cipher_list()
+
+ # Test Cipher_Stack iterator
+ i = 0
+ for cipher in cipher_stack:
+ i += 1
+ assert cipher.name() == 'AES128-SHA', '"%s"' % cipher.name()
+ self.assertEqual('AES128-SHA-128', str(cipher))
+ # For some reason there are 2 entries in the stack
+ #assert i == 1, i
+ self.assertEqual(i, len(cipher_stack))
+
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def verify_cb_new(self, ok, store):
+ return verify_cb_new_function(ok, store)
+
+ def test_verify_cb_new(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+ self.verify_cb_new)
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ assert 0, e
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_verify_cb_new_class(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+ VerifyCB())
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ assert 0, e
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_verify_cb_new_function(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+ verify_cb_new_function)
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ assert 0, e
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_verify_cb_lambda(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+ lambda ok, store: 1)
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ assert 0, e
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def verify_cb_exception(self, ok, store):
+ raise Exception, 'We should fail verification'
+
+ def test_verify_cb_exception(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+ self.verify_cb_exception)
+ s = SSL.Connection(ctx)
+ self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_verify_cb_not_callable(self):
+ ctx = SSL.Context()
+ self.assertRaises(TypeError,
+ ctx.set_verify,
+ SSL.verify_peer | SSL.verify_fail_if_no_peer_cert,
+ 9,
+ 1)
+
+ def test_verify_cb_wrong_callable(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+ lambda _: '')
+ s = SSL.Connection(ctx)
+ self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def verify_cb_old(self, ctx_ptr, x509_ptr, err, depth, ok):
+ try:
+ from M2Crypto import X509
+ assert not ok
+ assert err == m2.X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT or \
+ err == m2.X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY or \
+ err == m2.X509_V_ERR_CERT_UNTRUSTED or \
+ err == m2.X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE
+ assert m2.ssl_ctx_get_cert_store(ctx_ptr)
+ assert X509.X509(x509_ptr).as_pem()
+ except AssertionError:
+ # If we let exceptions propagate from here the
+ # caller may see strange errors. This is cleaner.
+ return 0
+ return 1
+
+ def test_verify_cb_old(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+ self.verify_cb_old)
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ assert 0, e
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_verify_allow_unknown_old(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+ SSL.cb.ssl_verify_callback)
+ ctx.set_allow_unknown_ca(1)
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ assert 0, e
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_verify_allow_unknown_new(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9,
+ SSL.cb.ssl_verify_callback_allow_unknown_ca)
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ assert 0, e
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_verify_cert(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/ca.pem')
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ assert 0, e
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_verify_cert_fail(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/server.pem')
+ s = SSL.Connection(ctx)
+ self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_verify_cert_mutual_auth(self):
+ self.args.extend(['-Verify', '2', '-CAfile', 'ca.pem'])
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/ca.pem')
+ ctx.load_cert('tests/x509.pem')
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ assert 0, e
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_verify_cert_mutual_auth_servernbio(self):
+ self.args.extend(['-Verify', '2', '-CAfile', 'ca.pem', '-nbio'])
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/ca.pem')
+ ctx.load_cert('tests/x509.pem')
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ assert 0, e
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_verify_cert_mutual_auth_fail(self):
+ self.args.extend(['-Verify', '2', '-CAfile', 'ca.pem'])
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/ca.pem')
+ s = SSL.Connection(ctx)
+ self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_verify_nocert_fail(self):
+ self.args.extend(['-nocert'])
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/ca.pem')
+ s = SSL.Connection(ctx)
+ self.assertRaises(SSL.SSLError, s.connect, self.srv_addr)
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_blocking0(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ s = SSL.Connection(ctx)
+ s.setblocking(0)
+ self.assertRaises(Exception, s.connect, self.srv_addr)
+ s.close()
+ finally:
+ self.stop_server(pid)
+
+ def test_blocking1(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ s = SSL.Connection(ctx)
+ s.setblocking(1)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ assert 0, e
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_makefile(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ assert 0, e
+ bio = s.makefile('rw')
+ #s.close() # XXX bug 6628?
+ bio.write('GET / HTTP/1.0\n\n')
+ bio.flush()
+ data = bio.read()
+ bio.close()
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_makefile_err(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ s = SSL.Connection(ctx)
+ try:
+ s.connect(self.srv_addr)
+ except SSL.SSLError, e:
+ assert 0, e
+ f = s.makefile()
+ data = self.http_get(s)
+ s.close()
+ del f
+ del s
+ err_code = Err.peek_error_code()
+ assert not err_code, 'Unexpected error: %s' % err_code
+ err = Err.get_error()
+ assert not err, 'Unexpected error: %s' % err
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_info_callback(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_info_callback()
+ s = SSL.Connection(ctx)
+ s.connect(self.srv_addr)
+ data = self.http_get(s)
+ s.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+
+class UrllibSSLClientTestCase(BaseSSLClientTestCase):
+
+ def test_urllib(self):
+ pid = self.start_server(self.args)
+ try:
+ from M2Crypto import m2urllib
+ url = m2urllib.FancyURLopener()
+ url.addheader('Connection', 'close')
+ u = url.open('https://%s:%s/' % (srv_host, srv_port))
+ data = u.read()
+ u.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ # XXX Don't actually know how to use m2urllib safely!
+ #def test_urllib_secure_context(self):
+ #def test_urllib_secure_context_fail(self):
+
+ # XXX Don't actually know how to use m2urllib safely!
+ #def test_urllib_safe_context(self):
+ #def test_urllib_safe_context_fail(self):
+
+
+class Urllib2SSLClientTestCase(BaseSSLClientTestCase):
+
+ if sys.version_info >= (2,4):
+ def test_urllib2(self):
+ pid = self.start_server(self.args)
+ try:
+ from M2Crypto import m2urllib2
+ opener = m2urllib2.build_opener()
+ opener.addheaders = [('Connection', 'close')]
+ u = opener.open('https://%s:%s/' % (srv_host, srv_port))
+ data = u.read()
+ u.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_urllib2_secure_context(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/ca.pem')
+
+ from M2Crypto import m2urllib2
+ opener = m2urllib2.build_opener(ctx)
+ opener.addheaders = [('Connection', 'close')]
+ u = opener.open('https://%s:%s/' % (srv_host, srv_port))
+ data = u.read()
+ u.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_urllib2_secure_context_fail(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/server.pem')
+
+ from M2Crypto import m2urllib2
+ opener = m2urllib2.build_opener(ctx)
+ opener.addheaders = [('Connection', 'close')]
+ self.assertRaises(SSL.SSLError, opener.open, 'https://%s:%s/' % (srv_host, srv_port))
+ finally:
+ self.stop_server(pid)
+
+ def test_z_urllib2_opener(self):
+ pid = self.start_server(self.args)
+ try:
+ ctx = SSL.Context()
+
+ from M2Crypto import m2urllib2
+ opener = m2urllib2.build_opener(ctx, m2urllib2.HTTPBasicAuthHandler())
+ m2urllib2.install_opener(opener)
+ req = m2urllib2.Request('https://%s:%s/' % (srv_host, srv_port))
+ u = m2urllib2.urlopen(req)
+ data = u.read()
+ u.close()
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(data, 's_server -quiet -www') == -1)
+
+ def test_urllib2_opener_handlers(self):
+ ctx = SSL.Context()
+
+ from M2Crypto import m2urllib2
+ opener = m2urllib2.build_opener(ctx,
+ m2urllib2.HTTPBasicAuthHandler())
+
+ def test_urllib2_leak(self):
+ pid = self.start_server(self.args)
+ try:
+ import gc
+ from M2Crypto import m2urllib2
+ o = m2urllib2.build_opener()
+ r = o.open('https://%s:%s/' % (srv_host, srv_port))
+ s = [r.fp._sock.fp]
+ r.close()
+ self.assertEqual(len(gc.get_referrers(s[0])), 1)
+ finally:
+ self.stop_server(pid)
+
+
+class TwistedSSLClientTestCase(BaseSSLClientTestCase):
+
+ def test_twisted_wrapper(self):
+ # Test only when twisted and ZopeInterfaces are present
+ try:
+ from twisted.internet.protocol import ClientFactory
+ from twisted.protocols.basic import LineReceiver
+ from twisted.internet import reactor
+ import M2Crypto.SSL.TwistedProtocolWrapper as wrapper
+ except ImportError:
+ import warnings
+ warnings.warn('Skipping twisted wrapper test because twisted not found')
+ return
+
+ class EchoClient(LineReceiver):
+ def connectionMade(self):
+ self.sendLine('GET / HTTP/1.0\n\n')
+
+ def lineReceived(self, line):
+ global twisted_data
+ twisted_data += line
+
+ class EchoClientFactory(ClientFactory):
+ protocol = EchoClient
+
+ def clientConnectionFailed(self, connector, reason):
+ reactor.stop()
+ assert 0, reason
+
+ def clientConnectionLost(self, connector, reason):
+ reactor.stop()
+
+ pid = self.start_server(self.args)
+
+ class ContextFactory:
+ def getContext(self):
+ return SSL.Context()
+
+ try:
+ global twisted_data
+ twisted_data = ''
+
+ contextFactory = ContextFactory()
+ factory = EchoClientFactory()
+ wrapper.connectSSL(srv_host, srv_port, factory, contextFactory)
+ reactor.run() # This will block until reactor.stop() is called
+ finally:
+ self.stop_server(pid)
+ self.failIf(string.find(twisted_data, 's_server -quiet -www') == -1)
+
+
+twisted_data = ''
+
+
+class XmlRpcLibTestCase(unittest.TestCase):
+ def test_lib(self):
+ from M2Crypto import m2xmlrpclib
+ m2xmlrpclib.SSL_Transport()
+ # XXX need server to test against
+
+
+class FtpsLibTestCase(unittest.TestCase):
+ def test_lib(self):
+ from M2Crypto import ftpslib
+ ftpslib.FTP_TLS()
+ # XXX need server to test against
+
+
+class SessionTestCase(unittest.TestCase):
+ def test_session_load_bad(self):
+ self.assertRaises(SSL.SSLError, SSL.Session.load_session,
+ 'tests/signer.pem')
+
+class FtpslibTestCase(unittest.TestCase):
+ def test_26_compat(self):
+ from M2Crypto import ftpslib
+ f = ftpslib.FTP_TLS()
+ # 2.6 used to raise AttributeError:
+ self.assertRaises(socket.gaierror, f.connect, 'no-such-host-dfgHJK56789', 990)
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(SessionTestCase))
+ suite.addTest(unittest.makeSuite(XmlRpcLibTestCase))
+ suite.addTest(unittest.makeSuite(FtpsLibTestCase))
+ suite.addTest(unittest.makeSuite(PassSSLClientTestCase))
+ suite.addTest(unittest.makeSuite(HttpslibSSLClientTestCase))
+ suite.addTest(unittest.makeSuite(UrllibSSLClientTestCase))
+ suite.addTest(unittest.makeSuite(Urllib2SSLClientTestCase))
+ suite.addTest(unittest.makeSuite(MiscSSLClientTestCase))
+ suite.addTest(unittest.makeSuite(FtpslibTestCase))
+ try:
+ import M2Crypto.SSL.TwistedProtocolWrapper as wrapper
+ suite.addTest(unittest.makeSuite(TwistedSSLClientTestCase))
+ except ImportError:
+ pass
+ return suite
+
+
+def zap_servers():
+ s = 's_server'
+ fn = tempfile.mktemp()
+ cmd = 'ps | egrep %s > %s' % (s, fn)
+ os.system(cmd)
+ f = open(fn)
+ while 1:
+ ps = f.readline()
+ if not ps:
+ break
+ chunk = string.split(ps)
+ pid, cmd = chunk[0], chunk[4]
+ if cmd == s:
+ os.kill(int(pid), 1)
+ f.close()
+ os.unlink(fn)
+
+
+if __name__ == '__main__':
+ report_leaks = 0
+
+ if report_leaks:
+ import gc
+ gc.enable()
+ gc.set_debug(gc.DEBUG_LEAK & ~gc.DEBUG_SAVEALL)
+
+ try:
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+ finally:
+ zap_servers()
+
+ if report_leaks:
+ import alltests
+ alltests.dump_garbage()
diff --git a/tests/test_ssl_offline.py b/tests/test_ssl_offline.py
new file mode 100644
index 0000000..ba77434
--- /dev/null
+++ b/tests/test_ssl_offline.py
@@ -0,0 +1,60 @@
+"""Unit tests for M2Crypto.SSL offline parts
+
+Copyright (C) 2006 Open Source Applications Foundation. All Rights Reserved.
+
+Copyright (C) 2009-2010 Heikki Toivonen. All Rights Reserved.
+"""
+
+import unittest, doctest
+from M2Crypto.SSL import Checker
+from M2Crypto import X509
+from M2Crypto import SSL
+from test_ssl import srv_host
+
+
+class CheckerTestCase(unittest.TestCase):
+ def test_checker(self):
+
+ check = Checker.Checker(host=srv_host,
+ peerCertHash='7B754EFA41A264AAD370D43460BC8229F9354ECE')
+ x509 = X509.load_cert('tests/server.pem')
+ assert check(x509, srv_host)
+ self.assertRaises(Checker.WrongHost, check, x509, 'example.com')
+
+ doctest.testmod(Checker)
+
+
+class ContextTestCase(unittest.TestCase):
+ def test_ctx_load_verify_locations(self):
+ ctx = SSL.Context()
+ self.assertRaises(ValueError, ctx.load_verify_locations, None, None)
+
+ def test_map(self):
+ from M2Crypto.SSL.Context import map, _ctxmap
+ assert isinstance(map(), _ctxmap)
+ ctx = SSL.Context()
+ assert map()
+ ctx.close()
+ assert map() is _ctxmap.singleton
+
+ def test_certstore(self):
+ ctx = SSL.Context()
+ ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 9)
+ ctx.load_verify_locations('tests/ca.pem')
+ ctx.load_cert('tests/x509.pem')
+
+ store = ctx.get_cert_store()
+ assert isinstance(store, X509.X509_Store)
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(CheckerTestCase))
+ suite.addTest(unittest.makeSuite(ContextTestCase))
+ return suite
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
diff --git a/tests/test_ssl_win.py b/tests/test_ssl_win.py
new file mode 100644
index 0000000..0d13bd0
--- /dev/null
+++ b/tests/test_ssl_win.py
@@ -0,0 +1,71 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.SSL.
+
+Win32 version - requires Mark Hammond's Win32 extensions and openssl.exe
+on your PATH.
+
+Copyright (c) 2000-2001 Ng Pheng Siong. All rights reserved."""
+
+import os, os.path, string, time, unittest
+try:
+ import win32process
+except ImportError:
+ win32process = None
+
+if win32process:
+ from M2Crypto import Rand, SSL
+ import test_ssl
+
+ def find_openssl():
+ plist = os.environ['PATH'].split(';')
+ for p in plist:
+ try:
+ dir = os.listdir(p)
+ if 'openssl.exe' in dir:
+ return os.path.join(p, 'openssl.exe')
+ except WindowsError:
+ pass
+ return None
+
+
+ srv_host = 'localhost'
+ srv_port = 64000
+
+ class SSLWinClientTestCase(test_ssl.SSLClientTestCase):
+
+ startupinfo = win32process.STARTUPINFO()
+ openssl = find_openssl()
+
+ def start_server(self, args):
+ # openssl must be started in the tests directory for it
+ # to find the .pem files
+ os.chdir('tests')
+ try:
+ hproc, hthread, pid, tid = win32process.CreateProcess(self.openssl,
+ string.join(args), None, None, 0, win32process.DETACHED_PROCESS,
+ None, None, self.startupinfo)
+ finally:
+ os.chdir('..')
+ time.sleep(0.3)
+ return hproc
+
+ def stop_server(self, hproc):
+ win32process.TerminateProcess(hproc, 0)
+
+
+ def suite():
+ return unittest.makeSuite(SSLWinClientTestCase)
+
+ def zap_servers():
+ pass
+
+
+ if __name__ == '__main__':
+ try:
+ if find_openssl() is not None:
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
+ finally:
+ zap_servers()
diff --git a/tests/test_threading.py b/tests/test_threading.py
new file mode 100644
index 0000000..7912a61
--- /dev/null
+++ b/tests/test_threading.py
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.threading.
+
+Copyright (C) 2007 Open Source Applications Foundation. All Rights Reserved.
+"""
+
+import unittest
+from M2Crypto import threading as m2threading, Rand
+
+
+class ThreadingTestCase(unittest.TestCase):
+
+ def setUp(self):
+ m2threading.init()
+
+ def tearDown(self):
+ m2threading.cleanup()
+
+ def test_pass(self):
+ pass
+
+ def test_multiInitCleanup(self):
+ m2threading.init()
+ m2threading.init()
+ m2threading.cleanup()
+ m2threading.cleanup()
+
+ m2threading.init()
+ m2threading.cleanup()
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(ThreadingTestCase))
+ return suite
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
diff --git a/tests/test_x509.py b/tests/test_x509.py
new file mode 100644
index 0000000..7ea86df
--- /dev/null
+++ b/tests/test_x509.py
@@ -0,0 +1,559 @@
+#!/usr/bin/env python
+
+"""Unit tests for M2Crypto.X509.
+
+Contributed by Toby Allsopp <toby@MI6.GEN.NZ> under M2Crypto's license.
+
+Portions created by Open Source Applications Foundation (OSAF) are
+Copyright (C) 2004-2005 OSAF. All Rights Reserved.
+Author: Heikki Toivonen
+"""
+
+import unittest
+import os, time, base64, sys
+from M2Crypto import X509, EVP, RSA, Rand, ASN1, m2, util, BIO
+
+class X509TestCase(unittest.TestCase):
+
+ def callback(self, *args):
+ pass
+
+ def mkreq(self, bits, ca=0):
+ pk = EVP.PKey()
+ x = X509.Request()
+ rsa = RSA.gen_key(bits, 65537, self.callback)
+ pk.assign_rsa(rsa)
+ rsa = None # should not be freed here
+ x.set_pubkey(pk)
+ name = x.get_subject()
+ name.C = "UK"
+ name.CN = "OpenSSL Group"
+ if not ca:
+ ext1 = X509.new_extension('subjectAltName', 'DNS:foobar.example.com')
+ ext2 = X509.new_extension('nsComment', 'Hello there')
+ extstack = X509.X509_Extension_Stack()
+ extstack.push(ext1)
+ extstack.push(ext2)
+ x.add_extensions(extstack)
+ self.assertRaises(ValueError, x.sign, pk, 'sha513')
+ x.sign(pk,'sha1')
+ assert x.verify(pk)
+ pk2 = x.get_pubkey()
+ assert x.verify(pk2)
+ return x, pk
+
+ def test_ext(self):
+ self.assertRaises(ValueError, X509.new_extension,
+ 'subjectKeyIdentifier', 'hash')
+ ext = X509.new_extension('subjectAltName', 'DNS:foobar.example.com')
+ assert ext.get_value() == 'DNS:foobar.example.com'
+ assert ext.get_value(indent=2) == ' DNS:foobar.example.com'
+ assert ext.get_value(flag=m2.X509V3_EXT_PARSE_UNKNOWN) == 'DNS:foobar.example.com'
+
+ def test_extstack(self):
+ # new
+ ext1 = X509.new_extension('subjectAltName', 'DNS:foobar.example.com')
+ ext2 = X509.new_extension('nsComment', 'Hello there')
+ extstack = X509.X509_Extension_Stack()
+
+ # push
+ extstack.push(ext1)
+ extstack.push(ext2)
+ assert(extstack[1].get_name() == 'nsComment')
+ assert len(extstack) == 2
+
+ # iterator
+ i = 0
+ for e in extstack:
+ i += 1
+ assert len(e.get_name()) > 0
+ assert i == 2
+
+ # pop
+ ext3 = extstack.pop()
+ assert len(extstack) == 1
+ assert(extstack[0].get_name() == 'subjectAltName')
+ extstack.push(ext3)
+ assert len(extstack) == 2
+ assert(extstack[1].get_name() == 'nsComment')
+
+ assert extstack.pop() is not None
+ assert extstack.pop() is not None
+ assert extstack.pop() is None
+
+ def test_x509_name(self):
+ n = X509.X509_Name()
+ n.C = 'US' # It seems this actually needs to be a real 2 letter country code
+ assert n.C == 'US'
+ n.SP = 'State or Province'
+ assert n.SP == 'State or Province'
+ n.L = 'locality name'
+ assert n.L == 'locality name'
+ n.O = 'orhanization name'
+ assert n.O == 'orhanization name'
+ n.OU = 'org unit'
+ assert n.OU == 'org unit'
+ n.CN = 'common name'
+ assert n.CN == 'common name'
+ n.Email = 'bob@example.com'
+ assert n.Email == 'bob@example.com'
+ n.serialNumber = '1234'
+ assert n.serialNumber == '1234'
+ n.SN = 'surname'
+ assert n.SN == 'surname'
+ n.GN = 'given name'
+ assert n.GN == 'given name'
+ assert n.as_text() == 'C=US, ST=State or Province, L=locality name, O=orhanization name, OU=org unit, CN=common name/emailAddress=bob@example.com/serialNumber=1234, SN=surname, GN=given name', '"%s"' % n.as_text()
+ assert len(n) == 10, len(n)
+ n.givenName = 'name given'
+ assert n.GN == 'given name' # Just gets the first
+ assert n.as_text() == 'C=US, ST=State or Province, L=locality name, O=orhanization name, OU=org unit, CN=common name/emailAddress=bob@example.com/serialNumber=1234, SN=surname, GN=given name, GN=name given', '"%s"' % n.as_text()
+ assert len(n) == 11, len(n)
+ n.add_entry_by_txt(field="CN", type=ASN1.MBSTRING_ASC,
+ entry="Proxy", len=-1, loc=-1, set=0)
+ assert len(n) == 12, len(n)
+ assert n.entry_count() == 12, n.entry_count()
+ assert n.as_text() == 'C=US, ST=State or Province, L=locality name, O=orhanization name, OU=org unit, CN=common name/emailAddress=bob@example.com/serialNumber=1234, SN=surname, GN=given name, GN=name given, CN=Proxy', '"%s"' % n.as_text()
+
+ self.assertRaises(AttributeError, n.__getattr__, 'foobar')
+ n.foobar = 1
+ assert n.foobar == 1, n.foobar
+
+ # X509_Name_Entry tests
+ l = 0
+ for entry in n:
+ assert isinstance(entry, X509.X509_Name_Entry), entry
+ assert isinstance(entry.get_object(), ASN1.ASN1_Object), entry
+ assert isinstance(entry.get_data(), ASN1.ASN1_String), entry
+ l += 1
+ assert l == 12, l
+
+ l = 0
+ for cn in n.get_entries_by_nid(m2.NID_commonName):
+ assert isinstance(cn, X509.X509_Name_Entry), cn
+ assert isinstance(cn.get_object(), ASN1.ASN1_Object), cn
+ data = cn.get_data()
+ assert isinstance(data, ASN1.ASN1_String), data
+ t = data.as_text()
+ assert t == "common name" or t == "Proxy", t
+ l += 1
+ assert l == 2, l
+
+ cn.set_data("Hello There!")
+ assert cn.get_data().as_text() == "Hello There!", cn.get_data().as_text()
+
+ assert n.as_hash() == 1697185131
+
+ self.assertRaises(IndexError, lambda: n[100])
+ self.assert_(n[10])
+
+ def test_mkreq(self):
+ (req, _) = self.mkreq(1024)
+ req.save_pem('tests/tmp_request.pem')
+ req2 = X509.load_request('tests/tmp_request.pem')
+ os.remove('tests/tmp_request.pem')
+ req.save('tests/tmp_request.pem')
+ req3 = X509.load_request('tests/tmp_request.pem')
+ os.remove('tests/tmp_request.pem')
+ req.save('tests/tmp_request.der', format=X509.FORMAT_DER)
+ req4 = X509.load_request('tests/tmp_request.der',
+ format=X509.FORMAT_DER)
+ os.remove('tests/tmp_request.der')
+ assert req.as_pem() == req2.as_pem()
+ assert req.as_text() == req2.as_text()
+ assert req.as_der() == req2.as_der()
+ assert req.as_pem() == req3.as_pem()
+ assert req.as_text() == req3.as_text()
+ assert req.as_der() == req3.as_der()
+ assert req.as_pem() == req4.as_pem()
+ assert req.as_text() == req4.as_text()
+ assert req.as_der() == req4.as_der()
+ self.assertEqual(req.get_version(), 0)
+ req.set_version(1)
+ self.assertEqual(req.get_version(), 1)
+ req.set_version(0)
+ self.assertEqual(req.get_version(), 0)
+
+
+ def test_mkcert(self):
+ req, pk = self.mkreq(1024)
+ pkey = req.get_pubkey()
+ assert(req.verify(pkey))
+ sub = req.get_subject()
+ assert len(sub) == 2, len(sub)
+ cert = X509.X509()
+ cert.set_serial_number(1)
+ cert.set_version(2)
+ cert.set_subject(sub)
+ t = long(time.time()) + time.timezone
+ now = ASN1.ASN1_UTCTIME()
+ now.set_time(t)
+ nowPlusYear = ASN1.ASN1_UTCTIME()
+ nowPlusYear.set_time(t + 60 * 60 * 24 * 365)
+ cert.set_not_before(now)
+ cert.set_not_after(nowPlusYear)
+ assert str(cert.get_not_before()) == str(now)
+ assert str(cert.get_not_after()) == str(nowPlusYear)
+ issuer = X509.X509_Name()
+ issuer.CN = 'The Issuer Monkey'
+ issuer.O = 'The Organization Otherwise Known as My CA, Inc.'
+ cert.set_issuer(issuer)
+ cert.set_pubkey(pkey)
+ cert.set_pubkey(cert.get_pubkey()) # Make sure get/set work
+ ext = X509.new_extension('subjectAltName', 'DNS:foobar.example.com')
+ ext.set_critical(0)
+ assert ext.get_critical() == 0
+ cert.add_ext(ext)
+ cert.sign(pk, 'sha1')
+ self.assertRaises(ValueError, cert.sign, pk, 'nosuchalgo')
+ assert(cert.get_ext('subjectAltName').get_name() == 'subjectAltName')
+ assert(cert.get_ext_at(0).get_name() == 'subjectAltName')
+ assert(cert.get_ext_at(0).get_value() == 'DNS:foobar.example.com')
+ assert cert.get_ext_count() == 1, cert.get_ext_count()
+ self.assertRaises(IndexError, cert.get_ext_at, 1)
+ assert cert.verify()
+ assert cert.verify(pkey)
+ assert cert.verify(cert.get_pubkey())
+ assert cert.get_version() == 2
+ assert cert.get_serial_number() == 1
+ assert cert.get_issuer().CN == 'The Issuer Monkey'
+
+ if m2.OPENSSL_VERSION_NUMBER >= 0x90800f:
+ assert not cert.check_ca()
+ assert not cert.check_purpose(m2.X509_PURPOSE_SSL_SERVER, 1)
+ assert not cert.check_purpose(m2.X509_PURPOSE_NS_SSL_SERVER, 1)
+ assert cert.check_purpose(m2.X509_PURPOSE_SSL_SERVER, 0)
+ assert cert.check_purpose(m2.X509_PURPOSE_NS_SSL_SERVER, 0)
+ assert cert.check_purpose(m2.X509_PURPOSE_ANY, 0)
+ else:
+ self.assertRaises(AttributeError, cert.check_ca)
+
+ def mkcacert(self):
+ req, pk = self.mkreq(1024, ca=1)
+ pkey = req.get_pubkey()
+ sub = req.get_subject()
+ cert = X509.X509()
+ cert.set_serial_number(1)
+ cert.set_version(2)
+ cert.set_subject(sub)
+ t = long(time.time()) + time.timezone
+ now = ASN1.ASN1_UTCTIME()
+ now.set_time(t)
+ nowPlusYear = ASN1.ASN1_UTCTIME()
+ nowPlusYear.set_time(t + 60 * 60 * 24 * 365)
+ cert.set_not_before(now)
+ cert.set_not_after(nowPlusYear)
+ issuer = X509.X509_Name()
+ issuer.C = "UK"
+ issuer.CN = "OpenSSL Group"
+ cert.set_issuer(issuer)
+ cert.set_pubkey(pkey)
+ ext = X509.new_extension('basicConstraints', 'CA:TRUE')
+ cert.add_ext(ext)
+ cert.sign(pk, 'sha1')
+
+ if m2.OPENSSL_VERSION_NUMBER >= 0x0090800fL:
+ assert cert.check_ca()
+ assert cert.check_purpose(m2.X509_PURPOSE_SSL_SERVER, 1)
+ assert cert.check_purpose(m2.X509_PURPOSE_NS_SSL_SERVER, 1)
+ assert cert.check_purpose(m2.X509_PURPOSE_ANY, 1)
+ assert cert.check_purpose(m2.X509_PURPOSE_SSL_SERVER, 0)
+ assert cert.check_purpose(m2.X509_PURPOSE_NS_SSL_SERVER, 0)
+ assert cert.check_purpose(m2.X509_PURPOSE_ANY, 0)
+ else:
+ self.assertRaises(AttributeError, cert.check_ca)
+
+ return cert, pk, pkey
+
+ def test_mkcacert(self):
+ cacert, pk, pkey = self.mkcacert()
+ assert cacert.verify(pkey)
+
+
+ def test_mkproxycert(self):
+ cacert, pk1, pkey = self.mkcacert()
+ end_entity_cert_req, pk2 = self.mkreq(1024)
+ end_entity_cert = self.make_eecert(cacert)
+ end_entity_cert.set_subject(end_entity_cert_req.get_subject())
+ end_entity_cert.set_pubkey(end_entity_cert_req.get_pubkey())
+ end_entity_cert.sign(pk1, 'sha1')
+ proxycert = self.make_proxycert(end_entity_cert)
+ proxycert.sign(pk2, 'sha1')
+ assert proxycert.verify(pk2)
+ assert proxycert.get_ext_at(0).get_name() == 'proxyCertInfo', proxycert.get_ext_at(0).get_name()
+ assert proxycert.get_ext_at(0).get_value() == 'Path Length Constraint: infinite\nPolicy Language: Inherit all\n', '"%s"' % proxycert.get_ext_at(0).get_value()
+ assert proxycert.get_ext_count() == 1, proxycert.get_ext_count()
+ assert proxycert.get_subject().as_text() == 'C=UK, CN=OpenSSL Group, CN=Proxy', proxycert.get_subject().as_text()
+ assert proxycert.get_subject().as_text(indent=2, flags=m2.XN_FLAG_RFC2253) == ' CN=Proxy,CN=OpenSSL Group,C=UK', '"%s"' % proxycert.get_subject().as_text(indent=2, flags=m2.XN_FLAG_RFC2253)
+
+ def make_eecert(self, cacert):
+ eecert = X509.X509()
+ eecert.set_serial_number(2)
+ eecert.set_version(2)
+ t = long(time.time()) + time.timezone
+ now = ASN1.ASN1_UTCTIME()
+ now.set_time(t)
+ now_plus_year = ASN1.ASN1_UTCTIME()
+ now_plus_year.set_time(t + 60 * 60 * 24 * 365)
+ eecert.set_not_before(now)
+ eecert.set_not_after(now_plus_year)
+ eecert.set_issuer(cacert.get_subject())
+ return eecert
+
+ def make_proxycert(self, eecert):
+ proxycert = X509.X509()
+ pk2 = EVP.PKey()
+ proxykey = RSA.gen_key(1024, 65537, self.callback)
+ pk2.assign_rsa(proxykey)
+ proxycert.set_pubkey(pk2)
+ proxycert.set_version(2)
+ not_before = ASN1.ASN1_UTCTIME()
+ not_after = ASN1.ASN1_UTCTIME()
+ not_before.set_time(int(time.time()))
+ offset = 12 * 3600
+ not_after.set_time(int(time.time()) + offset )
+ proxycert.set_not_before(not_before)
+ proxycert.set_not_after(not_after)
+ proxycert.set_issuer_name(eecert.get_subject())
+ proxycert.set_serial_number(12345678)
+ proxy_subject_name = X509.X509_Name()
+ issuer_name_string = eecert.get_subject().as_text()
+ seq = issuer_name_string.split(",")
+
+ subject_name = X509.X509_Name()
+ for entry in seq:
+ l = entry.split("=")
+ subject_name.add_entry_by_txt(field=l[0].strip(),
+ type=ASN1.MBSTRING_ASC,
+ entry=l[1], len=-1, loc=-1, set=0)
+
+ subject_name.add_entry_by_txt(field="CN", type=ASN1.MBSTRING_ASC,
+ entry="Proxy", len=-1, loc=-1, set=0)
+
+
+ proxycert.set_subject_name(subject_name)
+ pci_ext = X509.new_extension("proxyCertInfo",
+ "critical,language:Inherit all", 1) # XXX leaks 8 bytes
+ proxycert.add_ext(pci_ext)
+ return proxycert
+
+ def test_fingerprint(self):
+ x509 = X509.load_cert('tests/x509.pem')
+ fp = x509.get_fingerprint('sha1')
+ expected = '8D2EB9E203B5FFDC7F4FA7DC4103E852A55B808D'
+ assert fp == expected, '%s != %s' % (fp, expected)
+
+ def test_load_der_string(self):
+ f = open('tests/x509.der', 'rb')
+ x509 = X509.load_cert_der_string(''.join(f.readlines()))
+ fp = x509.get_fingerprint('sha1')
+ expected = '8D2EB9E203B5FFDC7F4FA7DC4103E852A55B808D'
+ assert fp == expected, '%s != %s' % (fp, expected)
+
+ def test_save_der_string(self):
+ x509 = X509.load_cert('tests/x509.pem')
+ s = x509.as_der()
+ f = open('tests/x509.der', 'rb')
+ s2 = f.read()
+ f.close()
+ assert s == s2
+
+ def test_load(self):
+ x509 = X509.load_cert('tests/x509.pem')
+ x5092 = X509.load_cert('tests/x509.der', format=X509.FORMAT_DER)
+ assert x509.as_text() == x5092.as_text()
+ assert x509.as_pem() == x5092.as_pem()
+ assert x509.as_der() == x5092.as_der()
+ return
+
+ def test_load_bio(self):
+ bio = BIO.openfile('tests/x509.pem')
+ bio2 = BIO.openfile('tests/x509.der')
+ x509 = X509.load_cert_bio(bio)
+ x5092 = X509.load_cert_bio(bio2, format=X509.FORMAT_DER)
+
+ self.assertRaises(ValueError, X509.load_cert_bio, bio2, format=45678)
+
+ assert x509.as_text() == x5092.as_text()
+ assert x509.as_pem() == x5092.as_pem()
+ assert x509.as_der() == x5092.as_der()
+ return
+
+ def test_load_string(self):
+ f = open('tests/x509.pem')
+ s = f.read()
+ f.close()
+ f2 = open('tests/x509.der', 'rb')
+ s2 = f2.read()
+ f2.close()
+ x509 = X509.load_cert_string(s)
+ x5092 = X509.load_cert_string(s2, X509.FORMAT_DER)
+ assert x509.as_text() == x5092.as_text()
+ assert x509.as_pem() == x5092.as_pem()
+ assert x509.as_der() == x5092.as_der()
+ return
+
+ def test_load_request_bio(self):
+ (req, _) = self.mkreq(512)
+
+ r1 = X509.load_request_der_string(req.as_der())
+ r2 = X509.load_request_string(req.as_der(), X509.FORMAT_DER)
+ r3 = X509.load_request_string(req.as_pem(), X509.FORMAT_PEM)
+
+ r4 = X509.load_request_bio(BIO.MemoryBuffer(req.as_der()), X509.FORMAT_DER)
+ r5 = X509.load_request_bio(BIO.MemoryBuffer(req.as_pem()), X509.FORMAT_PEM)
+
+ for r in [r1, r2, r3, r4, r5]:
+ assert req.as_der() == r.as_der()
+
+ self.assertRaises(ValueError, X509.load_request_bio, BIO.MemoryBuffer(req.as_pem()), 345678)
+
+ def test_save(self):
+ x509 = X509.load_cert('tests/x509.pem')
+ f = open('tests/x509.pem', 'r')
+ lTmp = f.readlines()
+ x509_pem = ''.join(lTmp[44:60]) # -----BEGIN CERTIFICATE----- : -----END CERTIFICATE-----
+ f.close()
+ f = open('tests/x509.der', 'rb')
+ x509_der = f.read()
+ f.close()
+ x509.save('tests/tmpcert.pem')
+ f = open('tests/tmpcert.pem')
+ s = f.read()
+ f.close()
+ self.assertEquals(s, x509_pem)
+ os.remove('tests/tmpcert.pem')
+ x509.save('tests/tmpcert.der', format=X509.FORMAT_DER)
+ f = open('tests/tmpcert.der', 'rb')
+ s = f.read()
+ f.close()
+ self.assertEquals(s, x509_der)
+ os.remove('tests/tmpcert.der')
+
+ def test_malformed_data(self):
+ self.assertRaises(X509.X509Error, X509.load_cert_string, 'Hello')
+ self.assertRaises(X509.X509Error, X509.load_cert_der_string, 'Hello')
+ self.assertRaises(X509.X509Error, X509.new_stack_from_der, 'Hello')
+ self.assertRaises(X509.X509Error, X509.load_cert, 'tests/alltests.py')
+ self.assertRaises(X509.X509Error, X509.load_request, 'tests/alltests.py')
+ self.assertRaises(X509.X509Error, X509.load_request_string, 'Hello')
+ self.assertRaises(X509.X509Error, X509.load_request_der_string, 'Hello')
+ self.assertRaises(X509.X509Error, X509.load_crl, 'tests/alltests.py')
+
+ def test_long_serial(self):
+ from M2Crypto import X509
+ cert = X509.load_cert('tests/long_serial_cert.pem')
+ self.assertEquals(cert.get_serial_number(), 17616841808974579194)
+
+ cert = X509.load_cert('tests/thawte.pem')
+ self.assertEquals(cert.get_serial_number(), 127614157056681299805556476275995414779)
+
+
+class X509_StackTestCase(unittest.TestCase):
+
+ def test_make_stack_from_der(self):
+ f = open("tests/der_encoded_seq.b64")
+ b64 = f.read(1304)
+ seq = base64.decodestring(b64)
+ stack = X509.new_stack_from_der(seq)
+ cert = stack.pop()
+ assert stack.pop() is None
+
+ cert.foobar = 1
+ assert cert.foobar == 1
+
+ subject = cert.get_subject()
+ assert str(subject) == "/DC=org/DC=doegrids/OU=Services/CN=host/bosshog.lbl.gov"
+
+ def test_make_stack_check_num(self):
+ f = open("tests/der_encoded_seq.b64")
+ b64 = f.read(1304)
+ seq = base64.decodestring(b64)
+ stack = X509.new_stack_from_der(seq)
+ num = len(stack)
+ assert num == 1
+ cert = stack.pop()
+ num = len(stack)
+ assert num == 0
+ subject = cert.get_subject()
+ assert str(subject) == "/DC=org/DC=doegrids/OU=Services/CN=host/bosshog.lbl.gov"
+
+ def test_make_stack(self):
+ stack = X509.X509_Stack()
+ cert = X509.load_cert("tests/x509.pem")
+ issuer = X509.load_cert("tests/ca.pem")
+ cert_subject1 = cert.get_subject()
+ issuer_subject1 = issuer.get_subject()
+ stack.push(cert)
+ stack.push(issuer)
+
+ # Test stack iterator
+ i = 0
+ for c in stack:
+ i += 1
+ assert len(c.get_subject().CN) > 0
+ assert i == 2
+
+ issuer_pop = stack.pop()
+ cert_pop = stack.pop()
+ cert_subject2 = cert_pop.get_subject()
+ issuer_subject2 = issuer.get_subject()
+ assert str(cert_subject1) == str(cert_subject2)
+ assert str(issuer_subject1) == str(issuer_subject2)
+
+ def test_as_der(self):
+ stack = X509.X509_Stack()
+ cert = X509.load_cert("tests/x509.pem")
+ issuer = X509.load_cert("tests/ca.pem")
+ cert_subject1 = cert.get_subject()
+ issuer_subject1 = issuer.get_subject()
+ stack.push(cert)
+ stack.push(issuer)
+ der_seq = stack.as_der()
+ stack2 = X509.new_stack_from_der(der_seq)
+ issuer_pop = stack2.pop()
+ cert_pop = stack2.pop()
+ cert_subject2 = cert_pop.get_subject()
+ issuer_subject2 = issuer.get_subject()
+ assert str(cert_subject1) == str(cert_subject2)
+ assert str(issuer_subject1) == str(issuer_subject2)
+
+
+class X509_ExtTestCase(unittest.TestCase):
+
+ def test_ext(self):
+ if 0: # XXX
+ # With this leaks 8 bytes:
+ name = "proxyCertInfo"
+ value = "critical,language:Inherit all"
+ else:
+ # With this there are no leaks:
+ name = "nsComment"
+ value = "Hello"
+
+ lhash = m2.x509v3_lhash()
+ ctx = m2.x509v3_set_conf_lhash(lhash)
+ x509_ext_ptr = m2.x509v3_ext_conf(lhash, ctx, name, value)
+ x509_ext = X509.X509_Extension(x509_ext_ptr, 1)
+
+
+class CRLTestCase(unittest.TestCase):
+ def test_new(self):
+ crl = X509.CRL()
+ self.assertEqual(crl.as_text()[:34],
+ 'Certificate Revocation List (CRL):')
+
+
+def suite():
+ suite = unittest.TestSuite()
+ suite.addTest(unittest.makeSuite(X509TestCase))
+ suite.addTest(unittest.makeSuite(X509_StackTestCase))
+ suite.addTest(unittest.makeSuite(X509_ExtTestCase))
+ suite.addTest(unittest.makeSuite(CRLTestCase))
+ return suite
+
+
+if __name__ == '__main__':
+ Rand.load_file('randpool.dat', -1)
+ unittest.TextTestRunner().run(suite())
+ Rand.save_file('randpool.dat')
diff --git a/tests/thawte.pem b/tests/thawte.pem
new file mode 100644
index 0000000..34af29e
--- /dev/null
+++ b/tests/thawte.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEKjCCAxKgAwIBAgIQYAGXt0an6rS0mtZLL/eQ+zANBgkqhkiG9w0BAQsFADCB
+rjELMAkGA1UEBhMCVVMxFTATBgNVBAoTDHRoYXd0ZSwgSW5jLjEoMCYGA1UECxMf
+Q2VydGlmaWNhdGlvbiBTZXJ2aWNlcyBEaXZpc2lvbjE4MDYGA1UECxMvKGMpIDIw
+MDggdGhhd3RlLCBJbmMuIC0gRm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxJDAiBgNV
+BAMTG3RoYXd0ZSBQcmltYXJ5IFJvb3QgQ0EgLSBHMzAeFw0wODA0MDIwMDAwMDBa
+Fw0zNzEyMDEyMzU5NTlaMIGuMQswCQYDVQQGEwJVUzEVMBMGA1UEChMMdGhhd3Rl
+LCBJbmMuMSgwJgYDVQQLEx9DZXJ0aWZpY2F0aW9uIFNlcnZpY2VzIERpdmlzaW9u
+MTgwNgYDVQQLEy8oYykgMjAwOCB0aGF3dGUsIEluYy4gLSBGb3IgYXV0aG9yaXpl
+ZCB1c2Ugb25seTEkMCIGA1UEAxMbdGhhd3RlIFByaW1hcnkgUm9vdCBDQSAtIEcz
+MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsr8nLPvb2FvdeHsbnndm
+gcs+vHyu86YnmjSjaDFxODNi5PNxZnmxqWWjpYvVj2AtP0LMqmsywCPLLEHd5N/8
+YZzic7IilRFDGF/Eth9XbAoFWCLINkw6fKXRz4aviKdEAhN0cXMKQlkC+BsUa0Lf
+b1+6a4KinVvnSr0eAXLbS3ToO39/fR8EtCab4LRarEc9VbjXsCZSKAExQGbY2SS9
+9irY7CFJXJv2eul/VTV+lmuNk5Mny5K76qxAwJ/C+IDPXfRa3M50hqY+bAtTyr2S
+zhkGcuYMXDhpxwTWvGzOW/b3aJzcJRVIiKHpqfiYnODz1TEoYRFsZ5aNOZnLwkUk
+OQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAdBgNV
+HQ4EFgQUrWyqlGCc7eT/+j4KdCtjA/e2Wb8wDQYJKoZIhvcNAQELBQADggEBABpA
+2JVlrAmSicY59BDlqQ5mU1143vokkbvnRFHfxhY0Cu9qRFHqKweKA3rD6z8KLFIW
+oCtDuSWQP3CpMyVtRRooOyfPqsMpQhvfO0zAMzRbQYi/aytlryjvsvXDqmbOe1bu
+t8jLZ8HJnBoYuMTDSQPxYA5QzUbF83d597YV4Djbxy8ooAw/dyZ02SUS2jHaGh7c
+KUGRIjxpp7sC8rZcJwOJ9Abqm+RyguOhCcHpABnTPtRwa7pxpqpYrvS76Wy274fM
+m7v/OeZWYdMKp8RcTGB7BXcmer/YB1IsYvdwY9k5vG8cwnncdimvzsUsZAReiDZu
+MdRAGmI0Nj81Aa6sY6A=
+-----END CERTIFICATE-----
diff --git a/tests/x509.der b/tests/x509.der
new file mode 100644
index 0000000..fbc9e81
--- /dev/null
+++ b/tests/x509.der
Binary files differ
diff --git a/tests/x509.pem b/tests/x509.pem
new file mode 100644
index 0000000..4a1f1ee
--- /dev/null
+++ b/tests/x509.pem
@@ -0,0 +1,75 @@
+Certificate:
+ Data:
+ Version: 3 (0x2)
+ Serial Number:
+ d1:b6:bf:af:06:17:8c:bf
+ Signature Algorithm: sha1WithRSAEncryption
+ Issuer: C=US, ST=California, O=M2Crypto, CN=Heikki Toivonen
+ Validity
+ Not Before: Jul 28 04:34:34 2009 GMT
+ Not After : Jul 26 04:34:34 2019 GMT
+ Subject: C=US, ST=California, O=M2Crypto, CN=X509
+ Subject Public Key Info:
+ Public Key Algorithm: rsaEncryption
+ RSA Public Key: (1024 bit)
+ Modulus (1024 bit):
+ 00:d3:62:55:12:30:b8:dc:84:7c:63:bd:80:1d:19:
+ 1a:72:f2:28:f8:59:0b:2a:6b:f2:2a:23:9d:bb:0f:
+ 7f:92:5e:dd:27:74:bc:78:0a:27:ab:1c:2e:23:1c:
+ 26:77:48:b6:8f:03:ef:57:1c:a0:54:ae:1a:e8:f5:
+ 24:a1:46:a1:27:48:55:33:98:fc:db:6a:83:2e:89:
+ 3f:e0:f3:91:9d:da:4f:db:74:90:9d:a6:8d:4a:46:
+ cb:9f:ba:b8:60:df:ae:ee:22:4b:3f:80:55:f7:1d:
+ 89:3c:2b:28:df:46:19:d5:18:ac:e9:07:4e:40:81:
+ 75:bc:da:5b:d5:e1:c2:04:15
+ Exponent: 65537 (0x10001)
+ X509v3 extensions:
+ X509v3 Basic Constraints:
+ CA:FALSE
+ Netscape Comment:
+ OpenSSL Generated Certificate
+ X509v3 Subject Key Identifier:
+ B1:C4:6F:98:6F:E8:3B:8C:A1:26:11:81:97:9A:12:50:4A:1A:6C:88
+ X509v3 Authority Key Identifier:
+ keyid:AD:64:45:74:8F:83:C7:2C:D5:D7:A0:85:91:10:40:9A:9C:96:CF:EE
+
+ Signature Algorithm: sha1WithRSAEncryption
+ 3f:0b:44:bc:d2:da:5f:a9:39:be:08:53:e6:fd:10:ff:d6:f0:
+ a3:51:f6:be:03:20:cc:b3:52:cf:0f:7c:3f:56:42:6f:9d:72:
+ 9b:09:a5:64:3f:43:29:24:2b:d6:79:94:54:2f:99:e8:ce:fe:
+ fd:de:bb:ca:43:28:16:ff:32:ac:3d:c5:56:db:87:23:3c:d4:
+ 69:f7:4e:1b:c4:be:c9:d8:27:99:2a:64:be:3a:6b:7e:51:85:
+ db:75:35:40:a5:6c:ae:53:c3:09:e7:00:35:17:64:1a:17:71:
+ c5:d5:59:e5:8f:fc:96:4a:f9:81:33:23:4c:c1:60:71:93:18:
+ 0a:c4
+-----BEGIN CERTIFICATE-----
+MIICjDCCAfWgAwIBAgIJANG2v68GF4y/MA0GCSqGSIb3DQEBBQUAME8xCzAJBgNV
+BAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMREwDwYDVQQKEwhNMkNyeXB0bzEY
+MBYGA1UEAxMPSGVpa2tpIFRvaXZvbmVuMB4XDTA5MDcyODA0MzQzNFoXDTE5MDcy
+NjA0MzQzNFowRDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExETAP
+BgNVBAoTCE0yQ3J5cHRvMQ0wCwYDVQQDEwRYNTA5MIGfMA0GCSqGSIb3DQEBAQUA
+A4GNADCBiQKBgQDTYlUSMLjchHxjvYAdGRpy8ij4WQsqa/IqI527D3+SXt0ndLx4
+CierHC4jHCZ3SLaPA+9XHKBUrhro9SShRqEnSFUzmPzbaoMuiT/g85Gd2k/bdJCd
+po1KRsufurhg367uIks/gFX3HYk8KyjfRhnVGKzpB05AgXW82lvV4cIEFQIDAQAB
+o3sweTAJBgNVHRMEAjAAMCwGCWCGSAGG+EIBDQQfFh1PcGVuU1NMIEdlbmVyYXRl
+ZCBDZXJ0aWZpY2F0ZTAdBgNVHQ4EFgQUscRvmG/oO4yhJhGBl5oSUEoabIgwHwYD
+VR0jBBgwFoAUrWRFdI+DxyzV16CFkRBAmpyWz+4wDQYJKoZIhvcNAQEFBQADgYEA
+PwtEvNLaX6k5vghT5v0Q/9bwo1H2vgMgzLNSzw98P1ZCb51ymwmlZD9DKSQr1nmU
+VC+Z6M7+/d67ykMoFv8yrD3FVtuHIzzUafdOG8S+ydgnmSpkvjprflGF23U1QKVs
+rlPDCecANRdkGhdxxdVZ5Y/8lkr5gTMjTMFgcZMYCsQ=
+-----END CERTIFICATE-----
+-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQDTYlUSMLjchHxjvYAdGRpy8ij4WQsqa/IqI527D3+SXt0ndLx4
+CierHC4jHCZ3SLaPA+9XHKBUrhro9SShRqEnSFUzmPzbaoMuiT/g85Gd2k/bdJCd
+po1KRsufurhg367uIks/gFX3HYk8KyjfRhnVGKzpB05AgXW82lvV4cIEFQIDAQAB
+AoGATPipcY48QlAb21XNqMrTTrfPI1+JKVFVRPLjJJJoKaxRa2SenDdWaoBAbJh7
+iUP49erA5D+QQkWDlwBs7i0B0NqSkZAUVTfzRjGackTNJUQ+smfeqRLMH+Oru6DS
+VFbb818nJOJKqMMhMz8SrPrrbg+qiHlJ3JUQnNzTYohOMAECQQDvTJBSSit34ZBO
+ABj4vWYucCnOygcpICQnIsG97sZmF8tuF55tA5e+0v9R7BPuyAjrQnKJqDj3r/AY
+AxhgngGVAkEA4iMGoHzoSQvh+gT0A2rPCtVo+URNswIEZhQmMuA0VjrFCphWkZE+
+3jgDsJTNQUJs4mczQMcBzL34Nh1cJThYgQJARMMrdXn6o6gdX0yH4HIMOqvgV5uW
+Eys5OEW0hm9mc0/DFQ+UZp7xq9PVqiS8VZEFfxTI9OVx+TqFM2EwUBMXQQJBAIge
+n0mRhl0Z6v+NZbh83X3e8h5BUCf1ieJMNKYhMT/KhnsXMdzTui0XOJldKKQksNgj
+WMWgROQSYctpJuM8pIECQQCNN27XVHs4YAQ6GvBkrHsK5w6LZkm6UaJgbCqDqyeS
+eqfPp9VRurZ/FhK1mPbgNN67U4Ik1nwjR0o8wD4mreIj
+-----END RSA PRIVATE KEY-----