Skip to content
Merged

Ecdhe #101

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
7b8d57a
Expose support for using ecdhe with SSL connections
alex Jan 17, 2014
a683fc0
Corrected a typo
alex Jan 17, 2014
12dc084
Added tests
alex Jan 17, 2014
2c04e86
Added a gitignore
alex Jan 17, 2014
807853c
Use the internal name
alex Jan 17, 2014
d5419e2
Added documentation
alex Jan 20, 2014
479878d
Merge branch 'master' into ecdhe-support
alex Feb 21, 2014
32f016f
Merge branch 'master' into ecdhe-support
alex Feb 22, 2014
b4e5c8d
Expose all of the EC curve name constants
amluto Mar 5, 2014
9bca0ed
Add SSL.ELLIPTIC_CURVE_DESCRIPTIONS to expose the actual supported cu…
amluto Mar 5, 2014
76a6133
Fix docstring and Python 2.6 issues in ECDHE support
amluto Mar 12, 2014
f05a273
Identify elliptic curves by short name, not NID
amluto Mar 14, 2014
4064ea1
Stop exposing all the NID and SN objects for elliptic curves, now
amluto Mar 14, 2014
63e99fe
Require cryptography>=0.3
lvh Mar 28, 2014
e8b2d30
Merge branch 'ecdhe' of git://github.com/amluto/pyopenssl into ecdhe
lvh Mar 29, 2014
f5df243
Move test that checks for curve descriptions to a separate test
lvh Apr 3, 2014
5fb416a
ecdh: curve names are strings, not bytes
amluto Apr 4, 2014
61d7d39
ecdh: Use different exception types for different errors
amluto Apr 4, 2014
256090b
Merge remote-tracking branch 'pyca/master' into ecdhe
exarkun Apr 17, 2014
594b980
Try reporting the version of OpenSSL that was used at the end of each…
exarkun Apr 17, 2014
b41d1f4
Add a test for the False case of Cryptography_HAS_EC.
exarkun Apr 17, 2014
3dfd451
Some docstring cleanups.
exarkun Apr 17, 2014
5c7b52f
explicitly fail with a useful message
exarkun Apr 17, 2014
32b59af
Try explicitly installing the Python dependencies since "setup.py tes…
exarkun Apr 17, 2014
f11e339
Factor the guts out so they can be unit tested separately.
exarkun Apr 17, 2014
6749ec2
Fix some rST markup.
exarkun Apr 17, 2014
689c489
Support for Python 2.6 - which lacks addCleanup
exarkun Apr 17, 2014
01787c4
Change the version reporter to work on Python 3.
exarkun Apr 17, 2014
c48cd81
Add a test for the failure condition of EC_KEY_new_by_curve_name
exarkun Apr 17, 2014
cc30c93
Some versions of Python do not support the unicode literal syntax.
exarkun Apr 17, 2014
2799097
Merge remote-tracking branch 'pyca/master' into ecdhe
exarkun Apr 17, 2014
f43678b
Get rid of some top-level code, remove the code that reflects a weird…
exarkun Apr 17, 2014
c09fd58
Switch to an explicit curve object.
exarkun Apr 19, 2014
f49adae
revert to pyca/master .gitignore
exarkun Apr 19, 2014
2065be5
These three exceptions are no longer used.
exarkun Apr 19, 2014
aaf516d
Some doc fixups
exarkun Apr 19, 2014
d5839e2
strings are hard, be explicit
exarkun Apr 19, 2014
3e4e335
Mirror the OpenSSL API as is our idiom.
exarkun Apr 19, 2014
3b04e35
all the way
exarkun Apr 19, 2014
eb86f3a
restore python 3.2 compatibility
exarkun Apr 19, 2014
5a82db9
restore even more python 3.2 compat!
exarkun Apr 19, 2014
bdb3986
Merge branch 'master' into ecdhe
exarkun Apr 19, 2014
3611b4e
ChangeLog
exarkun Apr 19, 2014
9c7f069
Add a helper for testing equality.
exarkun Apr 30, 2014
1be7708
Use that helper to define tests for equality of _EllipticCurve instan…
exarkun Apr 30, 2014
73945e3
Cache the _EllipticCurve instances so the inherited-from-object equal…
exarkun Apr 30, 2014
22c3124
Directly test that curves work well as set elements.
exarkun May 1, 2014
15f3644
Add some Python 2.6 compatibility.
exarkun May 1, 2014
40da72d
Try to work around the strange Python 3 behavior.
exarkun May 1, 2014
f22abcd
Apparently that code is a no-go on Python 2. It seemed to work on Py…
exarkun May 1, 2014
a538105
do some explaining
exarkun May 1, 2014
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
9 changes: 9 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
2014-04-19 Jean-Paul Calderone <exarkun@twistedmatrix.com>

