Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement missing methods #422

Merged
merged 4 commits into from
Mar 18, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,19 @@ matrix:
- python: "pypy"
env: TOXENV=pypy-cryptographyMaster

# And older cryptography versions.
# And current minimum cryptography version.
- python: "2.6"
env: TOXENV=py26-cryptography1.2
env: TOXENV=py26-cryptographyMinimum
- python: "2.7"
env: TOXENV=py27-cryptography1.2
env: TOXENV=py27-cryptographyMinimum
- python: "3.3"
env: TOXENV=py33-cryptography1.2
env: TOXENV=py33-cryptographyMinimum
- python: "3.4"
env: TOXENV=py34-cryptography1.2
env: TOXENV=py34-cryptographyMinimum
- python: "3.5"
env: TOXENV=py35-cryptography1.2
env: TOXENV=py35-cryptographyMinimum
- python: "pypy"
env: TOXENV=pypy-cryptography1.2
env: TOXENV=pypy-cryptographyMinimum


# Make sure we don't break Twisted or urllib3
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ Deprecations:
Changes:
^^^^^^^^

- Fixed ``OpenSSL.SSL.Context.set_session_id``, ``OpenSSL.SSL.Connection.renegotiate``, ``OpenSSL.SSL.Connection.renegotiate_pending``, and ``OpenSSL.SSL.Context.load_client_ca``.
They were lacking an implementation since 0.14.
`#422 <https://github.com/pyca/pyopenssl/pull/422>`_
- Fixed segmentation fault when using keys larger than 4096-bit to sign data.
`#428 <https://github.com/pyca/pyopenssl/pull/428>`_
- Fixed ``AttributeError`` when ``OpenSSL.SSL.Connection.get_app_data()`` was called before setting any app data.
Expand Down
18 changes: 5 additions & 13 deletions doc/api/ssl.rst
Original file line number Diff line number Diff line change
Expand Up @@ -274,10 +274,7 @@ Context objects have the following methods:
Retrieve the Context object's verify mode, as set by :py:meth:`set_verify`.


.. py:method:: Context.load_client_ca(pemfile)

Read a file with PEM-formatted certificates that will be sent to the client
when requesting a client certificate.
.. automethod:: Context.load_client_ca


.. py:method:: Context.set_client_ca_list(certificate_authorities)
Expand Down Expand Up @@ -387,12 +384,7 @@ Context objects have the following methods:
.. versionadded:: 0.14


.. py:method:: Context.set_session_id(name)

Set the context *name* within which a session can be reused for this
Context object. This is needed when doing session resumption, because there is
no way for a stored session to know which Context object it is associated with.
*name* may be any binary data.
.. automethod:: Context.set_session_id


.. py:method:: Context.set_timeout(timeout)
Expand Down Expand Up @@ -684,11 +676,11 @@ Connection objects have the following methods:
bytes (for example, in response to a call to :py:meth:`recv`).


.. py:method:: Connection.renegotiate()
.. automethod:: Connection.renegotiate

Renegotiate the SSL session. Call this if you wish to change cipher suites or
anything like that.
.. automethod:: Connection.renegotiate_pending

.. automethod:: Connection.total_renegotiations

.. py:method:: Connection.send(string)

Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,8 @@ def find_meta(meta):
packages=find_packages(where="src"),
package_dir={"": "src"},
install_requires=[
"cryptography>=1.2",
# Fix cryptographyMinimum in tox.ini when changing this!
"cryptography>=1.3",
"six>=1.5.2"
],
)
44 changes: 34 additions & 10 deletions src/OpenSSL/SSL.py
Original file line number Diff line number Diff line change
Expand Up @@ -689,23 +689,39 @@ def check_privatekey(self):

def load_client_ca(self, cafile):
"""
Load the trusted certificates that will be sent to the client
(basically telling the client "These are the guys I trust"). Does not
actually imply any of the certificates are trusted; that must be
Load the trusted certificates that will be sent to the client. Does
not actually imply any of the certificates are trusted; that must be
configured separately.
:param cafile: The name of the certificates file
:param bytes cafile: The path to a certificates file in PEM format.
:return: None
"""
ca_list = _lib.SSL_load_client_CA_file(
_text_to_bytes_and_warn("cafile", cafile)
)
_openssl_assert(ca_list != _ffi.NULL)
# SSL_CTX_set_client_CA_list doesn't return anything.
_lib.SSL_CTX_set_client_CA_list(self._context, ca_list)

