Repository: pyca/cryptography

Issue:
Include OpenSSL in our Windows wheels
Installing OpenSSL isn't super easy, even with the binaries for it. Maybe we should bundle it?



Discussion:
A big downside is that this means any release for upstream requires a release for us today (including, and especially, security releases), and users will need to be trained to upgrade us. I'm not sure that something like heartbleed would necessarily trigger in people's minds that thye need to upgrade PyCA cryptography.

This definitely would dramatically ease the use of `cryptography` on Windows. If we do release a new wheel when OpenSSL is updated what versioning do we use? sdist users would get no code changes so do they just recompile for no reason?

@Alex says just bump the version and re-release everything for everyone.

If you've already compiled PyCA cryptography once, doing it again isn't a
particular burden.

On Mon, Jun 9, 2014 at 4:28 PM, David Reid notifications@github.com wrote:

> @Alex https://github.com/Alex says just bump the version and re-release
> everything for everyone.
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pyca/cryptography/issues/1121#issuecomment-45557316.

## 

"I disapprove of what you say, but I will defend to the death your right to
say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero
GPG Key fingerprint: 125F 5C67 DFE9 4084

I agree with @alex 

Okay, well I'm :+1: on doing this mostly because I feel terrible every time I have to explain to a Windows user how to install this project. If there's consensus we could change to static wheels on Windows for 0.5.

Techincally wheels have a build number, although I'm not sure if it's exposed in an easy to use fashion or anything. I'd have to double check to make sure that it worked how I'm remembering and test it to make sure it actually worked, but it's theoretically there and can be used.

Course just jumping the version isn't a big deal either.

Something that @lukasa brought up. If we're bundling OpenSSL for Windows should we also do it for OSX? Even though OSX has a copy of OpenSSL it's an unmaintained copy.

Well, not unmaintained, but they'll never upgrade from 0.9.8 (and it will become unmaintained if the OpenSSL developers cut off 0.9.8 support).

If we can target major OS X releases reliably I guess I'd support doing 10.7 through 10.9 wheels including OpenSSL statically (assuming we choose to do so for Windows).

If by "target" you mean "Will pip select the correct Wheel", then the answer is yes. In 1.4.x and 1.5.x pip does exact matches against the OSX version in the filename (which I believe is taken from the SDK version that was compiled against). In pip 1.6.x that changes so that it does >= on the OSX version in the wheel filename (again the SDK version it was compiled against) since according to OSX people that's a safe thing to do.

pyenv's pythons are compiled against the 10.4 SDK (pip wheel <whatever> will provide `macosx_10_4_x86_64`), so does that means we'd need to custom compile all supported pythons on each supported OS X release and then build wheels from those?

Do you want a different wheel for every OSX release, or is one wheel for all OSX releases 10.4+ OK.

CommonCrypto forces us to require a different wheel for each release.

