diff --git a/README.md b/README.md index 66888a1..d99e4e9 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,97 @@ -Noise-python -============ +noiseprotocol +============= + +This repository contains source code of **noiseprotocol** - a Python 3 implementation of [Noise Protocol Framework](http://www.noiseprotocol.org/). + +### Warning +This package shall not be used (yet) for production purposes. There was little to none peer review done so far. +Use common sense while using - until this package becomes stable. + +## Installation and prerequisites +For now, only Python 3.6 is supported. + +Install via pip: +``` +pip install noiseprotocol +``` +*noiseprotocol* depends on [Cryptography](https://github.com/pyca/cryptography/) package (and its' pre-packaged OpenSSL v1.1) as a source of crypto-primitives. + +## Usage + +#### Basic usage +NoiseBuilder class provides highest level of abstraction for the package. You can access full functionality of the package +through this class' interfaces. An example for setting up NoiseBuilder could look like this: + +```python +from noise.builder import NoiseBuilder + +# Create instance of NoiseBuilder, set up to use NN handshake pattern, Curve25519 for +# elliptic curve keypair, ChaCha20Poly1305 as cipher function and SHA256 for hashing. +proto = NoiseBuilder.from_name('Noise_NN_25519_ChaChaPoly_SHA256') + +# Set role in this connection as initiator +proto.set_as_initiator() +# Enter handshake mode +proto.start_handshake() + +# Perform handshake - as we are the initiator, we need to generate first message. +# We don't provide any payload (although we could, but it would be cleartext for this pattern). +message = proto.write_message() +# Send the message to the responder - you may simply use sockets or any other way +# to exchange bytes between communicating parties. +# For clarity - we omit socket creation in this example. +sock.send(message) +# Receive the message from the responder +received = sock.recv() +# Feed the received message into noise +payload = proto.read_message(received) + +# As of now, the handshake should be finished (as we are using NN pattern). +# Any further calls to write_message or read_message would raise NoiseHandshakeError exception. +# We can use encrypt/decrypt methods of NoiseBuilder now for encryption and decryption of messages. +encrypted_message = proto.encrypt('This is an example payload') + +ciphertext = sock.recv() +plaintext = proto.decrypt(ciphertext) +``` + +#### Wireguard integration example +In *examples* directory, there is an example of interoperation of this package with Wireguard VPN solution. Please refer to [README.md](examples/wireguard/README.md) of that example for details. + +---- +## Bug reports +This software was tested only on Linux. It may or may not work on Windows, explicit support for this system will be added in future. + +Please file any bug reports in project's [issue tracker](https://github.com/plizonczyk/noiseprotocol/issues). + +## Development & contributing +The only additional package that may be useful during development is pytest - for unit testing. +Installation: + +``` +pip install pytest +``` + +Running tests (from root directory): +``` +pytest +``` + +### Todo-list for the project: + +- [ ] fallback patterns support +- [ ] documentation on Read the Docs and more extensive readme +- [ ] scripts for keypair generation (+ console entry points) +- [ ] "echo" (noise-c like) example +- [ ] extensive logging +- [ ] bringing back Python 3.5 support and supporting Python 3.7 (dependent on Cryptography package updates) +- [ ] move away from custom ed448 implementation +- [ ] implement countermeasures for side-channel attacks +- [ ] **get peer review of the code** + +You are more than welcome to propose new things to this list and/or implement them and file a merge request. + +Contact the author: plizonczyk.public [at] gmail.com + +## License +This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. diff --git a/noise/noise_protocol.py b/noise/noise_protocol.py index a82e016..7491f60 100644 --- a/noise/noise_protocol.py +++ b/noise/noise_protocol.py @@ -50,7 +50,6 @@ class NoiseProtocol(object): self.prologue = None self.initiator = None - self.one_way = False self.handshake_hash = None self.handshake_state = Empty() diff --git a/noise/patterns.py b/noise/patterns.py index 9d68198..978766b 100644 --- a/noise/patterns.py +++ b/noise/patterns.py @@ -20,10 +20,12 @@ class Pattern(object): self.tokens = [] self.name = '' - self.has_pre_messages = any(map(lambda x: len(x) > 0, self.pre_messages)) self.one_way = False self.psk_count = 0 + def has_pre_messages(self): + return any(map(lambda x: len(x) > 0, self.pre_messages)) + def get_initiator_pre_messages(self) -> list: return self.pre_messages[0].copy() diff --git a/noise/state.py b/noise/state.py index 2a0bc78..52317cd 100644 --- a/noise/state.py +++ b/noise/state.py @@ -77,6 +77,7 @@ class SymmetricState(object): self.h = None self.ck = None self.noise_protocol = None + self.cipher_state = None @classmethod def initialize_symmetric(cls, noise_protocol: 'NoiseProtocol') -> 'SymmetricState': @@ -103,9 +104,9 @@ class SymmetricState(object): instance.ck = instance.h # Calls InitializeKey(empty). - cipher_state = CipherState(noise_protocol) - cipher_state.initialize_key(Empty()) - noise_protocol.cipher_state_handshake = cipher_state + instance.cipher_state = CipherState(noise_protocol) + instance.cipher_state.initialize_key(Empty()) + noise_protocol.cipher_state_handshake = instance.cipher_state return instance @@ -121,7 +122,7 @@ class SymmetricState(object): temp_k = temp_k[:32] # Calls InitializeKey(temp_k). - self.noise_protocol.cipher_state_handshake.initialize_key(temp_k) + self.cipher_state.initialize_key(temp_k) def mix_hash(self, data: bytes): """ @@ -139,7 +140,7 @@ class SymmetricState(object): if self.noise_protocol.hash_fn.hashlen == 64: temp_k = temp_k[:32] # Calls InitializeKey(temp_k). - self.noise_protocol.cipher_state_handshake.initialize_key(temp_k) + self.cipher_state.initialize_key(temp_k) def encrypt_and_hash(self, plaintext: bytes) -> bytes: """ @@ -148,7 +149,7 @@ class SymmetricState(object): :param plaintext: bytes sequence :return: ciphertext bytes sequence """ - ciphertext = self.noise_protocol.cipher_state_handshake.encrypt_with_ad(self.h, plaintext) + ciphertext = self.cipher_state.encrypt_with_ad(self.h, plaintext) self.mix_hash(ciphertext) return ciphertext @@ -159,7 +160,7 @@ class SymmetricState(object): :param ciphertext: bytes sequence :return: plaintext bytes sequence """ - plaintext = self.noise_protocol.cipher_state_handshake.decrypt_with_ad(self.h, ciphertext) + plaintext = self.cipher_state.decrypt_with_ad(self.h, ciphertext) self.mix_hash(ciphertext) return plaintext diff --git a/requirements.txt b/requirements.txt index 881b7f7..aa49793 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -pytest>=3.2.2 cryptography==2.0.3 diff --git a/setup.py b/setup.py index a190e28..f7231cc 100644 --- a/setup.py +++ b/setup.py @@ -8,13 +8,13 @@ with open(path.join(here, 'README.md'), encoding='utf-8') as f: long_description = f.read() setup( - name='noise-python', + name='noiseprotocol', version='0.1.0', - description='A sample Python project', # TODO + description='Implementation of Noise Protocol Framework', long_description=long_description, - url='https://github.com/plizonczyk/', + url='https://github.com/plizonczyk/noiseprotocol', author='Piotr Lizonczyk', - author_email='piotr.lizonczyk@gmail.com', + author_email='plizonczyk.public@gmail.com', license='MIT', classifiers=[ 'Development Status :: 3 - Alpha', @@ -22,10 +22,12 @@ setup( 'Topic :: Security :: Cryptography', 'License :: OSI Approved :: MIT License', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', + # 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + # 'Programming Language :: Python :: 3.7', ], - keywords='', # TODO - packages=find_packages(exclude=['contrib', 'docs', 'tests']), - install_requires=[], # TODO + keywords='cryptography noiseprotocol noise security', + packages=find_packages(exclude=['contrib', 'docs', 'tests', 'examples']), + install_requires=['cryptography==2.0.3'], + python_requires='~=3.6', )