Repository: pyca/pyopenssl
Pull request #734 (2018-02-06 10:07:04)

Add Context.set_tlsext_use_srtp
This allows negotiating SRTP keying material, which is useful when using
DTLS-SRTP, as WebRTC does for example.

This depends on:
https://github.com/pyca/cryptography/pull/4099

codecov[bot]: # [Codecov](https://codecov.io/gh/pyca/pyopenssl/pull/734?src=pr&el=h1) Report
> Merging [#734](https://codecov.io/gh/pyca/pyopenssl/pull/734?src=pr&el=desc) into [master](https://codecov.io/gh/pyca/pyopenssl/commit/3d231f03ed24f760d1896f5e2be3adf1cbf13127?src=pr&el=desc) will **increase** coverage by `<.01%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/pyca/pyopenssl/pull/734/graphs/tree.svg?token=KKfpMZOTgK&src=pr&width=650&height=150)](https://codecov.io/gh/pyca/pyopenssl/pull/734?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #734      +/-   ##
==========================================
+ Coverage   97.05%   97.06%   +<.01%     
==========================================
  Files          18       18              
  Lines        5711     5726      +15     
  Branches      395      396       +1     
==========================================
+ Hits         5543     5558      +15     
  Misses        112      112              
  Partials       56       56
```


| [Impacted Files](https://codecov.io/gh/pyca/pyopenssl/pull/734?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [tests/test\_ssl.py](https://codecov.io/gh/pyca/pyopenssl/pull/734/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9zc2wucHk=) | `99.13% <100%> (ø)` | :arrow_up: |
| [src/OpenSSL/SSL.py](https://codecov.io/gh/pyca/pyopenssl/pull/734/diff?src=pr&el=tree#diff-c3JjL09wZW5TU0wvU1NMLnB5) | `94.97% <100%> (+0.02%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pyca/pyopenssl/pull/734?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pyca/pyopenssl/pull/734?src=pr&el=footer). Last update [3d231f0...e8b810e](https://codecov.io/gh/pyca/pyopenssl/pull/734?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

jlaine: I'm not sure what the best form would be for the "profiles" argument. Two possibilities:

- A colon-delimited bytestring : this mimics the OpenSSL API exactly, and works the same as existing Context.set_cipher_list for example

- A list of bytestrings : this feels somewhat more pythonic, but deviates from the OpenSSL API

Suggestions?
reaperhulk: I dislike the OpenSSL APIs for this, but pyOpenSSL generally doesn't abstract much away and since we already have an API that exposes colon delimited behavior it probably makes sense for this one to look like that as well.

One day we'll have a nice TLS API. pyOpenSSL won't be it...
jlaine: OK thanks for the feedback I'll write the corresponding API docs.

Quick question : any reason why we don't use sphinx.autodoc's automethod? It seems a shame to have to duplicate the docstrings everywhere.
hynek: We do for new code. Nobody wanted to spend their time to move everything to docstrings yet.
jlaine: Duly noted, I've updated my pull request to use automethod
jlaine: This requires #742 
jlaine: @reaperhulk anything you'd like me to change with this PR?
reaperhulk: @jlaine could you rebase this?
reaperhulk: Actually I can resolve, nevermind :) 

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index ab28dcb..0d8765f 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -26,6 +26,8 @@ Changes:
 
 - ``OpenSSL.SSL.Connection`` now sets ``SSL_MODE_AUTO_RETRY`` by default.
   `#753 <https://github.com/pyca/pyopenssl/pull/753>`_
+- Added ``Context.set_tlsext_use_srtp`` to enable negotiation of SRTP keying material.
+  `#734 <https://github.com/pyca/pyopenssl/pull/734>`_
 
 
 ----
diff --git a/src/OpenSSL/SSL.py b/src/OpenSSL/SSL.py
index 1bf6450..8d8cfe3 100644
--- a/src/OpenSSL/SSL.py
+++ b/src/OpenSSL/SSL.py
@@ -1371,6 +1371,21 @@ class Context(object):
         _lib.SSL_CTX_set_tlsext_servername_callback(
             self._context, self._tlsext_servername_callback)
 
+    def set_tlsext_use_srtp(self, profiles):
+        """
+        Enable support for negotiating SRTP keying material.
+
+        :param bytes profiles: A colon delimited list of protection profile
+            names, like ``b'SRTP_AES128_CM_SHA1_80:SRTP_AES128_CM_SHA1_32'``.
+        :return: None
+        """
+        if not isinstance(profiles, bytes):
+            raise TypeError("profiles must be a byte string.")
+
+        _openssl_assert(
+            _lib.SSL_CTX_set_tlsext_use_srtp(self._context, profiles) == 0
+        )
+
     @_requires_npn
     def set_npn_advertise_callback(self, callback):
         """


Test patch:
diff --git a/tests/test_ssl.py b/tests/test_ssl.py
index 87bd18c..b09fce7 100644
--- a/tests/test_ssl.py
+++ b/tests/test_ssl.py
@@ -1593,6 +1593,35 @@ class TestContext(object):
         store = context.get_cert_store()
         assert isinstance(store, X509Store)
 
+    def test_set_tlsext_use_srtp_not_bytes(self):
+        """
+        `Context.set_tlsext_use_srtp' enables negotiating SRTP keying material.
+
+        It raises a TypeError if the list of profiles is not a byte string.
+        """
+        context = Context(TLSv1_METHOD)
+        with pytest.raises(TypeError):
+            context.set_tlsext_use_srtp(text_type('SRTP_AES128_CM_SHA1_80'))
+
+    def test_set_tlsext_use_srtp_invalid_profile(self):
+        """
+        `Context.set_tlsext_use_srtp' enables negotiating SRTP keying material.
+
+        It raises an Error if the call to OpenSSL fails.
+        """
+        context = Context(TLSv1_METHOD)
+        with pytest.raises(Error):
+            context.set_tlsext_use_srtp(b'SRTP_BOGUS')
+
+    def test_set_tlsext_use_srtp_valid(self):
+        """
+        `Context.set_tlsext_use_srtp' enables negotiating SRTP keying material.
+
+        It does not return anything.
+        """
+        context = Context(TLSv1_METHOD)
+        assert context.set_tlsext_use_srtp(b'SRTP_AES128_CM_SHA1_80') is None
+
 
 class TestServerNameCallback(object):
     """