* OpenSSL/crypto.py: Based on work from Alex Gaynor, Andrew
Lutomirski, Tobias Oberstein, Laurens Van Houtven, and Hynek
Schlawack, add ``get_elliptic_curve`` and ``get_elliptic_curves``
to support TLS ECDHE modes.
* OpenSSL/SSL.py: Add ``Context.set_tmp_ecdh`` to configure a TLS
context with a particular elliptic curve for ECDHE modes.

2014-04-19 Markus Unterwaditzer <markus@unterwaditzer.net>

* OpenSSL/SSL.py: ``Connection.send`` and ``Connection.sendall``
Expand Down
16 changes: 14 additions & 2 deletions OpenSSL/SSL.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,6 @@ class _buffer(object):
SSL_CB_HANDSHAKE_START = _lib.SSL_CB_HANDSHAKE_START
SSL_CB_HANDSHAKE_DONE = _lib.SSL_CB_HANDSHAKE_DONE


class Error(Exception):
"""
An error occurred in an `OpenSSL.SSL` API.
Expand Down Expand Up @@ -604,6 +603,19 @@ def load_tmp_dh(self, dhfile):
_lib.SSL_CTX_set_tmp_dh(self._context, dh)


def set_tmp_ecdh(self, curve):
"""
Select a curve to use for ECDHE key exchange.

:param curve: A curve object to use as returned by either
:py:meth:`OpenSSL.crypto.get_elliptic_curve` or
:py:meth:`OpenSSL.crypto.get_elliptic_curves`.

:return: None
"""
_lib.SSL_CTX_set_tmp_ecdh(self._context, curve._to_EC_KEY())


def set_cipher_list(self, cipher_list):
"""
Change the cipher list
Expand Down Expand Up @@ -1224,7 +1236,7 @@ def makefile(self):
The makefile() method is not implemented, since there is no dup semantics
for SSL connections

:raise NotImplementedError
:raise: NotImplementedError
"""
raise NotImplementedError("Cannot make file object of OpenSSL.SSL.Connection")

Expand Down
153 changes: 152 additions & 1 deletion OpenSSL/crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@

from six import (
integer_types as _integer_types,
text_type as _text_type)
text_type as _text_type,
PY3 as _PY3)

from OpenSSL._util import (
ffi as _ffi,
Expand Down Expand Up @@ -263,6 +264,156 @@ def bits(self):



class _EllipticCurve(object):
"""
A representation of a supported elliptic curve.

@cvar _curves: :py:obj:`None` until an attempt is made to load the curves.
Thereafter, a :py:type:`set` containing :py:type:`_EllipticCurve`
instances each of which represents one curve supported by the system.
@type _curves: :py:type:`NoneType` or :py:type:`set`
"""
_curves = None

if _PY3:
# This only necessary on Python 3. Morever, it is broken on Python 2.
def __ne__(self, other):
"""
Implement cooperation with the right-hand side argument of ``!=``.

Python 3 seems to have dropped this cooperation in this very narrow
circumstance.
"""
if isinstance(other, _EllipticCurve):
return super(_EllipticCurve, self).__ne__(other)
return NotImplemented


@classmethod
def _load_elliptic_curves(cls, lib):
"""
Get the curves supported by OpenSSL.

:param lib: The OpenSSL library binding object.

