Skip to content

Commit

Permalink
Allow using OpenSSL on Windows or OS X
Browse files Browse the repository at this point in the history
  • Loading branch information
wbond committed Aug 29, 2016
1 parent d302505 commit 3de6038
Show file tree
Hide file tree
Showing 12 changed files with 232 additions and 38 deletions.
121 changes: 121 additions & 0 deletions oscrypto/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,127 @@
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function

import os
import sys
import threading

from ._ffi import LibraryNotFoundError
from ._types import str_cls, type_name


__version__ = '0.16.2'
__version_info__ = (0, 16, 2)


_backend_lock = threading.Lock()
_module_values = {
'backend': None,
'backend_config': None
}


def backend():
"""
:return:
A unicode string of the backend being used: "openssl", "osx", "win"
"""

if _module_values['backend'] is not None:
return _module_values['backend']

with _backend_lock:
if _module_values['backend'] is not None:
return _module_values['backend']

if sys.platform == 'win32':
_module_values['backend'] = 'win'
elif sys.platform == 'darwin':
_module_values['backend'] = 'osx'
else:
_module_values['backend'] = 'openssl'

return _module_values['backend']


def backend_config():
"""
:return:
A dict of config info for the backend. Only currently used by "openssl",
it may contains zero or more of the following keys:
- "libcrypto_path"
- "libssl_path"
"""

if backend() != 'openssl':
return {}

if _module_values['backend_config'] is not None:
return _module_values['backend_config']

with _backend_lock:
if _module_values['backend_config'] is not None:
return _module_values['backend_config']

_module_values['backend_config'] = {}
return _module_values['backend_config']


def use_openssl(libcrypto_path, libssl_path, trust_list_path=None):
"""
Forces using OpenSSL dynamic libraries on OS X (.dylib) or Windows (.dll),
or using a specific dynamic library on Linux/BSD (.so).
This can also be used to configure oscrypto to use LibreSSL dynamic
libraries.
This method must be called before any oscrypto submodules are imported.
:param libcrypto_path:
A unicode string of the file path to the OpenSSL/LibreSSL libcrypto
dynamic library.
:param libssl_path:
A unicode string of the file path to the OpenSSL/LibreSSL libssl
dynamic library.
:param trust_list_path:
An optional unicode string of the path to a file containing
OpenSSL-compatible CA certificates in PEM format. If this is not
provided and the platform is OS X or Windows, the system trust roots
will be exported from the OS and used for all TLS connections.
:raises:
ValueError - when one of the paths is not a unicode string
OSError - when the trust_list_path does not exist on the filesystem
LibraryNotFoundError - when one of the path does not exist on the filesystem
RuntimeError - when this function is called after another part of oscrypto has been imported
"""

if not isinstance(libcrypto_path, str_cls):
raise ValueError('libcrypto_path must be a unicode string, not %s' % type_name(libcrypto_path))

if not isinstance(libssl_path, str_cls):
raise ValueError('libssl_path must be a unicode string, not %s' % type_name(libssl_path))

if not os.path.exists(libcrypto_path):
raise LibraryNotFoundError('libcrypto does not exist at %s' % libcrypto_path)

if not os.path.exists(libssl_path):
raise LibraryNotFoundError('libssl does not exist at %s' % libssl_path)

if trust_list_path is not None:
if not isinstance(trust_list_path, str_cls):
raise ValueError('trust_list_path must be a unicode string, not %s' % type_name(trust_list_path))
if not os.path.exists(trust_list_path):
raise OSError('trust_list_path does not exist at %s' % trust_list_path)

with _backend_lock:
if _module_values['backend'] is not None:
raise RuntimeError('Another part of oscrypto has already been imported, unable to force use of OpenSSL')

