diff options
| author | Jean-Paul Calderone <exarkun@twistedmatrix.com> | 2013-03-05 11:56:19 -0800 |
|---|---|---|
| committer | Jean-Paul Calderone <exarkun@twistedmatrix.com> | 2013-03-05 11:56:19 -0800 |
| commit | 131052e950628de6df90e66c58c2848caa92ec9c (patch) | |
| tree | 024ce0d4fe38d8c7d2421018513824755270abcc /OpenSSL/SSL.py | |
| parent | 8a1bea59efd04f8a971e7914f204cb22c223a950 (diff) | |
| download | pyopenssl-131052e950628de6df90e66c58c2848caa92ec9c.tar.gz | |
All of ContextTests passes
Diffstat (limited to 'OpenSSL/SSL.py')
| -rw-r--r-- | OpenSSL/SSL.py | 507 |
1 files changed, 502 insertions, 5 deletions
diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py index 6b8d78a..36605a8 100644 --- a/OpenSSL/SSL.py +++ b/OpenSSL/SSL.py @@ -1,12 +1,14 @@ from functools import wraps +from itertools import count from OpenSSL.xSSL import * from tls.c import api as _api from OpenSSL.crypto import ( - FILETYPE_PEM, _PassphraseHelper, PKey, X509, _raise_current_error) + FILETYPE_PEM, _PassphraseHelper, PKey, X509, _raise_current_error, + _new_mem_buf) _unspecified = object() @@ -51,6 +53,32 @@ class Error(Exception): +class WantReadError(Error): + pass + + + +def _asFileDescriptor(obj): + fd = None + + if not isinstance(obj, int): + meth = getattr(obj, "fileno", None) + if meth is not None: + obj = meth() + + if isinstance(obj, int): + fd = obj + + if not isinstance(fd, int): + raise TypeError("argument must be an int, or have a fileno() method.") + elif fd < 0: + raise ValueError( + "file descriptor cannot be a negative integer (%i)" % (fd,)) + + return fd + + + def SSLeay_version(type): """ Return a string describing the version of OpenSSL in use. @@ -133,10 +161,10 @@ class Context(object): def _wrap_callback(self, callback): @wraps(callback) - def wrapped(size, verify, userdata): + def wrapper(size, verify, userdata): return callback(size, verify, self._passphrase_userdata) return _PassphraseHelper( - FILETYPE_PEM, wrapped, more_args=True, truncate=True) + FILETYPE_PEM, wrapper, more_args=True, truncate=True) def set_passwd_cb(self, callback, userdata=None): @@ -164,6 +192,11 @@ class Context(object): :return: None """ + set_result = _api.SSL_CTX_set_default_verify_paths(self._context) + if not set_result: + 1/0 + _raise_current_error(Error) + def use_certificate_chain_file(self, certfile): """ @@ -172,6 +205,13 @@ class Context(object): :param certfile: The name of the certificate chain file :return: None """ + if not isinstance(certfile, bytes): + raise TypeError("certfile must be a byte string") + + result = _api.SSL_CTX_use_certificate_chain_file(self._context, certfile) + if not result: + _raise_current_error(Error) + def use_certificate_file(self, certfile, filetype=_unspecified): """ @@ -189,6 +229,13 @@ class Context(object): :param cert: The X509 object :return: None """ + if not isinstance(cert, X509): + raise TypeError("cert must be an X509 instance") + + use_result = _api.SSL_CTX_use_certificate(self._context, cert._x509) + if not use_result: + 1/0 + def add_extra_chain_cert(self, certobj): """ @@ -313,8 +360,27 @@ class Context(object): if not callable(callback): raise TypeError("callback must be callable") - callback = _api.ffi.callback("int(*)(int, X509_STORE_CTX*)", callback) - _api.SSL_CTX_set_verify(self._context, mode, callback) + @wraps(callback) + def wrapper(ok, store_ctx): + cert = X509.__new__(X509) + cert._x509 = _api.X509_STORE_CTX_get_current_cert(store_ctx) + error_number = _api.X509_STORE_CTX_get_error(store_ctx) + error_depth = _api.X509_STORE_CTX_get_error_depth(store_ctx) + + try: + result = callback(self, cert, error_number, error_depth, ok) + except Exception as e: + # TODO + pass + else: + if result: + _api.X509_STORE_CTX_set_error(store_ctx, _api.X509_V_OK) + return 1 + else: + return 0 + + self._verify_callback = _api.ffi.callback("verify_callback", wrapper) + _api.SSL_CTX_set_verify(self._context, mode, self._verify_callback) def set_verify_depth(self, depth): @@ -375,6 +441,13 @@ class Context(object): :param cipher_list: A cipher list, see ciphers(1) :return: None """ + if not isinstance(cipher_list, bytes): + raise TypeError("cipher_list must be a byte string") + + result = _api.SSL_CTX_set_cipher_list(self._context, cipher_list) + if not result: + _raise_current_error(Error) + def set_client_ca_list(self, certificate_authorities): """ @@ -427,6 +500,12 @@ class Context(object): :param callback: The Python callback to use :return: None """ + @wraps(callback) + def wrapper(ssl, where, return_code): + callback(self, where, return_code) + self._info_callback = _api.callback('info_callback', wrapper) + _api.SSL_CTX_set_info_callback(self._context, self._info_callback) + def get_app_data(self): """ @@ -489,3 +568,421 @@ class Context(object): """ ContextType = Context + + + +class Connection(object): + """ + """ + def __init__(self, context, socket=None): + """ + Create a new Connection object, using the given OpenSSL.SSL.Context + instance and socket. + + :param context: An SSL Context to use for this connection + :param socket: The socket to use for transport layer + """ + if not isinstance(context, Context): + raise TypeError("context must be a Context instance") + + self._ssl = _api.SSL_new(context._context) + + if socket is None: + self._socket = None + self._into_ssl = _new_mem_buf() + self._from_ssl = _new_mem_buf() + + if self._into_ssl == _api.NULL or self._from_ssl == _api.NULL: + 1/0 + + _api.SSL_set_bio(self._ssl, self._into_ssl, self._from_ssl) + else: + self._socket = socket + set_result = _api.SSL_set_fd(self._ssl, _asFileDescriptor(self._socket)) + if not set_result: + 1/0 + + + def __getattr__(self, name): + """ + Look up attributes on the wrapped socket object if they are not found on + the Connection object. + """ + return getattr(self._socket, name) + + + def _raise_ssl_error(self, error, result): + if error == _api.SSL_ERROR_WANT_READ: + raise WantReadError() + else: + _raise_current_error(Error) + + + def get_context(self): + """ + Get session context + """ + + + def set_context(self): + """ + Switch this connection to a new session context + + :param context: A :py:class:`Context` instance giving the new session context to use. + """ + + def get_servername(self): + """ + Retrieve the servername extension value if provided in the client hello + message, or None if there wasn't one. + + :return: A byte string giving the server name or :py:data:`None`. + """ + + def set_tlsext_host_name(self): + """ + Set the value of the servername extension to send in the client hello. + + :param name: A byte string giving the name. + """ + + def pending(self): + """ + Get the number of bytes that can be safely read from the connection + + :return: The number of bytes available in the receive buffer. + """ + + + def _handle_SSL_result(self, ssl, result): + error = _api.SSL_get_error(ssl, result) + if error != _api.SSL_ERROR_NONE: + self._raise_ssl_error(error, result) + + + def send(self, data, flags=None): + """ + Send data on the connection. NOTE: If you get one of the WantRead, + WantWrite or WantX509Lookup exceptions on this, you have to call the + method again with the SAME buffer. + + :param buf: The string to send + :param flags: (optional) Included for compatibility with the socket + API, the value is ignored + :return: The number of bytes written + """ + result = _api.SSL_write(self._ssl, data, len(data)) + self._handle_SSL_result(self._ssl, result) + return result + write = send + + + def sendall(self): + """ + Send \"all\" data on the connection. This calls send() repeatedly until + all data is sent. If an error occurs, it's impossible to tell how much data + has been sent. + + :param buf: The string to send + :param flags: (optional) Included for compatibility with the socket + API, the value is ignored + :return: The number of bytes written + """ + + def recv(self, bufsiz, flags=None): + """ + Receive data on the connection. NOTE: If you get one of the WantRead, + WantWrite or WantX509Lookup exceptions on this, you have to call the + method again with the SAME buffer. + + :param bufsiz: The maximum number of bytes to read + :param flags: (optional) Included for compatibility with the socket + API, the value is ignored + :return: The string read from the Connection + """ + buf = _api.new("char[]", bufsiz) + result = _api.SSL_read(self._ssl, buf, bufsiz) + self._handle_SSL_result(self._ssl, result) + return _api.buffer(buf, result) + read = recv + + + def bio_read(self): + """ + When using non-socket connections this function reads + the \"dirty\" data that would have traveled away on the network. + + :param bufsiz: The maximum number of bytes to read + :return: The string read. + """ + + def bio_write(self): + """ + When using non-socket connections this function sends + \"dirty\" data that would have traveled in on the network. + + :param buf: The string to put into the memory BIO. + :return: The number of bytes written + """ + + def renegotiate(self): + """ + Renegotiate the session + + :return: True if the renegotiation can be started, false otherwise + """ + + def do_handshake(self): + """ + Perform an SSL handshake (usually called after renegotiate() or one of + set_*_state()). This can raise the same exceptions as send and recv. + + :return: None. + """ + result = _api.SSL_do_handshake(self._ssl) + self._handle_SSL_result(self._ssl, result) + + + def renegotiate_pending(self): + """ + Check if there's a renegotiation in progress, it will return false once + a renegotiation is finished. + + :return: Whether there's a renegotiation in progress + """ + + def total_renegotiations(self): + """ + Find out the total number of renegotiations. + + :return: The number of renegotiations. + """ + + def connect(self): + """ + Connect to remote host and set up client-side SSL + + :param addr: A remote address + :return: What the socket's connect method returns + """ + + def connect_ex(self): + """ + Connect to remote host and set up client-side SSL. Note that if the socket's + connect_ex method doesn't return 0, SSL won't be initialized. + + :param addr: A remove address + :return: What the socket's connect_ex method returns + """ + + def accept(self): + """ + Accept incoming connection and set up SSL on it + + :return: A (conn,addr) pair where conn is a Connection and addr is an + address + """ + + + def bio_shutdown(self): + """ + When using non-socket connections this function signals end of + data on the input for this connection. + + :return: None + """ + + def shutdown(self): + """ + Send closure alert + + :return: True if the shutdown completed successfully (i.e. both sides + have sent closure alerts), false otherwise (i.e. you have to + wait for a ZeroReturnError on a recv() method call + """ + result = _api.SSL_shutdown(self._ssl) + if result < 0: + 1/0 + elif result > 0: + 2/0 + else: + return False + + + def get_cipher_list(self): + """ + Get the session cipher list + + :return: A list of cipher strings + """ + ciphers = [] + for i in count(): + result = _api.SSL_get_cipher_list(self._ssl, i) + if result == _api.NULL: + break + ciphers.append(_api.string(result)) + return ciphers + + + def get_client_ca_list(self): + """ + Get CAs whose certificates are suggested for client authentication. + + :return: If this is a server connection, a list of X509Names representing + the acceptable CAs as set by :py:meth:`OpenSSL.SSL.Context.set_client_ca_list` or + :py:meth:`OpenSSL.SSL.Context.add_client_ca`. If this is a client connection, + the list of such X509Names sent by the server, or an empty list if that + has not yet happened. + """ + + def makefile(self): + """ + The makefile() method is not implemented, since there is no dup semantics + for SSL connections + + :raise NotImplementedError + """ + + + def get_app_data(self): + """ + Get application data + + :return: The application data + """ + + def set_app_data(self): + """ + Set application data + + :param data - The application data + :return: None + """ + + def get_shutdown(self): + """ + Get shutdown state + + :return: The shutdown state, a bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN. + """ + return _api.SSL_get_shutdown(self._ssl) + + + def set_shutdown(self): + """ + Set shutdown state + + :param state - bitvector of SENT_SHUTDOWN, RECEIVED_SHUTDOWN. + :return: None + """ + + def state_string(self): + """ + Get a verbose state description + + :return: A string representing the state + """ + + def server_random(self): + """ + Get a copy of the server hello nonce. + + :return: A string representing the state + """ + + def client_random(self): + """ + Get a copy of the client hello nonce. + + :return: A string representing the state + """ + + def master_key(self): + """ + Get a copy of the master key. + + :return: A string representing the state + """ + + def sock_shutdown(self): + """ + See shutdown(2) + + :return: What the socket's shutdown() method returns + """ + + def get_peer_certificate(self): + """ + Retrieve the other side's certificate (if any) + + :return: The peer's certificate + """ + cert = _api.SSL_get_peer_certificate(self._ssl) + if cert != _api.NULL: + pycert = X509.__new__(X509) + pycert._x509 = _api.ffi.gc(cert, _api.X509_free) + return pycert + return None + + + def get_peer_cert_chain(self): + """ + Retrieve the other side's certificate (if any) + + :return: A list of X509 instances giving the peer's certificate chain, + or None if it does not have one. + """ + + def want_read(self): + """ + Checks if more data has to be read from the transport layer to complete an + operation. + + :return: True iff more data has to be read + """ + + def want_write(self): + """ + Checks if there is data to write to the transport layer to complete an + operation. + + :return: True iff there is data to write + """ + + def set_accept_state(self): + """ + Set the connection to work in server mode. The handshake will be handled + automatically by read/write. + + :return: None + """ + _api.SSL_set_accept_state(self._ssl) + + + def set_connect_state(self): + """ + Set the connection to work in client mode. The handshake will be handled + automatically by read/write. + + :return: None + """ + _api.SSL_set_connect_state(self._ssl) + + + def get_session(self): + """ + Returns the Session currently used. + + @return: An instance of :py:class:`OpenSSL.SSL.Session` or :py:obj:`None` if + no session exists. + """ + + def set_session(self): + """ + Set the session to be used when the TLS/SSL connection is established. + + :param session: A Session instance representing the session to use. + :returns: None + """ + +ConnectionType = Connection |