:return: A :py:type:`set` of ``cls`` instances giving the names of the
elliptic curves the underlying library supports.
"""
if lib.Cryptography_HAS_EC:
num_curves = lib.EC_get_builtin_curves(_ffi.NULL, 0)
builtin_curves = _ffi.new('EC_builtin_curve[]', num_curves)
# The return value on this call should be num_curves again. We could
# check it to make sure but if it *isn't* then.. what could we do?
# Abort the whole process, I suppose...? -exarkun
lib.EC_get_builtin_curves(builtin_curves, num_curves)
return set(
cls.from_nid(lib, c.nid)
for c in builtin_curves)
return set()


@classmethod
def _get_elliptic_curves(cls, lib):
"""
Get, cache, and return the curves supported by OpenSSL.

:param lib: The OpenSSL library binding object.

:return: A :py:type:`set` of ``cls`` instances giving the names of the
elliptic curves the underlying library supports.
"""
if cls._curves is None:
cls._curves = cls._load_elliptic_curves(lib)
return cls._curves


@classmethod
def from_nid(cls, lib, nid):
"""
Instantiate a new :py:class:`_EllipticCurve` associated with the given
OpenSSL NID.

:param lib: The OpenSSL library binding object.

:param nid: The OpenSSL NID the resulting curve object will represent.
This must be a curve NID (and not, for example, a hash NID) or
subsequent operations will fail in unpredictable ways.
:type nid: :py:class:`int`

:return: The curve object.
"""
return cls(lib, nid, _ffi.string(lib.OBJ_nid2sn(nid)).decode("ascii"))


def __init__(self, lib, nid, name):
"""
:param _lib: The :py:mod:`cryptography` binding instance used to
interface with OpenSSL.

:param _nid: The OpenSSL NID identifying the curve this object
represents.
:type _nid: :py:class:`int`

:param name: The OpenSSL short name identifying the curve this object
represents.
:type name: :py:class:`unicode`
"""
self._lib = lib
self._nid = nid
self.name = name


def __repr__(self):
return "<Curve %r>" % (self.name,)


def _to_EC_KEY(self):
"""
Create a new OpenSSL EC_KEY structure initialized to use this curve.

The structure is automatically garbage collected when the Python object
is garbage collected.
"""
key = self._lib.EC_KEY_new_by_curve_name(self._nid)
return _ffi.gc(key, _lib.EC_KEY_free)



def get_elliptic_curves():
"""
Return a set of objects representing the elliptic curves supported in the
OpenSSL build in use.

The curve objects have a :py:class:`unicode` ``name`` attribute by which
they identify themselves.

The curve objects are useful as values for the argument accepted by
:py:meth:`Context.set_tmp_ecdh` to specify which elliptical curve should be
used for ECDHE key exchange.
"""
return _EllipticCurve._get_elliptic_curves(_lib)



def get_elliptic_curve(name):
"""
Return a single curve object selected by name.

See :py:func:`get_elliptic_curves` for information about curve objects.

:param name: The OpenSSL short name identifying the curve object to
retrieve.
:type name: :py:class:`unicode`

If the named curve is not supported then :py:class:`ValueError` is raised.
"""
for curve in get_elliptic_curves():
if curve.name == name:
return curve
raise ValueError("unknown curve name", name)



class X509Name(object):
def __init__(self, name):
"""
Expand Down
158 changes: 154 additions & 4 deletions OpenSSL/test/test_crypto.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from subprocess import PIPE, Popen
from datetime import datetime, timedelta

from six import binary_type
from six import u, b, binary_type

from OpenSSL.crypto import TYPE_RSA, TYPE_DSA, Error, PKey, PKeyType
from OpenSSL.crypto import X509, X509Type, X509Name, X509NameType
Expand All @@ -25,9 +25,10 @@
from OpenSSL.crypto import PKCS12, PKCS12Type, load_pkcs12
from OpenSSL.crypto import CRL, Revoked, load_crl
from OpenSSL.crypto import NetscapeSPKI, NetscapeSPKIType
from OpenSSL.crypto import sign, verify
from OpenSSL.test.util import TestCase, b
from OpenSSL._util import native
from OpenSSL.crypto import (
sign, verify, get_elliptic_curve, get_elliptic_curves)
from OpenSSL.test.util import EqualityTestsMixin, TestCase
from OpenSSL._util import native, lib

def normalize_certificate_pem(pem):
return dump_certificate(FILETYPE_PEM, load_certificate(FILETYPE_PEM, pem))
Expand Down Expand Up @@ -3058,5 +3059,154 @@ def test_sign_nulls(self):
verify(good_cert, sig, content, "sha1")



