mirror of
https://github.com/morgan9e/noiseprotocol
synced 2026-04-14 00:14:05 +09:00
Merge pull request #12 from plizonczyk/trunk
Trunk to master - towards 0.2.0 release
This commit is contained in:
@@ -2,9 +2,9 @@ language: python
|
|||||||
notifications:
|
notifications:
|
||||||
email: false
|
email: false
|
||||||
python:
|
python:
|
||||||
# - "3.5"
|
- "3.5"
|
||||||
- "3.6"
|
- "3.6"
|
||||||
# - "3.5-dev" # 3.5 development branch
|
- "3.5-dev" # 3.5 development branch
|
||||||
- "3.6-dev" # 3.6 development branch
|
- "3.6-dev" # 3.6 development branch
|
||||||
# - "3.7-dev" # 3.7 development branch
|
# - "3.7-dev" # 3.7 development branch
|
||||||
# command to install dependencies
|
# command to install dependencies
|
||||||
|
|||||||
22
CHANGELOG.rst
Normal file
22
CHANGELOG.rst
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
Changelog
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. _v0-2-0:
|
||||||
|
|
||||||
|
0.2.0 - `trunk`
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. note:: This version is not yet released and is under active development.
|
||||||
|
|
||||||
|
* Compatible with revision 33 (doesn't break compatibility with revision 32).
|
||||||
|
* Cryptography requirement updated to the newest version (2.1.1) - **Python 3.5** is supported again.
|
||||||
|
* Adding sphinx documentation for Read the Docs publication.
|
||||||
|
* Minor fixes for better performance.
|
||||||
|
|
||||||
|
|
||||||
|
.. _v0-1-0:
|
||||||
|
|
||||||
|
0.1.1 - 2017-09-12
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Initial release.
|
||||||
69
README.md
69
README.md
@@ -2,15 +2,21 @@ noiseprotocol
|
|||||||
=============
|
=============
|
||||||
[](https://travis-ci.org/plizonczyk/noiseprotocol)
|
[](https://travis-ci.org/plizonczyk/noiseprotocol)
|
||||||
[](https://pypi.python.org/pypi/noiseprotocol)
|
[](https://pypi.python.org/pypi/noiseprotocol)
|
||||||
|
[](http://noiseprotocol.readthedocs.io/)
|
||||||
|
|
||||||
This repository contains source code of **noiseprotocol** - a Python 3 implementation of [Noise Protocol Framework](http://www.noiseprotocol.org/).
|
This repository contains source code of **noiseprotocol** - a Python 3 implementation of [Noise Protocol Framework](http://www.noiseprotocol.org/).
|
||||||
|
Compatible with revisions 32 and 33.
|
||||||
|
|
||||||
### Warning
|
### Warning
|
||||||
This package shall not be used (yet) for production purposes. There was little to none peer review done so far.
|
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.
|
Use common sense while using - until this package becomes stable.
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
Available on [Read the Docs](https://noiseprotocol.readthedocs.io). For now it provides basic documentation on
|
||||||
|
HandshakeState, CipherState and SymmetricState. Refer to the rest of the README below for more information.
|
||||||
|
|
||||||
## Installation and prerequisites
|
## Installation and prerequisites
|
||||||
For now, only Python 3.6 is supported.
|
For now, only Python 3.5+ is supported.
|
||||||
|
|
||||||
Install via pip:
|
Install via pip:
|
||||||
```
|
```
|
||||||
@@ -25,11 +31,16 @@ NoiseBuilder class provides highest level of abstraction for the package. You ca
|
|||||||
through this class' interfaces. An example for setting up NoiseBuilder could look like this:
|
through this class' interfaces. An example for setting up NoiseBuilder could look like this:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
import socket
|
||||||
|
|
||||||
from noise.builder import NoiseBuilder
|
from noise.builder import NoiseBuilder
|
||||||
|
|
||||||
|
sock = socket.socket()
|
||||||
|
sock.connect(('localhost', 2000))
|
||||||
|
|
||||||
# Create instance of NoiseBuilder, set up to use NN handshake pattern, Curve25519 for
|
# Create instance of NoiseBuilder, set up to use NN handshake pattern, Curve25519 for
|
||||||
# elliptic curve keypair, ChaCha20Poly1305 as cipher function and SHA256 for hashing.
|
# elliptic curve keypair, ChaCha20Poly1305 as cipher function and SHA256 for hashing.
|
||||||
proto = NoiseBuilder.from_name('Noise_NN_25519_ChaChaPoly_SHA256')
|
proto = NoiseBuilder.from_name(b'Noise_NN_25519_ChaChaPoly_SHA256')
|
||||||
|
|
||||||
# Set role in this connection as initiator
|
# Set role in this connection as initiator
|
||||||
proto.set_as_initiator()
|
proto.set_as_initiator()
|
||||||
@@ -41,20 +52,62 @@ proto.start_handshake()
|
|||||||
message = proto.write_message()
|
message = proto.write_message()
|
||||||
# Send the message to the responder - you may simply use sockets or any other way
|
# Send the message to the responder - you may simply use sockets or any other way
|
||||||
# to exchange bytes between communicating parties.
|
# to exchange bytes between communicating parties.
|
||||||
# For clarity - we omit socket creation in this example.
|
sock.sendall(message)
|
||||||
sock.send(message)
|
|
||||||
# Receive the message from the responder
|
# Receive the message from the responder
|
||||||
received = sock.recv()
|
received = sock.recv(2048)
|
||||||
# Feed the received message into noise
|
# Feed the received message into noise
|
||||||
payload = proto.read_message(received)
|
payload = proto.read_message(received)
|
||||||
|
|
||||||
# As of now, the handshake should be finished (as we are using NN pattern).
|
# 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.
|
# 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.
|
# We can use encrypt/decrypt methods of NoiseBuilder now for encryption and decryption of messages.
|
||||||
encrypted_message = proto.encrypt('This is an example payload')
|
encrypted_message = proto.encrypt(b'This is an example payload')
|
||||||
|
sock.sendall(encrypted_message)
|
||||||
|
|
||||||
ciphertext = sock.recv()
|
ciphertext = sock.recv(2048)
|
||||||
plaintext = proto.decrypt(ciphertext)
|
plaintext = proto.decrypt(ciphertext)
|
||||||
|
print(plaintext)
|
||||||
|
```
|
||||||
|
|
||||||
|
The example above covers the connection from the initiator's ("client") point of view. The snippet below is an example of responder's code ("server") using a socket connection to send and receive ciphertext.
|
||||||
|
|
||||||
|
```python
|
||||||
|
import socket
|
||||||
|
from itertools import cycle
|
||||||
|
|
||||||
|
from noise.builder import NoiseBuilder
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
s = socket.socket()
|
||||||
|
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||||
|
s.bind(('localhost', 2000))
|
||||||
|
s.listen(1)
|
||||||
|
|
||||||
|
conn, addr = s.accept()
|
||||||
|
print('Accepted connection from', addr)
|
||||||
|
|
||||||
|
noise = NoiseBuilder.from_name(b'Noise_NN_25519_ChaChaPoly_SHA256')
|
||||||
|
noise.set_as_responder()
|
||||||
|
noise.start_handshake()
|
||||||
|
|
||||||
|
# Perform handshake. Break when finished
|
||||||
|
for action in cycle(['receive', 'send']):
|
||||||
|
if noise.handshake_finished:
|
||||||
|
break
|
||||||
|
elif action == 'send':
|
||||||
|
ciphertext = noise.write_message()
|
||||||
|
conn.sendall(ciphertext)
|
||||||
|
elif action == 'receive':
|
||||||
|
data = conn.recv(2048)
|
||||||
|
plaintext = noise.read_message(data)
|
||||||
|
|
||||||
|
# Endless loop "echoing" received data
|
||||||
|
while True:
|
||||||
|
data = conn.recv(2048)
|
||||||
|
if not data:
|
||||||
|
break
|
||||||
|
received = noise.decrypt(data)
|
||||||
|
conn.sendall(noise.encrypt(received))
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Wireguard integration example
|
#### Wireguard integration example
|
||||||
@@ -82,11 +135,9 @@ pytest
|
|||||||
### Todo-list for the project:
|
### Todo-list for the project:
|
||||||
|
|
||||||
- [ ] fallback patterns support
|
- [ ] fallback patterns support
|
||||||
- [ ] documentation on Read the Docs and more extensive readme
|
|
||||||
- [ ] scripts for keypair generation (+ console entry points)
|
- [ ] scripts for keypair generation (+ console entry points)
|
||||||
- [ ] "echo" (noise-c like) example
|
- [ ] "echo" (noise-c like) example
|
||||||
- [ ] extensive logging
|
- [ ] extensive logging
|
||||||
- [ ] bringing back Python 3.5 support and supporting Python 3.7 (dependent on Cryptography package updates)
|
|
||||||
- [ ] move away from custom ed448 implementation
|
- [ ] move away from custom ed448 implementation
|
||||||
- [ ] implement countermeasures for side-channel attacks
|
- [ ] implement countermeasures for side-channel attacks
|
||||||
- [ ] **get peer review of the code**
|
- [ ] **get peer review of the code**
|
||||||
|
|||||||
4
dev_requirements.txt
Normal file
4
dev_requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
pytest>=3.2.0
|
||||||
|
sphinx>=1.6.4
|
||||||
|
sphinx-autobuild>=0.7.1
|
||||||
|
sphinx_rtd_theme>=0.2.4
|
||||||
20
docs/Makefile
Normal file
20
docs/Makefile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Minimal makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = python -msphinx
|
||||||
|
SPHINXPROJ = noiseprotocol
|
||||||
|
SOURCEDIR = .
|
||||||
|
BUILDDIR = _build
|
||||||
|
|
||||||
|
# Put it first so that "make" without argument is like "make help".
|
||||||
|
help:
|
||||||
|
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
|
|
||||||
|
.PHONY: help Makefile
|
||||||
|
|
||||||
|
# Catch-all target: route all unknown targets to Sphinx using the new
|
||||||
|
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
||||||
|
%: Makefile
|
||||||
|
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
||||||
108
docs/conf.py
Normal file
108
docs/conf.py
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# noiseprotocol documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Sun Oct 8 01:15:26 2017.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its
|
||||||
|
# containing dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, os.path.abspath('..'))
|
||||||
|
|
||||||
|
|
||||||
|
# -- General configuration ------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#
|
||||||
|
# needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
# ones.
|
||||||
|
extensions = ['sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.todo',
|
||||||
|
'sphinx.ext.coverage',
|
||||||
|
'sphinx.ext.viewcode']
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix(es) of source filenames.
|
||||||
|
# You can specify multiple suffix as a list of string:
|
||||||
|
#
|
||||||
|
# source_suffix = ['.rst', '.md']
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = 'noiseprotocol'
|
||||||
|
copyright = '2017, Piotr Lizonczyk'
|
||||||
|
author = 'Piotr Lizonczyk'
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
version = '0.2'
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = '0.2.0'
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#
|
||||||
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
|
# Usually you set "language" from the command line for these cases.
|
||||||
|
language = None
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
# This patterns also effect to html_static_path and html_extra_path
|
||||||
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
|
todo_include_todos = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output ----------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
#
|
||||||
|
import sphinx_rtd_theme
|
||||||
|
html_theme = "sphinx_rtd_theme"
|
||||||
|
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#
|
||||||
|
# html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
# -- Options for HTMLHelp output ------------------------------------------
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'noiseprotocoldoc'
|
||||||
|
|
||||||
26
docs/index.rst
Normal file
26
docs/index.rst
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
.. noiseprotocol documentation master file, created by
|
||||||
|
sphinx-quickstart on Sun Oct 8 01:15:26 2017.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
|
Welcome to noiseprotocol's documentation!
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 2
|
||||||
|
:caption: Contents:
|
||||||
|
|
||||||
|
|
||||||
|
Documentation for the Code
|
||||||
|
**************************
|
||||||
|
|
||||||
|
.. automodule:: noise.state
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
Indices and tables
|
||||||
|
==================
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
||||||
|
* :ref:`search`
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
from enum import Enum, auto
|
from enum import Enum
|
||||||
from typing import Union, List
|
from typing import Union, List
|
||||||
|
|
||||||
from cryptography.exceptions import InvalidTag
|
from cryptography.exceptions import InvalidTag
|
||||||
@@ -9,10 +9,10 @@ from .noise_protocol import NoiseProtocol
|
|||||||
|
|
||||||
|
|
||||||
class Keypair(Enum):
|
class Keypair(Enum):
|
||||||
STATIC = auto()
|
STATIC = 1
|
||||||
REMOTE_STATIC = auto()
|
REMOTE_STATIC = 2
|
||||||
EPHEMERAL = auto()
|
EPHEMERAL = 3
|
||||||
REMOTE_EPHEMERAL = auto()
|
REMOTE_EPHEMERAL = 4
|
||||||
|
|
||||||
|
|
||||||
_keypairs = {Keypair.STATIC: 's', Keypair.REMOTE_STATIC: 'rs',
|
_keypairs = {Keypair.STATIC: 's', Keypair.REMOTE_STATIC: 'rs',
|
||||||
|
|||||||
@@ -61,8 +61,8 @@ class X448(object):
|
|||||||
d = (x3 - z3) % P
|
d = (x3 - z3) % P
|
||||||
da = (d * a) % P
|
da = (d * a) % P
|
||||||
cb = (c * b) % P
|
cb = (c * b) % P
|
||||||
x3 = (((da + cb) % P) ** 2) % P
|
x3 = pow((da + cb) % P, 2, P)
|
||||||
z3 = (x1 * (((da - cb) % P) ** 2) % P) % P
|
z3 = (x1 * pow((da - cb) % P, 2, P)) % P
|
||||||
x2 = (aa * bb) % P
|
x2 = (aa * bb) % P
|
||||||
z2 = (e * ((aa + (A24 * e) % P) % P)) % P
|
z2 = (e * ((aa + (A24 * e) % P) % P)) % P
|
||||||
|
|
||||||
@@ -82,31 +82,29 @@ class X448(object):
|
|||||||
|
|
||||||
# Self-test
|
# Self-test
|
||||||
# Test vectors taken from RFC 7748 section 5.2 and 6.2
|
# Test vectors taken from RFC 7748 section 5.2 and 6.2
|
||||||
scalar1 = bytes.fromhex('203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b437'
|
scalar1 = bytes.fromhex(
|
||||||
'1282dd2c8d5be3095f')
|
'203d494428b8399352665ddca42f9de8fef600908e0d461cb021f8c538345dd77c3e4806e25f46d3315c44e0a5b4371282dd2c8d5be3095f')
|
||||||
u1 = bytes.fromhex('0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfb'
|
u1 = bytes.fromhex(
|
||||||
'de71ce8d157db')
|
'0fbcc2f993cd56d3305b0b7d9e55d4c1a8fb5dbb52f8e9a1e9b6201b165d015894e56c4d3570bee52fe205e28a78b91cdfbde71ce8d157db')
|
||||||
assert X448.mul(scalar1, u1) == bytes.fromhex('884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df3'
|
assert X448.mul(scalar1, u1) == bytes.fromhex(
|
||||||
'4321d62077e63633c575c1c954514e99da7c179d')
|
'884a02576239ff7a2f2f63b2db6a9ff37047ac13568e1e30fe63c4a7ad1b3ee3a5700df34321d62077e63633c575c1c954514e99da7c179d')
|
||||||
|
|
||||||
scalar2 = bytes.fromhex('3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f5'
|
scalar2 = bytes.fromhex(
|
||||||
'4b29259a4f1c600ad3')
|
'3d262fddf9ec8e88495266fea19a34d28882acef045104d0d1aae121700a779c984c24f8cdd78fbff44943eba368f54b29259a4f1c600ad3')
|
||||||
u2 = bytes.fromhex('06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa'
|
u2 = bytes.fromhex(
|
||||||
'7a7fb4ef8a086')
|
'06fce640fa3487bfda5f6cf2d5263f8aad88334cbd07437f020f08f9814dc031ddbdc38c19c6da2583fa5429db94ada18aa7a7fb4ef8a086')
|
||||||
assert X448.mul(scalar2, u2) == bytes.fromhex('ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc6'
|
assert X448.mul(scalar2, u2) == bytes.fromhex(
|
||||||
'6a01b0779d98223961111e21766282f73dd96b6f')
|
'ce3e4ff95a60dc6697da1db1d85e6afbdf79b50a2412d7546d5f239fe14fbaadeb445fc66a01b0779d98223961111e21766282f73dd96b6f')
|
||||||
|
|
||||||
alice_priv = bytes.fromhex('9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f12'
|
alice_priv = bytes.fromhex(
|
||||||
'7ab1d9ac2d8c0a598726b')
|
'9a8f4925d1519f5775cf46b04b5800d4ee9ee8bae8bc5565d498c28dd9c9baf574a9419744897391006382a6f127ab1d9ac2d8c0a598726b')
|
||||||
alice_pub = bytes.fromhex('9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da8812'
|
alice_pub = bytes.fromhex(
|
||||||
'0d53177f80e532c41fa0')
|
'9b08f7cc31b7e3e67d22d5aea121074a273bd2b83de09c63faa73d2c22c5d9bbc836647241d953d40c5b12da88120d53177f80e532c41fa0')
|
||||||
bob_priv = bytes.fromhex('1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d'
|
bob_priv = bytes.fromhex(
|
||||||
'1b0366f10b65173992d')
|
'1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d')
|
||||||
bob_pub = bytes.fromhex('3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3d'
|
bob_pub = bytes.fromhex(
|
||||||
'ae07bdc1c67bf33609')
|
'3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609')
|
||||||
assert alice_pub == X448.mul_5(alice_priv)
|
assert alice_pub == X448.mul_5(alice_priv)
|
||||||
assert bob_pub == X448.mul_5(bob_priv)
|
assert bob_pub == X448.mul_5(bob_priv)
|
||||||
assert X448.mul(alice_priv, bob_pub) == X448.mul(bob_priv, alice_pub) == bytes.fromhex('07fff4181ac6cc95ec1c16a94a0f74d'
|
assert X448.mul(alice_priv, bob_pub) == X448.mul(bob_priv, alice_pub) == bytes.fromhex(
|
||||||
'12da232ce40a77552281d282bb60c0b'
|
'07fff4181ac6cc95ec1c16a94a0f74d12da232ce40a77552281d282bb60c0b56fd2464c335543936521c24403085d59a449a5037514a879d')
|
||||||
'56fd2464c335543936521c24403085d'
|
|
||||||
'59a449a5037514a879d')
|
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
import abc
|
import abc
|
||||||
import warnings
|
import warnings
|
||||||
from functools import partial # Turn back on when Cryptography gets fixed
|
from functools import partial
|
||||||
import hashlib
|
|
||||||
import hmac
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
# from cryptography.hazmat.primitives import hashes # Turn back on when Cryptography gets fixed
|
from cryptography.hazmat.primitives import hashes
|
||||||
from cryptography.hazmat.primitives.asymmetric import x25519
|
from cryptography.hazmat.primitives.asymmetric import x25519
|
||||||
from cryptography.hazmat.primitives.ciphers.aead import AESGCM, ChaCha20Poly1305
|
from cryptography.hazmat.primitives.ciphers.aead import AESGCM, ChaCha20Poly1305
|
||||||
# from cryptography.hazmat.primitives.hmac import HMAC # Turn back on when Cryptography gets fixed
|
from cryptography.hazmat.primitives.hmac import HMAC
|
||||||
from noise.constants import MAX_NONCE
|
from noise.constants import MAX_NONCE
|
||||||
from noise.exceptions import NoiseValueError
|
from noise.exceptions import NoiseValueError
|
||||||
from .crypto import X448
|
from .crypto import X448
|
||||||
@@ -67,28 +65,22 @@ class Cipher(object):
|
|||||||
self.rekey = self._default_rekey
|
self.rekey = self._default_rekey
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError('Cipher method: {}'.format(method))
|
raise NotImplementedError('Cipher method: {}'.format(method))
|
||||||
|
self.cipher = None
|
||||||
|
|
||||||
def _aesgcm_encrypt(self, k, n, ad, plaintext):
|
def _aesgcm_encrypt(self, k, n, ad, plaintext):
|
||||||
# Might be expensive to initialise AESGCM with the same key every time. The key should be (as per spec) kept in
|
return self.cipher.encrypt(nonce=self._aesgcm_nonce(n), data=plaintext, associated_data=ad)
|
||||||
# CipherState, but we may as well hold an initialised AESGCM and manage reinitialisation on CipherState.rekey
|
|
||||||
cipher = self._cipher(k)
|
|
||||||
return cipher.encrypt(nonce=self._aesgcm_nonce(n), data=plaintext, associated_data=ad)
|
|
||||||
|
|
||||||
def _aesgcm_decrypt(self, k, n, ad, ciphertext):
|
def _aesgcm_decrypt(self, k, n, ad, ciphertext):
|
||||||
cipher = self._cipher(k)
|
return self.cipher.decrypt(nonce=self._aesgcm_nonce(n), data=ciphertext, associated_data=ad)
|
||||||
return cipher.decrypt(nonce=self._aesgcm_nonce(n), data=ciphertext, associated_data=ad)
|
|
||||||
|
|
||||||
def _aesgcm_nonce(self, n):
|
def _aesgcm_nonce(self, n):
|
||||||
return b'\x00\x00\x00\x00' + n.to_bytes(length=8, byteorder='big')
|
return b'\x00\x00\x00\x00' + n.to_bytes(length=8, byteorder='big')
|
||||||
|
|
||||||
def _chacha20_encrypt(self, k, n, ad, plaintext):
|
def _chacha20_encrypt(self, k, n, ad, plaintext):
|
||||||
# Same comment as with AESGCM
|
return self.cipher.encrypt(nonce=self._chacha20_nonce(n), data=plaintext, associated_data=ad)
|
||||||
cipher = self._cipher(k)
|
|
||||||
return cipher.encrypt(nonce=self._chacha20_nonce(n), data=plaintext, associated_data=ad)
|
|
||||||
|
|
||||||
def _chacha20_decrypt(self, k, n, ad, ciphertext):
|
def _chacha20_decrypt(self, k, n, ad, ciphertext):
|
||||||
cipher = self._cipher(k)
|
return self.cipher.decrypt(nonce=self._chacha20_nonce(n), data=ciphertext, associated_data=ad)
|
||||||
return cipher.decrypt(nonce=self._chacha20_nonce(n), data=ciphertext, associated_data=ad)
|
|
||||||
|
|
||||||
def _chacha20_nonce(self, n):
|
def _chacha20_nonce(self, n):
|
||||||
return b'\x00\x00\x00\x00' + n.to_bytes(length=8, byteorder='little')
|
return b'\x00\x00\x00\x00' + n.to_bytes(length=8, byteorder='little')
|
||||||
@@ -96,6 +88,9 @@ class Cipher(object):
|
|||||||
def _default_rekey(self, k):
|
def _default_rekey(self, k):
|
||||||
return self.encrypt(k, MAX_NONCE, b'', b'\x00' * 32)[:32]
|
return self.encrypt(k, MAX_NONCE, b'', b'\x00' * 32)[:32]
|
||||||
|
|
||||||
|
def initialize(self, key):
|
||||||
|
self.cipher = self._cipher(key)
|
||||||
|
|
||||||
|
|
||||||
class Hash(object):
|
class Hash(object):
|
||||||
def __init__(self, method):
|
def __init__(self, method):
|
||||||
@@ -103,60 +98,44 @@ class Hash(object):
|
|||||||
self.hashlen = 32
|
self.hashlen = 32
|
||||||
self.blocklen = 64
|
self.blocklen = 64
|
||||||
self.hash = self._hash_sha256
|
self.hash = self._hash_sha256
|
||||||
# self.fn = hashes.SHA256 # Turn back on when Cryptography gets fixed
|
self.fn = hashes.SHA256
|
||||||
self.fn = 'SHA256'
|
|
||||||
elif method == 'SHA512':
|
elif method == 'SHA512':
|
||||||
self.hashlen = 64
|
self.hashlen = 64
|
||||||
self.blocklen = 128
|
self.blocklen = 128
|
||||||
self.hash = self._hash_sha512
|
self.hash = self._hash_sha512
|
||||||
# self.fn = hashes.SHA512 # Turn back on when Cryptography gets fixed
|
self.fn = hashes.SHA512
|
||||||
self.fn = 'SHA512'
|
|
||||||
elif method == 'BLAKE2s':
|
elif method == 'BLAKE2s':
|
||||||
self.hashlen = 32
|
self.hashlen = 32
|
||||||
self.blocklen = 64
|
self.blocklen = 64
|
||||||
self.hash = self._hash_blake2s
|
self.hash = self._hash_blake2s
|
||||||
# self.fn = partial(hashes.BLAKE2s, digest_size=self.hashlen) # Turn back on when Cryptography gets fixed
|
self.fn = partial(hashes.BLAKE2s, digest_size=self.hashlen)
|
||||||
self.fn = 'blake2s'
|
|
||||||
elif method == 'BLAKE2b':
|
elif method == 'BLAKE2b':
|
||||||
self.hashlen = 64
|
self.hashlen = 64
|
||||||
self.blocklen = 128
|
self.blocklen = 128
|
||||||
self.hash = self._hash_blake2b
|
self.hash = self._hash_blake2b
|
||||||
# self.fn = partial(hashes.BLAKE2b, digest_size=self.hashlen) # Turn back on when Cryptography gets fixed
|
self.fn = partial(hashes.BLAKE2b, digest_size=self.hashlen)
|
||||||
self.fn = 'blake2b'
|
|
||||||
else:
|
else:
|
||||||
raise NotImplementedError('Hash method: {}'.format(method))
|
raise NotImplementedError('Hash method: {}'.format(method))
|
||||||
|
|
||||||
def _hash_sha256(self, data):
|
def _hash_sha256(self, data):
|
||||||
return hashlib.sha256(data).digest()
|
digest = hashes.Hash(hashes.SHA256(), backend)
|
||||||
|
digest.update(data)
|
||||||
|
return digest.finalize()
|
||||||
|
|
||||||
def _hash_sha512(self, data):
|
def _hash_sha512(self, data):
|
||||||
return hashlib.sha512(data).digest()
|
digest = hashes.Hash(hashes.SHA512(), backend)
|
||||||
|
digest.update(data)
|
||||||
|
return digest.finalize()
|
||||||
|
|
||||||
def _hash_blake2s(self, data):
|
def _hash_blake2s(self, data):
|
||||||
return hashlib.blake2s(data).digest()
|
digest = hashes.Hash(hashes.BLAKE2s(digest_size=self.hashlen), backend)
|
||||||
|
digest.update(data)
|
||||||
|
return digest.finalize()
|
||||||
|
|
||||||
def _hash_blake2b(self, data):
|
def _hash_blake2b(self, data):
|
||||||
return hashlib.blake2b(data).digest()
|
digest = hashes.Hash(hashes.BLAKE2b(digest_size=self.hashlen), backend)
|
||||||
|
digest.update(data)
|
||||||
# def _hash_sha256(self, data): # Turn back on when Cryptography gets fixed
|
return digest.finalize()
|
||||||
# digest = hashes.Hash(hashes.SHA256(), backend)
|
|
||||||
# digest.update(data)
|
|
||||||
# return digest.finalize()
|
|
||||||
#
|
|
||||||
# def _hash_sha512(self, data): # Turn back on when Cryptography gets fixed
|
|
||||||
# digest = hashes.Hash(hashes.SHA512(), backend)
|
|
||||||
# digest.update(data)
|
|
||||||
# return digest.finalize()
|
|
||||||
#
|
|
||||||
# def _hash_blake2s(self, data): # Turn back on when Cryptography gets fixed
|
|
||||||
# digest = hashes.Hash(hashes.BLAKE2s(digest_size=self.hashlen), backend)
|
|
||||||
# digest.update(data)
|
|
||||||
# return digest.finalize()
|
|
||||||
#
|
|
||||||
# def _hash_blake2b(self, data): # Turn back on when Cryptography gets fixed
|
|
||||||
# digest = hashes.Hash(hashes.BLAKE2b(digest_size=self.hashlen), backend)
|
|
||||||
# digest.update(data)
|
|
||||||
# return digest.finalize()
|
|
||||||
|
|
||||||
|
|
||||||
class _KeyPair(object):
|
class _KeyPair(object):
|
||||||
@@ -227,8 +206,8 @@ dh_map = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cipher_map = {
|
cipher_map = {
|
||||||
'AESGCM': Cipher('AESGCM'),
|
'AESGCM': partial(Cipher, 'AESGCM'),
|
||||||
'ChaChaPoly': Cipher('ChaCha20')
|
'ChaChaPoly': partial(Cipher, 'ChaCha20')
|
||||||
}
|
}
|
||||||
|
|
||||||
hash_map = {
|
hash_map = {
|
||||||
@@ -244,15 +223,11 @@ keypair_map = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# def hmac_hash(key, data, algorithm): # Turn back on when Cryptography gets fixed
|
|
||||||
# # Applies HMAC using the HASH() function.
|
|
||||||
# hmac = HMAC(key=key, algorithm=algorithm(), backend=backend)
|
|
||||||
# hmac.update(data=data)
|
|
||||||
# return hmac.finalize()
|
|
||||||
|
|
||||||
def hmac_hash(key, data, algorithm):
|
def hmac_hash(key, data, algorithm):
|
||||||
# Applies HMAC using the HASH() function.
|
# Applies HMAC using the HASH() function.
|
||||||
return hmac.new(key, data, algorithm).digest()
|
hmac = HMAC(key=key, algorithm=algorithm(), backend=backend)
|
||||||
|
hmac.update(data=data)
|
||||||
|
return hmac.finalize()
|
||||||
|
|
||||||
|
|
||||||
def hkdf(chaining_key, input_key_material, num_outputs, hmac_hash_fn):
|
def hkdf(chaining_key, input_key_material, num_outputs, hmac_hash_fn):
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ class NoiseProtocol(object):
|
|||||||
self.cipher_state_decrypt = None
|
self.cipher_state_decrypt = None
|
||||||
else:
|
else:
|
||||||
self.cipher_state_encrypt = None
|
self.cipher_state_encrypt = None
|
||||||
self.handshake_hash = self.symmetric_state.h
|
self.handshake_hash = self.symmetric_state.get_handshake_hash()
|
||||||
del self.handshake_state
|
del self.handshake_state
|
||||||
del self.symmetric_state
|
del self.symmetric_state
|
||||||
del self.cipher_state_handshake
|
del self.cipher_state_handshake
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class Pattern(object):
|
|||||||
def get_required_keypairs(self, initiator: bool) -> list:
|
def get_required_keypairs(self, initiator: bool) -> list:
|
||||||
required = []
|
required = []
|
||||||
if initiator:
|
if initiator:
|
||||||
if self.name[0] in ['K', 'X', 'I']:
|
if self.name[0] in ('K', 'X', 'I'):
|
||||||
required.append('s')
|
required.append('s')
|
||||||
if self.one_way or self.name[1] == 'K':
|
if self.one_way or self.name[1] == 'K':
|
||||||
required.append('rs')
|
required.append('rs')
|
||||||
|
|||||||
@@ -6,31 +6,42 @@ from .constants import Empty, TOKEN_E, TOKEN_S, TOKEN_EE, TOKEN_ES, TOKEN_SE, TO
|
|||||||
|
|
||||||
class CipherState(object):
|
class CipherState(object):
|
||||||
"""
|
"""
|
||||||
Implemented as per Noise Protocol specification (rev 32) - paragraph 5.1.
|
Implemented as per Noise Protocol specification - paragraph 5.1.
|
||||||
|
|
||||||
The initialize_key() function takes additional required argument - noise_protocol.
|
The initialize_key() function takes additional required argument - noise_protocol.
|
||||||
|
|
||||||
|
This class holds an instance of Cipher wrapper. It manages initialisation of underlying cipher function
|
||||||
|
with appropriate key in initialize_key() and rekey() methods.
|
||||||
"""
|
"""
|
||||||
def __init__(self, noise_protocol):
|
def __init__(self, noise_protocol):
|
||||||
self.k = Empty()
|
self.k = Empty()
|
||||||
self.n = None
|
self.n = None
|
||||||
self.noise_protocol = noise_protocol
|
self.cipher = noise_protocol.cipher_fn()
|
||||||
|
|
||||||
def initialize_key(self, key):
|
def initialize_key(self, key):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
:param key: Key to set within CipherState
|
:param key: Key to set within CipherState
|
||||||
"""
|
"""
|
||||||
self.k = key
|
self.k = key
|
||||||
self.n = 0
|
self.n = 0
|
||||||
|
if self.has_key():
|
||||||
|
self.cipher.initialize(key)
|
||||||
|
|
||||||
def has_key(self):
|
def has_key(self):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
:return: True if self.k is not an instance of Empty
|
:return: True if self.k is not an instance of Empty
|
||||||
"""
|
"""
|
||||||
return not isinstance(self.k, Empty)
|
return not isinstance(self.k, Empty)
|
||||||
|
|
||||||
|
def set_nonce(self, nonce):
|
||||||
|
self.n = nonce
|
||||||
|
|
||||||
def encrypt_with_ad(self, ad: bytes, plaintext: bytes) -> bytes:
|
def encrypt_with_ad(self, ad: bytes, plaintext: bytes) -> bytes:
|
||||||
"""
|
"""
|
||||||
If k is non-empty returns ENCRYPT(k, n++, ad, plaintext). Otherwise returns plaintext.
|
If k is non-empty returns ENCRYPT(k, n++, ad, plaintext). Otherwise returns plaintext.
|
||||||
|
|
||||||
:param ad: bytes sequence
|
:param ad: bytes sequence
|
||||||
:param plaintext: bytes sequence
|
:param plaintext: bytes sequence
|
||||||
:return: ciphertext bytes sequence
|
:return: ciphertext bytes sequence
|
||||||
@@ -41,7 +52,7 @@ class CipherState(object):
|
|||||||
if not self.has_key():
|
if not self.has_key():
|
||||||
return plaintext
|
return plaintext
|
||||||
|
|
||||||
ciphertext = self.noise_protocol.cipher_fn.encrypt(self.k, self.n, ad, plaintext)
|
ciphertext = self.cipher.encrypt(self.k, self.n, ad, plaintext)
|
||||||
self.n = self.n + 1
|
self.n = self.n + 1
|
||||||
return ciphertext
|
return ciphertext
|
||||||
|
|
||||||
@@ -49,6 +60,7 @@ class CipherState(object):
|
|||||||
"""
|
"""
|
||||||
If k is non-empty returns DECRYPT(k, n++, ad, ciphertext). Otherwise returns ciphertext. If an authentication
|
If k is non-empty returns DECRYPT(k, n++, ad, ciphertext). Otherwise returns ciphertext. If an authentication
|
||||||
failure occurs in DECRYPT() then n is not incremented and an error is signaled to the caller.
|
failure occurs in DECRYPT() then n is not incremented and an error is signaled to the caller.
|
||||||
|
|
||||||
:param ad: bytes sequence
|
:param ad: bytes sequence
|
||||||
:param ciphertext: bytes sequence
|
:param ciphertext: bytes sequence
|
||||||
:return: plaintext bytes sequence
|
:return: plaintext bytes sequence
|
||||||
@@ -59,17 +71,18 @@ class CipherState(object):
|
|||||||
if not self.has_key():
|
if not self.has_key():
|
||||||
return ciphertext
|
return ciphertext
|
||||||
|
|
||||||
plaintext = self.noise_protocol.cipher_fn.decrypt(self.k, self.n, ad, ciphertext)
|
plaintext = self.cipher.decrypt(self.k, self.n, ad, ciphertext)
|
||||||
self.n = self.n + 1
|
self.n = self.n + 1
|
||||||
return plaintext
|
return plaintext
|
||||||
|
|
||||||
def rekey(self):
|
def rekey(self):
|
||||||
self.k = self.noise_protocol.cipher_fn.rekey(self.k)
|
self.k = self.cipher.rekey(self.k)
|
||||||
|
self.cipher.initialize(self.k)
|
||||||
|
|
||||||
|
|
||||||
class SymmetricState(object):
|
class SymmetricState(object):
|
||||||
"""
|
"""
|
||||||
Implemented as per Noise Protocol specification (rev 32) - paragraph 5.2.
|
Implemented as per Noise Protocol specification - paragraph 5.2.
|
||||||
|
|
||||||
The initialize_symmetric function takes different required argument - noise_protocol, which contains protocol_name.
|
The initialize_symmetric function takes different required argument - noise_protocol, which contains protocol_name.
|
||||||
"""
|
"""
|
||||||
@@ -86,6 +99,7 @@ class SymmetricState(object):
|
|||||||
protocol name and crypto functions
|
protocol name and crypto functions
|
||||||
|
|
||||||
Comments below are mostly copied from specification.
|
Comments below are mostly copied from specification.
|
||||||
|
|
||||||
:param noise_protocol: a valid NoiseProtocol instance
|
:param noise_protocol: a valid NoiseProtocol instance
|
||||||
:return: initialised SymmetricState instance
|
:return: initialised SymmetricState instance
|
||||||
"""
|
"""
|
||||||
@@ -112,6 +126,7 @@ class SymmetricState(object):
|
|||||||
|
|
||||||
def mix_key(self, input_key_material: bytes):
|
def mix_key(self, input_key_material: bytes):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
:param input_key_material:
|
:param input_key_material:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
@@ -127,6 +142,7 @@ class SymmetricState(object):
|
|||||||
def mix_hash(self, data: bytes):
|
def mix_hash(self, data: bytes):
|
||||||
"""
|
"""
|
||||||
Sets h = HASH(h + data).
|
Sets h = HASH(h + data).
|
||||||
|
|
||||||
:param data: bytes sequence
|
:param data: bytes sequence
|
||||||
"""
|
"""
|
||||||
self.h = self.noise_protocol.hash_fn.hash(self.h + data)
|
self.h = self.noise_protocol.hash_fn.hash(self.h + data)
|
||||||
@@ -142,10 +158,14 @@ class SymmetricState(object):
|
|||||||
# Calls InitializeKey(temp_k).
|
# Calls InitializeKey(temp_k).
|
||||||
self.cipher_state.initialize_key(temp_k)
|
self.cipher_state.initialize_key(temp_k)
|
||||||
|
|
||||||
|
def get_handshake_hash(self):
|
||||||
|
return self.h
|
||||||
|
|
||||||
def encrypt_and_hash(self, plaintext: bytes) -> bytes:
|
def encrypt_and_hash(self, plaintext: bytes) -> bytes:
|
||||||
"""
|
"""
|
||||||
Sets ciphertext = EncryptWithAd(h, plaintext), calls MixHash(ciphertext), and returns ciphertext. Note that if
|
Sets ciphertext = EncryptWithAd(h, plaintext), calls MixHash(ciphertext), and returns ciphertext. Note that if
|
||||||
k is empty, the EncryptWithAd() call will set ciphertext equal to plaintext.
|
k is empty, the EncryptWithAd() call will set ciphertext equal to plaintext.
|
||||||
|
|
||||||
:param plaintext: bytes sequence
|
:param plaintext: bytes sequence
|
||||||
:return: ciphertext bytes sequence
|
:return: ciphertext bytes sequence
|
||||||
"""
|
"""
|
||||||
@@ -157,6 +177,7 @@ class SymmetricState(object):
|
|||||||
"""
|
"""
|
||||||
Sets plaintext = DecryptWithAd(h, ciphertext), calls MixHash(ciphertext), and returns plaintext. Note that if
|
Sets plaintext = DecryptWithAd(h, ciphertext), calls MixHash(ciphertext), and returns plaintext. Note that if
|
||||||
k is empty, the DecryptWithAd() call will set plaintext equal to ciphertext.
|
k is empty, the DecryptWithAd() call will set plaintext equal to ciphertext.
|
||||||
|
|
||||||
:param ciphertext: bytes sequence
|
:param ciphertext: bytes sequence
|
||||||
:return: plaintext bytes sequence
|
:return: plaintext bytes sequence
|
||||||
"""
|
"""
|
||||||
@@ -167,6 +188,7 @@ class SymmetricState(object):
|
|||||||
def split(self):
|
def split(self):
|
||||||
"""
|
"""
|
||||||
Returns a pair of CipherState objects for encrypting/decrypting transport messages.
|
Returns a pair of CipherState objects for encrypting/decrypting transport messages.
|
||||||
|
|
||||||
:return: tuple (CipherState, CipherState)
|
:return: tuple (CipherState, CipherState)
|
||||||
"""
|
"""
|
||||||
# Sets temp_k1, temp_k2 = HKDF(ck, b'', 2).
|
# Sets temp_k1, temp_k2 = HKDF(ck, b'', 2).
|
||||||
@@ -197,7 +219,7 @@ class SymmetricState(object):
|
|||||||
|
|
||||||
class HandshakeState(object):
|
class HandshakeState(object):
|
||||||
"""
|
"""
|
||||||
Implemented as per Noise Protocol specification (rev 32) - paragraph 5.3.
|
Implemented as per Noise Protocol specification - paragraph 5.3.
|
||||||
|
|
||||||
The initialize() function takes different required argument - noise_protocol, which contains handshake_pattern.
|
The initialize() function takes different required argument - noise_protocol, which contains handshake_pattern.
|
||||||
"""
|
"""
|
||||||
@@ -223,7 +245,7 @@ class HandshakeState(object):
|
|||||||
:param noise_protocol: a valid NoiseProtocol instance
|
:param noise_protocol: a valid NoiseProtocol instance
|
||||||
:param initiator: boolean indicating the initiator or responder role
|
:param initiator: boolean indicating the initiator or responder role
|
||||||
:param prologue: byte sequence which may be zero-length, or which may contain context information that both
|
:param prologue: byte sequence which may be zero-length, or which may contain context information that both
|
||||||
parties want to confirm is identical
|
parties want to confirm is identical
|
||||||
:param s: local static key pair
|
:param s: local static key pair
|
||||||
:param e: local ephemeral key pair
|
:param e: local ephemeral key pair
|
||||||
:param rs: remote party’s static public key
|
:param rs: remote party’s static public key
|
||||||
@@ -270,6 +292,7 @@ class HandshakeState(object):
|
|||||||
def write_message(self, payload: Union[bytes, bytearray], message_buffer: bytearray):
|
def write_message(self, payload: Union[bytes, bytearray], message_buffer: bytearray):
|
||||||
"""
|
"""
|
||||||
Comments below are mostly copied from specification.
|
Comments below are mostly copied from specification.
|
||||||
|
|
||||||
:param payload: byte sequence which may be zero-length
|
:param payload: byte sequence which may be zero-length
|
||||||
:param message_buffer: buffer-like object
|
:param message_buffer: buffer-like object
|
||||||
:return: None or result of SymmetricState.split() - tuple (CipherState, CipherState)
|
:return: None or result of SymmetricState.split() - tuple (CipherState, CipherState)
|
||||||
@@ -328,6 +351,7 @@ class HandshakeState(object):
|
|||||||
def read_message(self, message: Union[bytes, bytearray], payload_buffer: bytearray):
|
def read_message(self, message: Union[bytes, bytearray], payload_buffer: bytearray):
|
||||||
"""
|
"""
|
||||||
Comments below are mostly copied from specification.
|
Comments below are mostly copied from specification.
|
||||||
|
|
||||||
:param message: byte sequence containing a Noise handshake message
|
:param message: byte sequence containing a Noise handshake message
|
||||||
:param payload_buffer: buffer-like object
|
:param payload_buffer: buffer-like object
|
||||||
:return: None or result of SymmetricState.split() - tuple (CipherState, CipherState)
|
:return: None or result of SymmetricState.split() - tuple (CipherState, CipherState)
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
cryptography==2.0.3
|
cryptography==2.1.1
|
||||||
|
|||||||
8
setup.py
8
setup.py
@@ -13,7 +13,7 @@ except (IOError, ImportError):
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='noiseprotocol',
|
name='noiseprotocol',
|
||||||
version='0.1.1',
|
version='0.2.0',
|
||||||
description='Implementation of Noise Protocol Framework',
|
description='Implementation of Noise Protocol Framework',
|
||||||
long_description=long_description,
|
long_description=long_description,
|
||||||
url='https://github.com/plizonczyk/noiseprotocol',
|
url='https://github.com/plizonczyk/noiseprotocol',
|
||||||
@@ -26,12 +26,12 @@ setup(
|
|||||||
'Topic :: Security :: Cryptography',
|
'Topic :: Security :: Cryptography',
|
||||||
'License :: OSI Approved :: MIT License',
|
'License :: OSI Approved :: MIT License',
|
||||||
'Programming Language :: Python :: 3',
|
'Programming Language :: Python :: 3',
|
||||||
# 'Programming Language :: Python :: 3.5',
|
'Programming Language :: Python :: 3.5',
|
||||||
'Programming Language :: Python :: 3.6',
|
'Programming Language :: Python :: 3.6',
|
||||||
# 'Programming Language :: Python :: 3.7',
|
# 'Programming Language :: Python :: 3.7',
|
||||||
],
|
],
|
||||||
keywords='cryptography noiseprotocol noise security',
|
keywords='cryptography noiseprotocol noise security',
|
||||||
packages=find_packages(exclude=['contrib', 'docs', 'tests', 'examples']),
|
packages=find_packages(exclude=['contrib', 'docs', 'tests', 'examples']),
|
||||||
install_requires=['cryptography==2.0.3'],
|
install_requires=['cryptography==2.1.1'],
|
||||||
python_requires='~=3.6',
|
python_requires='~=3.5,~=3.6',
|
||||||
)
|
)
|
||||||
|
|||||||
26
tests/test_rev33_compat.py
Normal file
26
tests/test_rev33_compat.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
from noise.noise_protocol import NoiseProtocol
|
||||||
|
from noise.state import CipherState, SymmetricState
|
||||||
|
|
||||||
|
|
||||||
|
class TestRevision33Compatibility(object):
|
||||||
|
def test_noise_protocol_accepts_slash(self):
|
||||||
|
class FakeSHA3_256():
|
||||||
|
fn = None
|
||||||
|
|
||||||
|
noise_name = b"Noise_NN_25519_AESGCM_SHA3/256"
|
||||||
|
modified_class = NoiseProtocol
|
||||||
|
modified_class.methods['hash']['SHA3/256'] = FakeSHA3_256 # Add callable to hash functions mapping
|
||||||
|
modified_class(noise_name)
|
||||||
|
|
||||||
|
def test_cipher_state_set_nonce(self):
|
||||||
|
noise_protocol = NoiseProtocol(b"Noise_NN_25519_AESGCM_SHA256")
|
||||||
|
cipher_state = CipherState(noise_protocol)
|
||||||
|
cipher_state.initialize_key(b'\x00'*32)
|
||||||
|
assert cipher_state.n == 0
|
||||||
|
cipher_state.set_nonce(42)
|
||||||
|
assert cipher_state.n == 42
|
||||||
|
|
||||||
|
def test_symmetric_state_get_handshake_hash(self):
|
||||||
|
symmetric_state = SymmetricState()
|
||||||
|
symmetric_state.h = 42
|
||||||
|
assert symmetric_state.get_handshake_hash() == 42
|
||||||
@@ -15,10 +15,10 @@ vector_files = [
|
|||||||
|
|
||||||
# As in test vectors specification (https://github.com/noiseprotocol/noise_wiki/wiki/Test-vectors)
|
# As in test vectors specification (https://github.com/noiseprotocol/noise_wiki/wiki/Test-vectors)
|
||||||
# We use this to cast read strings into bytes
|
# We use this to cast read strings into bytes
|
||||||
byte_fields = ['protocol_name']
|
byte_field = 'protocol_name'
|
||||||
hexbyte_fields = ['init_prologue', 'init_static', 'init_ephemeral', 'init_remote_static', 'resp_static',
|
hexbyte_fields = ('init_prologue', 'init_static', 'init_ephemeral', 'init_remote_static', 'resp_static',
|
||||||
'resp_prologue', 'resp_ephemeral', 'resp_remote_static', 'handshake_hash']
|
'resp_prologue', 'resp_ephemeral', 'resp_remote_static', 'handshake_hash')
|
||||||
list_fields = ['init_psks', 'resp_psks']
|
list_fields = ('init_psks', 'resp_psks')
|
||||||
dict_field = 'messages'
|
dict_field = 'messages'
|
||||||
|
|
||||||
|
|
||||||
@@ -31,7 +31,7 @@ def _prepare_test_vectors():
|
|||||||
|
|
||||||
for vector in vectors_list:
|
for vector in vectors_list:
|
||||||
for key, value in vector.copy().items():
|
for key, value in vector.copy().items():
|
||||||
if key in byte_fields:
|
if key == byte_field:
|
||||||
vector[key] = value.encode()
|
vector[key] = value.encode()
|
||||||
if key in hexbyte_fields:
|
if key in hexbyte_fields:
|
||||||
vector[key] = bytes.fromhex(value)
|
vector[key] = bytes.fromhex(value)
|
||||||
|
|||||||
Reference in New Issue
Block a user