├── setup.cfg ├── .gitignore ├── .travis.yml ├── NOTICE ├── sslpsk ├── test │ ├── __main__.py │ ├── __init__.py │ ├── example_client.py │ ├── example_server.py │ └── test_sslpsk.py ├── __init__.py ├── sslpsk.py └── _sslpsk.c ├── appveyor └── build.cmd ├── appveyor.yml ├── setup.py ├── README.md └── LICENSE /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | dist/ 3 | 4 | *.pyc 5 | *.so 6 | *.egg-info 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | env: PYTHON_BIN=python 4 | 5 | matrix: 6 | include: 7 | - os: linux 8 | python: 2.7 9 | - os: linux 10 | python: 3.4 11 | - os: linux 12 | python: 3.5 13 | - os: linux 14 | python: 3.6 15 | - os: linux 16 | python: 3.7-dev 17 | - os: osx 18 | language: generic 19 | env: PYTHON_BIN=python2 20 | 21 | script: 22 | - $PYTHON_BIN setup.py install 23 | - cd .. 24 | - $PYTHON_BIN -m sslpsk.test 25 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2017 David R. Bild 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /sslpsk/test/__main__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 David R. Bild 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License 14 | 15 | import sslpsk.test 16 | 17 | sslpsk.test.run() 18 | -------------------------------------------------------------------------------- /sslpsk/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 David R. Bild 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License 14 | 15 | from __future__ import absolute_import 16 | 17 | from sslpsk.sslpsk import wrap_socket 18 | -------------------------------------------------------------------------------- /sslpsk/test/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 David R. Bild 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License 14 | 15 | import os 16 | import unittest 17 | 18 | def tests(): 19 | return unittest.TestLoader().discover(os.path.dirname(__file__)) 20 | 21 | def run(): 22 | unittest.TextTestRunner(verbosity=1).run(tests()) 23 | -------------------------------------------------------------------------------- /appveyor/build.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | :: To build extensions for 64 bit Python 3, we need to configure environment 3 | :: variables to use the MSVC 2010 C++ compilers from GRMSDKX_EN_DVD.iso of: 4 | :: MS Windows SDK for Windows 7 and .NET Framework 4 5 | :: 6 | :: More details at: 7 | :: https://github.com/cython/cython/wiki/CythonExtensionsOnWindows 8 | 9 | IF "%DISTUTILS_USE_SDK%"=="1" ( 10 | ECHO Configuring environment to build with MSVC on a 64bit architecture 11 | ECHO Using Windows SDK 7.1 12 | "C:\Program Files\Microsoft SDKs\Windows\v7.1\Setup\WindowsSdkVer.exe" -q -version:v7.1 13 | CALL "C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.cmd" /x64 /release 14 | SET MSSdk=1 15 | REM Need the following to allow tox to see the SDK compiler 16 | SET TOX_TESTENV_PASSENV=DISTUTILS_USE_SDK MSSdk INCLUDE LIB 17 | ) ELSE ( 18 | ECHO Using default MSVC build environment 19 | ) 20 | 21 | CALL %* 22 | -------------------------------------------------------------------------------- /sslpsk/test/example_client.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import socket 3 | import ssl 4 | import sslpsk 5 | 6 | PSKS = {'server1' : b'abcdef', 7 | 'server2' : b'uvwxyz'} 8 | 9 | def client(host, port, psk): 10 | tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 11 | tcp_socket.connect((host, port)) 12 | 13 | ssl_sock = sslpsk.wrap_socket(tcp_socket, 14 | ssl_version=ssl.PROTOCOL_TLSv1, 15 | ciphers='ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH', 16 | psk=lambda hint: (PSKS[hint], b'client1')) 17 | 18 | msg = "ping" 19 | ssl_sock.sendall(msg.encode()) 20 | msg = ssl_sock.recv(4).decode() 21 | print('Client received: %s'%(msg)) 22 | 23 | ssl_sock.shutdown(socket.SHUT_RDWR) 24 | ssl_sock.close() 25 | 26 | def main(): 27 | host = '127.0.0.1' 28 | port = 6000 29 | client(host, port, PSKS) 30 | 31 | if __name__ == '__main__': 32 | main() 33 | -------------------------------------------------------------------------------- /sslpsk/test/example_server.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import socket 3 | import ssl 4 | import sslpsk 5 | 6 | PSKS = {'client1' : b'abcdef', 7 | 'client2' : b'123456'} 8 | 9 | def server(host, port): 10 | tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 11 | tcp_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 12 | tcp_sock.bind((host, port)) 13 | tcp_sock.listen(1) 14 | 15 | sock, _ = tcp_sock.accept() 16 | ssl_sock = sslpsk.wrap_socket(sock, 17 | server_side = True, 18 | ssl_version=ssl.PROTOCOL_TLSv1, 19 | ciphers='ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH', 20 | psk=lambda identity: PSKS[identity], 21 | hint=b'server1') 22 | 23 | msg = ssl_sock.recv(4).decode() 24 | print('Server received: %s'%(msg)) 25 | msg = "pong" 26 | ssl_sock.sendall(msg.encode()) 27 | 28 | ssl_sock.shutdown(socket.SHUT_RDWR) 29 | ssl_sock.close() 30 | 31 | def main(): 32 | host = '127.0.0.1' 33 | port = 6000 34 | server(host, port) 35 | 36 | if __name__ == '__main__': 37 | main() 38 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - PYTHON: "C:\\Python27" 4 | PYTHON_ARCH: "32" 5 | OPENSSL_LIB: "openssl-1.0.2k-vs2008" 6 | 7 | - PYTHON: "C:\\Python33" 8 | PYTHON_ARCH: "32" 9 | OPENSSL_LIB: "openssl-1.0.2k-vs2010" 10 | 11 | - PYTHON: "C:\\Python34" 12 | PYTHON_ARCH: "32" 13 | OPENSSL_LIB: "openssl-1.0.2k-vs2010" 14 | 15 | - PYTHON: "C:\\Python35" 16 | PYTHON_ARCH: "32" 17 | OPENSSL_LIB: "openssl-1.0.2k-vs2015" 18 | 19 | - PYTHON: "C:\\Python36" 20 | PYTHON_ARCH: "32" 21 | OPENSSL_LIB: "openssl-1.0.2k-vs2015" 22 | 23 | - PYTHON: "C:\\Python27-x64" 24 | PYTHON_ARCH: "64" 25 | OPENSSL_LIB: "openssl-1.0.2k-vs2008" 26 | 27 | - PYTHON: "C:\\Python33-x64" 28 | PYTHON_ARCH: "64" 29 | OPENSSL_LIB: "openssl-1.0.2k-vs2010" 30 | DISTUTILS_USE_SDK: "1" 31 | 32 | - PYTHON: "C:\\Python34-x64" 33 | PYTHON_ARCH: "64" 34 | OPENSSL_LIB: "openssl-1.0.2k-vs2010" 35 | DISTUTILS_USE_SDK: "1" 36 | 37 | - PYTHON: "C:\\Python35-x64" 38 | PYTHON_ARCH: "64" 39 | OPENSSL_LIB: "openssl-1.0.2k-vs2015" 40 | 41 | - PYTHON: "C:\\Python36-x64" 42 | PYTHON_ARCH: "64" 43 | OPENSSL_LIB: "openssl-1.0.2k-vs2015" 44 | 45 | install: 46 | - "%PYTHON%\\python.exe -m pip install wheel" 47 | 48 | - cd .. 49 | - ps: Invoke-WebRequest "https://www.npcglib.org/~stathis/downloads/$env:OPENSSL_LIB.7z" -OutFile "openssl.7z" 50 | - 7z x openssl.7z 51 | - cd sslpsk 52 | 53 | - ps: >- 54 | If ($env:PYTHON_ARCH -Match "32") { 55 | $env:OPENSSL_BIN_DIR="bin" 56 | $env:OPENSSL_LIB_DIR="lib" 57 | $env:OPENSSL_INCLUDE_DIR="include" 58 | } Else { 59 | $env:OPENSSL_BIN_DIR="bin64" 60 | $env:OPENSSL_LIB_DIR="lib64" 61 | $env:OPENSSL_INCLUDE_DIR="include64" 62 | } 63 | 64 | - mkdir openssl 65 | - mklink /D openssl\bin ..\..\%OPENSSL_LIB%\%OPENSSL_BIN_DIR% 66 | - mklink /D openssl\lib ..\..\%OPENSSL_LIB%\%OPENSSL_LIB_DIR% 67 | - mklink /D openssl\include ..\..\%OPENSSL_LIB%\%OPENSSL_INCLUDE_DIR% 68 | 69 | build_script: 70 | - "appveyor\\build.cmd %PYTHON%\\python.exe setup.py build_ext 71 | -Iopenssl/include 72 | -Lopenssl/lib" 73 | - "appveyor\\build.cmd %PYTHON%\\python.exe setup.py build" 74 | 75 | after_build: 76 | - "appveyor\\build.cmd %PYTHON%\\python.exe setup.py install" 77 | 78 | test_script: 79 | - cd openssl 80 | - "%PYTHON%\\python.exe -m sslpsk.test" 81 | - cd .. 82 | 83 | after_test: 84 | - "appveyor\\build.cmd %PYTHON%\\python.exe setup.py bdist" 85 | - "appveyor\\build.cmd %PYTHON%\\python.exe setup.py bdist_wheel" 86 | 87 | artifacts: 88 | - path: dist\* 89 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 David R. Bild 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License 14 | 15 | from setuptools import setup, Extension 16 | 17 | import os, shutil, sys 18 | 19 | if sys.platform == 'win32': 20 | LIB_NAMES = ['ssleay32MD', 'libeay32MD'] 21 | else: 22 | LIB_NAMES = ['ssl'] 23 | 24 | _sslpsk = Extension('sslpsk._sslpsk', 25 | sources = ['sslpsk/_sslpsk.c'], 26 | libraries = LIB_NAMES 27 | ) 28 | 29 | try: 30 | # Symlink the libs so they can be included in the package data 31 | if sys.platform == 'win32': 32 | for lib in LIB_NAMES: 33 | shutil.copy2('openssl/bin/%s.dll'%lib, 'sslpsk/') 34 | 35 | setup( 36 | name = 'sslpsk', 37 | version = '1.0.0', 38 | description = 'Adds TLS-PSK support to the Python ssl package', 39 | author = 'David R. Bild', 40 | author_email = 'david@davidbild.org', 41 | license="Apache 2.0", 42 | url = 'https://github.com/drbild/sslpsk', 43 | download_url = 'https://github.com/drbild/sslpsk/archive/1.0.0.tar.gz', 44 | keywords = ['ssl', 'tls', 'psk', 'tls-psk', 'preshared key'], 45 | classifiers = [ 46 | 'Development Status :: 5 - Production/Stable', 47 | 'Intended Audience :: Developers', 48 | 'License :: OSI Approved :: Apache Software License', 49 | 'Programming Language :: Python', 50 | 'Programming Language :: Python :: 2', 51 | 'Programming Language :: Python :: 2.7', 52 | 'Programming Language :: Python :: 3', 53 | 'Programming Language :: Python :: 3.4', 54 | 'Programming Language :: Python :: 3.5', 55 | 'Programming Language :: Python :: 3.6', 56 | 'Programming Language :: Python :: Implementation :: CPython', 57 | 'Operating System :: POSIX', 58 | 'Operating System :: Unix', 59 | 'Operating System :: MacOS', 60 | 'Operating System :: Microsoft' 61 | ], 62 | packages = ['sslpsk', 'sslpsk.test'], 63 | ext_modules = [_sslpsk], 64 | package_data = {'' : ['%s.dll'%lib for lib in LIB_NAMES]}, 65 | test_suite = 'sslpsk.test', 66 | zip_safe = False 67 | ) 68 | 69 | finally: 70 | if sys.platform == 'win32': 71 | for lib in LIB_NAMES: 72 | os.remove('sslpsk/%s.dll'%lib) 73 | -------------------------------------------------------------------------------- /sslpsk/test/test_sslpsk.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 David R. Bild 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License 14 | 15 | import os 16 | import socket 17 | import ssl 18 | import sslpsk 19 | import sys 20 | import threading 21 | import unittest 22 | 23 | HOST='localhost' 24 | PORT=6000 25 | TEST_DATA=b'abcdefghi' 26 | 27 | class SSLPSKTest(unittest.TestCase): 28 | # ---------- setup/tear down functions 29 | def setUp(self): 30 | self.psk = b'c033f52671c61c8128f7f8a40be88038bcf2b07a6eb3095c36e3759f0cf40837' 31 | self.addr = (HOST, PORT) 32 | self.client_socket = socket.socket() 33 | self.server_socket = None 34 | self.accept_socket = socket.socket() 35 | self.client_psk_sock = None 36 | self.server_psk_sock = None 37 | 38 | self.startServer() 39 | 40 | def tearDown(self): 41 | for sock in [self.client_psk_sock or self.client_socket, 42 | self.server_psk_sock or self.server_socket, 43 | self.accept_socket]: 44 | try: 45 | sock.shutdown(socket.SHUT_RDWR) 46 | except socket.error: 47 | pass 48 | finally: 49 | sock.close() 50 | 51 | self.client_socket = None 52 | self.server_socket = None 53 | self.accept_socket = None 54 | self.client_psk_sock = None 55 | self.server_psk_sock = None 56 | 57 | def startServer(self): 58 | self.accept_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 59 | self.accept_socket.bind(self.addr) 60 | self.accept_socket.listen(1) 61 | 62 | def accept(): 63 | self.server_socket, _ = self.accept_socket.accept() 64 | 65 | # wrap socket with TLS-PSK 66 | self.server_psk_sock = sslpsk.wrap_socket(self.server_socket, psk=self.psk, ciphers='PSK-AES256-CBC-SHA', 67 | ssl_version=ssl.PROTOCOL_TLSv1, server_side=True) 68 | 69 | # accept data from client 70 | data = self.server_psk_sock.recv(10) 71 | self.server_psk_sock.sendall(data.upper()) 72 | 73 | threading.Thread(target = accept).start() 74 | 75 | def testClient(self): 76 | # initialize 77 | self.client_socket.connect(self.addr) 78 | 79 | # wrap socket with TLS-PSK 80 | self.client_psk_sock = sslpsk.wrap_socket(self.client_socket, psk=self.psk, ciphers='PSK-AES256-CBC-SHA', 81 | ssl_version=ssl.PROTOCOL_TLSv1, server_side=False) 82 | 83 | self.client_psk_sock.sendall(TEST_DATA) 84 | data = self.client_psk_sock.recv(10) 85 | print('data: %s' % data) 86 | self.assertTrue(data == TEST_DATA.upper(), 'Test Failed') 87 | 88 | def main(): 89 | unittest.main(buffer=False) 90 | 91 | if __name__ == '__main__': 92 | main() 93 | -------------------------------------------------------------------------------- /sslpsk/sslpsk.py: -------------------------------------------------------------------------------- 1 | # Copyright 2017 David R. Bild 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License 14 | 15 | from __future__ import absolute_import 16 | 17 | import ssl 18 | import _ssl 19 | import sys 20 | import weakref 21 | 22 | from sslpsk import _sslpsk 23 | 24 | _callbacks = {} 25 | 26 | class FinalizerRef(weakref.ref): 27 | """subclass weakref.ref so that attributes can be added""" 28 | pass 29 | 30 | def _register_callback(sock, ssl_id, callback): 31 | _callbacks[ssl_id] = callback 32 | callback.unregister = FinalizerRef(sock, _unregister_callback) 33 | callback.unregister.ssl_id = ssl_id 34 | 35 | def _unregister_callback(ref): 36 | del _callbacks[ref.ssl_id] 37 | 38 | def _python_psk_client_callback(ssl_id, hint): 39 | """Called by _sslpsk.c to return the (psk, identity) tuple for the socket with 40 | the specified ssl socket. 41 | 42 | """ 43 | if ssl_id not in _callbacks: 44 | return (b"", b"") 45 | else: 46 | res = _callbacks[ssl_id](hint) 47 | return res if isinstance(res, tuple) else (res, b"") 48 | 49 | def _sslobj(sock): 50 | """Returns the underlying PySLLSocket object with which the C extension 51 | functions interface. 52 | 53 | """ 54 | pass 55 | if isinstance(sock._sslobj, _ssl._SSLSocket): 56 | return sock._sslobj 57 | else: 58 | return sock._sslobj._sslobj 59 | 60 | def _python_psk_server_callback(ssl_id, identity): 61 | """Called by _sslpsk.c to return the psk for the socket with the specified 62 | ssl socket. 63 | 64 | """ 65 | if ssl_id not in _callbacks: 66 | return b"" 67 | else: 68 | return _callbacks[ssl_id](identity) 69 | 70 | _sslpsk.sslpsk_set_python_psk_client_callback(_python_psk_client_callback) 71 | _sslpsk.sslpsk_set_python_psk_server_callback(_python_psk_server_callback) 72 | 73 | def _ssl_set_psk_client_callback(sock, psk_cb): 74 | ssl_id = _sslpsk.sslpsk_set_psk_client_callback(_sslobj(sock)) 75 | _register_callback(sock, ssl_id, psk_cb) 76 | 77 | def _ssl_set_psk_server_callback(sock, psk_cb, hint): 78 | ssl_id = _sslpsk.sslpsk_set_accept_state(_sslobj(sock)) 79 | _ = _sslpsk.sslpsk_set_psk_server_callback(_sslobj(sock)) 80 | _ = _sslpsk.sslpsk_use_psk_identity_hint(_sslobj(sock), hint if hint else b"") 81 | _register_callback(sock, ssl_id, psk_cb) 82 | 83 | def wrap_socket(*args, **kwargs): 84 | """ 85 | """ 86 | do_handshake_on_connect = kwargs.get('do_handshake_on_connect', True) 87 | kwargs['do_handshake_on_connect'] = False 88 | 89 | psk = kwargs.setdefault('psk', None) 90 | del kwargs['psk'] 91 | 92 | hint = kwargs.setdefault('hint', None) 93 | del kwargs['hint'] 94 | 95 | server_side = kwargs.setdefault('server_side', False) 96 | if psk: 97 | del kwargs['server_side'] # bypass need for cert 98 | 99 | sock = ssl.wrap_socket(*args, **kwargs) 100 | 101 | if psk: 102 | if server_side: 103 | cb = psk if callable(psk) else lambda _identity: psk 104 | _ssl_set_psk_server_callback(sock, cb, hint) 105 | else: 106 | cb = psk if callable(psk) else lambda _hint: psk if isinstance(psk, tuple) else (psk, b"") 107 | _ssl_set_psk_client_callback(sock, cb) 108 | 109 | if do_handshake_on_connect: 110 | sock.do_handshake() 111 | 112 | return sock 113 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sslpsk 2 | 3 | [![PyPI version](https://badge.fury.io/py/sslpsk.svg)](https://badge.fury.io/py/sslpsk) 4 | [![Build Status](https://travis-ci.org/drbild/sslpsk.svg?branch=master)](https://travis-ci.org/drbild/sslpsk) 5 | [![Build Status](https://ci.appveyor.com/api/projects/status/github/drbild/sslpsk?branch=master)](https://ci.appveyor.com/project/drbild/sslpsk) 6 | 7 | This module adds TLS-PSK support to the Python 2.7 and 3.x `ssl` 8 | package. Simply use 9 | 10 | sslpsk.wrap_socket(sock, psk=b'...', ...) 11 | 12 | instead of 13 | 14 | ssl.wrap_socket(sock, ...) 15 | 16 | ## Installation 17 | 18 | ```pip install sslpsk``` 19 | 20 | `pip` builds from source for Linux and Mac OSX, so a C compiler, the Python 21 | development headers, and the openSSL development headers are required. For 22 | Microsoft Windows, pre-built binaries are available so there are no such 23 | prerequisites. 24 | 25 | ## Usage 26 | 27 | `sslpsk.wrap_socket(...)` is a drop-in replacement for `ssl.wrap_socket(...)` that 28 | supports two additional arguments, `psk` and `hint`. 29 | 30 | `psk` sets the preshared key and, optionally, the identity for a client 31 | connection. `hint` sets the identity hint for a server connection and is 32 | optional. 33 | 34 | For client connections, `psk` can be one of four things: 35 | 36 | 1. Just the preshared key. 37 | 38 | ```python 39 | sslpsk.wrap_socket(sock, psk=b'mypsk') 40 | ``` 41 | 42 | 2. A tuple of the preshared key and client identity. 43 | 44 | ```python 45 | sslpsk.wrap_socket(sock, psk=(b'mypsk', b'myidentity')) 46 | ``` 47 | 48 | 3. A function mapping the server identity hint to the preshared key. 49 | 50 | ```python 51 | PSK_FOR = {b'server1' : b'abcdef', 52 | b'server2' : b'123456'} 53 | 54 | sslpsk.wrap_socket(sock, psk=lambda hint: PSK_FOR[hint]) 55 | ``` 56 | 57 | 4. A function mapping the server identity hint to a tuple of the preshared key 58 | and client identity. 59 | 60 | ```python 61 | PSK_FOR = {b'server1' : b'abcdef', 62 | b'server2' : b'123456'} 63 | 64 | ID_FOR = {b'server1' : b'clientA', 65 | b'server2' : b'clientB'} 66 | 67 | sslpsk.wrap_socket(sock, psk=lambda hint: (PSK_FOR[hint], ID_FOR[hint])) 68 | ``` 69 | 70 | For server connections, `psk` can be one of two things: 71 | 72 | 1. Just the preshared key. 73 | 74 | ```python 75 | sslpsk.wrap_socket(sock, server_side=True, psk=b'mypsk') 76 | ``` 77 | 78 | 2. A function mapping the client identity to the preshared key. 79 | 80 | ```python 81 | PSK_FOR = {b'clientA' : b'abcdef', 82 | b'clientB' : b'123456'} 83 | 84 | sslpsk.wrap_socket(sock, server_side=True, psk=lambda identity: PSK_FOR[identity]) 85 | ``` 86 | 87 | Additionally for server connections, the optional server identity hint is 88 | specified using the `hint` argument. 89 | 90 | ```python 91 | sslpsk.wrap_socket(sock, server_side=True, hint=b'myidentity', psk=b'mypsk') 92 | ``` 93 | 94 | If `hint` is not specified, `None`, or the empty string, the identity hint 95 | will not be sent to the client. 96 | 97 | ### Example Server 98 | 99 | ```python 100 | from __future__ import print_function 101 | import socket 102 | import ssl 103 | import sslpsk 104 | 105 | PSKS = {'client1' : 'abcdef', 106 | 'client2' : '123456'} 107 | 108 | def server(host, port): 109 | tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 110 | tcp_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 111 | tcp_sock.bind((host, port)) 112 | tcp_sock.listen(1) 113 | 114 | sock, _ = tcp_sock.accept() 115 | ssl_sock = sslpsk.wrap_socket(sock, 116 | server_side = True, 117 | ssl_version=ssl.PROTOCOL_TLSv1, 118 | ciphers='ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH', 119 | psk=lambda identity: PSKS[identity], 120 | hint=b'server1') 121 | 122 | msg = ssl_sock.recv(4).decode() 123 | print('Server received: %s'%(msg)) 124 | msg = "pong" 125 | ssl_sock.sendall(msg.encode()) 126 | 127 | ssl_sock.shutdown(socket.SHUT_RDWR) 128 | ssl_sock.close() 129 | 130 | def main(): 131 | host = '127.0.0.1' 132 | port = 6000 133 | server(host, port) 134 | 135 | if __name__ == '__main__': 136 | main() 137 | ``` 138 | 139 | ### Example Client 140 | 141 | ```python 142 | from __future__ import print_function 143 | import socket 144 | import ssl 145 | import sslpsk 146 | 147 | PSKS = {b'server1' : b'abcdef', 148 | b'server2' : b'uvwxyz'} 149 | 150 | def client(host, port, psk): 151 | tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 152 | tcp_socket.connect((host, port)) 153 | 154 | ssl_sock = sslpsk.wrap_socket(tcp_socket, 155 | ssl_version=ssl.PROTOCOL_TLSv1, 156 | ciphers='ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH', 157 | psk=lambda hint: (PSKS[hint], b'client1')) 158 | 159 | msg = "ping" 160 | ssl_sock.sendall(msg.encode()) 161 | msg = ssl_sock.recv(4).decode() 162 | print('Client received: %s'%(msg)) 163 | 164 | ssl_sock.shutdown(socket.SHUT_RDWR) 165 | ssl_sock.close() 166 | 167 | def main(): 168 | host = '127.0.0.1' 169 | port = 6000 170 | client(host, port, PSKS) 171 | 172 | if __name__ == '__main__': 173 | main() 174 | ``` 175 | 176 | ## Changelog 177 | 178 | + 0.1.0 (July 31, 2017) 179 | + initial release 180 | + 1.0.0 (August 2, 2017) 181 | + include tests in pip distribution 182 | + add support for Windows 183 | 184 | ## Acknowledgments 185 | 186 | The main approach was borrowed from 187 | [webgravel/common-ssl](https://github.com/webgravel/common-ssl). 188 | 189 | ## Contributing 190 | 191 | Please submit bugs, questions, suggestions, or (ideally) contributions as 192 | issues and pull requests on GitHub. 193 | 194 | ### Maintainers 195 | **David R. Bild** 196 | 197 | + [https://www.davidbild.org](https://www.davidbild.org) 198 | + [https://github.com/drbild](https://github.com/drbild) 199 | 200 | ## License 201 | Copyright 2017 David R. Bild 202 | 203 | Licensed under the Apache License, Version 2.0 (the "License"); you may not 204 | use this work except in compliance with the License. You may obtain a copy of 205 | the License from the LICENSE.txt file or at 206 | 207 | [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0) 208 | 209 | Unless required by applicable law or agreed to in writing, software 210 | distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 211 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 212 | License for the specific language governing permissions and limitations under 213 | the License. 214 | -------------------------------------------------------------------------------- /sslpsk/_sslpsk.c: -------------------------------------------------------------------------------- 1 | /* Copyright 2017 David R. Bild 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License 14 | */ 15 | 16 | #define PY_SSIZE_T_CLEAN 17 | #include 18 | 19 | #include 20 | 21 | /* Copy PySSLObject/PySSLSocket from _ssl.c to expose the SSL*. */ 22 | #if !defined(PY_MAJOR_VERSION) || (PY_VERSION_HEX < 0x02070000) 23 | #error Only Python 2.7 and later are supported 24 | #endif 25 | 26 | #define PY_VERSION_BETWEEN(start, end) ((PY_VERSION_HEX >= start) && \ 27 | (PY_VERSION_HEX < end)) 28 | 29 | typedef struct { 30 | PyObject_HEAD 31 | #if PY_VERSION_BETWEEN(0x02070000, 0x03000000) 32 | void* PySocketSockObject; 33 | #endif 34 | PyObject* socket; 35 | #if PY_VERSION_BETWEEN(0x03000000, 0x03020000) 36 | void* SSL_CTX; 37 | #endif 38 | SSL* ssl; 39 | /* etc */ 40 | } PySSLSocket; 41 | 42 | #if PY_VERSION_BETWEEN(0x02070000, 0x03000000) 43 | #define BYTESFMT "s" 44 | #else 45 | #define BYTESFMT "y" 46 | #endif 47 | 48 | /* 49 | * Python function that returns the client psk and identity. 50 | * 51 | * (ssl_id, hint) => (psk, idenity) 52 | */ 53 | static PyObject* python_psk_client_callback; 54 | 55 | /* 56 | * Python function that returns the server psk. 57 | * 58 | * (ssl_id, identity) => psk 59 | */ 60 | static PyObject* python_psk_server_callback; 61 | 62 | /* 63 | * Returns the index for an SSL socket, used to identity the socket across the 64 | * C/Python interface. 65 | */ 66 | long ssl_id(SSL* ssl) 67 | { 68 | return (long) ssl; 69 | } 70 | 71 | /* 72 | * Called from Python to set python_psk_client_callback; 73 | */ 74 | PyObject* sslpsk_set_python_psk_client_callback(PyObject* self, PyObject* args) 75 | { 76 | PyObject* cb; 77 | if (!PyArg_ParseTuple(args, "O", &cb)) { 78 | return NULL; 79 | } 80 | Py_XINCREF(cb); 81 | Py_XDECREF(python_psk_client_callback); 82 | python_psk_client_callback = cb; 83 | 84 | Py_RETURN_NONE; 85 | } 86 | 87 | /* 88 | * Called from Python to set python_psk_server_callback; 89 | */ 90 | PyObject* sslpsk_set_python_psk_server_callback(PyObject* self, PyObject* args) 91 | { 92 | PyObject* cb; 93 | if (!PyArg_ParseTuple(args, "O", &cb)) { 94 | return NULL; 95 | } 96 | Py_XINCREF(cb); 97 | Py_XDECREF(python_psk_server_callback); 98 | python_psk_server_callback = cb; 99 | 100 | Py_RETURN_NONE; 101 | } 102 | 103 | /* 104 | * Client callback for openSSL. Delegates to python_psk_client_callback. 105 | */ 106 | static unsigned int sslpsk_psk_client_callback(SSL* ssl, 107 | const char* hint, 108 | char* identity, 109 | unsigned int max_identity_len, 110 | unsigned char* psk, 111 | unsigned int max_psk_len) 112 | { 113 | int ret = 0; 114 | 115 | PyGILState_STATE gstate; 116 | 117 | PyObject* result; 118 | 119 | const char* psk_; 120 | const char* identity_; 121 | 122 | Py_ssize_t psk_len_; 123 | Py_ssize_t identity_len_; 124 | 125 | gstate = PyGILState_Ensure(); 126 | 127 | if (python_psk_client_callback == NULL) { 128 | goto release; 129 | } 130 | 131 | // Call python callback 132 | result = PyObject_CallFunction(python_psk_client_callback, "l"BYTESFMT, ssl_id(ssl), hint); 133 | if (result == NULL) { 134 | goto release; 135 | } 136 | 137 | // Parse result 138 | 139 | if (!PyArg_Parse(result, "("BYTESFMT"#"BYTESFMT"#)", &psk_, &psk_len_, &identity_, &identity_len_)) { 140 | goto decref; 141 | } 142 | 143 | // Copy to caller 144 | if (psk_len_ > max_psk_len) { 145 | goto decref; 146 | } 147 | memcpy(psk, psk_, psk_len_); 148 | 149 | if (identity_len_ + 1 > max_identity_len) { 150 | goto decref; 151 | } 152 | memcpy(identity, identity_, identity_len_); 153 | identity[identity_len_] = 0; 154 | 155 | ret = psk_len_; 156 | 157 | decref: 158 | Py_DECREF(result); 159 | 160 | release: 161 | PyGILState_Release(gstate); 162 | 163 | return ret; 164 | } 165 | 166 | /* 167 | * Server callback for openSSL. Delegates to python_psk_server_callback. 168 | */ 169 | static unsigned int sslpsk_psk_server_callback(SSL* ssl, 170 | const char* identity, 171 | unsigned char* psk, 172 | unsigned int max_psk_len) 173 | { 174 | int ret = 0; 175 | 176 | PyGILState_STATE gstate; 177 | 178 | PyObject* result; 179 | 180 | const char* psk_; 181 | Py_ssize_t psk_len_; 182 | 183 | gstate = PyGILState_Ensure(); 184 | 185 | if (python_psk_server_callback == NULL) { 186 | goto release; 187 | } 188 | 189 | // Call python callback 190 | result = PyObject_CallFunction(python_psk_server_callback, "l"BYTESFMT, ssl_id(ssl), identity); 191 | if (result == NULL) { 192 | goto release; 193 | } 194 | 195 | // Parse result 196 | if (!PyArg_Parse(result, BYTESFMT"#", &psk_, &psk_len_)) { 197 | goto decref; 198 | } 199 | 200 | // Copy to caller 201 | if (psk_len_ > max_psk_len) { 202 | goto decref; 203 | } 204 | memcpy(psk, psk_, psk_len_); 205 | 206 | ret = psk_len_; 207 | 208 | decref: 209 | Py_DECREF(result); 210 | 211 | release: 212 | PyGILState_Release(gstate); 213 | 214 | return ret; 215 | } 216 | 217 | /* 218 | * Called from Python to set the client psk callback. 219 | */ 220 | PyObject* sslpsk_set_psk_client_callback(PyObject* self, PyObject* args) 221 | { 222 | PyObject* socket; 223 | SSL* ssl; 224 | 225 | if (!PyArg_ParseTuple(args, "O", &socket)) 226 | { 227 | return NULL; 228 | } 229 | 230 | ssl = ((PySSLSocket*) socket)->ssl; 231 | SSL_set_psk_client_callback(ssl, sslpsk_psk_client_callback); 232 | 233 | return Py_BuildValue("l", ssl_id(ssl)); 234 | } 235 | 236 | /* 237 | * Called from Python to set the server psk callback. 238 | */ 239 | PyObject* sslpsk_set_psk_server_callback(PyObject* self, PyObject* args) 240 | { 241 | PyObject* socket; 242 | SSL* ssl; 243 | 244 | if (!PyArg_ParseTuple(args, "O", &socket)) 245 | { 246 | return NULL; 247 | } 248 | 249 | ssl = ((PySSLSocket*) socket)->ssl; 250 | SSL_set_psk_server_callback(ssl, sslpsk_psk_server_callback); 251 | 252 | return Py_BuildValue("l", ssl_id(ssl)); 253 | } 254 | 255 | /* 256 | * Called from Python to set the server identity hint. 257 | */ 258 | PyObject* sslpsk_use_psk_identity_hint(PyObject* self, PyObject* args) 259 | { 260 | PyObject* socket; 261 | const char *hint; 262 | SSL* ssl; 263 | 264 | if (!PyArg_ParseTuple(args, "O"BYTESFMT, &socket, &hint)) 265 | { 266 | return NULL; 267 | } 268 | 269 | ssl = ((PySSLSocket*) socket) ->ssl; 270 | SSL_use_psk_identity_hint(ssl, hint); 271 | 272 | return Py_BuildValue("l", ssl_id(ssl)); 273 | } 274 | 275 | /* 276 | * Called from Python to place the socket into server mode 277 | */ 278 | PyObject* sslpsk_set_accept_state(PyObject* self, PyObject* args) 279 | { 280 | PyObject* socket; 281 | SSL* ssl; 282 | 283 | if (!PyArg_ParseTuple(args, "O", &socket)) 284 | { 285 | return NULL; 286 | } 287 | 288 | ssl = ((PySSLSocket*) socket) ->ssl; 289 | SSL_set_accept_state(ssl); 290 | 291 | return Py_BuildValue("l", ssl_id(ssl)); 292 | } 293 | 294 | static PyMethodDef sslpsk_methods[] = 295 | { 296 | {"sslpsk_set_python_psk_client_callback", sslpsk_set_python_psk_client_callback, METH_VARARGS, ""}, 297 | {"sslpsk_set_python_psk_server_callback", sslpsk_set_python_psk_server_callback, METH_VARARGS, ""}, 298 | {"sslpsk_set_psk_client_callback", sslpsk_set_psk_client_callback, METH_VARARGS, ""}, 299 | {"sslpsk_set_psk_server_callback", sslpsk_set_psk_server_callback, METH_VARARGS, ""}, 300 | {"sslpsk_use_psk_identity_hint", sslpsk_use_psk_identity_hint, METH_VARARGS, ""}, 301 | {"sslpsk_set_accept_state", sslpsk_set_accept_state, METH_VARARGS, ""}, 302 | {NULL, NULL, 0, NULL} 303 | }; 304 | 305 | #if PY_MAJOR_VERSION >= 3 306 | static struct PyModuleDef sslpsk_moduledef = { 307 | PyModuleDef_HEAD_INIT, 308 | "sslpsk", 309 | NULL, 310 | 0, 311 | sslpsk_methods, 312 | NULL, 313 | NULL, 314 | NULL, 315 | NULL 316 | }; 317 | #endif 318 | 319 | #if PY_MAJOR_VERSION >= 3 320 | PyMODINIT_FUNC PyInit__sslpsk(void) 321 | #else 322 | void init_sslpsk(void) 323 | #endif 324 | { 325 | #if PY_MAJOR_VERSION >= 3 326 | PyObject* m = PyModule_Create(&sslpsk_moduledef); 327 | #else 328 | PyObject* m = Py_InitModule("_sslpsk", sslpsk_methods); 329 | #endif 330 | 331 | if (m == NULL) { 332 | #if PY_MAJOR_VERSION >= 3 333 | return NULL; 334 | #else 335 | return ; 336 | #endif 337 | } 338 | 339 | #if PY_MAJOR_VERSION >= 3 340 | return m; 341 | #endif 342 | } 343 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | --------------------------------------------------------------------------------