_module_values['backend'] = 'openssl'
_module_values['backend_config'] = {
'libcrypto_path': libcrypto_path,
'libssl_path': libssl_path,
'trust_list_path': trust_list_path,
}
8 changes: 7 additions & 1 deletion oscrypto/_openssl/_libcrypto_cffi.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import re
from ctypes.util import find_library

from .. import backend_config
from .._errors import pretty_message
from .._ffi import LibraryNotFoundError, FFIEngineError, register_ffi

Expand All @@ -21,11 +22,16 @@
]


_backend_config = backend_config()


ffi = FFI()

ffi.cdef("const char *SSLeay_version(int type);")

libcrypto_path = find_library('crypto')
libcrypto_path = _backend_config.get('libcrypto_path')
if libcrypto_path is None:
libcrypto_path = find_library('crypto')
if not libcrypto_path:
raise LibraryNotFoundError('The library libcrypto could not be found')

Expand Down
8 changes: 7 additions & 1 deletion oscrypto/_openssl/_libcrypto_ctypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from ctypes.util import find_library
from ctypes import CDLL, c_void_p, c_char_p, c_int, c_ulong, c_uint, c_long, c_size_t, POINTER

from .. import backend_config
from .._errors import pretty_message
from .._ffi import LibraryNotFoundError, FFIEngineError

Expand All @@ -16,7 +17,12 @@
]


libcrypto_path = find_library('crypto')
_backend_config = backend_config()


libcrypto_path = _backend_config.get('libcrypto_path')
if libcrypto_path is None:
libcrypto_path = find_library('crypto')
if not libcrypto_path:
raise LibraryNotFoundError('The library libcrypto could not be found')

Expand Down
8 changes: 7 additions & 1 deletion oscrypto/_openssl/_libssl_cffi.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from ctypes.util import find_library

from .. import backend_config
from .._ffi import LibraryNotFoundError, FFIEngineError, register_ffi

try:
Expand All @@ -17,9 +18,14 @@
]


_backend_config = backend_config()


ffi = FFI()

libssl_path = find_library('ssl')
libssl_path = _backend_config.get('libssl_path')
if libssl_path is None:
libssl_path = find_library('ssl')
if not libssl_path:
raise LibraryNotFoundError('The library libssl could not be found')

Expand Down
8 changes: 7 additions & 1 deletion oscrypto/_openssl/_libssl_ctypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from ctypes.util import find_library
from ctypes import CDLL, CFUNCTYPE, POINTER, c_void_p, c_char_p, c_int, c_size_t, c_long

from .. import backend_config
from .._ffi import LibraryNotFoundError, FFIEngineError


Expand All @@ -12,7 +13,12 @@
]


libssl_path = find_library('ssl')
_backend_config = backend_config()


libssl_path = _backend_config.get('libssl_path')
if libssl_path is None:
libssl_path = find_library('ssl')
if not libssl_path:
raise LibraryNotFoundError('The library libssl could not be found')

Expand Down
21 changes: 20 additions & 1 deletion oscrypto/_openssl/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

from ._libssl import libssl, LibsslConst
from ._libcrypto import libcrypto, handle_openssl_error, peek_openssl_error
from .. import backend_config
from .._errors import pretty_message
from .._ffi import null, bytes_from_buffer, buffer_from_bytes, is_null, buffer_pointer
from .._types import type_name, str_cls, byte_cls, int_types
Expand All @@ -34,6 +35,7 @@
)
from .asymmetric import load_certificate, Certificate
from ..keys import parse_certificate
from ..trust_list import get_path

if sys.version_info < (3,):
range = xrange # noqa
Expand All @@ -45,6 +47,7 @@
]