class EllipticCurveTests(TestCase):
"""
Tests for :py:class:`_EllipticCurve`, :py:obj:`get_elliptic_curve`, and
:py:obj:`get_elliptic_curves`.
"""
def test_set(self):
"""
:py:obj:`get_elliptic_curves` returns a :py:obj:`set`.
"""
self.assertIsInstance(get_elliptic_curves(), set)


def test_some_curves(self):
"""
If :py:mod:`cryptography` has elliptic curve support then the set
returned by :py:obj:`get_elliptic_curves` has some elliptic curves in
it.

There could be an OpenSSL that violates this assumption. If so, this
test will fail and we'll find out.
"""
curves = get_elliptic_curves()
if lib.Cryptography_HAS_EC:
self.assertTrue(curves)
else:
self.assertFalse(curves)


def test_a_curve(self):
"""
:py:obj:`get_elliptic_curve` can be used to retrieve a particular
supported curve.
"""
curves = get_elliptic_curves()
if curves:
curve = next(iter(curves))
self.assertEqual(curve.name, get_elliptic_curve(curve.name).name)
else:
self.assertRaises(ValueError, get_elliptic_curve, u("prime256v1"))


def test_not_a_curve(self):
"""
:py:obj:`get_elliptic_curve` raises :py:class:`ValueError` if called
with a name which does not identify a supported curve.
"""
self.assertRaises(
ValueError, get_elliptic_curve, u("this curve was just invented"))


def test_repr(self):
"""
The string representation of a curve object includes simply states the
object is a curve and what its name is.
"""
curves = get_elliptic_curves()
if curves:
curve = next(iter(curves))
self.assertEqual("<Curve %r>" % (curve.name,), repr(curve))


def test_to_EC_KEY(self):
"""
The curve object can export a version of itself as an EC_KEY* via the
private :py:meth:`_EllipticCurve._to_EC_KEY`.
"""
curves = get_elliptic_curves()
if curves:
curve = next(iter(curves))
# It's not easy to assert anything about this object. However, see
# leakcheck/crypto.py for a test that demonstrates it at least does
# not leak memory.
curve._to_EC_KEY()



class EllipticCurveFactory(object):
"""
A helper to get the names of two curves.
"""
def __init__(self):
curves = iter(get_elliptic_curves())
try:
self.curve_name = next(curves).name
self.another_curve_name = next(curves).name
except StopIteration:
self.curve_name = self.another_curve_name = None



class EllipticCurveEqualityTests(TestCase, EqualityTestsMixin):
"""
Tests :py:type:`_EllipticCurve`\ 's implementation of ``==`` and ``!=``.
"""
curve_factory = EllipticCurveFactory()

if curve_factory.curve_name is None:
skip = "There are no curves available there can be no curve objects."


def anInstance(self):
"""
Get the curve object for an arbitrary curve supported by the system.
"""
return get_elliptic_curve(self.curve_factory.curve_name)


def anotherInstance(self):
"""
Get the curve object for an arbitrary curve supported by the system -
but not the one returned by C{anInstance}.
"""
return get_elliptic_curve(self.curve_factory.another_curve_name)



class EllipticCurveHashTests(TestCase):
"""
Tests for :py:type:`_EllipticCurve`\ 's implementation of hashing (thus use
as an item in a :py:type:`dict` or :py:type:`set`).
"""
curve_factory = EllipticCurveFactory()

if curve_factory.curve_name is None:
skip = "There are no curves available there can be no curve objects."


def test_contains(self):
"""
The ``in`` operator reports that a :py:type:`set` containing a curve
does contain that curve.
"""
curve = get_elliptic_curve(self.curve_factory.curve_name)
curves = set([curve])
self.assertIn(curve, curves)


def test_does_not_contain(self):
"""
The ``in`` operator reports that a :py:type:`set` not containing a
curve does not contain that curve.
"""
curve = get_elliptic_curve(self.curve_factory.curve_name)
curves = set([get_elliptic_curve(self.curve_factory.another_curve_name)])
self.assertNotIn(curve, curves)



if __name__ == '__main__':
main()
Loading