Add support for in-memory BIOs
OpenSSL.SSL.Connection now accepts None as the value for the socket/file object to
run over. In this case, it creates an in-memory buffer to use for reading and writing
instead. Connection objects also have several new methods; some for interacting with
this in-memory buffer, and others for inspecting the state of the SSL handshake (in
particular, for extracting the master, client, and session keys).
A bug in the crypto tests which caused them to fail on Windows has also been fixed.
diff --git a/ChangeLog b/ChangeLog
index 32067d8..842f857 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,16 @@
+2009-05-11 Jean-Paul Calderone <exarkun@twistedmatrix.com>
+
+ * test/test_crypto.py: Use binary mode for the pipe to talk to the
+ external openssl binary. The data being transported over this
+ pipe is indeed binary, so previously it would often be truncated
+ or otherwise mangled.
+
+ * src/ssl/connection.h, src/ssl/connection.c, test/test_ssl.py:
+ Extend the Connection class with support for in-memory BIOs. This
+ allows SSL to be run without a real socket, useful for
+ implementing EAP-TLS or using SSL with Windows IO completion
+ ports, for example.
+
2009-04-25 Jean-Paul Calderone <exarkun@twistedmatrix.com>
* Release 0.9
diff --git a/doc/pyOpenSSL.tex b/doc/pyOpenSSL.tex
index d0b5f17..b9d1f20 100644
--- a/doc/pyOpenSSL.tex
+++ b/doc/pyOpenSSL.tex
@@ -681,10 +681,12 @@
\end{datadesc}
\begin{funcdesc}{Connection}{context, socket}
-Factory fucnction that creates a new Connection object given an SSL context and
+Factory function that creates a new Connection object given an SSL context and
a socket \footnote{Actually, all that is required is an object that
-\emph{behaves} like a socket, you could even use files, even though it'd be
-tricky to get the handshakes right!} object.
+ \emph{behaves} like a socket, you could even use files, even though it'd be
+ tricky to get the handshakes right!} object. \var{socket} may be \var{None};
+in this case, the Connection is created with a memory BIO: see the
+\method{bio_read}, \method{bio_write}, and \method{bio_shutdown} methods.
\end{funcdesc}
\begin{excdesc}{Error}
@@ -978,6 +980,12 @@
by \var{bufsize}.
\end{methoddesc}
+\begin{methoddesc}[Connection]{bio_write}{bytes}
+If the Connection was created with a memory BIO, this method can be used to add
+bytes to the read end of that memory BIO. The Connection can then read the
+bytes (for example, in response to a call to \method{recv}).
+\end{methoddesc}
+
\begin{methoddesc}[Connection]{renegotiate}{}
Renegotiate the SSL session. Call this if you wish to change cipher suites or
anything like that.
@@ -987,6 +995,13 @@
Send the \var{string} data to the Connection.
\end{methoddesc}
+\begin{methoddesc}[Connection]{bio_read}{bufsize}
+If the Connection was created with a memory BIO, this method can be used to
+read bytes from the write end of that memory BIO. Many Connection methods will
+add bytes which must be read in this manner or the buffer will eventually fill
+up and the Connection will be able to take no further actions.
+\end{methoddesc}
+
\begin{methoddesc}[Connection]{sendall}{string}
Send all of the \var{string} data to the Connection. This calls \method{send}
repeatedly until all data is sent. If an error occurs, it's impossible to tell
@@ -1037,10 +1052,28 @@
Call the \method{shutdown} method of the underlying socket.
\end{methoddesc}
+\begin{methoddesc}[Connection]{bio_shutdown}{}
+If the Connection was created with a memory BIO, this method can be used to
+indicate that ``end of file'' has been reached on the read end of that memory
+BIO.
+\end{methoddesc}
+
\begin{methoddesc}[Connection]{state_string}{}
Retrieve a verbose string detailing the state of the Connection.
\end{methoddesc}
+\begin{methoddesc}[Connection]{client_random}{}
+Retrieve the random value used with the client hello message.
+\end{methoddesc}
+
+\begin{methoddesc}[Connection]{server_random}{}
+Retrieve the random value used with the server hello message.
+\end{methoddesc}
+
+\begin{methoddesc}[Connection]{master_key}{}
+Retrieve the value of the master key for this session.
+\end{methoddesc}
+
\begin{methoddesc}[Connection]{want_read}{}
Checks if more data has to be read from the transport layer to complete an
operation.
diff --git a/src/ssl/connection.c b/src/ssl/connection.c
index a9778de..f78e93f 100755
--- a/src/ssl/connection.c
+++ b/src/ssl/connection.c
@@ -23,8 +23,8 @@
#endif
#define SSL_MODULE
+#include <openssl/bio.h>
#include <openssl/err.h>
-
#include "ssl.h"
/**
@@ -125,6 +125,50 @@
}
/*
+ * Handle errors raised by BIO functions.
+ *
+ * Arguments: bio - The BIO object
+ * ret - The return value of the BIO_ function.
+ * Returns: None, the calling function should return NULL;
+ */
+static void
+handle_bio_errors(BIO* bio, int ret)
+{
+ if (BIO_should_retry(bio)) {
+ if (BIO_should_read(bio)) {
+ PyErr_SetNone(ssl_WantReadError);
+ } else if (BIO_should_write(bio)) {
+ PyErr_SetNone(ssl_WantWriteError);
+ } else if (BIO_should_io_special(bio)) {
+ /*
+ * It's somewhat unclear what this means. From the OpenSSL source,
+ * it seems like it should not be triggered by the memory BIO, so
+ * for the time being, this case shouldn't come up. The SSL BIO
+ * (which I think should be named the socket BIO) may trigger this
+ * case if its socket is not yet connected or it is busy doing
+ * something related to x509.
+ */
+ PyErr_SetString(PyExc_ValueError, "BIO_should_io_special");
+ } else {
+ /*
+ * I hope this is dead code. The BIO documentation suggests that
+ * one of the above three checks should always be true.
+ */
+ PyErr_SetString(PyExc_ValueError, "unknown bio failure");
+ }
+ } else {
+ /*
+ * If we aren't to retry, it's really an error, so fall back to the
+ * normal error reporting code. However, the BIO interface does not
+ * specify a uniform error reporting mechanism. We can only hope that
+ * the code which triggered the error also kindly pushed something onto
+ * the error stack.
+ */
+ exception_from_error_queue();
+ }
+}
+
+/*
* Handle errors raised by SSL I/O functions. NOTE: Not SSL_shutdown ;)
*
* Arguments: ssl - The SSL object
@@ -239,6 +283,49 @@
return PyInt_FromLong((long)ret);
}
+static char ssl_Connection_bio_write_doc[] = "\n\
+When using non-socket connections this function sends\n\
+\"dirty\" data that would have traveled in on the network.\n\
+\n\
+Arguments: self - The Connection object\n\
+ args - The Python argument tuple, should be:\n\
+ buf - The string to bio_write\n\
+Returns: The number of bytes written\n\
+";
+static PyObject *
+ssl_Connection_bio_write(ssl_ConnectionObj *self, PyObject *args)
+{
+ char *buf;
+ int len, ret;
+
+ if (self->into_ssl == NULL)
+ {
+ PyErr_SetString(PyExc_TypeError, "Connection sock was not None");
+ return NULL;
+ }
+
+ if (!PyArg_ParseTuple(args, "s#|i:bio_write", &buf, &len))
+ return NULL;
+
+ ret = BIO_write(self->into_ssl, buf, len);
+
+ if (PyErr_Occurred())
+ {
+ flush_error_queue();
+ return NULL;
+ }
+
+ if (ret <= 0) {
+ /*
+ * There was a problem with the BIO_write of some sort.
+ */
+ handle_bio_errors(self->into_ssl, ret);
+ return NULL;
+ }
+
+ return PyInt_FromLong((long)ret);
+}
+
static char ssl_Connection_send_doc[] = "\n\
Send data on the connection. NOTE: If you get one of the WantRead,\n\
WantWrite or WantX509Lookup exceptions on this, you have to call the\n\
@@ -384,6 +471,63 @@
}
}
+static char ssl_Connection_bio_read_doc[] = "\n\
+When using non-socket connections this function reads\n\
+the \"dirty\" data that would have traveled away on the network.\n\
+\n\
+Arguments: self - The Connection object\n\
+ args - The Python argument tuple, should be:\n\
+ bufsiz - The maximum number of bytes to read\n\
+Returns: The string read.\n\
+";
+static PyObject *
+ssl_Connection_bio_read(ssl_ConnectionObj *self, PyObject *args)
+{
+ int bufsiz, ret;
+ PyObject *buf;
+
+ if (self->from_ssl == NULL)
+ {
+ PyErr_SetString(PyExc_TypeError, "Connection sock was not None");
+ return NULL;
+ }
+
+ if (!PyArg_ParseTuple(args, "i:bio_read", &bufsiz))
+ return NULL;
+
+ buf = PyString_FromStringAndSize(NULL, bufsiz);
+ if (buf == NULL)
+ return NULL;
+
+ ret = BIO_read(self->from_ssl, PyString_AsString(buf), bufsiz);
+
+ if (PyErr_Occurred())
+ {
+ Py_DECREF(buf);
+ flush_error_queue();
+ return NULL;
+ }
+
+ if (ret <= 0) {
+ /*
+ * There was a problem with the BIO_read of some sort.
+ */
+ handle_bio_errors(self->from_ssl, ret);
+ Py_DECREF(buf);
+ return NULL;
+ }
+
+ /*
+ * Shrink the string to match the number of bytes we actually read.
+ */
+ if (ret != bufsiz && _PyString_Resize(&buf, ret) < 0)
+ {
+ Py_DECREF(buf);
+ return NULL;
+ }
+ return buf;
+}
+
static char ssl_Connection_renegotiate_doc[] = "\n\
Renegotiate the session\n\
\n\
@@ -626,6 +770,31 @@
return tuple;
}
+static char ssl_Connection_bio_shutdown_doc[] = "\n\
+When using non-socket connections this function signals end of\n\
+data on the input for this connection.\n\
+\n\
+Arguments: self - The Connection object\n\
+ args - The Python argument tuple, should be empty.\n\
+Returns: Nothing\n\
+";
+
+static PyObject *
+ssl_Connection_bio_shutdown(ssl_ConnectionObj *self, PyObject *args)
+{
+ if (self->from_ssl == NULL)
+ {
+ PyErr_SetString(PyExc_TypeError, "Connection sock was not None");
+ return NULL;
+ }
+
+ BIO_set_mem_eof_return(self->into_ssl, 0);
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+
+
static char ssl_Connection_shutdown_doc[] = "\n\
Send closure alert\n\
\n\
@@ -810,6 +979,66 @@
return PyString_FromString(SSL_state_string_long(self->ssl));
}
+static char ssl_Connection_client_random_doc[] = "\n\
+Get a copy of the client hello nonce.\n\
+\n\
+Arguments: self - The Connection object\n\
+ args - The Python argument tuple, should be empty\n\
+Returns: A string representing the state\n\
+";
+static PyObject *
+ssl_Connection_client_random(ssl_ConnectionObj *self, PyObject *args)
+{
+ if (!PyArg_ParseTuple(args, ":client_random"))
+ return NULL;
+
+ if (self->ssl->session == NULL) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ return PyString_FromStringAndSize( (const char *) self->ssl->s3->client_random, SSL3_RANDOM_SIZE);
+}
+
+static char ssl_Connection_server_random_doc[] = "\n\
+Get a copy of the server hello nonce.\n\
+\n\
+Arguments: self - The Connection object\n\
+ args - The Python argument tuple, should be empty\n\
+Returns: A string representing the state\n\
+";
+static PyObject *
+ssl_Connection_server_random(ssl_ConnectionObj *self, PyObject *args)
+{
+ if (!PyArg_ParseTuple(args, ":server_random"))
+ return NULL;
+
+ if (self->ssl->session == NULL) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ return PyString_FromStringAndSize( (const char *) self->ssl->s3->server_random, SSL3_RANDOM_SIZE);
+}
+
+static char ssl_Connection_master_key_doc[] = "\n\
+Get a copy of the master key.\n\
+\n\
+Arguments: self - The Connection object\n\
+ args - The Python argument tuple, should be empty\n\
+Returns: A string representing the state\n\
+";
+static PyObject *
+ssl_Connection_master_key(ssl_ConnectionObj *self, PyObject *args)
+{
+ if (!PyArg_ParseTuple(args, ":master_key"))
+ return NULL;
+
+ if (self->ssl->session == NULL) {
+ Py_INCREF(Py_None);
+ return Py_None;
+ }
+ return PyString_FromStringAndSize( (const char *) self->ssl->session->master_key, self->ssl->session->master_key_length);
+}
+
static char ssl_Connection_sock_shutdown_doc[] = "\n\
See shutdown(2)\n\
\n\
@@ -912,6 +1141,8 @@
ADD_METHOD(sendall),
ADD_METHOD(recv),
ADD_ALIAS (read, recv),
+ ADD_METHOD(bio_read),
+ ADD_METHOD(bio_write),
ADD_METHOD(renegotiate),
ADD_METHOD(do_handshake),
#if defined(OPENSSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x00907000L
@@ -921,6 +1152,7 @@
ADD_METHOD(connect),
ADD_METHOD(connect_ex),
ADD_METHOD(accept),
+ ADD_METHOD(bio_shutdown),
ADD_METHOD(shutdown),
ADD_METHOD(get_cipher_list),
ADD_METHOD(makefile),
@@ -929,6 +1161,9 @@
ADD_METHOD(get_shutdown),
ADD_METHOD(set_shutdown),
ADD_METHOD(state_string),
+ ADD_METHOD(server_random),
+ ADD_METHOD(client_random),
+ ADD_METHOD(master_key),
ADD_METHOD(sock_shutdown),
ADD_METHOD(get_peer_certificate),
ADD_METHOD(want_read),
@@ -965,26 +1200,50 @@
self->socket = sock;
self->ssl = NULL;
+ self->from_ssl = NULL;
+ self->into_ssl = NULL;
Py_INCREF(Py_None);
self->app_data = Py_None;
self->tstate = NULL;
- fd = PyObject_AsFileDescriptor(self->socket);
- if (fd < 0)
- {
- Py_DECREF(self);
- return NULL;
- }
-
self->ssl = SSL_new(self->context->ctx);
SSL_set_app_data(self->ssl, self);
- SSL_set_fd(self->ssl, (SOCKET_T)fd);
+
+ if (self->socket == Py_None)
+ {
+ /* If it's not a socket or file, treat it like a memory buffer,
+ * so crazy people can do things like EAP-TLS. */
+ self->into_ssl = BIO_new(BIO_s_mem());
+ self->from_ssl = BIO_new(BIO_s_mem());
+ if (self->into_ssl == NULL || self->from_ssl == NULL)
+ goto error;
+ SSL_set_bio(self->ssl, self->into_ssl, self->from_ssl);
+ }
+ else
+ {
+ fd = PyObject_AsFileDescriptor(self->socket);
+ if (fd < 0)
+ {
+ Py_DECREF(self);
+ return NULL;
+ }
+ else
+ {
+ SSL_set_fd(self->ssl, (SOCKET_T)fd);
+ }
+ }
PyObject_GC_Track(self);
return self;
+
+error:
+ BIO_free(self->into_ssl); /* NULL safe */
+ BIO_free(self->from_ssl); /* NULL safe */
+ Py_DECREF(self);
+ return NULL;
}
/*
@@ -1050,6 +1309,8 @@
self->socket = NULL;
Py_XDECREF(self->app_data);
self->app_data = NULL;
+ self->into_ssl = NULL; /* was cleaned up by SSL_free() */
+ self->from_ssl = NULL; /* was cleaned up by SSL_free() */
return 0;
}
diff --git a/src/ssl/connection.h b/src/ssl/connection.h
index 13f42f0..4e1e4d2 100644
--- a/src/ssl/connection.h
+++ b/src/ssl/connection.h
@@ -44,6 +44,7 @@
PyObject *socket;
PyThreadState *tstate; /* This field is no longer used. */
PyObject *app_data;
+ BIO *into_ssl, *from_ssl; /* for connections without file descriptors */
} ssl_ConnectionObj;
diff --git a/test/test_crypto.py b/test/test_crypto.py
index 214a4b8..87e9048 100644
--- a/test/test_crypto.py
+++ b/test/test_crypto.py
@@ -732,7 +732,7 @@
Run the command line openssl tool with the given arguments and write
the given PEM to its stdin.
"""
- write, read = popen2(" ".join(("openssl",) + args))
+ write, read = popen2(" ".join(("openssl",) + args), "b")
write.write(pem)
write.close()
return read.read()
diff --git a/test/test_ssl.py b/test/test_ssl.py
index 32d8f74..51ff5c7 100644
--- a/test/test_ssl.py
+++ b/test/test_ssl.py
@@ -15,12 +15,13 @@
from twisted.trial.unittest import TestCase
except ImportError:
# Fall back to the stdlib TestCase though, since it kind of works.
- from unittest import TestCase, main
+ from unittest import TestCase
from OpenSSL.crypto import TYPE_RSA, FILETYPE_PEM, PKey, dump_privatekey, load_certificate, load_privatekey
from OpenSSL.SSL import WantReadError, Context, Connection, Error
from OpenSSL.SSL import SSLv2_METHOD, SSLv3_METHOD, SSLv23_METHOD, TLSv1_METHOD
-from OpenSSL.SSL import VERIFY_PEER
+from OpenSSL.SSL import OP_NO_SSLv2, OP_NO_SSLv3, OP_SINGLE_DH_USE
+from OpenSSL.SSL import VERIFY_PEER, VERIFY_FAIL_IF_NO_PEER_CERT, VERIFY_CLIENT_ONCE
from OpenSSL.test.test_crypto import _Python23TestCaseHelper, cleartextCertificatePEM, cleartextPrivateKeyPEM
try:
from OpenSSL.SSL import OP_NO_QUERY_MTU
@@ -313,6 +314,287 @@
"OP_NO_TICKET unavailable - OpenSSL version may be too old"
-if __name__ == '__main__':
- main()
+root_cert_pem = """-----BEGIN CERTIFICATE-----
+MIIC7TCCAlagAwIBAgIIPQzE4MbeufQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UE
+BhMCVVMxCzAJBgNVBAgTAklMMRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdU
+ZXN0aW5nMRgwFgYDVQQDEw9UZXN0aW5nIFJvb3QgQ0EwIhgPMjAwOTAzMjUxMjM2
+NThaGA8yMDE3MDYxMTEyMzY1OFowWDELMAkGA1UEBhMCVVMxCzAJBgNVBAgTAklM
+MRAwDgYDVQQHEwdDaGljYWdvMRAwDgYDVQQKEwdUZXN0aW5nMRgwFgYDVQQDEw9U
+ZXN0aW5nIFJvb3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPmaQumL
+urpE527uSEHdL1pqcDRmWzu+98Y6YHzT/J7KWEamyMCNZ6fRW1JCR782UQ8a07fy
+2xXsKy4WdKaxyG8CcatwmXvpvRQ44dSANMihHELpANTdyVp6DCysED6wkQFurHlF
+1dshEaJw8b/ypDhmbVIo6Ci1xvCJqivbLFnbAgMBAAGjgbswgbgwHQYDVR0OBBYE
+FINVdy1eIfFJDAkk51QJEo3IfgSuMIGIBgNVHSMEgYAwfoAUg1V3LV4h8UkMCSTn
+VAkSjch+BK6hXKRaMFgxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UE
+BxMHQ2hpY2FnbzEQMA4GA1UEChMHVGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBS
+b290IENBggg9DMTgxt659DAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GB
+AGGCDazMJGoWNBpc03u6+smc95dEead2KlZXBATOdFT1VesY3+nUOqZhEhTGlDMi
+hkgaZnzoIq/Uamidegk4hirsCT/R+6vsKAAxNTcBjUeZjlykCJWy5ojShGftXIKY
+w/njVbKMXrvc83qmTdGl3TAM0fxQIpqgcglFLveEBgzn
+-----END CERTIFICATE-----
+"""
+
+root_key_pem = """-----BEGIN RSA PRIVATE KEY-----
+MIICXQIBAAKBgQD5mkLpi7q6ROdu7khB3S9aanA0Zls7vvfGOmB80/yeylhGpsjA
+jWen0VtSQke/NlEPGtO38tsV7CsuFnSmschvAnGrcJl76b0UOOHUgDTIoRxC6QDU
+3claegwsrBA+sJEBbqx5RdXbIRGicPG/8qQ4Zm1SKOgotcbwiaor2yxZ2wIDAQAB
+AoGBAPCgMpmLxzwDaUmcFbTJUvlLW1hoxNNYSu2jIZm1k/hRAcE60JYwvBkgz3UB
+yMEh0AtLxYe0bFk6EHah11tMUPgscbCq73snJ++8koUw+csk22G65hOs51bVb7Aa
+6JBe67oLzdtvgCUFAA2qfrKzWRZzAdhUirQUZgySZk+Xq1pBAkEA/kZG0A6roTSM
+BVnx7LnPfsycKUsTumorpXiylZJjTi9XtmzxhrYN6wgZlDOOwOLgSQhszGpxVoMD
+u3gByT1b2QJBAPtL3mSKdvwRu/+40zaZLwvSJRxaj0mcE4BJOS6Oqs/hS1xRlrNk
+PpQ7WJ4yM6ZOLnXzm2mKyxm50Mv64109FtMCQQDOqS2KkjHaLowTGVxwC0DijMfr
+I9Lf8sSQk32J5VWCySWf5gGTfEnpmUa41gKTMJIbqZZLucNuDcOtzUaeWZlZAkA8
+ttXigLnCqR486JDPTi9ZscoZkZ+w7y6e/hH8t6d5Vjt48JVyfjPIaJY+km58LcN3
+6AWSeGAdtRFHVzR7oHjVAkB4hutvxiOeiIVQNBhM6RSI9aBPMI21DoX2JRoxvNW2
+cbvAhow217X9V0dVerEOKxnNYspXRrh36h7k4mQA+sDq
+-----END RSA PRIVATE KEY-----
+"""
+
+server_cert_pem = """-----BEGIN CERTIFICATE-----
+MIICKDCCAZGgAwIBAgIJAJn/HpR21r/8MA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UEBxMHQ2hpY2FnbzEQMA4GA1UEChMH
+VGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBSb290IENBMCIYDzIwMDkwMzI1MTIz
+NzUzWhgPMjAxNzA2MTExMjM3NTNaMBgxFjAUBgNVBAMTDWxvdmVseSBzZXJ2ZXIw
+gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAL6m+G653V0tpBC/OKl22VxOi2Cv
+lK4TYu9LHSDP9uDVTe7V5D5Tl6qzFoRRx5pfmnkqT5B+W9byp2NU3FC5hLm5zSAr
+b45meUhjEJ/ifkZgbNUjHdBIGP9MAQUHZa5WKdkGIJvGAvs8UzUqlr4TBWQIB24+
+lJ+Ukk/CRgasrYwdAgMBAAGjNjA0MB0GA1UdDgQWBBS4kC7Ij0W1TZXZqXQFAM2e
+gKEG2DATBgNVHSUEDDAKBggrBgEFBQcDATANBgkqhkiG9w0BAQUFAAOBgQBh30Li
+dJ+NlxIOx5343WqIBka3UbsOb2kxWrbkVCrvRapCMLCASO4FqiKWM+L0VDBprqIp
+2mgpFQ6FHpoIENGvJhdEKpptQ5i7KaGhnDNTfdy3x1+h852G99f1iyj0RmbuFcM8
+uzujnS8YXWvM7DM1Ilozk4MzPug8jzFp5uhKCQ==
+-----END CERTIFICATE-----
+"""
+
+server_key_pem = """-----BEGIN RSA PRIVATE KEY-----
+MIICWwIBAAKBgQC+pvhuud1dLaQQvzipdtlcTotgr5SuE2LvSx0gz/bg1U3u1eQ+
+U5eqsxaEUceaX5p5Kk+QflvW8qdjVNxQuYS5uc0gK2+OZnlIYxCf4n5GYGzVIx3Q
+SBj/TAEFB2WuVinZBiCbxgL7PFM1Kpa+EwVkCAduPpSflJJPwkYGrK2MHQIDAQAB
+AoGAbwuZ0AR6JveahBaczjfnSpiFHf+mve2UxoQdpyr6ROJ4zg/PLW5K/KXrC48G
+j6f3tXMrfKHcpEoZrQWUfYBRCUsGD5DCazEhD8zlxEHahIsqpwA0WWssJA2VOLEN
+j6DuV2pCFbw67rfTBkTSo32ahfXxEKev5KswZk0JIzH3ooECQQDgzS9AI89h0gs8
+Dt+1m11Rzqo3vZML7ZIyGApUzVan+a7hbc33nbGRkAXjHaUBJO31it/H6dTO+uwX
+msWwNG5ZAkEA2RyFKs5xR5USTFaKLWCgpH/ydV96KPOpBND7TKQx62snDenFNNbn
+FwwOhpahld+vqhYk+pfuWWUpQciE+Bu7ZQJASjfT4sQv4qbbKK/scePicnDdx9th
+4e1EeB9xwb+tXXXUo/6Bor/AcUNwfiQ6Zt9PZOK9sR3lMZSsP7rMi7kzuQJABie6
+1sXXjFH7nNJvRG4S39cIxq8YRYTy68II/dlB2QzGpKxV/POCxbJ/zu0CU79tuYK7
+NaeNCFfH3aeTrX0LyQJAMBWjWmeKM2G2sCExheeQK0ROnaBC8itCECD4Jsve4nqf
+r50+LF74iLXFwqysVCebPKMOpDWp/qQ1BbJQIPs7/A==
+-----END RSA PRIVATE KEY-----
+"""
+
+client_cert_pem = """-----BEGIN CERTIFICATE-----
+MIICJjCCAY+gAwIBAgIJAKxpFI5lODkjMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJJTDEQMA4GA1UEBxMHQ2hpY2FnbzEQMA4GA1UEChMH
+VGVzdGluZzEYMBYGA1UEAxMPVGVzdGluZyBSb290IENBMCIYDzIwMDkwMzI1MTIz
+ODA1WhgPMjAxNzA2MTExMjM4MDVaMBYxFDASBgNVBAMTC3VnbHkgY2xpZW50MIGf
+MA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDAZh/SRtNm5ntMT4qb6YzEpTroMlq2
+rn+GrRHRiZ+xkCw/CGNhbtPir7/QxaUj26BSmQrHw1bGKEbPsWiW7bdXSespl+xK
+iku4G/KvnnmWdeJHqsiXeUZtqurMELcPQAw9xPHEuhqqUJvvEoMTsnCEqGM+7Dtb
+oCRajYyHfluARQIDAQABozYwNDAdBgNVHQ4EFgQUNQB+qkaOaEVecf1J3TTUtAff
+0fAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDQYJKoZIhvcNAQEFBQADgYEAyv/Jh7gM
+Q3OHvmsFEEvRI+hsW8y66zK4K5de239Y44iZrFYkt7Q5nBPMEWDj4F2hLYWL/qtI
+9Zdr0U4UDCU9SmmGYh4o7R4TZ5pGFvBYvjhHbkSFYFQXZxKUi+WUxplP6I0wr2KJ
+PSTJCjJOn3xo2NTKRgV1gaoTf2EhL+RG8TQ=
+-----END CERTIFICATE-----
+"""
+
+client_key_pem = """-----BEGIN RSA PRIVATE KEY-----
+MIICXgIBAAKBgQDAZh/SRtNm5ntMT4qb6YzEpTroMlq2rn+GrRHRiZ+xkCw/CGNh
+btPir7/QxaUj26BSmQrHw1bGKEbPsWiW7bdXSespl+xKiku4G/KvnnmWdeJHqsiX
+eUZtqurMELcPQAw9xPHEuhqqUJvvEoMTsnCEqGM+7DtboCRajYyHfluARQIDAQAB
+AoGATkZ+NceY5Glqyl4mD06SdcKfV65814vg2EL7V9t8+/mi9rYL8KztSXGlQWPX
+zuHgtRoMl78yQ4ZJYOBVo+nsx8KZNRCEBlE19bamSbQLCeQMenWnpeYyQUZ908gF
+h6L9qsFVJepgA9RDgAjyDoS5CaWCdCCPCH2lDkdcqC54SVUCQQDseuduc4wi8h4t
+V8AahUn9fn9gYfhoNuM0gdguTA0nPLVWz4hy1yJiWYQe0H7NLNNTmCKiLQaJpAbb
+TC6vE8C7AkEA0Ee8CMJUc20BnGEmxwgWcVuqFWaKCo8jTH1X38FlATUsyR3krjW2
+dL3yDD9NwHxsYP7nTKp/U8MV7U9IBn4y/wJBAJl7H0/BcLeRmuJk7IqJ7b635iYB
+D/9beFUw3MUXmQXZUfyYz39xf6CDZsu1GEdEC5haykeln3Of4M9d/4Kj+FcCQQCY
+si6xwT7GzMDkk/ko684AV3KPc/h6G0yGtFIrMg7J3uExpR/VdH2KgwMkZXisSMvw
+JJEQjOMCVsEJlRk54WWjAkEAzoZNH6UhDdBK5F38rVt/y4SEHgbSfJHIAmPS32Kq
+f6GGcfNpip0Uk7q7udTKuX7Q/buZi/C4YW7u3VKAquv9NA==
+-----END RSA PRIVATE KEY-----
+"""
+
+def verify_cb(conn, cert, errnum, depth, ok):
+ return ok
+
+class MemoryBIOTests(TestCase):
+ """
+ Tests for L{OpenSSL.SSL.Connection} using a memory BIO.
+ """
+ def _server(self):
+ # Create the server side Connection. This is mostly setup boilerplate
+ # - use TLSv1, use a particular certificate, etc.
+ server_ctx = Context(TLSv1_METHOD)
+ server_ctx.set_options(OP_NO_SSLv2 | OP_NO_SSLv3 | OP_SINGLE_DH_USE )
+ server_ctx.set_verify(VERIFY_PEER|VERIFY_FAIL_IF_NO_PEER_CERT|VERIFY_CLIENT_ONCE, verify_cb)
+ server_store = server_ctx.get_cert_store()
+ server_ctx.use_privatekey(load_privatekey(FILETYPE_PEM, server_key_pem))
+ server_ctx.use_certificate(load_certificate(FILETYPE_PEM, server_cert_pem))
+ server_ctx.check_privatekey()
+ server_store.add_cert(load_certificate(FILETYPE_PEM, root_cert_pem))
+ # Here the Connection is actually created. None is passed as the 2nd
+ # parameter, indicating a memory BIO should be created.
+ server_conn = Connection(server_ctx, None)
+ server_conn.set_accept_state()
+ return server_conn
+
+
+ def _client(self):
+ # Now create the client side Connection. Similar boilerplate to the above.
+ client_ctx = Context(TLSv1_METHOD)
+ client_ctx.set_options(OP_NO_SSLv2 | OP_NO_SSLv3 | OP_SINGLE_DH_USE )
+ client_ctx.set_verify(VERIFY_PEER|VERIFY_FAIL_IF_NO_PEER_CERT|VERIFY_CLIENT_ONCE, verify_cb)
+ client_store = client_ctx.get_cert_store()
+ client_ctx.use_privatekey(load_privatekey(FILETYPE_PEM, client_key_pem))
+ client_ctx.use_certificate(load_certificate(FILETYPE_PEM, client_cert_pem))
+ client_ctx.check_privatekey()
+ client_store.add_cert(load_certificate(FILETYPE_PEM, root_cert_pem))
+ # Again, None to create a new memory BIO.
+ client_conn = Connection(client_ctx, None)
+ client_conn.set_connect_state()
+ return client_conn
+
+
+ def _loopback(self, client_conn, server_conn):
+ """
+ Try to read application bytes from each of the two L{Connection}
+ objects. Copy bytes back and forth between their send/receive buffers
+ for as long as there is anything to copy. When there is nothing more
+ to copy, return C{None}. If one of them actually manages to deliver
+ some application bytes, return a two-tuple of the connection from which
+ the bytes were read and the bytes themselves.
+ """
+ wrote = True
+ while wrote:
+ # Loop until neither side has anything to say
+ wrote = False
+
+ # Copy stuff from each side's send buffer to the other side's
+ # receive buffer.
+ for (read, write) in [(client_conn, server_conn),
+ (server_conn, client_conn)]:
+
+ # Give the side a chance to generate some more bytes, or
+ # succeed.
+ try:
+ bytes = read.recv(2 ** 16)
+ except WantReadError:
+ # It didn't succeed, so we'll hope it generated some
+ # output.
+ pass
+ else:
+ # It did succeed, so we'll stop now and let the caller deal
+ # with it.
+ return (read, bytes)
+
+ while True:
+ # Keep copying as long as there's more stuff there.
+ try:
+ dirty = read.bio_read(4096)
+ except WantReadError:
+ # Okay, nothing more waiting to be sent. Stop
+ # processing this send buffer.
+ break
+ else:
+ # Keep track of the fact that someone generated some
+ # output.
+ wrote = True
+ write.bio_write(dirty)
+
+
+ def test_connect(self):
+ """
+ Two L{Connection}s which use memory BIOs can be manually connected by
+ reading from the output of each and writing those bytes to the input of
+ the other and in this way establish a connection and exchange
+ application-level bytes with each other.
+ """
+ server_conn = self._server()
+ client_conn = self._client()
+
+ # There should be no key or nonces yet.
+ self.assertIdentical(server_conn.master_key(), None)
+ self.assertIdentical(server_conn.client_random(), None)
+ self.assertIdentical(server_conn.server_random(), None)
+
+ # First, the handshake needs to happen. We'll deliver bytes back and
+ # forth between the client and server until neither of them feels like
+ # speaking any more.
+ self.assertIdentical(self._loopback(client_conn, server_conn), None)
+
+ # Now that the handshake is done, there should be a key and nonces.
+ self.assertNotIdentical(server_conn.master_key(), None)
+ self.assertNotIdentical(server_conn.client_random(), None)
+ self.assertNotIdentical(server_conn.server_random(), None)
+ self.assertNotIdentical(server_conn.client_random(), client_conn.client_random())
+ self.assertNotIdentical(server_conn.server_random(), client_conn.server_random())
+
+ # Here are the bytes we'll try to send.
+ important_message = 'One if by land, two if by sea.'
+
+ server_conn.write(important_message)
+ self.assertEquals(
+ self._loopback(client_conn, server_conn),
+ (client_conn, important_message))
+
+ client_conn.write(important_message[::-1])
+ self.assertEquals(
+ self._loopback(client_conn, server_conn),
+ (server_conn, important_message[::-1]))
+
+
+ def test_socketOverridesMemory(self):
+ """
+ Test that L{OpenSSL.SSL.bio_read} and L{OpenSSL.SSL.bio_write} don't
+ work on L{OpenSSL.SSL.Connection}() that use sockets.
+ """
+ context = Context(SSLv3_METHOD)
+ client = socket()
+ clientSSL = Connection(context, client)
+ self.assertRaises( TypeError, clientSSL.bio_read, 100)
+ self.assertRaises( TypeError, clientSSL.bio_write, "foo")
+ self.assertRaises( TypeError, clientSSL.bio_shutdown )
+
+
+ def test_outgoingOverflow(self):
+ """
+ If more bytes than can be written to the memory BIO are passed to
+ L{Connection.send} at once, the number of bytes which were written is
+ returned and that many bytes from the beginning of the input can be
+ read from the other end of the connection.
+ """
+ server = self._server()
+ client = self._client()
+
+ self._loopback(client, server)
+
+ size = 2 ** 15
+ sent = client.send("x" * size)
+ # Sanity check. We're trying to test what happens when the entire
+ # input can't be sent. If the entire input was sent, this test is
+ # meaningless.
+ self.assertTrue(sent < size)
+
+ receiver, received = self._loopback(client, server)
+ self.assertIdentical(receiver, server)
+
+ # We can rely on all of these bytes being received at once because
+ # _loopback passes 2 ** 16 to recv - more than 2 ** 15.
+ self.assertEquals(len(received), sent)
+
+
+ def test_shutdown(self):
+ """
+ L{Connection.bio_shutdown} signals the end of the data stream from
+ which the L{Connection} reads.
+ """
+ server = self._server()
+ server.bio_shutdown()
+ e = self.assertRaises(Error, server.recv, 1024)
+ # We don't want WantReadError or ZeroReturnError or anything - it's a
+ # handshake failure.
+ self.assertEquals(e.__class__, Error)