Merge remote-tracking branch 'pyca/master' into ecdhe
diff --git a/.gitignore b/.gitignore
index 867434b..276d4e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
+*.py[co]
 build
 dist
-*.egg-info
\ No newline at end of file
+*.egg-info
diff --git a/OpenSSL/SSL.py b/OpenSSL/SSL.py
index fbb18f0..86410c0 100644
--- a/OpenSSL/SSL.py
+++ b/OpenSSL/SSL.py
@@ -124,6 +124,19 @@
 SSL_CB_HANDSHAKE_START = _lib.SSL_CB_HANDSHAKE_START
 SSL_CB_HANDSHAKE_DONE = _lib.SSL_CB_HANDSHAKE_DONE
 
+_Cryptography_HAS_EC = _lib.Cryptography_HAS_EC
+ELLIPTIC_CURVE_DESCRIPTIONS = {}  # In case there's no EC support
+if _Cryptography_HAS_EC:
+    _num_curves = _lib.EC_get_builtin_curves(_ffi.NULL, 0)
+    _curves = _ffi.new('EC_builtin_curve[]', _num_curves)
+    if _lib.EC_get_builtin_curves(_curves, _num_curves) == _num_curves:
+        ELLIPTIC_CURVE_DESCRIPTIONS = dict(
+            (_ffi.string(_lib.OBJ_nid2sn(c.nid)).decode('ascii'),
+             _ffi.string(c.comment).decode('utf-8'))
+            for c in _curves)
+    del _num_curves
+    del _curves
+
 
 class Error(Exception):
     """
@@ -217,6 +230,37 @@
 
 
 
+class ECNotAvailable(ValueError):
+    """
+    Raised if a request for an elliptic curve fails because OpenSSL
+    is compiled without elliptic curve support.
+    """
+    def __init__(self):
+        ValueError.__init__(self, "OpenSSL is compiled without EC support")
+
+
+
+class UnknownObject(ValueError):
+    """
+    Raised if OpenSSL does not recognize the requested object.
+    """
+    def __init__(self, sn):
+        ValueError.__init__(self, "OpenSSL does not recognize %r" % sn)
+        self.sn = sn
+
+
+
+class UnsupportedEllipticCurve(ValueError):
+    """
+    Raised if OpenSSL does not support the requested elliptic curve.
+    """
+    def __init__(self, sn):
+        ValueError.__init__(
+            self, "OpenSSL does not support the elliptic curve %r" % sn)
+        self.sn = sn
+
+
+
 def SSLeay_version(type):
     """
     Return a string describing the version of OpenSSL in use.
@@ -598,6 +642,50 @@
         _lib.SSL_CTX_set_tmp_dh(self._context, dh)
 
 