_backend_config = backend_config()
_line_regex = re.compile(b'(\r\n|\r|\n)')
_PROTOCOL_MAP = {
'SSLv2': LibsslConst.SSL_OP_NO_SSLv2,
Expand Down Expand Up @@ -174,7 +177,23 @@ def __init__(self, protocol=None, manual_validation=False, extra_trust_roots=Non
null()
)

result = libssl.SSL_CTX_set_default_verify_paths(ssl_ctx)
if sys.platform in set(['win32', 'darwin']):
trust_list_path = _backend_config.get('trust_list_path')
if trust_list_path is None:
trust_list_path = get_path()

if sys.platform == 'win32':
path_encoding = 'mbcs'
else:
path_encoding = 'utf-8'
result = libssl.SSL_CTX_load_verify_locations(
ssl_ctx,
trust_list_path.encode(path_encoding),
null()
)

else:
result = libssl.SSL_CTX_set_default_verify_paths(ssl_ctx)
handle_openssl_error(result)

verify_mode = LibsslConst.SSL_VERIFY_NONE if manual_validation else LibsslConst.SSL_VERIFY_PEER
Expand Down
11 changes: 7 additions & 4 deletions oscrypto/asymmetric.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function

import sys
import hashlib
import binascii

from asn1crypto import keys, x509, algos, core
import asn1crypto.pem
from asn1crypto.util import OrderedDict

from . import backend
from .symmetric import aes_cbc_pkcs7_encrypt
from .kdf import pbkdf2, pbkdf2_iteration_calculator
from .util import rand_bytes
Expand All @@ -17,7 +17,10 @@
from ._types import type_name, str_cls


if sys.platform == 'darwin':
_backend = backend()


if _backend == 'osx':
_has_openssl = False
from ._osx.asymmetric import (
Certificate,
Expand Down Expand Up @@ -59,7 +62,7 @@
except (LibraryNotFoundError):
pass

elif sys.platform == 'win32':
elif _backend == 'win':
from ._win.asymmetric import (
Certificate,
dsa_sign,
Expand Down Expand Up @@ -465,7 +468,7 @@ def dump_openssl_private_key(private_key, passphrase):
return asn1crypto.pem.armor(object_type, output, headers=headers)


if sys.platform == 'darwin':
if _backend == 'osx':
def generate_pair(algorithm, bit_size=None, curve=None): # noqa
"""
Generates a public/private key pair
Expand Down
17 changes: 11 additions & 6 deletions oscrypto/kdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,30 @@
import hashlib
from datetime import datetime

from . import backend
from .util import rand_bytes
from ._types import type_name, byte_cls, int_types
from ._errors import pretty_message
from ._ffi import new, deref

if sys.platform == 'darwin':
from ._osx.util import pbkdf2, pkcs12_kdf, rand_bytes
elif sys.platform == 'win32':
from ._win.util import pbkdf2, pkcs12_kdf, rand_bytes

_backend = backend()


if _backend == 'osx':
from ._osx.util import pbkdf2, pkcs12_kdf
elif _backend == 'win':
from ._win.util import pbkdf2, pkcs12_kdf
from ._win._kernel32 import kernel32, handle_error
else:
from ._openssl.util import pbkdf2, pkcs12_kdf, rand_bytes
from ._openssl.util import pbkdf2, pkcs12_kdf


__all__ = [
'pbkdf1',
'pbkdf2',
'pbkdf2_iteration_calculator',
'pkcs12_kdf',
'rand_bytes',
]


Expand Down
9 changes: 6 additions & 3 deletions oscrypto/symmetric.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# coding: utf-8
from __future__ import unicode_literals, division, absolute_import, print_function

import sys
from . import backend


if sys.platform == 'darwin':
_backend = backend()


if _backend == 'osx':
from ._osx.symmetric import (
aes_cbc_no_padding_decrypt,
aes_cbc_no_padding_encrypt,
Expand All @@ -20,7 +23,7 @@
tripledes_cbc_pkcs5_encrypt,
)

elif sys.platform == 'win32':
elif _backend == 'win':
from ._win.symmetric import (
aes_cbc_no_padding_decrypt,
aes_cbc_no_padding_encrypt,
Expand Down
Loading

0 comments on commit 3de6038

Please sign in to comment.