def set_session_id(self, buf):
"""
Set the session identifier. This is needed if you want to do session
resumption.
Set the session id to *buf* within which a session can be reused for
this Context object. This is needed when doing session resumption,
because there is no way for a stored session to know which Context
object it is associated with.
:param bytes buf: The session id.
:param buf: A Python object that can be safely converted to a string
:returns: None
"""
buf = _text_to_bytes_and_warn("buf", buf)
_openssl_assert(
_lib.SSL_CTX_set_session_id_context(
self._context,
buf,
len(buf),
) == 1
)

def set_session_cache_mode(self, mode):
"""
Expand Down Expand Up @@ -1406,10 +1422,15 @@ def bio_write(self, buf):

def renegotiate(self):
"""
Renegotiate the session
Renegotiate the session.
:return: True if the renegotiation can be started, false otherwise
:return: True if the renegotiation can be started, False otherwise
:rtype: bool
"""
if not self.renegotiate_pending():
_openssl_assert(_lib.SSL_renegotiate(self._ssl) == 1)
return True
return False

def do_handshake(self):
"""
Expand All @@ -1423,17 +1444,20 @@ def do_handshake(self):

def renegotiate_pending(self):
"""
Check if there's a renegotiation in progress, it will return false once
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
:rtype: bool
"""
return _lib.SSL_renegotiate_pending(self._ssl) == 1

def total_renegotiations(self):
"""
Find out the total number of renegotiations.
:return: The number of renegotiations.
:rtype: int
"""
return _lib.SSL_total_renegotiations(self._ssl)

Expand Down
154 changes: 133 additions & 21 deletions tests/test_ssl.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
Unit tests for :mod:`OpenSSL.SSL`.
"""

import datetime
import uuid

from gc import collect, get_referrers
from errno import ECONNREFUSED, EINPROGRESS, EWOULDBLOCK, EPIPE, ESHUTDOWN
from sys import platform, getfilesystemencoding, version_info
Expand All @@ -19,6 +22,14 @@

from six import PY3, text_type

from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.x509.oid import NameOID


from OpenSSL.crypto import TYPE_RSA, FILETYPE_PEM
from OpenSSL.crypto import PKey, X509, X509Extension, X509Store
from OpenSSL.crypto import dump_privatekey, load_privatekey
Expand Down Expand Up @@ -347,6 +358,49 @@ def test_SSLeay_version(self):
self.assertEqual(len(versions), 5)


@pytest.fixture
def ca_file(tmpdir):
"""
Create a valid PEM file with CA certificates and return the path.
"""
key = rsa.generate_private_key(
public_exponent=65537,
key_size=2048,
backend=default_backend()
)
public_key = key.public_key()

builder = x509.CertificateBuilder()
builder = builder.subject_name(x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, u"pyopenssl.org"),
]))
builder = builder.issuer_name(x509.Name([
x509.NameAttribute(NameOID.COMMON_NAME, u"pyopenssl.org"),
]))
one_day = datetime.timedelta(1, 0, 0)
builder = builder.not_valid_before(datetime.datetime.today() - one_day)
builder = builder.not_valid_after(datetime.datetime.today() + one_day)
builder = builder.serial_number(int(uuid.uuid4()))
builder = builder.public_key(public_key)
builder = builder.add_extension(
x509.BasicConstraints(ca=True, path_length=None), critical=True,
)

certificate = builder.sign(
private_key=key, algorithm=hashes.SHA256(),
backend=default_backend()
)

ca_file = tmpdir.join("test.pem")
ca_file.write_binary(
certificate.public_bytes(
encoding=serialization.Encoding.PEM,
)
)

return str(ca_file).encode("ascii")


@pytest.fixture
def context():
"""
Expand Down Expand Up @@ -389,6 +443,59 @@ def test_set_cipher_list_wrong_args(self, context, cipher_list, error):
with pytest.raises(error):
context.set_cipher_list(cipher_list)