+    def _set_tmp_ecdh_curve_by_nid(self, name, nid):
+        """
+        Select a curve to use by the OpenSSL NID associated with that curve.
+
+        :param name: The name of the curve identified by the NID.
+        :type name: str
+
+        :param nid: The OpenSSL NID to use.
+        :type nid: int
+
+        :raise UnsupportedEllipticCurve: If the given NID does not identify a
+            supported curve.
+        """
+        ecdh = _lib.EC_KEY_new_by_curve_name(nid)
+        if ecdh == _ffi.NULL:
+            raise UnsupportedEllipticCurve(name)
+        _lib.SSL_CTX_set_tmp_ecdh(self._context, ecdh)
+        _lib.EC_KEY_free(ecdh)
+
+
+    def set_tmp_ecdh_curve(self, curve_name):
+        """
+        Select a curve to use for ECDHE key exchange.
+
+        The valid values of *curve_name* are the keys in
+        :py:data:OpenSSL.SSL.ELLIPTIC_CURVE_DESCRIPTIONS.
+
+        Raises a subclass of ``ValueError`` if the linked OpenSSL was
+        not compiled with elliptical curve support or the specified
+        curve is not available.  You can check the specific subclass,
+        but, in general, you should just handle ``ValueError``.
+
+        :param curve_name: The 'short name' of a curve, e.g. 'prime256v1'
+        :type curve_name: str
+        :return: None
+        """
+        if _lib.Cryptography_HAS_EC:
+            nid = _lib.OBJ_sn2nid(curve_name.encode('ascii'))
+            if nid == _lib.NID_undef:
+                raise UnknownObject(curve_name)
+            return self._set_tmp_ecdh_curve_by_nid(curve_name, nid)
+        raise ECNotAvailable()
+
+
     def set_cipher_list(self, cipher_list):
         """
         Change the cipher list
@@ -1214,7 +1302,7 @@
         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")
 
diff --git a/OpenSSL/test/test_ssl.py b/OpenSSL/test/test_ssl.py
index bfe3114..fdeff9d 100644
--- a/OpenSSL/test/test_ssl.py
+++ b/OpenSSL/test/test_ssl.py
@@ -21,6 +21,7 @@
 from OpenSSL.crypto import dump_privatekey, load_privatekey
 from OpenSSL.crypto import dump_certificate, load_certificate
 
+from OpenSSL.SSL import _lib
 from OpenSSL.SSL import OPENSSL_VERSION_NUMBER, SSLEAY_VERSION, SSLEAY_CFLAGS
 from OpenSSL.SSL import SSLEAY_PLATFORM, SSLEAY_DIR, SSLEAY_BUILT_ON
 from OpenSSL.SSL import SENT_SHUTDOWN, RECEIVED_SHUTDOWN
@@ -35,6 +36,9 @@
     SESS_CACHE_OFF, SESS_CACHE_CLIENT, SESS_CACHE_SERVER, SESS_CACHE_BOTH,
     SESS_CACHE_NO_AUTO_CLEAR, SESS_CACHE_NO_INTERNAL_LOOKUP,
     SESS_CACHE_NO_INTERNAL_STORE, SESS_CACHE_NO_INTERNAL)
+from OpenSSL.SSL import (
+    _Cryptography_HAS_EC, ELLIPTIC_CURVE_DESCRIPTIONS,
+    ECNotAvailable, UnknownObject, UnsupportedEllipticCurve)
 
 from OpenSSL.SSL import (
     Error, SysCallError, WantReadError, WantWriteError, ZeroReturnError)
@@ -1172,6 +1176,106 @@
         # XXX What should I assert here? -exarkun
 
 
+    def test_set_tmp_ecdh_curve(self):
+        """
+        :py:obj:`Context.set_tmp_ecdh_curve` sets the elliptic curve for
+        Diffie-Hellman to the specified named curve.
+        """
+        context = Context(TLSv1_METHOD)
+        for curve in ELLIPTIC_CURVE_DESCRIPTIONS.keys():
+            context.set_tmp_ecdh_curve(curve)  # Must not throw.
+
+
+    def test_set_tmp_ecdh_curve_not_available(self):
+        """
+        :py:obj:`Context.set_tmp_ecdh_curve` raises :py:obj:`ECNotAvailable` if
+        elliptic curve support is not available from the underlying OpenSSL
+        version at all.
+        """
+        has_ec = _lib.Cryptography_HAS_EC
+        try:
+            _lib.Cryptography_HAS_EC = False
+
+            context = Context(TLSv1_METHOD)
+            self.assertRaises(
+                ECNotAvailable,
+                context.set_tmp_ecdh_curve, next(iter(ELLIPTIC_CURVE_DESCRIPTIONS)))
+        finally:
+            _lib.Cryptography_HAS_EC = has_ec
+
+
+    def test_set_tmp_ecdh_curve_bad_curve_name(self):
+        """
+        :py:obj:`Context.set_tmp_ecdh_curve` raises :py:obj:`UnknownObject` if
+        passed a curve_name that OpenSSL does not recognize and EC is
+        available.
+        """
+        context = Context(TLSv1_METHOD)
+        try:
+            context.set_tmp_ecdh_curve('not_an_elliptic_curve')
+        except ECNotAvailable:
+            self.assertFalse(_Cryptography_HAS_EC)
+        except UnknownObject:
+            self.assertTrue(_Cryptography_HAS_EC)
+        else:
+            self.fail(
+                "set_tmp_ecdh_curve did not fail when called with "
+                "non-existent curve name")
+
+
+    def test_set_tmp_ecdh_curve_bad_nid(self):
+        """
+        :py:obj:`Context._set_tmp_ecdh_curve_by_nid`, an implementation detail
+        of :py:obj:`Context.set_tmp_ecdh_curve`, raises
+        :py:obj:`UnsupportedEllipticCurve` raises if passed a NID that does not
+        identify a supported curve.
+        """
+        context = Context(TLSv1_METHOD)
+        try:
+            context._set_tmp_ecdh_curve_by_nid(
+                u("curve"), _lib.OBJ_sn2nid(b"sha256"))
+        except UnsupportedEllipticCurve:
+            pass
+        else:
+            self.fail(
+                "_set_tmp_ecdh_curve_by_nid did not raise "
+                "UnsupportedEllipticCurve for a NID that does not "
+                "identify a supported curve.")
+
+
+    def test_set_tmp_ecdh_curve_not_a_curve(self):
+        """
+        :py:obj:`Context.set_tmp_ecdh_curve` raises
+        :py:obj:`UnsupportedEllipticCurve` if passed a curve_name that OpenSSL
+        cannot instantiate as an elliptic curve.  It raises
+        :py:obj:`ECNotAvailable` if EC is not available at all.
+        """
+        context = Context(TLSv1_METHOD)
+        try:
+            context.set_tmp_ecdh_curve('sha256')
+        except ECNotAvailable:
+            self.assertFalse(_Cryptography_HAS_EC)
+        except UnknownObject:
+            self.assertTrue(_Cryptography_HAS_EC)
+        else:
+            self.fail(
+                "set_tmp_ecdh_curve did not fail when called with "
+                "a name that is not an elliptic curve")
+
+
+    def test_has_curve_descriptions(self):
+        """
+        If the underlying cryptography bindings claim to have elliptic
+        curve support, there should be at least one curve.
+
+        (In theory there could be an OpenSSL that violates this
+        assumption. If so, this test will fail and we'll find out.)
+
+        """
+        if _Cryptography_HAS_EC:
+            self.assertNotEqual(len(ELLIPTIC_CURVE_DESCRIPTIONS), 0)
+
+
     def test_set_cipher_list_bytes(self):
         """
         :py:obj:`Context.set_cipher_list` accepts a :py:obj:`bytes` naming the
diff --git a/doc/api/ssl.rst b/doc/api/ssl.rst
index e1c1d8a..b7eca70 100644
--- a/doc/api/ssl.rst
+++ b/doc/api/ssl.rst
@@ -110,6 +110,17 @@
      .. versionadded:: 0.14
 
 
+.. py:data:: ELLIPTIC_CURVE_DESCRIPTIONS
+
+    A dictionary mapping short names of elliptic curves to textual
+    descriptions.  This dictionary contains exactly the set of curves
+    supported by the OpenSSL build in use.
+
+    The keys are the curve names that can be passed into
+    Constants used with :py:meth:`Context.set_tmp_ecdh_curve` to
+    specify which elliptical curve should be used for ECDHE key exchange.
+
+
 .. py:data:: OPENSSL_VERSION_NUMBER
 
     An integer giving the version number of the OpenSSL library used to build this
@@ -316,6 +327,21 @@
 
     Load parameters for Ephemeral Diffie-Hellman from *dhfile*.
 
+.. py:method:: Context.set_tmp_ecdh_curve(curve_name)
+
+   Select a curve to use for ECDHE key exchange.
+
+   The valid values of *curve_name* are the keys in
+   :py:data:`ELLIPTIC_CURVE_DESCRIPTIONS`.
+
+   Raises a subclass of ``ValueError`` if the linked OpenSSL was not
+   compiled with elliptical curve support or the specified curve is
+   not available.  You can check the specific subclass, but, in
+   general, you should just handle ``ValueError``.
+
+   :param curve_name: The 'short name' of a curve, e.g. 'prime256v1'
+   :type curve_name: str
+   :return: None
 
 .. py:method:: Context.set_app_data(data)