Unless distutils provides a way to specify the SDK you want to build against (I don't know if it does, AFAIK it just uses whatever SDK CPython was built against) then yes you'd need to compile that. Yet another reason to split the backends out!

But CommonCrypto really doesn't have backwards compatibility like the SDK does?

The commoncrypto API expanded substantially from 10.7 to 10.8 (but did not change in 10.9).  Since we need to bind to symbols available only in 10.8+ we would also need a 10.7 wheel that doesn't include commoncrypto.

On Sat, Jun 14, 2014 at 4:02 PM, Donald Stufft notifications@github.com
wrote:

> Unless distutils provides a way to specify the SDK you want to build against (I don't know if it does, AFAIK it just uses whatever SDK CPython was built against) then yes you'd need to compile that. Yet another reason to split the backends out!
> 
> ## But CommonCrypto really doesn't have backwards compatibility like the SDK does?
> 
> Reply to this email directly or view it on GitHub:
> https://github.com/pyca/cryptography/issues/1121#issuecomment-46099550

Ok, so we'd need a `macosx_10_8_x86_64` wheel and a `macosx_10_4_x86_64` (or whatever our minimum version is without CC) wheel.

That will only work with pip 1.6 (which isn't released yet?) correct?

Depends on what you mean by work. On pip 1.4 and 1.5 it will be as if you do not have Wheels for anything but 10.8 and 10.4. Which is already the case since as of right now we're not publishing Wheels for OSX at all. On pip 1.6+ it will be as if you published Wheels for everything 10.4+.

I am +1 on static builds for Windows wheels. 

For OS X though, I'm not so sure. What algorithms is missing from commoncrypto that requires OpenSSL? Ideally, I think not requiring OpenSSL at all on OS X by default is best. This would involve splitting up the backends that should hopefully be the focus of 0.6 though. 

The RSA/DSA/EC support in Security.framework is not (nearly) as flexible as what is in OpenSSL. There are some definite advantages to using OpenSSL in certain cases on OS X. But I am fine with doing windows static builds now and revisiting the (orthogonal) OS X wheel issue later.

Sounds good to me.

Okay, if we're going to move forward on this here are the steps:
- [ ] Change jenkins jobs to add the `lib\VC\static` directories to the `LIB` variable when building. (master, PR, and wheel jobs)
- [ ] Submit PR that alters the libraries we link against on Windows (libeay32 becomes libeay32MT, etc).
- [ ] Update windows documentation to explain static compile steps.

Does anybody have an objection to doing this for 0.5?

Have we settled on how to do versioning yet? Is the build number thing @dstufft mentioned doable or do we have to increment the version for any OpenSSL updates?

Either way, I'm :+1: on doing this for 0.5.

One note: since we'll have to do releases for upstream OpenSSL security releases, we should figure out what the appropriate channels for publication of those are. Initial thoughts:
- Our own mailing list
- Twisted mailing list
- pyOpenSSL mailing list
- requires.io security list

Have we figured out the packaging for this yet? Bumping the version number seems like it would work but would have an effect on all our platforms without actually fixing anything on platforms we don't ship OpenSSL on. Seems like that might be a bit confusing to users? If we get our release wording right I suppose that might be good enough though. 

Like @dstufft mentioned, it appears as though the wheels format has a build number. [PEP 0427, File Contents, Point #11](http://legacy.python.org/dev/peps/pep-0427/#file-contents). Is this something that we can set from setuptools?

@dstufft needs to weigh in here, he's the expert in all things packaging after all. :)

Despite it affecting all platforms I prefer incrementing the primary version. Otherwise when talking about security releases we'll need to do things like say "0.5 wheel build 1 is vulnerable" which is terrible. I'm not even sure how you easily see what wheel version was installed (`pip list` does not appear to show it).

I agree with @reaperhulk. I was thinking maybe that platform specific 
dependencies might be a thing so we could put OpenSSL in its own package 
but that's probably overkill at least until we have figured out how to 
separate the backends from the main package.

On 28 June 2014 15:16:24 Paul Kehrer notifications@github.com wrote:

> Despite it affecting all platforms I prefer incrementing the primary 
> version. Otherwise when talking about security releases we'll need to do 
> things like say "0.5 wheel build 1 is vulnerable" which is terrible. I'm 
> not even sure how you easily see what wheel version was installed (`pip 
> list` does not appear to show it).
> 
> ---
> 
> Reply to this email directly or view it on GitHub:
> https://github.com/pyca/cryptography/issues/1121#issuecomment-47428528

I think we should just bump the patch level (i.e. 0.5.{HERE}) and be done
with it.

On Sat, Jun 28, 2014 at 7:49 AM, Alex Stapleton notifications@github.com
wrote:

> I agree with @reaperhulk. I was thinking maybe that platform specific
> dependencies might be a thing so we could put OpenSSL in its own package
> but that's probably overkill at least until we have figured out how to
> separate the backends from the main package.
> 
> On 28 June 2014 15:16:24 Paul Kehrer notifications@github.com wrote:
> 
> > Despite it affecting all platforms I prefer incrementing the primary
> > version. Otherwise when talking about security releases we'll need to do
> > things like say "0.5 wheel build 1 is vulnerable" which is terrible. I'm
> > not even sure how you easily see what wheel version was installed (`pip
> > list` does not appear to show it).
> > 
> > ---
> > 
> > Reply to this email directly or view it on GitHub:
> > https://github.com/pyca/cryptography/issues/1121#issuecomment-47428528
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/pyca/cryptography/issues/1121#issuecomment-47429303.

## 

"I disapprove of what you say, but I will defend to the death your right to
say it." -- Evelyn Beatrice Hall (summarizing Voltaire)
"The people's good is the highest law." -- Cicero
GPG Key fingerprint: 125F 5C67 DFE9 4084


Resolving patch:
diff --git a/cryptography/hazmat/bindings/openssl/binding.py b/cryptography/hazmat/bindings/openssl/binding.py
--- a/cryptography/hazmat/bindings/openssl/binding.py
+++ b/cryptography/hazmat/bindings/openssl/binding.py
@@ -13,6 +13,7 @@
 
 from __future__ import absolute_import, division, print_function
 
+import os
 import sys
 import threading
 
@@ -97,7 +98,8 @@ def _ensure_ffi_initialized(cls):
         if sys.platform != "win32":
             libraries = ["crypto", "ssl"]
         else:  # pragma: no cover
-            libraries = ["libeay32", "ssleay32", "advapi32"]
+            link_type = os.environ.get("PYCA_WINDOWS_LINK_TYPE", "static")
+            libraries = _get_windows_libraries(link_type)
 
         cls.ffi, cls.lib = build_ffi(
             module_prefix=cls._module_prefix,
@@ -154,3 +156,15 @@ def _lock_cb(cls, mode, n, file, line):
                     mode, n, file, line
                 )
             )
+
+
+def _get_windows_libraries(link_type):
+    if link_type == "dynamic":
+        return ["libeay32", "ssleay32", "advapi32"]
+    elif link_type == "static" or link_type == "":
+        return ["libeay32mt", "ssleay32mt", "advapi32",
+                "crypt32", "gdi32", "user32", "ws2_32"]
+    else:
+        raise ValueError(
+            "PYCA_WINDOWS_LINK_TYPE must be 'static' or 'dynamic'"
+        )


Test patch:
diff --git a/tests/hazmat/bindings/test_openssl.py b/tests/hazmat/bindings/test_openssl.py
--- a/tests/hazmat/bindings/test_openssl.py
+++ b/tests/hazmat/bindings/test_openssl.py
@@ -15,7 +15,9 @@
 
 import pytest
 
-from cryptography.hazmat.bindings.openssl.binding import Binding
+from cryptography.hazmat.bindings.openssl.binding import (
+    Binding, _get_windows_libraries
+)
 
 
 class TestOpenSSL(object):
@@ -137,3 +139,13 @@ def test_ssl_mode(self):
         resp = b.lib.SSL_set_mode(ssl, b.lib.SSL_OP_ALL)
         assert resp == b.lib.SSL_OP_ALL
         assert b.lib.SSL_OP_ALL == b.lib.SSL_get_mode(ssl)
+
+    def test_windows_static_dynamic_libraries(self):
+        assert "ssleay32mt" in _get_windows_libraries("static")
+
+        assert "ssleay32mt" in _get_windows_libraries("")
+
+        assert "ssleay32" in _get_windows_libraries("dynamic")
+
+        with pytest.raises(ValueError):
+            _get_windows_libraries("notvalid")