def test_load_client_ca(self, context, ca_file):
"""
:meth:`Context.load_client_ca` works as far as we can tell.
"""
context.load_client_ca(ca_file)

def test_load_client_ca_invalid(self, context, tmpdir):
"""
:meth:`Context.load_client_ca` raises an Error if the ca file is
invalid.
"""
ca_file = tmpdir.join("test.pem")
ca_file.write("")

with pytest.raises(Error) as e:
context.load_client_ca(str(ca_file).encode("ascii"))

assert "PEM routines" == e.value.args[0][0][0]

def test_load_client_ca_unicode(self, context, ca_file):
"""
Passing the path as unicode raises a warning but works.
"""
pytest.deprecated_call(
context.load_client_ca, ca_file.decode("ascii")
)

def test_set_session_id(self, context):
"""
:meth:`Context.set_session_id` works as far as we can tell.
"""
context.set_session_id(b"abc")

def test_set_session_id_fail(self, context):
"""
:meth:`Context.set_session_id` errors are propagated.
"""
with pytest.raises(Error) as e:
context.set_session_id(b"abc" * 1000)

assert [
("SSL routines",
"SSL_CTX_set_session_id_context",
"ssl session id context too long")
] == e.value.args[0]

def test_set_session_id_unicode(self, context):
"""
:meth:`Context.set_session_id` raises a warning if a unicode string is
passed.
"""
pytest.deprecated_call(context.set_session_id, u"abc")


class ContextTests(TestCase, _LoopbackMixin):
"""
Expand Down Expand Up @@ -1210,9 +1317,10 @@ def verify_callback(*args):
raise Exception("silly verify failure")
clientContext.set_verify(VERIFY_PEER, verify_callback)

exc = self.assertRaises(
Exception, self._handshake_test, serverContext, clientContext)
self.assertEqual("silly verify failure", str(exc))
with pytest.raises(Exception) as exc:
self._handshake_test(serverContext, clientContext)

self.assertEqual("silly verify failure", str(exc.value))

def test_add_extra_chain_cert(self):
"""
Expand Down Expand Up @@ -1338,9 +1446,6 @@ def test_use_certificate_chain_file_wrong_args(self):
Error, context.use_certificate_chain_file, self.mktemp()
)

# XXX load_client_ca
# XXX set_session_id

def test_get_verify_mode_wrong_args(self):
"""
:py:obj:`Context.get_verify_mode` raises :py:obj:`TypeError` if called
Expand Down Expand Up @@ -2033,7 +2138,6 @@ class ConnectionTests(TestCase, _LoopbackMixin):
# XXX connect_ex -> TypeError
# XXX set_connect_state -> TypeError
# XXX set_accept_state -> TypeError
# XXX renegotiate_pending
# XXX do_handshake -> TypeError
# XXX bio_read -> TypeError
# XXX recv -> TypeError
Expand Down Expand Up @@ -3136,24 +3240,32 @@ def test_total_renegotiations(self):
connection = Connection(Context(TLSv1_METHOD), None)
self.assertEquals(connection.total_renegotiations(), 0)

# def test_renegotiate(self):
# """
# """
# server, client = self._loopback()
def test_renegotiate(self):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this test call renegotiate_pending during and after to confirm it properly returns True/False at the expected time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve added some asserts and the builders are still green.

"""
Go through a complete renegotiation cycle.
"""
server, client = self._loopback()

server.send(b"hello world")

assert b"hello world" == client.recv(len(b"hello world"))

# server.send("hello world")
# self.assertEquals(client.recv(len("hello world")), "hello world")
assert 0 == server.total_renegotiations()
assert False is server.renegotiate_pending()

# self.assertEquals(server.total_renegotiations(), 0)
# self.assertTrue(server.renegotiate())
assert True is server.renegotiate()

# server.setblocking(False)
# client.setblocking(False)
# while server.renegotiate_pending():
# client.do_handshake()
# server.do_handshake()
assert True is server.renegotiate_pending()

# self.assertEquals(server.total_renegotiations(), 1)
server.setblocking(False)
client.setblocking(False)

client.do_handshake()
server.do_handshake()

assert 1 == server.total_renegotiations()
while False is server.renegotiate_pending():
pass


class ErrorTests(TestCase):
Expand Down
Loading