mirror of
https://github.com/fluencelabs/js-libp2p-noise
synced 2025-04-28 20:02:26 +00:00
Compare commits
123 Commits
v1.1.0-rc1
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
bd737dc2c6 | ||
|
522d28cd8b | ||
|
d482e44882 | ||
|
82d093accc | ||
|
98aeb5a59b | ||
|
81a70d1e4f | ||
|
34bb8d4ef7 | ||
|
a9b99e4e26 | ||
|
cc982dd240 | ||
|
9b914ad94c | ||
|
a2abb93d8b | ||
|
c23ba08f58 | ||
|
5d36667b0c | ||
|
0e6ceaa72f | ||
|
3095240532 | ||
|
8373dec1af | ||
|
34effc2ef5 | ||
|
7a0d01abcc | ||
|
693c833942 | ||
|
c36b0a686a | ||
|
a00ed39a4f | ||
|
30a1955929 | ||
|
279c2e6f64 | ||
|
94e4ffe0a5 | ||
|
fd99dc88fe | ||
|
47e773509e | ||
|
ef3ae768d0 | ||
|
2038b26b76 | ||
|
89dd965bd2 | ||
|
ead151471e | ||
|
2df5f9546e | ||
|
8d3b130509 | ||
|
941fe281cb | ||
|
d41e9aa831 | ||
|
24d4eabb8c | ||
|
4f7bdf368f | ||
|
46c6458414 | ||
|
b484a5f39c | ||
|
00968da390 | ||
|
11d7f21920 | ||
|
d525a790a8 | ||
|
be8778e542 | ||
|
e362dbcf7b | ||
|
1db2cf19e8 | ||
|
f9d56d8c87 | ||
|
c5dac93733 | ||
|
2ac2261df2 | ||
|
0f47db25f1 | ||
|
c90aa1b018 | ||
|
f0fefdc530 | ||
|
0345aea790 | ||
|
7a1d0dc43a | ||
|
489d60ca6d | ||
|
c2784b1d37 | ||
|
2ab12403a6 | ||
|
59b6c96355 | ||
|
2a8e3ef8dd | ||
|
7a392bd1de | ||
|
14c1905307 | ||
|
661f48dcf5 | ||
|
7b54d65c03 | ||
|
5ae0639f52 | ||
|
b9805b3dc3 | ||
|
31705d5f89 | ||
|
006e35a3bc | ||
|
ec4eacadfa | ||
|
29efe156c0 | ||
|
66e569cb65 | ||
|
e20e4eb293 | ||
|
8da430cdc5 | ||
|
a6c6dc5ef0 | ||
|
de1cdb5bd0 | ||
|
e52fe108ad | ||
|
1a6490d829 | ||
|
f1b92a9f1b | ||
|
dccdc678b1 | ||
|
e2747854ec | ||
|
e01d8e293a | ||
|
08b1c7197b | ||
|
40b547a6b1 | ||
|
e9bc0dbe44 | ||
|
fd1cc28f41 | ||
|
c928cc1514 | ||
|
9dc300c65e | ||
|
d01d6f428a | ||
|
153f51ae5a | ||
|
e16e25cbef | ||
|
ca39bc5d99 | ||
|
8327a60356 | ||
|
9b11560183 | ||
|
c4469d55e4 | ||
|
273678aa49 | ||
|
7f2a37f692 | ||
|
069a2f9573 | ||
|
a8274ad416 | ||
|
f05150e640 | ||
|
3a782aadaa | ||
|
496dfd3005 | ||
|
a504e3abec | ||
|
088e43642a | ||
|
e35f067b5e | ||
|
0fdb309d8c | ||
|
d188709247 | ||
|
ef80af69c7 | ||
|
e9af13a1ee | ||
|
c8905cc774 | ||
|
8cdbc3dc70 | ||
|
324f555e0c | ||
|
635963062a | ||
|
6e38ba69b9 | ||
|
5cd8a902fb | ||
|
dff7f8bae0 | ||
|
3b0df24fbd | ||
|
60b2367dac | ||
|
f5f4b9f344 | ||
|
7b11f5a3ab | ||
|
866ae6d333 | ||
|
cc96cf5e37 | ||
|
2efbfcb105 | ||
|
fddab30049 | ||
|
c747634c4f | ||
|
2d3c6167db | ||
|
e7ec32ca2a |
28
.aegir.js
Normal file
28
.aegir.js
Normal file
@ -0,0 +1,28 @@
|
||||
const path = require('path')
|
||||
|
||||
/** @type {import('aegir').Options["build"]["config"]} */
|
||||
const esbuild = {
|
||||
inject: [path.join(__dirname, 'test/fixtures/node-globals.js')]
|
||||
}
|
||||
|
||||
|
||||
/** @type {import('aegir').PartialOptions} */
|
||||
const config = {
|
||||
tsRepo: true,
|
||||
docs: {
|
||||
entryPoint: "src/index.ts"
|
||||
},
|
||||
test: {
|
||||
browser :{
|
||||
config: {
|
||||
buildConfig: esbuild
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
bundlesizeMax: '214KB',
|
||||
config: esbuild
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = config
|
21
.babelrc
21
.babelrc
@ -1,21 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "10"
|
||||
}
|
||||
}
|
||||
],
|
||||
["@babel/preset-typescript", {
|
||||
"allowNamespaces": true
|
||||
}]
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"@babel/plugin-proposal-export-default-from",
|
||||
"@babel/plugin-proposal-async-generator-functions",
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
]
|
||||
}
|
21
.eslintrc
21
.eslintrc
@ -1,21 +0,0 @@
|
||||
{
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"project": "./tsconfig.json"
|
||||
},
|
||||
"env": {
|
||||
"mocha": true
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"extends": ["plugin:@typescript-eslint/recommended"],
|
||||
"rules": {
|
||||
"new-parens": "error",
|
||||
"no-caller": "error",
|
||||
"no-bitwise": "off",
|
||||
"@typescript-eslint/indent": ["error", 2],
|
||||
"@typescript-eslint/no-use-before-define": "off",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/interface-name-prefix": ["error", { "prefixWithI": "always" }],
|
||||
"no-console": "warn"
|
||||
}
|
||||
}
|
1
.github/CODEOWNERS
vendored
Normal file
1
.github/CODEOWNERS
vendored
Normal file
@ -0,0 +1 @@
|
||||
* @morrigan @mpetrunic
|
174
.github/workflows/ci.yml
vendored
Normal file
174
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
check:
|
||||
name: Lint and Typecheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 14
|
||||
- uses: actions/checkout@v2
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- uses: actions/cache@v2
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn --prefer-offline --frozen-lockfile
|
||||
- run: yarn run build
|
||||
- run: yarn run check
|
||||
- uses: ipfs/aegir/actions/bundle-size@master
|
||||
name: Check bundle size
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
test-node:
|
||||
name: Test Nodejs
|
||||
needs: check
|
||||
strategy:
|
||||
matrix:
|
||||
node: [14]
|
||||
os: ["ubuntu-latest", "macos-latest", "windows-latest"]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v2
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- uses: actions/cache@v2
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn --prefer-offline --frozen-lockfile
|
||||
- run: npx aegir test -t node --bail --cov
|
||||
- uses: codecov/codecov-action@v1
|
||||
|
||||
test-chrome:
|
||||
name: Test Chrome
|
||||
needs: check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v2
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- uses: actions/cache@v2
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn --prefer-offline --frozen-lockfile
|
||||
- run: npx aegir test -t browser -t webworker --bail
|
||||
|
||||
test-firefox:
|
||||
name: Test Firefox
|
||||
needs: check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v2
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- uses: actions/cache@v2
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn --prefer-offline --frozen-lockfile
|
||||
- run: npx aegir test -t browser -t webworker --bail -- --browser firefox
|
||||
|
||||
test-webkit:
|
||||
name: Test Webkit
|
||||
needs: check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v2
|
||||
- uses: microsoft/playwright-github-action@v1
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- uses: actions/cache@v2
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn --prefer-offline --frozen-lockfile
|
||||
- run: npx aegir test -t browser -t webworker --bail -- --browser webkit
|
||||
|
||||
test-electron-main:
|
||||
name: Test Electron Main
|
||||
needs: check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v2
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- uses: actions/cache@v2
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn --prefer-offline --frozen-lockfile
|
||||
- run: npx xvfb-maybe aegir test -t electron-main --bail
|
||||
|
||||
test-electron-renderer:
|
||||
name: Test Electron Renderer
|
||||
needs: check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
- uses: actions/checkout@v2
|
||||
- name: Get yarn cache directory path
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "::set-output name=dir::$(yarn cache dir)"
|
||||
- uses: actions/cache@v2
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-yarn-
|
||||
- run: yarn --prefer-offline --frozen-lockfile
|
||||
- run: npx xvfb-maybe aegir test -t electron-renderer --bail
|
27
.github/workflows/npm-publish.yml
vendored
27
.github/workflows/npm-publish.yml
vendored
@ -1,27 +0,0 @@
|
||||
name: npm-publish
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master # Change this to your default branch
|
||||
jobs:
|
||||
npm-publish:
|
||||
name: npm-publish
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@master
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@master
|
||||
with:
|
||||
node-version: 12.4.0
|
||||
- name: Build library
|
||||
run: yarn install --frozen-lockfile && yarn run build
|
||||
- name: Publish if version has been updated
|
||||
uses: pascalgn/npm-publish-action@51fdb4531e99aac1873764ef7271af448dc42ab4
|
||||
with: # All of theses inputs are optional
|
||||
tag_name: "v%s"
|
||||
tag_message: "v%s"
|
||||
commit_pattern: "^Release (\\S+)"
|
||||
env: # More info about the environment variables in the README
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Leave this as is, it's automatically generated
|
||||
NPM_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} # You need to set this in your repo settings
|
80
.github/workflows/publish.yml
vendored
Normal file
80
.github/workflows/publish.yml
vendored
Normal file
@ -0,0 +1,80 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
name: Check and Tag
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Create tag
|
||||
id: tag
|
||||
uses: butlerlogic/action-autotag@1.1.1
|
||||
with:
|
||||
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
|
||||
strategy: package # Optional, since "package" is the default strategy
|
||||
tag_prefix: "v"
|
||||
outputs:
|
||||
tag: ${{ steps.tag.outputs.tagname }}
|
||||
version: ${{ steps.tag.outputs.version }}
|
||||
|
||||
publish:
|
||||
name: Publish
|
||||
runs-on: ubuntu-latest
|
||||
needs: tag
|
||||
if: ${{ needs.tag.outputs.tag != ''}}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
- name: Setup Nodejs
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: '14'
|
||||
always-auth: true
|
||||
registry-url: 'https://registry.npmjs.org'
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
|
||||
- name: Install dependencies
|
||||
run: yarn install --frozen-lockfile --non-interactive
|
||||
|
||||
- name: Build packages
|
||||
run: yarn run build
|
||||
|
||||
- name: Publish packages
|
||||
# manual switch to latest
|
||||
run: yarn publish --ignore-scripts --no-git-tag-version --no-commit-hooks --non-interactive --tag beta
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }}
|
||||
|
||||
- name: Get Changelog Entry
|
||||
id: changelog_reader
|
||||
uses: mindsers/changelog-reader-action@v2
|
||||
with:
|
||||
version: ${{ needs.tag.outputs.version }}
|
||||
path: ./CHANGELOG.md
|
||||
|
||||
- name: Create Release
|
||||
id: create_release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
tag_name: ${{ needs.tag.outputs.tag }}
|
||||
body: ${{ steps.changelog_reader.outputs.changes }}
|
||||
prerelease: true
|
||||
release_name: Release ${{ needs.tag.outputs.tag }}
|
||||
#in case of failure
|
||||
- name: Rollback on failure
|
||||
if: failure()
|
||||
uses: author/action-rollback@9ec72a6af74774e00343c6de3e946b0901c23013
|
||||
with:
|
||||
id: ${{ steps.create_release.outputs.id }}
|
||||
tag: ${{ needs.tag.outputs.tag }}
|
||||
delete_orphan_tag: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,9 +1,11 @@
|
||||
bundle
|
||||
node_modules/
|
||||
.idea
|
||||
.env
|
||||
.nyc_output
|
||||
lib
|
||||
dist
|
||||
docs
|
||||
|
||||
# Logs
|
||||
logs
|
||||
|
12
.travis.yml
12
.travis.yml
@ -1,12 +0,0 @@
|
||||
language: node_js
|
||||
|
||||
cache: false
|
||||
|
||||
install:
|
||||
- yarn install --frozen-lockfile --network-timeout 1000000
|
||||
|
||||
script:
|
||||
set -e;
|
||||
yarn run lint;
|
||||
yarn run build;
|
||||
yarn run test
|
50
CHANGELOG.md
50
CHANGELOG.md
@ -5,6 +5,53 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [2.0.5]
|
||||
#### Bugfixes
|
||||
- update aegir to latest
|
||||
- update dependencies
|
||||
|
||||
## [2.0.4]
|
||||
|
||||
#### Bugfixes
|
||||
- downgrade aegir because js files are missing in dist directory
|
||||
## [2.0.3]
|
||||
|
||||
#### Bugfixes
|
||||
- update peer-id dependency
|
||||
|
||||
## [2.0.2]
|
||||
|
||||
#### Bugfixes
|
||||
- update dependencies
|
||||
|
||||
## [2.0.1]
|
||||
|
||||
#### Bugfixes
|
||||
- security update for bl dependency
|
||||
- add missing type declaration files in dist
|
||||
|
||||
## [2.0.0]
|
||||
|
||||
#### Features
|
||||
- switched to aegir for building and linting
|
||||
- using peer id with Uint8Arrays (breaking!)
|
||||
|
||||
## [1.1.2]
|
||||
|
||||
#### Bugfixes
|
||||
- fix issue where web build depends on global regeneratorRuntime
|
||||
|
||||
## [1.1.1] - 2020-05-08
|
||||
|
||||
#### Bugfixes
|
||||
- fix issue [#58](https://github.com/NodeFactoryIo/js-libp2p-noise/issues/58)
|
||||
|
||||
## [1.1.0] - 2020-04-23
|
||||
|
||||
Stable version, interoperable with go.
|
||||
|
||||
Using reduced size with bcrypto.
|
||||
|
||||
## [1.1.0-rc.1] - 2020-04-22
|
||||
|
||||
- Added early data API
|
||||
@ -19,6 +66,9 @@ Stable version, interobable with go-libp2p-noise!
|
||||
- fix types to be compatible with rest of libp2p typescript projects
|
||||
- update it-pb-rpc to 0.1.8 (contains proper typescript types)
|
||||
|
||||
### Bugfixes
|
||||
- changed bcrypto imports to use pure js versions (web bundle size reduction)
|
||||
|
||||
## [1.0.0-rc.9] - 2019-03-11
|
||||
|
||||
### Bugfixes
|
||||
|
@ -1,7 +1,7 @@
|
||||
# js-libp2p-noise
|
||||
|
||||

|
||||
[](https://travis-ci.com/NodeFactoryIo/js-libp2p-noise)
|
||||
[](https://github.com/NodeFactoryIo/js-libp2p-noise/actions/workflows/ci.yml)
|
||||
|
||||
[](https://libp2p.io/)
|
||||

|
||||
@ -15,14 +15,16 @@
|
||||
|
||||
This repository contains TypeScript implementation of noise protocol, an encryption protocol used in libp2p.
|
||||
|
||||
##### Warning: Even though this package works in browser, it will bundle around 1.5Mb of code
|
||||
##### Warning: Even though this package works in browser, it will bundle around 600Kb (200Kb gzipped) of code
|
||||
https://bundlephobia.com/result?p=libp2p-noise@latest
|
||||
|
||||
## Usage
|
||||
|
||||
Install with `yarn add libp2p-noise` or `npm i libp2p-noise`.
|
||||
|
||||
Example of using default noise configuration and passing it to the libp2p config:
|
||||
```
|
||||
|
||||
```js
|
||||
import {NOISE, Noise} from "libp2p-noise"
|
||||
|
||||
|
||||
|
@ -1,4 +0,0 @@
|
||||
require('@babel/register')({
|
||||
extensions: ['.ts'],
|
||||
ignore: ['node_modules'],
|
||||
})
|
@ -1,21 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"node": "10"
|
||||
}
|
||||
}
|
||||
],
|
||||
["@babel/preset-typescript", {
|
||||
"allowNamespaces": true
|
||||
}]
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"@babel/plugin-proposal-export-default-from",
|
||||
"@babel/plugin-proposal-async-generator-functions",
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
]
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"targets": {
|
||||
"browsers": "last 2 versions, not ie <=11"
|
||||
},
|
||||
"modules": false
|
||||
}
|
||||
],
|
||||
["@babel/preset-typescript", {
|
||||
"allowNamespaces": true
|
||||
}]
|
||||
],
|
||||
"plugins": [
|
||||
"@babel/plugin-proposal-object-rest-spread",
|
||||
"@babel/plugin-proposal-export-default-from",
|
||||
"@babel/plugin-proposal-async-generator-functions",
|
||||
"@babel/plugin-proposal-class-properties"
|
||||
]
|
||||
}
|
40
benchmarks/benchmark.js
Normal file
40
benchmarks/benchmark.js
Normal file
@ -0,0 +1,40 @@
|
||||
/* eslint-disable */
|
||||
|
||||
const { Noise } = require('../dist/src/index')
|
||||
const benchmark = require('benchmark')
|
||||
const DuplexPair = require('it-pair/duplex')
|
||||
const PeerId = require('peer-id')
|
||||
|
||||
const bench = async function () {
|
||||
console.log('Initializing handshake benchmark')
|
||||
const initiator = new Noise()
|
||||
const initiatorPeer = await PeerId.createFromJSON({
|
||||
id: '12D3KooWH45PiqBjfnEfDfCD6TqJrpqTBJvQDwGHvjGpaWwms46D',
|
||||
privKey: 'CAESYBtKXrMwawAARmLScynQUuSwi/gGSkwqDPxi15N3dqDHa4T4iWupkMe5oYGwGH3Hyfvd/QcgSTqg71oYZJadJ6prhPiJa6mQx7mhgbAYfcfJ+939ByBJOqDvWhhklp0nqg==',
|
||||
pubKey: 'CAESIGuE+IlrqZDHuaGBsBh9x8n73f0HIEk6oO9aGGSWnSeq'
|
||||
})
|
||||
const responder = new Noise()
|
||||
const responderPeer = await PeerId.createFromJSON({
|
||||
id: '12D3KooWP63uzL78BRMpkQ7augMdNi1h3VBrVWZucKjyhzGVaSi1',
|
||||
privKey: 'CAESYPxO3SHyfc2578hDmfkGGBY255JjiLuVavJWy+9ivlpsxSyVKf36ipyRGL6szGzHuFs5ceEuuGVrPMg/rW2Ch1bFLJUp/fqKnJEYvqzMbMe4Wzlx4S64ZWs8yD+tbYKHVg==',
|
||||
pubKey: 'CAESIMUslSn9+oqckRi+rMxsx7hbOXHhLrhlazzIP61tgodW'
|
||||
})
|
||||
console.log('Init complete, running benchmark')
|
||||
const bench = new benchmark('handshake', {
|
||||
defer: true,
|
||||
fn: async function (deffered) {
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
await Promise.all([
|
||||
initiator.secureOutbound(initiatorPeer, outboundConnection, responderPeer),
|
||||
responder.secureInbound(responderPeer, inboundConnection, initiatorPeer)
|
||||
])
|
||||
deffered.resolve()
|
||||
}
|
||||
})
|
||||
.on('complete', function (stats) {
|
||||
console.log(String(stats.currentTarget))
|
||||
})
|
||||
bench.run({ async: true })
|
||||
}
|
||||
|
||||
bench()
|
@ -1,26 +0,0 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
const webpackConfig = require("./webpack.config");
|
||||
|
||||
module.exports = function(config) {
|
||||
config.set({
|
||||
|
||||
basePath: "",
|
||||
frameworks: ["mocha", "chai"],
|
||||
files: ["test/**/*.test.ts"],
|
||||
exclude: [],
|
||||
preprocessors: {
|
||||
"test/**/*.ts": ["webpack"]
|
||||
},
|
||||
webpack: {
|
||||
mode: "production",
|
||||
node: webpackConfig.node,
|
||||
module: webpackConfig.module,
|
||||
resolve: webpackConfig.resolve
|
||||
},
|
||||
reporters: ["spec"],
|
||||
|
||||
browsers: ["ChromeHeadless"],
|
||||
|
||||
singleRun: true
|
||||
});
|
||||
};
|
10015
package-lock.json
generated
Normal file
10015
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
107
package.json
107
package.json
@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "libp2p-noise",
|
||||
"version": "1.1.0-rc1",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"module": "lib/index.js",
|
||||
"version": "2.0.5",
|
||||
"main": "dist/src/index.js",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"files": [
|
||||
"dist",
|
||||
"lib",
|
||||
"src"
|
||||
],
|
||||
"repository": "git@github.com:NodeFactoryIo/js-libp2p-noise.git",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/NodeFactoryIo/js-libp2p-noise.git"
|
||||
},
|
||||
"author": "NodeFactory <info@nodefactory.io>",
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
@ -18,71 +19,59 @@
|
||||
"crypto"
|
||||
],
|
||||
"scripts": {
|
||||
"prebuild": "rm -rf lib && rm -rf dist",
|
||||
"build": "yarn run build:node && yarn run build:web && yarn run build:types",
|
||||
"bundle": "webpack --config webpack.bundle.config.js",
|
||||
"build:node": "babel --no-babelrc --config-file ./babel.config.json src --copy-files -x .ts -d dist --source-maps",
|
||||
"build:web": "babel --no-babelrc --config-file ./babel.web.config.json src --copy-files -x .ts -d lib --source-maps",
|
||||
"build:types": "tsc --declaration --outDir dist --emitDeclarationOnly",
|
||||
"proto:gen": "pbjs -t static-module -o ./src/proto/payload.js ./src/proto/payload.proto && pbts -o ./src/proto/payload.d.ts ./src/proto/payload.js && yarn run lint --fix",
|
||||
"check-types": "tsc --incremental --noEmit",
|
||||
"lint": "eslint --ext .ts src/",
|
||||
"pretest": "yarn check-types",
|
||||
"test": "yarn run test:node && yarn run test:web",
|
||||
"test:node": "mocha -r ./babel-register.js \"test/**/*.test.ts\"",
|
||||
"test:web": "karma start"
|
||||
"bench": "node benchmarks/benchmark.js",
|
||||
"clean": "rm -rf dist",
|
||||
"check": "aegir dep-check && aegir ts -p check",
|
||||
"build": "aegir build",
|
||||
"lint": "aegir lint",
|
||||
"lint:fix": "aegir lint --fix",
|
||||
"pretest": "yarn run check",
|
||||
"test": "aegir test",
|
||||
"test:node": "aegir test -t node",
|
||||
"test:browser": "aegir test -t browser",
|
||||
"docs": "aegir docs",
|
||||
"proto:gen": "pbjs -t static-module -o ./src/proto/payload.js ./src/proto/payload.proto && pbts -o ./src/proto/payload.d.ts ./src/proto/payload.js && yarn run lint --fix"
|
||||
},
|
||||
"browser": {
|
||||
"util": false
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.6.4",
|
||||
"@babel/core": "^7.6.4",
|
||||
"@babel/plugin-proposal-async-generator-functions": "^7.7.0",
|
||||
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||
"@babel/plugin-proposal-export-default-from": "^7.8.3",
|
||||
"@babel/plugin-proposal-object-rest-spread": "^7.6.2",
|
||||
"@babel/preset-env": "^7.6.3",
|
||||
"@babel/preset-typescript": "^7.6.0",
|
||||
"@babel/register": "^7.6.2",
|
||||
"@babel/runtime": "^7.6.3",
|
||||
"@types/bl": "^2.1.0",
|
||||
"@types/chai": "^4.2.4",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"@typescript-eslint/eslint-plugin": "^2.6.0",
|
||||
"@typescript-eslint/parser": "^2.6.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"aegir": "^31.0.0",
|
||||
"benchmark": "^2.1.4",
|
||||
"buffer": "^5.7.1",
|
||||
"chai": "^4.2.0",
|
||||
"eslint": "^6.6.0",
|
||||
"karma": "^4.4.1",
|
||||
"karma-chai": "^0.1.0",
|
||||
"karma-chrome-launcher": "^3.1.0",
|
||||
"karma-cli": "^2.0.0",
|
||||
"karma-mocha": "^1.3.0",
|
||||
"karma-spec-reporter": "^0.0.32",
|
||||
"karma-webpack": "^4.0.2",
|
||||
"mocha": "^6.2.2",
|
||||
"sinon": "^8.1.0",
|
||||
"ts-loader": "^6.2.1",
|
||||
"typescript": "^3.6.4",
|
||||
"webpack": "^4.41.5",
|
||||
"webpack-bundle-analyzer": "^3.6.1",
|
||||
"webpack-cli": "^3.3.11"
|
||||
"events": "^3.2.0",
|
||||
"microtime": "^3.0.0",
|
||||
"mocha": "^8.2.1",
|
||||
"sinon": "^9.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"aead-js": "^0.1.0",
|
||||
"buffer": "^5.4.3",
|
||||
"debug": "^4.1.1",
|
||||
"futoin-hkdf": "^1.3.2",
|
||||
"hash.js": "^1.1.7",
|
||||
"bcrypto": "^5.4.0",
|
||||
"debug": "^4.3.1",
|
||||
"it-buffer": "^0.1.1",
|
||||
"it-length-prefixed": "^3.0.0",
|
||||
"it-pair": "^1.0.0",
|
||||
"it-pb-rpc": "^0.1.8",
|
||||
"it-pb-rpc": "^0.1.9",
|
||||
"it-pipe": "^1.1.0",
|
||||
"libp2p-crypto": "^0.17.6",
|
||||
"peer-id": "^0.13.5",
|
||||
"protobufjs": "6.8.8",
|
||||
"tweetnacl": "^1.0.1"
|
||||
"libp2p-crypto": "fluencelabs/js-libp2p-crypto",
|
||||
"peer-id": "^0.14.3",
|
||||
"protobufjs": "^6.10.1",
|
||||
"uint8arrays": "^2.1.4"
|
||||
},
|
||||
"resolutions": {
|
||||
"bn.js": "4.4.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "ipfs",
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": "error",
|
||||
"@typescript-eslint/explicit-function-return-type": "warn",
|
||||
"@typescript-eslint/strict-boolean-expressions": "off"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"src/proto/payload.js",
|
||||
"test/fixtures/node-globals.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
8
src/@types/basic.d.ts
vendored
Normal file
8
src/@types/basic.d.ts
vendored
Normal file
@ -0,0 +1,8 @@
|
||||
import { Buffer } from 'buffer'
|
||||
|
||||
export type bytes = Buffer
|
||||
export type bytes32 = Buffer
|
||||
export type bytes16 = Buffer
|
||||
|
||||
export type uint32 = number
|
||||
export type uint64 = number
|
@ -1,8 +0,0 @@
|
||||
import {Buffer} from 'buffer';
|
||||
|
||||
export type bytes = Buffer;
|
||||
export type bytes32 = Buffer;
|
||||
export type bytes16 = Buffer;
|
||||
|
||||
export type uint32 = number;
|
||||
export type uint64 = number;
|
11
src/@types/handshake-interface.d.ts
vendored
Normal file
11
src/@types/handshake-interface.d.ts
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
import { bytes } from './basic'
|
||||
import { NoiseSession } from './handshake'
|
||||
import PeerId from 'peer-id'
|
||||
|
||||
export interface IHandshake {
|
||||
session: NoiseSession
|
||||
remotePeer: PeerId
|
||||
remoteEarlyData: Buffer
|
||||
encrypt: (plaintext: bytes, session: NoiseSession) => bytes
|
||||
decrypt: (ciphertext: bytes, session: NoiseSession) => {plaintext: bytes, valid: boolean}
|
||||
}
|
@ -1,11 +0,0 @@
|
||||
import {bytes} from "./basic";
|
||||
import {NoiseSession} from "./handshake";
|
||||
import PeerId from "peer-id";
|
||||
|
||||
export interface IHandshake {
|
||||
session: NoiseSession;
|
||||
remotePeer: PeerId;
|
||||
remoteEarlyData: Buffer;
|
||||
encrypt(plaintext: bytes, session: NoiseSession): bytes;
|
||||
decrypt(ciphertext: bytes, session: NoiseSession): {plaintext: bytes; valid: boolean};
|
||||
}
|
45
src/@types/handshake.d.ts
vendored
Normal file
45
src/@types/handshake.d.ts
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
import { bytes, bytes32, uint32, uint64 } from './basic'
|
||||
import { KeyPair } from './libp2p'
|
||||
|
||||
export type Hkdf = [bytes, bytes, bytes]
|
||||
|
||||
export interface MessageBuffer {
|
||||
ne: bytes32
|
||||
ns: bytes
|
||||
ciphertext: bytes
|
||||
}
|
||||
|
||||
export interface CipherState {
|
||||
k: bytes32
|
||||
n: uint32
|
||||
}
|
||||
|
||||
export interface SymmetricState {
|
||||
cs: CipherState
|
||||
ck: bytes32 // chaining key
|
||||
h: bytes32 // handshake hash
|
||||
}
|
||||
|
||||
export interface HandshakeState {
|
||||
ss: SymmetricState
|
||||
s: KeyPair
|
||||
e?: KeyPair
|
||||
rs: bytes32
|
||||
re: bytes32
|
||||
psk: bytes32
|
||||
}
|
||||
|
||||
export interface NoiseSession {
|
||||
hs: HandshakeState
|
||||
h?: bytes32
|
||||
cs1?: CipherState
|
||||
cs2?: CipherState
|
||||
mc: uint64
|
||||
i: boolean
|
||||
}
|
||||
|
||||
export interface INoisePayload {
|
||||
identityKey: bytes
|
||||
identitySig: bytes
|
||||
data: bytes
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
import {bytes, bytes32, uint32, uint64} from "./basic";
|
||||
import {KeyPair} from "./libp2p";
|
||||
|
||||
export type Hkdf = [bytes, bytes, bytes];
|
||||
|
||||
export type MessageBuffer = {
|
||||
ne: bytes32;
|
||||
ns: bytes;
|
||||
ciphertext: bytes;
|
||||
}
|
||||
|
||||
export type CipherState = {
|
||||
k: bytes32;
|
||||
n: uint32;
|
||||
}
|
||||
|
||||
export type SymmetricState = {
|
||||
cs: CipherState;
|
||||
ck: bytes32; // chaining key
|
||||
h: bytes32; // handshake hash
|
||||
}
|
||||
|
||||
export type HandshakeState = {
|
||||
ss: SymmetricState;
|
||||
s: KeyPair;
|
||||
e?: KeyPair;
|
||||
rs: bytes32;
|
||||
re: bytes32;
|
||||
psk: bytes32;
|
||||
}
|
||||
|
||||
export type NoiseSession = {
|
||||
hs: HandshakeState;
|
||||
h?: bytes32;
|
||||
cs1?: CipherState;
|
||||
cs2?: CipherState;
|
||||
mc: uint64;
|
||||
i: boolean;
|
||||
}
|
||||
|
||||
export interface INoisePayload {
|
||||
identityKey: bytes;
|
||||
identitySig: bytes;
|
||||
data: bytes;
|
||||
}
|
41
src/@types/it-length-prefixed/index.d.ts
vendored
41
src/@types/it-length-prefixed/index.d.ts
vendored
@ -1,39 +1,38 @@
|
||||
declare module "it-length-prefixed" {
|
||||
/* eslint-disable @typescript-eslint/interface-name-prefix */
|
||||
import BufferList from "bl";
|
||||
import {Buffer} from "buffer"
|
||||
declare module 'it-length-prefixed' {
|
||||
import BufferList from 'bl'
|
||||
import { Buffer } from 'buffer'
|
||||
|
||||
interface LengthDecoderFunction {
|
||||
(data: Buffer | BufferList): number;
|
||||
bytes: number;
|
||||
(data: Buffer | BufferList): number
|
||||
bytes: number
|
||||
}
|
||||
|
||||
interface LengthEncoderFunction {
|
||||
(value: Buffer, target: number, offset: number): number|Buffer;
|
||||
bytes: number;
|
||||
(value: number, target: Buffer, offset: number): number|Buffer
|
||||
bytes: number
|
||||
}
|
||||
|
||||
interface Encoder {
|
||||
(options?: Partial<{lengthEncoder: LengthEncoderFunction}>): AsyncGenerator<BufferList, Buffer>;
|
||||
single: (chunk: Buffer, options?: Partial<{lengthEncoder: LengthEncoderFunction}>) => BufferList;
|
||||
MIN_POOL_SIZE: number;
|
||||
DEFAULT_POOL_SIZE: number;
|
||||
(options?: Partial<{lengthEncoder: LengthEncoderFunction}>): AsyncGenerator<BufferList, Buffer>
|
||||
single: (chunk: Buffer, options?: Partial<{lengthEncoder: LengthEncoderFunction}>) => BufferList
|
||||
MIN_POOL_SIZE: number
|
||||
DEFAULT_POOL_SIZE: number
|
||||
}
|
||||
|
||||
interface DecoderOptions {
|
||||
lengthDecoder: LengthDecoderFunction;
|
||||
maxLengthLength: number;
|
||||
maxDataLength: number;
|
||||
lengthDecoder: LengthDecoderFunction
|
||||
maxLengthLength: number
|
||||
maxDataLength: number
|
||||
}
|
||||
|
||||
interface Decoder {
|
||||
(options?: Partial<DecoderOptions>): AsyncGenerator<BufferList, BufferList>;
|
||||
fromReader: (reader: any, options?: Partial<DecoderOptions>) => BufferList;
|
||||
MAX_LENGTH_LENGTH: number;
|
||||
MAX_DATA_LENGTH: number;
|
||||
(options?: Partial<DecoderOptions>): AsyncGenerator<BufferList, BufferList>
|
||||
fromReader: (reader: any, options?: Partial<DecoderOptions>) => BufferList
|
||||
MAX_LENGTH_LENGTH: number
|
||||
MAX_DATA_LENGTH: number
|
||||
}
|
||||
|
||||
export const encode: Encoder;
|
||||
export const decode: Decoder;
|
||||
export const encode: Encoder
|
||||
export const decode: Decoder
|
||||
|
||||
}
|
||||
|
8
src/@types/it-pair/index.d.ts
vendored
8
src/@types/it-pair/index.d.ts
vendored
@ -1,8 +1,8 @@
|
||||
declare module 'it-pair' {
|
||||
export type Duplex = [Stream, Stream];
|
||||
export type Duplex = [Stream, Stream]
|
||||
|
||||
type Stream = {
|
||||
sink(source: Iterable<any>): void;
|
||||
source: Record<string, any>;
|
||||
interface Stream {
|
||||
sink: (source: Iterable<any>) => void
|
||||
source: Record<string, any>
|
||||
}
|
||||
}
|
||||
|
19
src/@types/libp2p.d.ts
vendored
Normal file
19
src/@types/libp2p.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
import { bytes, bytes32 } from './basic'
|
||||
import PeerId from 'peer-id'
|
||||
|
||||
export interface KeyPair {
|
||||
publicKey: bytes32
|
||||
privateKey: bytes32
|
||||
}
|
||||
|
||||
export interface INoiseConnection {
|
||||
remoteEarlyData?: () => bytes
|
||||
secureOutbound: (localPeer: PeerId, insecure: any, remotePeer: PeerId) => Promise<SecureOutbound>
|
||||
secureInbound: (localPeer: PeerId, insecure: any, remotePeer: PeerId) => Promise<SecureOutbound>
|
||||
}
|
||||
|
||||
export interface SecureOutbound {
|
||||
conn: any
|
||||
remoteEarlyData: Buffer
|
||||
remotePeer: PeerId
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
import { bytes, bytes32 } from "./basic";
|
||||
import PeerId from "peer-id";
|
||||
|
||||
export type KeyPair = {
|
||||
publicKey: bytes32;
|
||||
privateKey: bytes32;
|
||||
}
|
||||
|
||||
export interface INoiseConnection {
|
||||
remoteEarlyData?(): bytes;
|
||||
secureOutbound(localPeer: PeerId, insecure: any, remotePeer: PeerId): Promise<SecureOutbound>;
|
||||
secureInbound(localPeer: PeerId, insecure: any, remotePeer: PeerId): Promise<SecureOutbound>;
|
||||
}
|
||||
|
||||
export type SecureOutbound = {
|
||||
conn: any;
|
||||
remoteEarlyData: Buffer;
|
||||
remotePeer: PeerId;
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
export const NOISE_MSG_MAX_LENGTH_BYTES = 65535;
|
||||
export const NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG = NOISE_MSG_MAX_LENGTH_BYTES - 16;
|
||||
|
||||
export const DUMP_SESSION_KEYS = process.env.DUMP_SESSION_KEYS;
|
||||
|
||||
export const NOISE_MSG_MAX_LENGTH_BYTES = 65535
|
||||
export const NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG = NOISE_MSG_MAX_LENGTH_BYTES - 16
|
||||
|
||||
export const DUMP_SESSION_KEYS = process.env.DUMP_SESSION_KEYS
|
||||
|
@ -1,49 +1,48 @@
|
||||
import { Buffer } from "buffer";
|
||||
import {IHandshake} from "./@types/handshake-interface";
|
||||
import {NOISE_MSG_MAX_LENGTH_BYTES, NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG} from "./constants";
|
||||
import { Buffer } from 'buffer'
|
||||
import { IHandshake } from './@types/handshake-interface'
|
||||
import { NOISE_MSG_MAX_LENGTH_BYTES, NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG } from './constants'
|
||||
|
||||
interface IReturnEncryptionWrapper {
|
||||
(source: Iterable<Uint8Array>): AsyncIterableIterator<Uint8Array>;
|
||||
(source: Iterable<Uint8Array>): AsyncIterableIterator<Uint8Array>
|
||||
}
|
||||
|
||||
// Returns generator that encrypts payload from the user
|
||||
export function encryptStream(handshake: IHandshake): IReturnEncryptionWrapper {
|
||||
export function encryptStream (handshake: IHandshake): IReturnEncryptionWrapper {
|
||||
return async function * (source) {
|
||||
for await (const chunk of source) {
|
||||
const chunkBuffer = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.length);
|
||||
const chunkBuffer = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.length)
|
||||
|
||||
for (let i = 0; i < chunkBuffer.length; i += NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG) {
|
||||
let end = i + NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG;
|
||||
let end = i + NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG
|
||||
if (end > chunkBuffer.length) {
|
||||
end = chunkBuffer.length;
|
||||
end = chunkBuffer.length
|
||||
}
|
||||
|
||||
const data = handshake.encrypt(chunkBuffer.slice(i, end), handshake.session);
|
||||
yield data;
|
||||
const data = handshake.encrypt(chunkBuffer.slice(i, end), handshake.session)
|
||||
yield data
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Decrypt received payload to the user
|
||||
export function decryptStream(handshake: IHandshake): IReturnEncryptionWrapper {
|
||||
export function decryptStream (handshake: IHandshake): IReturnEncryptionWrapper {
|
||||
return async function * (source) {
|
||||
for await (const chunk of source) {
|
||||
const chunkBuffer = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.length);
|
||||
const chunkBuffer = Buffer.from(chunk.buffer, chunk.byteOffset, chunk.length)
|
||||
|
||||
for (let i = 0; i < chunkBuffer.length; i += NOISE_MSG_MAX_LENGTH_BYTES) {
|
||||
let end = i + NOISE_MSG_MAX_LENGTH_BYTES;
|
||||
let end = i + NOISE_MSG_MAX_LENGTH_BYTES
|
||||
if (end > chunkBuffer.length) {
|
||||
end = chunkBuffer.length;
|
||||
end = chunkBuffer.length
|
||||
}
|
||||
|
||||
const chunk = chunkBuffer.slice(i, end);
|
||||
const {plaintext: decrypted, valid} = await handshake.decrypt(chunk, handshake.session);
|
||||
if(!valid) {
|
||||
throw new Error("Failed to validate decrypted chunk");
|
||||
const chunk = chunkBuffer.slice(i, end)
|
||||
const { plaintext: decrypted, valid } = await handshake.decrypt(chunk, handshake.session)
|
||||
if (!valid) {
|
||||
throw new Error('Failed to validate decrypted chunk')
|
||||
}
|
||||
yield decrypted;
|
||||
yield decrypted
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,66 +1,67 @@
|
||||
import {Buffer} from "buffer";
|
||||
import {bytes} from "./@types/basic";
|
||||
import {MessageBuffer} from "./@types/handshake";
|
||||
import { Buffer } from 'buffer'
|
||||
import { bytes } from './@types/basic'
|
||||
import { MessageBuffer } from './@types/handshake'
|
||||
import BufferList from 'bl'
|
||||
|
||||
export const uint16BEEncode = (value, target, offset) => {
|
||||
target = target || Buffer.allocUnsafe(2);
|
||||
target.writeUInt16BE(value, offset);
|
||||
return target;
|
||||
};
|
||||
uint16BEEncode.bytes = 2;
|
||||
export const uint16BEEncode = (value: number, target: Buffer, offset: number): Buffer => {
|
||||
target = target || Buffer.allocUnsafe(2)
|
||||
target.writeUInt16BE(value, offset)
|
||||
return target
|
||||
}
|
||||
uint16BEEncode.bytes = 2
|
||||
|
||||
export const uint16BEDecode = data => {
|
||||
if (data.length < 2) throw RangeError('Could not decode int16BE');
|
||||
return data.readUInt16BE(0);
|
||||
};
|
||||
uint16BEDecode.bytes = 2;
|
||||
export const uint16BEDecode = (data: Buffer | BufferList): number => {
|
||||
if (data.length < 2) throw RangeError('Could not decode int16BE')
|
||||
return data.readUInt16BE(0)
|
||||
}
|
||||
uint16BEDecode.bytes = 2
|
||||
|
||||
// Note: IK and XX encoder usage is opposite (XX uses in stages encode0 where IK uses encode1)
|
||||
|
||||
export function encode0(message: MessageBuffer): bytes {
|
||||
return Buffer.concat([message.ne, message.ciphertext]);
|
||||
export function encode0 (message: MessageBuffer): bytes {
|
||||
return Buffer.concat([message.ne, message.ciphertext])
|
||||
}
|
||||
|
||||
export function encode1(message: MessageBuffer): bytes {
|
||||
return Buffer.concat([message.ne, message.ns, message.ciphertext]);
|
||||
export function encode1 (message: MessageBuffer): bytes {
|
||||
return Buffer.concat([message.ne, message.ns, message.ciphertext])
|
||||
}
|
||||
|
||||
export function encode2(message: MessageBuffer): bytes {
|
||||
return Buffer.concat([message.ns, message.ciphertext]);
|
||||
export function encode2 (message: MessageBuffer): bytes {
|
||||
return Buffer.concat([message.ns, message.ciphertext])
|
||||
}
|
||||
|
||||
export function decode0(input: bytes): MessageBuffer {
|
||||
export function decode0 (input: bytes): MessageBuffer {
|
||||
if (input.length < 32) {
|
||||
throw new Error("Cannot decode stage 0 MessageBuffer: length less than 32 bytes.");
|
||||
throw new Error('Cannot decode stage 0 MessageBuffer: length less than 32 bytes.')
|
||||
}
|
||||
|
||||
return {
|
||||
ne: input.slice(0, 32),
|
||||
ciphertext: input.slice(32, input.length),
|
||||
ns: Buffer.alloc(0),
|
||||
ns: Buffer.alloc(0)
|
||||
}
|
||||
}
|
||||
|
||||
export function decode1(input: bytes): MessageBuffer {
|
||||
export function decode1 (input: bytes): MessageBuffer {
|
||||
if (input.length < 80) {
|
||||
throw new Error("Cannot decode stage 1 MessageBuffer: length less than 80 bytes.");
|
||||
throw new Error('Cannot decode stage 1 MessageBuffer: length less than 80 bytes.')
|
||||
}
|
||||
|
||||
return {
|
||||
ne: input.slice(0, 32),
|
||||
ns: input.slice(32, 80),
|
||||
ciphertext: input.slice(80, input.length),
|
||||
ciphertext: input.slice(80, input.length)
|
||||
}
|
||||
}
|
||||
|
||||
export function decode2(input: bytes): MessageBuffer {
|
||||
export function decode2 (input: bytes): MessageBuffer {
|
||||
if (input.length < 48) {
|
||||
throw new Error("Cannot decode stage 2 MessageBuffer: length less than 48 bytes.");
|
||||
throw new Error('Cannot decode stage 2 MessageBuffer: length less than 48 bytes.')
|
||||
}
|
||||
|
||||
return {
|
||||
ne: Buffer.alloc(0),
|
||||
ns: input.slice(0, 48),
|
||||
ciphertext: input.slice(48, input.length),
|
||||
ciphertext: input.slice(48, input.length)
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
import BufferList from 'bl'
|
||||
|
||||
export class FailedIKError extends Error {
|
||||
public initialMsg;
|
||||
public initialMsg: string|BufferList|Buffer
|
||||
|
||||
constructor(initialMsg, message?: string) {
|
||||
super(message);
|
||||
constructor (initialMsg: string|BufferList|Buffer, message?: string) {
|
||||
super(message)
|
||||
|
||||
this.initialMsg = initialMsg;
|
||||
this.name = "FailedIKhandshake";
|
||||
this.initialMsg = initialMsg
|
||||
this.name = 'FailedIKhandshake'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
import {WrappedConnection} from "./noise";
|
||||
import {IK} from "./handshakes/ik";
|
||||
import {NoiseSession} from "./@types/handshake";
|
||||
import {bytes, bytes32} from "./@types/basic";
|
||||
import {KeyPair} from "./@types/libp2p";
|
||||
import {IHandshake} from "./@types/handshake-interface";
|
||||
import {Buffer} from "buffer";
|
||||
import {decode0, decode1, encode0, encode1} from "./encoder";
|
||||
import {decodePayload, getPeerIdFromPayload, verifySignedPayload} from "./utils";
|
||||
import {FailedIKError} from "./errors";
|
||||
import { WrappedConnection } from './noise'
|
||||
import { IK } from './handshakes/ik'
|
||||
import { NoiseSession } from './@types/handshake'
|
||||
import { bytes, bytes32 } from './@types/basic'
|
||||
import { KeyPair } from './@types/libp2p'
|
||||
import { IHandshake } from './@types/handshake-interface'
|
||||
import { Buffer } from 'buffer'
|
||||
import { decode0, decode1, encode0, encode1 } from './encoder'
|
||||
import { decodePayload, getPeerIdFromPayload, verifySignedPayload } from './utils'
|
||||
import { FailedIKError } from './errors'
|
||||
import {
|
||||
logger,
|
||||
logLocalStaticKeys,
|
||||
@ -15,22 +15,22 @@ import {
|
||||
logLocalEphemeralKeys,
|
||||
logRemoteEphemeralKey,
|
||||
logCipherState
|
||||
} from "./logger";
|
||||
import PeerId from "peer-id";
|
||||
} from './logger'
|
||||
import PeerId from 'peer-id'
|
||||
|
||||
export class IKHandshake implements IHandshake {
|
||||
public isInitiator: boolean;
|
||||
public session: NoiseSession;
|
||||
public remotePeer!: PeerId;
|
||||
public remoteEarlyData: Buffer;
|
||||
public isInitiator: boolean
|
||||
public session: NoiseSession
|
||||
public remotePeer!: PeerId
|
||||
public remoteEarlyData: Buffer
|
||||
|
||||
private payload: bytes;
|
||||
private prologue: bytes32;
|
||||
private staticKeypair: KeyPair;
|
||||
private connection: WrappedConnection;
|
||||
private ik: IK;
|
||||
private readonly payload: bytes
|
||||
private readonly prologue: bytes32
|
||||
private readonly staticKeypair: KeyPair
|
||||
private readonly connection: WrappedConnection
|
||||
private readonly ik: IK
|
||||
|
||||
constructor(
|
||||
constructor (
|
||||
isInitiator: boolean,
|
||||
payload: bytes,
|
||||
prologue: bytes32,
|
||||
@ -38,118 +38,120 @@ export class IKHandshake implements IHandshake {
|
||||
connection: WrappedConnection,
|
||||
remoteStaticKey: bytes,
|
||||
remotePeer?: PeerId,
|
||||
handshake?: IK,
|
||||
handshake?: IK
|
||||
) {
|
||||
this.isInitiator = isInitiator;
|
||||
this.payload = Buffer.from(payload);
|
||||
this.prologue = prologue;
|
||||
this.staticKeypair = staticKeypair;
|
||||
this.connection = connection;
|
||||
if(remotePeer) {
|
||||
this.remotePeer = remotePeer;
|
||||
this.isInitiator = isInitiator
|
||||
this.payload = Buffer.from(payload)
|
||||
this.prologue = prologue
|
||||
this.staticKeypair = staticKeypair
|
||||
this.connection = connection
|
||||
if (remotePeer) {
|
||||
this.remotePeer = remotePeer
|
||||
}
|
||||
this.ik = handshake || new IK();
|
||||
this.session = this.ik.initSession(this.isInitiator, this.prologue, this.staticKeypair, remoteStaticKey);
|
||||
this.ik = handshake ?? new IK()
|
||||
this.session = this.ik.initSession(this.isInitiator, this.prologue, this.staticKeypair, remoteStaticKey)
|
||||
this.remoteEarlyData = Buffer.alloc(0)
|
||||
}
|
||||
|
||||
public async stage0(): Promise<void> {
|
||||
public async stage0 (): Promise<void> {
|
||||
logLocalStaticKeys(this.session.hs.s)
|
||||
logRemoteStaticKey(this.session.hs.rs)
|
||||
if (this.isInitiator) {
|
||||
logger("IK Stage 0 - Initiator sending message...");
|
||||
const messageBuffer = this.ik.sendMessage(this.session, this.payload);
|
||||
this.connection.writeLP(encode1(messageBuffer));
|
||||
logger("IK Stage 0 - Initiator sent message.");
|
||||
logger('IK Stage 0 - Initiator sending message...')
|
||||
const messageBuffer = this.ik.sendMessage(this.session, this.payload)
|
||||
this.connection.writeLP(encode1(messageBuffer))
|
||||
logger('IK Stage 0 - Initiator sent message.')
|
||||
logLocalEphemeralKeys(this.session.hs.e)
|
||||
} else {
|
||||
logger("IK Stage 0 - Responder receiving message...");
|
||||
const receivedMsg = await this.connection.readLP();
|
||||
logger('IK Stage 0 - Responder receiving message...')
|
||||
const receivedMsg = await this.connection.readLP()
|
||||
try {
|
||||
const receivedMessageBuffer = decode1(receivedMsg.slice());
|
||||
const {plaintext, valid} = this.ik.recvMessage(this.session, receivedMessageBuffer);
|
||||
if(!valid) {
|
||||
throw new Error("ik handshake stage 0 decryption validation fail");
|
||||
const receivedMessageBuffer = decode1(receivedMsg.slice())
|
||||
const { plaintext, valid } = this.ik.recvMessage(this.session, receivedMessageBuffer)
|
||||
if (!valid) {
|
||||
throw new Error('ik handshake stage 0 decryption validation fail')
|
||||
}
|
||||
logger("IK Stage 0 - Responder got message, going to verify payload.");
|
||||
const decodedPayload = await decodePayload(plaintext);
|
||||
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload);
|
||||
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer);
|
||||
this.setRemoteEarlyData(decodedPayload.data);
|
||||
logger("IK Stage 0 - Responder successfully verified payload!");
|
||||
logger('IK Stage 0 - Responder got message, going to verify payload.')
|
||||
const decodedPayload = await decodePayload(plaintext)
|
||||
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
|
||||
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer)
|
||||
this.setRemoteEarlyData(decodedPayload.data)
|
||||
logger('IK Stage 0 - Responder successfully verified payload!')
|
||||
logRemoteEphemeralKey(this.session.hs.re)
|
||||
} catch (e) {
|
||||
logger("Responder breaking up with IK handshake in stage 0.");
|
||||
const err = e as Error
|
||||
logger('Responder breaking up with IK handshake in stage 0.')
|
||||
|
||||
throw new FailedIKError(receivedMsg, `Error occurred while verifying initiator's signed payload: ${e.message}`);
|
||||
throw new FailedIKError(receivedMsg, `Error occurred while verifying initiator's signed payload: ${err.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async stage1(): Promise<void> {
|
||||
public async stage1 (): Promise<void> {
|
||||
if (this.isInitiator) {
|
||||
logger("IK Stage 1 - Initiator receiving message...");
|
||||
const receivedMsg = (await this.connection.readLP()).slice();
|
||||
const receivedMessageBuffer = decode0(Buffer.from(receivedMsg));
|
||||
const {plaintext, valid} = this.ik.recvMessage(this.session, receivedMessageBuffer);
|
||||
logger("IK Stage 1 - Initiator got message, going to verify payload.");
|
||||
logger('IK Stage 1 - Initiator receiving message...')
|
||||
const receivedMsg = (await this.connection.readLP()).slice()
|
||||
const receivedMessageBuffer = decode0(Buffer.from(receivedMsg))
|
||||
const { plaintext, valid } = this.ik.recvMessage(this.session, receivedMessageBuffer)
|
||||
logger('IK Stage 1 - Initiator got message, going to verify payload.')
|
||||
try {
|
||||
if(!valid) {
|
||||
throw new Error("ik stage 1 decryption validation fail");
|
||||
if (!valid) {
|
||||
throw new Error('ik stage 1 decryption validation fail')
|
||||
}
|
||||
const decodedPayload = await decodePayload(plaintext);
|
||||
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload);
|
||||
await verifySignedPayload(receivedMessageBuffer.ns.slice(0, 32), decodedPayload, this.remotePeer);
|
||||
this.setRemoteEarlyData(decodedPayload.data);
|
||||
logger("IK Stage 1 - Initiator successfully verified payload!");
|
||||
const decodedPayload = await decodePayload(plaintext)
|
||||
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
|
||||
await verifySignedPayload(receivedMessageBuffer.ns.slice(0, 32), decodedPayload, this.remotePeer)
|
||||
this.setRemoteEarlyData(decodedPayload.data)
|
||||
logger('IK Stage 1 - Initiator successfully verified payload!')
|
||||
logRemoteEphemeralKey(this.session.hs.re)
|
||||
} catch (e) {
|
||||
logger("Initiator breaking up with IK handshake in stage 1.");
|
||||
throw new FailedIKError(receivedMsg, `Error occurred while verifying responder's signed payload: ${e.message}`);
|
||||
const err = e as Error
|
||||
logger('Initiator breaking up with IK handshake in stage 1.')
|
||||
throw new FailedIKError(receivedMsg, `Error occurred while verifying responder's signed payload: ${err.message}`)
|
||||
}
|
||||
} else {
|
||||
logger("IK Stage 1 - Responder sending message...");
|
||||
const messageBuffer = this.ik.sendMessage(this.session, this.payload);
|
||||
this.connection.writeLP(encode0(messageBuffer));
|
||||
logger("IK Stage 1 - Responder sent message...");
|
||||
logger('IK Stage 1 - Responder sending message...')
|
||||
const messageBuffer = this.ik.sendMessage(this.session, this.payload)
|
||||
this.connection.writeLP(encode0(messageBuffer))
|
||||
logger('IK Stage 1 - Responder sent message...')
|
||||
logLocalEphemeralKeys(this.session.hs.e)
|
||||
}
|
||||
logCipherState(this.session)
|
||||
}
|
||||
|
||||
public decrypt(ciphertext: bytes, session: NoiseSession): {plaintext: bytes; valid: boolean} {
|
||||
const cs = this.getCS(session, false);
|
||||
return this.ik.decryptWithAd(cs, Buffer.alloc(0), ciphertext);
|
||||
public decrypt (ciphertext: bytes, session: NoiseSession): {plaintext: bytes, valid: boolean} {
|
||||
const cs = this.getCS(session, false)
|
||||
return this.ik.decryptWithAd(cs, Buffer.alloc(0), ciphertext)
|
||||
}
|
||||
|
||||
public encrypt(plaintext: Buffer, session: NoiseSession): Buffer {
|
||||
const cs = this.getCS(session);
|
||||
return this.ik.encryptWithAd(cs, Buffer.alloc(0), plaintext);
|
||||
public encrypt (plaintext: Buffer, session: NoiseSession): Buffer {
|
||||
const cs = this.getCS(session)
|
||||
return this.ik.encryptWithAd(cs, Buffer.alloc(0), plaintext)
|
||||
}
|
||||
|
||||
public getLocalEphemeralKeys(): KeyPair {
|
||||
public getLocalEphemeralKeys (): KeyPair {
|
||||
if (!this.session.hs.e) {
|
||||
throw new Error("Ephemeral keys do not exist.");
|
||||
throw new Error('Ephemeral keys do not exist.')
|
||||
}
|
||||
|
||||
return this.session.hs.e;
|
||||
return this.session.hs.e
|
||||
}
|
||||
|
||||
private getCS(session: NoiseSession, encryption = true) {
|
||||
private getCS (session: NoiseSession, encryption = true) {
|
||||
if (!session.cs1 || !session.cs2) {
|
||||
throw new Error("Handshake not completed properly, cipher state does not exist.");
|
||||
throw new Error('Handshake not completed properly, cipher state does not exist.')
|
||||
}
|
||||
|
||||
if (this.isInitiator) {
|
||||
return encryption ? session.cs1 : session.cs2;
|
||||
return encryption ? session.cs1 : session.cs2
|
||||
} else {
|
||||
return encryption ? session.cs2 : session.cs1;
|
||||
return encryption ? session.cs2 : session.cs1
|
||||
}
|
||||
}
|
||||
|
||||
private setRemoteEarlyData(data: Uint8Array|null|undefined): void {
|
||||
if(data){
|
||||
this.remoteEarlyData = Buffer.from(data.buffer, data.byteOffset, data.length);
|
||||
private setRemoteEarlyData (data: Uint8Array|null|undefined): void {
|
||||
if (data) {
|
||||
this.remoteEarlyData = Buffer.from(data.buffer, data.byteOffset, data.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,19 @@
|
||||
import {Buffer} from "buffer";
|
||||
import {XXHandshake} from "./handshake-xx";
|
||||
import {XX} from "./handshakes/xx";
|
||||
import {KeyPair} from "./@types/libp2p";
|
||||
import {bytes, bytes32} from "./@types/basic";
|
||||
import {decodePayload, getPeerIdFromPayload, verifySignedPayload} from "./utils";
|
||||
import {logger, logLocalEphemeralKeys, logRemoteEphemeralKey, logRemoteStaticKey} from "./logger";
|
||||
import {WrappedConnection} from "./noise";
|
||||
import {decode0, decode1} from "./encoder";
|
||||
import PeerId from "peer-id";
|
||||
import { Buffer } from 'buffer'
|
||||
import { XXHandshake } from './handshake-xx'
|
||||
import { XX } from './handshakes/xx'
|
||||
import { KeyPair } from './@types/libp2p'
|
||||
import { bytes, bytes32 } from './@types/basic'
|
||||
import { decodePayload, getPeerIdFromPayload, verifySignedPayload } from './utils'
|
||||
import { logger, logLocalEphemeralKeys, logRemoteEphemeralKey, logRemoteStaticKey } from './logger'
|
||||
import { WrappedConnection } from './noise'
|
||||
import { decode0, decode1 } from './encoder'
|
||||
import PeerId from 'peer-id'
|
||||
|
||||
export class XXFallbackHandshake extends XXHandshake {
|
||||
private ephemeralKeys?: KeyPair;
|
||||
private initialMsg: bytes;
|
||||
private readonly ephemeralKeys?: KeyPair
|
||||
private readonly initialMsg: bytes
|
||||
|
||||
constructor(
|
||||
constructor (
|
||||
isInitiator: boolean,
|
||||
payload: bytes,
|
||||
prologue: bytes32,
|
||||
@ -22,63 +22,65 @@ export class XXFallbackHandshake extends XXHandshake {
|
||||
initialMsg: bytes,
|
||||
remotePeer?: PeerId,
|
||||
ephemeralKeys?: KeyPair,
|
||||
handshake?: XX,
|
||||
handshake?: XX
|
||||
) {
|
||||
super(isInitiator, payload, prologue, staticKeypair, connection, remotePeer, handshake);
|
||||
super(isInitiator, payload, prologue, staticKeypair, connection, remotePeer, handshake)
|
||||
if (ephemeralKeys) {
|
||||
this.ephemeralKeys = ephemeralKeys;
|
||||
this.ephemeralKeys = ephemeralKeys
|
||||
}
|
||||
this.initialMsg = initialMsg;
|
||||
this.initialMsg = initialMsg
|
||||
}
|
||||
|
||||
// stage 0
|
||||
public async propose(): Promise<void> {
|
||||
// eslint-disable-next-line require-await
|
||||
public async propose (): Promise<void> {
|
||||
if (this.isInitiator) {
|
||||
this.xx.sendMessage(this.session, Buffer.alloc(0), this.ephemeralKeys);
|
||||
logger("XX Fallback Stage 0 - Initialized state as the first message was sent by initiator.");
|
||||
this.xx.sendMessage(this.session, Buffer.alloc(0), this.ephemeralKeys)
|
||||
logger('XX Fallback Stage 0 - Initialized state as the first message was sent by initiator.')
|
||||
logLocalEphemeralKeys(this.session.hs.e)
|
||||
} else {
|
||||
logger("XX Fallback Stage 0 - Responder decoding initial msg from IK.");
|
||||
const receivedMessageBuffer = decode0(this.initialMsg);
|
||||
const {valid} = this.xx.recvMessage(this.session, {
|
||||
logger('XX Fallback Stage 0 - Responder decoding initial msg from IK.')
|
||||
const receivedMessageBuffer = decode0(this.initialMsg)
|
||||
const { valid } = this.xx.recvMessage(this.session, {
|
||||
ne: receivedMessageBuffer.ne,
|
||||
ns: Buffer.alloc(0),
|
||||
ciphertext: Buffer.alloc(0),
|
||||
});
|
||||
if(!valid) {
|
||||
throw new Error("xx fallback stage 0 decryption validation fail");
|
||||
ciphertext: Buffer.alloc(0)
|
||||
})
|
||||
if (!valid) {
|
||||
throw new Error('xx fallback stage 0 decryption validation fail')
|
||||
}
|
||||
logger("XX Fallback Stage 0 - Responder used received message from IK.");
|
||||
logger('XX Fallback Stage 0 - Responder used received message from IK.')
|
||||
logRemoteEphemeralKey(this.session.hs.re)
|
||||
}
|
||||
}
|
||||
|
||||
// stage 1
|
||||
public async exchange(): Promise<void> {
|
||||
public async exchange (): Promise<void> {
|
||||
if (this.isInitiator) {
|
||||
const receivedMessageBuffer = decode1(this.initialMsg);
|
||||
const {plaintext, valid} = this.xx.recvMessage(this.session, receivedMessageBuffer);
|
||||
if(!valid) {
|
||||
throw new Error("xx fallback stage 1 decryption validation fail");
|
||||
const receivedMessageBuffer = decode1(this.initialMsg)
|
||||
const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
|
||||
if (!valid) {
|
||||
throw new Error('xx fallback stage 1 decryption validation fail')
|
||||
}
|
||||
logger('XX Fallback Stage 1 - Initiator used received message from IK.');
|
||||
logger('XX Fallback Stage 1 - Initiator used received message from IK.')
|
||||
logRemoteEphemeralKey(this.session.hs.re)
|
||||
logRemoteStaticKey(this.session.hs.rs)
|
||||
|
||||
logger("Initiator going to check remote's signature...");
|
||||
logger("Initiator going to check remote's signature...")
|
||||
try {
|
||||
const decodedPayload = await decodePayload(plaintext);
|
||||
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload);
|
||||
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer);
|
||||
const decodedPayload = await decodePayload(plaintext)
|
||||
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
|
||||
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer)
|
||||
this.setRemoteEarlyData(decodedPayload.data)
|
||||
} catch (e) {
|
||||
throw new Error(`Error occurred while verifying signed payload from responder: ${e.message}`);
|
||||
const err = e as Error
|
||||
throw new Error(`Error occurred while verifying signed payload from responder: ${err.message}`)
|
||||
}
|
||||
logger("All good with the signature!");
|
||||
logger('All good with the signature!')
|
||||
} else {
|
||||
logger("XX Fallback Stage 1 - Responder start");
|
||||
await super.exchange();
|
||||
logger("XX Fallback Stage 1 - Responder end");
|
||||
logger('XX Fallback Stage 1 - Responder start')
|
||||
await super.exchange()
|
||||
logger('XX Fallback Stage 1 - Responder end')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,173 +1,175 @@
|
||||
import { Buffer } from "buffer";
|
||||
import { Buffer } from 'buffer'
|
||||
|
||||
import { XX } from "./handshakes/xx";
|
||||
import { KeyPair } from "./@types/libp2p";
|
||||
import { bytes, bytes32 } from "./@types/basic";
|
||||
import { NoiseSession } from "./@types/handshake";
|
||||
import {IHandshake} from "./@types/handshake-interface";
|
||||
import { XX } from './handshakes/xx'
|
||||
import { KeyPair } from './@types/libp2p'
|
||||
import { bytes, bytes32 } from './@types/basic'
|
||||
import { NoiseSession } from './@types/handshake'
|
||||
import { IHandshake } from './@types/handshake-interface'
|
||||
import {
|
||||
decodePayload,
|
||||
getPeerIdFromPayload,
|
||||
verifySignedPayload,
|
||||
} from "./utils";
|
||||
verifySignedPayload
|
||||
} from './utils'
|
||||
import {
|
||||
logger,
|
||||
logLocalStaticKeys,
|
||||
logLocalEphemeralKeys,
|
||||
logRemoteEphemeralKey,
|
||||
logRemoteStaticKey,
|
||||
logCipherState,
|
||||
} from "./logger";
|
||||
import {decode0, decode1, decode2, encode0, encode1, encode2} from "./encoder";
|
||||
import { WrappedConnection } from "./noise";
|
||||
import PeerId from "peer-id";
|
||||
logCipherState
|
||||
} from './logger'
|
||||
import { decode0, decode1, decode2, encode0, encode1, encode2 } from './encoder'
|
||||
import { WrappedConnection } from './noise'
|
||||
import PeerId from 'peer-id'
|
||||
|
||||
export class XXHandshake implements IHandshake {
|
||||
public isInitiator: boolean;
|
||||
public session: NoiseSession;
|
||||
public remotePeer!: PeerId;
|
||||
public remoteEarlyData: Buffer;
|
||||
public isInitiator: boolean
|
||||
public session: NoiseSession
|
||||
public remotePeer!: PeerId
|
||||
public remoteEarlyData: Buffer
|
||||
|
||||
protected payload: bytes;
|
||||
protected connection: WrappedConnection;
|
||||
protected xx: XX;
|
||||
protected staticKeypair: KeyPair;
|
||||
protected payload: bytes
|
||||
protected connection: WrappedConnection
|
||||
protected xx: XX
|
||||
protected staticKeypair: KeyPair
|
||||
|
||||
private prologue: bytes32;
|
||||
private readonly prologue: bytes32
|
||||
|
||||
constructor(
|
||||
constructor (
|
||||
isInitiator: boolean,
|
||||
payload: bytes,
|
||||
prologue: bytes32,
|
||||
staticKeypair: KeyPair,
|
||||
connection: WrappedConnection,
|
||||
remotePeer?: PeerId,
|
||||
handshake?: XX,
|
||||
handshake?: XX
|
||||
) {
|
||||
this.isInitiator = isInitiator;
|
||||
this.payload = payload;
|
||||
this.prologue = prologue;
|
||||
this.staticKeypair = staticKeypair;
|
||||
this.connection = connection;
|
||||
if(remotePeer) {
|
||||
this.remotePeer = remotePeer;
|
||||
this.isInitiator = isInitiator
|
||||
this.payload = payload
|
||||
this.prologue = prologue
|
||||
this.staticKeypair = staticKeypair
|
||||
this.connection = connection
|
||||
if (remotePeer) {
|
||||
this.remotePeer = remotePeer
|
||||
}
|
||||
this.xx = handshake || new XX();
|
||||
this.session = this.xx.initSession(this.isInitiator, this.prologue, this.staticKeypair);
|
||||
this.xx = handshake ?? new XX()
|
||||
this.session = this.xx.initSession(this.isInitiator, this.prologue, this.staticKeypair)
|
||||
this.remoteEarlyData = Buffer.alloc(0)
|
||||
}
|
||||
|
||||
// stage 0
|
||||
public async propose(): Promise<void> {
|
||||
public async propose (): Promise<void> {
|
||||
logLocalStaticKeys(this.session.hs.s)
|
||||
if (this.isInitiator) {
|
||||
logger("Stage 0 - Initiator starting to send first message.");
|
||||
const messageBuffer = this.xx.sendMessage(this.session, Buffer.alloc(0));
|
||||
this.connection.writeLP(encode0(messageBuffer));
|
||||
logger("Stage 0 - Initiator finished sending first message.");
|
||||
logger('Stage 0 - Initiator starting to send first message.')
|
||||
const messageBuffer = this.xx.sendMessage(this.session, Buffer.alloc(0))
|
||||
this.connection.writeLP(encode0(messageBuffer))
|
||||
logger('Stage 0 - Initiator finished sending first message.')
|
||||
logLocalEphemeralKeys(this.session.hs.e)
|
||||
} else {
|
||||
logger("Stage 0 - Responder waiting to receive first message...");
|
||||
const receivedMessageBuffer = decode0((await this.connection.readLP()).slice());
|
||||
const {valid} = this.xx.recvMessage(this.session, receivedMessageBuffer);
|
||||
if(!valid) {
|
||||
throw new Error("xx handshake stage 0 validation fail");
|
||||
logger('Stage 0 - Responder waiting to receive first message...')
|
||||
const receivedMessageBuffer = decode0((await this.connection.readLP()).slice())
|
||||
const { valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
|
||||
if (!valid) {
|
||||
throw new Error('xx handshake stage 0 validation fail')
|
||||
}
|
||||
logger("Stage 0 - Responder received first message.");
|
||||
logger('Stage 0 - Responder received first message.')
|
||||
logRemoteEphemeralKey(this.session.hs.re)
|
||||
}
|
||||
}
|
||||
|
||||
// stage 1
|
||||
public async exchange(): Promise<void> {
|
||||
public async exchange (): Promise<void> {
|
||||
if (this.isInitiator) {
|
||||
logger('Stage 1 - Initiator waiting to receive first message from responder...');
|
||||
const receivedMessageBuffer = decode1((await this.connection.readLP()).slice());
|
||||
const {plaintext, valid} = this.xx.recvMessage(this.session, receivedMessageBuffer);
|
||||
if(!valid) {
|
||||
throw new Error("xx handshake stage 1 validation fail");
|
||||
logger('Stage 1 - Initiator waiting to receive first message from responder...')
|
||||
const receivedMessageBuffer = decode1((await this.connection.readLP()).slice())
|
||||
const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
|
||||
if (!valid) {
|
||||
throw new Error('xx handshake stage 1 validation fail')
|
||||
}
|
||||
logger('Stage 1 - Initiator received the message.');
|
||||
logger('Stage 1 - Initiator received the message.')
|
||||
logRemoteEphemeralKey(this.session.hs.re)
|
||||
logRemoteStaticKey(this.session.hs.rs)
|
||||
|
||||
logger("Initiator going to check remote's signature...");
|
||||
logger("Initiator going to check remote's signature...")
|
||||
try {
|
||||
const decodedPayload = await decodePayload(plaintext);
|
||||
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload);
|
||||
this.remotePeer = await verifySignedPayload(receivedMessageBuffer.ns, decodedPayload, this.remotePeer);
|
||||
const decodedPayload = await decodePayload(plaintext)
|
||||
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
|
||||
this.remotePeer = await verifySignedPayload(receivedMessageBuffer.ns, decodedPayload, this.remotePeer)
|
||||
this.setRemoteEarlyData(decodedPayload.data)
|
||||
} catch (e) {
|
||||
throw new Error(`Error occurred while verifying signed payload: ${e.message}`);
|
||||
const err = e as Error
|
||||
throw new Error(`Error occurred while verifying signed payload: ${err.message}`)
|
||||
}
|
||||
logger("All good with the signature!");
|
||||
logger('All good with the signature!')
|
||||
} else {
|
||||
logger('Stage 1 - Responder sending out first message with signed payload and static key.');
|
||||
const messageBuffer = this.xx.sendMessage(this.session, this.payload);
|
||||
this.connection.writeLP(encode1(messageBuffer));
|
||||
logger('Stage 1 - Responder sending out first message with signed payload and static key.')
|
||||
const messageBuffer = this.xx.sendMessage(this.session, this.payload)
|
||||
this.connection.writeLP(encode1(messageBuffer))
|
||||
logger('Stage 1 - Responder sent the second handshake message with signed payload.')
|
||||
logLocalEphemeralKeys(this.session.hs.e)
|
||||
}
|
||||
}
|
||||
|
||||
// stage 2
|
||||
public async finish(): Promise<void> {
|
||||
public async finish (): Promise<void> {
|
||||
if (this.isInitiator) {
|
||||
logger('Stage 2 - Initiator sending third handshake message.');
|
||||
const messageBuffer = this.xx.sendMessage(this.session, this.payload);
|
||||
this.connection.writeLP(encode2(messageBuffer));
|
||||
logger('Stage 2 - Initiator sent message with signed payload.');
|
||||
logger('Stage 2 - Initiator sending third handshake message.')
|
||||
const messageBuffer = this.xx.sendMessage(this.session, this.payload)
|
||||
this.connection.writeLP(encode2(messageBuffer))
|
||||
logger('Stage 2 - Initiator sent message with signed payload.')
|
||||
} else {
|
||||
logger('Stage 2 - Responder waiting for third handshake message...');
|
||||
const receivedMessageBuffer = decode2((await this.connection.readLP()).slice());
|
||||
const {plaintext, valid} = this.xx.recvMessage(this.session, receivedMessageBuffer);
|
||||
if(!valid) {
|
||||
throw new Error("xx handshake stage 2 validation fail");
|
||||
logger('Stage 2 - Responder waiting for third handshake message...')
|
||||
const receivedMessageBuffer = decode2((await this.connection.readLP()).slice())
|
||||
const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
|
||||
if (!valid) {
|
||||
throw new Error('xx handshake stage 2 validation fail')
|
||||
}
|
||||
logger('Stage 2 - Responder received the message, finished handshake.');
|
||||
logger('Stage 2 - Responder received the message, finished handshake.')
|
||||
|
||||
try {
|
||||
const decodedPayload = await decodePayload(plaintext);
|
||||
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload);
|
||||
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer);
|
||||
const decodedPayload = await decodePayload(plaintext)
|
||||
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
|
||||
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer)
|
||||
this.setRemoteEarlyData(decodedPayload.data)
|
||||
} catch (e) {
|
||||
throw new Error(`Error occurred while verifying signed payload: ${e.message}`);
|
||||
const err = e as Error
|
||||
throw new Error(`Error occurred while verifying signed payload: ${err.message}`)
|
||||
}
|
||||
}
|
||||
logCipherState(this.session)
|
||||
}
|
||||
|
||||
public encrypt(plaintext: bytes, session: NoiseSession): bytes {
|
||||
const cs = this.getCS(session);
|
||||
public encrypt (plaintext: bytes, session: NoiseSession): bytes {
|
||||
const cs = this.getCS(session)
|
||||
|
||||
return this.xx.encryptWithAd(cs, Buffer.alloc(0), plaintext);
|
||||
return this.xx.encryptWithAd(cs, Buffer.alloc(0), plaintext)
|
||||
}
|
||||
|
||||
public decrypt(ciphertext: bytes, session: NoiseSession): {plaintext: bytes; valid: boolean} {
|
||||
const cs = this.getCS(session, false);
|
||||
return this.xx.decryptWithAd(cs, Buffer.alloc(0), ciphertext);
|
||||
public decrypt (ciphertext: bytes, session: NoiseSession): {plaintext: bytes, valid: boolean} {
|
||||
const cs = this.getCS(session, false)
|
||||
return this.xx.decryptWithAd(cs, Buffer.alloc(0), ciphertext)
|
||||
}
|
||||
|
||||
public getRemoteStaticKey(): bytes {
|
||||
return this.session.hs.rs;
|
||||
public getRemoteStaticKey (): bytes {
|
||||
return this.session.hs.rs
|
||||
}
|
||||
|
||||
private getCS(session: NoiseSession, encryption = true) {
|
||||
private getCS (session: NoiseSession, encryption = true) {
|
||||
if (!session.cs1 || !session.cs2) {
|
||||
throw new Error("Handshake not completed properly, cipher state does not exist.");
|
||||
throw new Error('Handshake not completed properly, cipher state does not exist.')
|
||||
}
|
||||
|
||||
if (this.isInitiator) {
|
||||
return encryption ? session.cs1 : session.cs2;
|
||||
return encryption ? session.cs1 : session.cs2
|
||||
} else {
|
||||
return encryption ? session.cs2 : session.cs1;
|
||||
return encryption ? session.cs2 : session.cs1
|
||||
}
|
||||
}
|
||||
|
||||
protected setRemoteEarlyData(data: Uint8Array|null|undefined): void {
|
||||
if(data){
|
||||
this.remoteEarlyData = Buffer.from(data.buffer, data.byteOffset, data.length);
|
||||
protected setRemoteEarlyData (data: Uint8Array|null|undefined): void {
|
||||
if (data) {
|
||||
this.remoteEarlyData = Buffer.from(data.buffer, data.byteOffset, data.length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,179 +1,179 @@
|
||||
import {Buffer} from "buffer";
|
||||
import hash from 'hash.js';
|
||||
import {box} from 'tweetnacl';
|
||||
import {AEAD} from 'aead-js';
|
||||
import { Buffer } from 'buffer'
|
||||
import AEAD from 'bcrypto/lib/js/aead'
|
||||
import x25519 from 'bcrypto/lib/js/x25519'
|
||||
import SHA256 from 'bcrypto/lib/js/sha256'
|
||||
|
||||
import {bytes, bytes32, uint32} from "../@types/basic";
|
||||
import {CipherState, MessageBuffer, SymmetricState} from "../@types/handshake";
|
||||
import {getHkdf} from "../utils";
|
||||
import {logger} from "../logger";
|
||||
import { bytes, bytes32, uint32 } from '../@types/basic'
|
||||
import { CipherState, MessageBuffer, SymmetricState } from '../@types/handshake'
|
||||
import { getHkdf } from '../utils'
|
||||
import { logger } from '../logger'
|
||||
|
||||
export const MIN_NONCE = 0;
|
||||
export const MIN_NONCE = 0
|
||||
|
||||
export abstract class AbstractHandshake {
|
||||
public encryptWithAd(cs: CipherState, ad: bytes, plaintext: bytes): bytes {
|
||||
const e = this.encrypt(cs.k, cs.n, ad, plaintext);
|
||||
this.setNonce(cs, this.incrementNonce(cs.n));
|
||||
public encryptWithAd (cs: CipherState, ad: bytes, plaintext: bytes): bytes {
|
||||
const e = this.encrypt(cs.k, cs.n, ad, plaintext)
|
||||
this.setNonce(cs, this.incrementNonce(cs.n))
|
||||
|
||||
return e;
|
||||
return e
|
||||
}
|
||||
|
||||
public decryptWithAd(cs: CipherState, ad: bytes, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
|
||||
const {plaintext, valid} = this.decrypt(cs.k, cs.n, ad, ciphertext);
|
||||
this.setNonce(cs, this.incrementNonce(cs.n));
|
||||
public decryptWithAd (cs: CipherState, ad: bytes, ciphertext: bytes): {plaintext: bytes, valid: boolean} {
|
||||
const { plaintext, valid } = this.decrypt(cs.k, cs.n, ad, ciphertext)
|
||||
this.setNonce(cs, this.incrementNonce(cs.n))
|
||||
|
||||
return {plaintext, valid};
|
||||
return { plaintext, valid }
|
||||
}
|
||||
|
||||
|
||||
// Cipher state related
|
||||
protected hasKey(cs: CipherState): boolean {
|
||||
return !this.isEmptyKey(cs.k);
|
||||
protected hasKey (cs: CipherState): boolean {
|
||||
return !this.isEmptyKey(cs.k)
|
||||
}
|
||||
|
||||
protected setNonce(cs: CipherState, nonce: uint32): void {
|
||||
cs.n = nonce;
|
||||
protected setNonce (cs: CipherState, nonce: uint32): void {
|
||||
cs.n = nonce
|
||||
}
|
||||
|
||||
protected createEmptyKey(): bytes32 {
|
||||
return Buffer.alloc(32);
|
||||
protected createEmptyKey (): bytes32 {
|
||||
return Buffer.alloc(32)
|
||||
}
|
||||
|
||||
protected isEmptyKey(k: bytes32): boolean {
|
||||
const emptyKey = this.createEmptyKey();
|
||||
return emptyKey.equals(k);
|
||||
protected isEmptyKey (k: bytes32): boolean {
|
||||
const emptyKey = this.createEmptyKey()
|
||||
return emptyKey.equals(k)
|
||||
}
|
||||
|
||||
protected incrementNonce(n: uint32): uint32 {
|
||||
return n + 1;
|
||||
protected incrementNonce (n: uint32): uint32 {
|
||||
return n + 1
|
||||
}
|
||||
|
||||
protected nonceToBytes(n: uint32): bytes {
|
||||
const nonce = Buffer.alloc(12);
|
||||
nonce.writeUInt32LE(n, 4);
|
||||
protected nonceToBytes (n: uint32): bytes {
|
||||
const nonce = Buffer.alloc(12)
|
||||
nonce.writeUInt32LE(n, 4)
|
||||
|
||||
return nonce;
|
||||
return nonce
|
||||
}
|
||||
|
||||
protected encrypt(k: bytes32, n: uint32, ad: bytes, plaintext: bytes): bytes {
|
||||
const nonce = this.nonceToBytes(n);
|
||||
const ctx = new AEAD();
|
||||
plaintext = Buffer.from(plaintext);
|
||||
ctx.init(k, nonce);
|
||||
ctx.aad(ad);
|
||||
ctx.encrypt(plaintext);
|
||||
protected encrypt (k: bytes32, n: uint32, ad: bytes, plaintext: bytes): bytes {
|
||||
const nonce = this.nonceToBytes(n)
|
||||
const ctx = new AEAD()
|
||||
plaintext = Buffer.from(plaintext)
|
||||
ctx.init(k, nonce)
|
||||
ctx.aad(ad)
|
||||
ctx.encrypt(plaintext)
|
||||
|
||||
// Encryption is done on the sent reference
|
||||
return Buffer.concat([plaintext, ctx.final()]);
|
||||
return Buffer.concat([plaintext, ctx.final()])
|
||||
}
|
||||
|
||||
protected encryptAndHash(ss: SymmetricState, plaintext: bytes): bytes {
|
||||
let ciphertext;
|
||||
protected encryptAndHash (ss: SymmetricState, plaintext: bytes): bytes {
|
||||
let ciphertext
|
||||
if (this.hasKey(ss.cs)) {
|
||||
ciphertext = this.encryptWithAd(ss.cs, ss.h, plaintext);
|
||||
ciphertext = this.encryptWithAd(ss.cs, ss.h, plaintext)
|
||||
} else {
|
||||
ciphertext = plaintext;
|
||||
ciphertext = plaintext
|
||||
}
|
||||
|
||||
this.mixHash(ss, ciphertext);
|
||||
return ciphertext;
|
||||
this.mixHash(ss, ciphertext)
|
||||
return ciphertext
|
||||
}
|
||||
|
||||
protected decrypt(k: bytes32, n: uint32, ad: bytes, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
|
||||
const nonce = this.nonceToBytes(n);
|
||||
const ctx = new AEAD();
|
||||
ciphertext = Buffer.from(ciphertext);
|
||||
const tag = ciphertext.slice(ciphertext.length - 16);
|
||||
ciphertext = ciphertext.slice(0, ciphertext.length - 16);
|
||||
ctx.init(k, nonce);
|
||||
ctx.aad(ad);
|
||||
ctx.decrypt(ciphertext);
|
||||
protected decrypt (k: bytes32, n: uint32, ad: bytes, ciphertext: bytes): {plaintext: bytes, valid: boolean} {
|
||||
const nonce = this.nonceToBytes(n)
|
||||
const ctx = new AEAD()
|
||||
ciphertext = Buffer.from(ciphertext)
|
||||
const tag = ciphertext.slice(ciphertext.length - 16)
|
||||
ciphertext = ciphertext.slice(0, ciphertext.length - 16)
|
||||
ctx.init(k, nonce)
|
||||
ctx.aad(ad)
|
||||
ctx.decrypt(ciphertext)
|
||||
// Decryption is done on the sent reference
|
||||
return {plaintext: ciphertext, valid: ctx.verify(tag)};
|
||||
return { plaintext: ciphertext, valid: ctx.verify(tag) }
|
||||
}
|
||||
|
||||
protected decryptAndHash(ss: SymmetricState, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
|
||||
let plaintext: bytes, valid = true;
|
||||
protected decryptAndHash (ss: SymmetricState, ciphertext: bytes): {plaintext: bytes, valid: boolean} {
|
||||
let plaintext: bytes; let valid = true
|
||||
if (this.hasKey(ss.cs)) {
|
||||
({plaintext, valid} = this.decryptWithAd(ss.cs, ss.h, ciphertext));
|
||||
({ plaintext, valid } = this.decryptWithAd(ss.cs, ss.h, ciphertext))
|
||||
} else {
|
||||
plaintext = ciphertext;
|
||||
plaintext = ciphertext
|
||||
}
|
||||
|
||||
this.mixHash(ss, ciphertext);
|
||||
return {plaintext, valid};
|
||||
this.mixHash(ss, ciphertext)
|
||||
return { plaintext, valid }
|
||||
}
|
||||
|
||||
protected dh(privateKey: bytes32, publicKey: bytes32): bytes32 {
|
||||
protected dh (privateKey: bytes32, publicKey: bytes32): bytes32 {
|
||||
try {
|
||||
const sharedKey = box.before(publicKey, privateKey)
|
||||
return Buffer.from(sharedKey.buffer, sharedKey.byteOffset, sharedKey.length);
|
||||
const derived = x25519.derive(publicKey, privateKey)
|
||||
const result = Buffer.alloc(32)
|
||||
derived.copy(result)
|
||||
return result
|
||||
} catch (e) {
|
||||
logger(e.message);
|
||||
return Buffer.alloc(32);
|
||||
logger(e.message)
|
||||
return Buffer.alloc(32)
|
||||
}
|
||||
}
|
||||
|
||||
protected mixHash(ss: SymmetricState, data: bytes): void {
|
||||
ss.h = this.getHash(ss.h, data);
|
||||
protected mixHash (ss: SymmetricState, data: bytes): void {
|
||||
ss.h = this.getHash(ss.h, data)
|
||||
}
|
||||
|
||||
protected getHash(a: bytes, b: bytes): bytes32 {
|
||||
const hashValue = hash.sha256().update(Buffer.from([...a, ...b])).digest();
|
||||
return Buffer.from(hashValue);
|
||||
protected getHash (a: bytes, b: bytes): bytes32 {
|
||||
return SHA256.digest(Buffer.from([...a, ...b]))
|
||||
}
|
||||
|
||||
protected mixKey(ss: SymmetricState, ikm: bytes32): void {
|
||||
const [ ck, tempK ] = getHkdf(ss.ck, ikm);
|
||||
ss.cs = this.initializeKey(tempK) as CipherState;
|
||||
ss.ck = ck;
|
||||
protected mixKey (ss: SymmetricState, ikm: bytes32): void {
|
||||
const [ck, tempK] = getHkdf(ss.ck, ikm)
|
||||
ss.cs = this.initializeKey(tempK)
|
||||
ss.ck = ck
|
||||
}
|
||||
|
||||
protected initializeKey(k: bytes32): CipherState {
|
||||
const n = MIN_NONCE;
|
||||
return { k, n };
|
||||
protected initializeKey (k: bytes32): CipherState {
|
||||
const n = MIN_NONCE
|
||||
return { k, n }
|
||||
}
|
||||
|
||||
// Symmetric state related
|
||||
|
||||
protected initializeSymmetric(protocolName: string): SymmetricState {
|
||||
const protocolNameBytes: bytes = Buffer.from(protocolName, 'utf-8');
|
||||
const h = this.hashProtocolName(protocolNameBytes);
|
||||
protected initializeSymmetric (protocolName: string): SymmetricState {
|
||||
const protocolNameBytes: bytes = Buffer.from(protocolName, 'utf-8')
|
||||
const h = this.hashProtocolName(protocolNameBytes)
|
||||
|
||||
const ck = h;
|
||||
const key = this.createEmptyKey();
|
||||
const cs: CipherState = this.initializeKey(key);
|
||||
const ck = h
|
||||
const key = this.createEmptyKey()
|
||||
const cs: CipherState = this.initializeKey(key)
|
||||
|
||||
return { cs, ck, h };
|
||||
return { cs, ck, h }
|
||||
}
|
||||
|
||||
protected hashProtocolName(protocolName: bytes): bytes32 {
|
||||
protected hashProtocolName (protocolName: bytes): bytes32 {
|
||||
if (protocolName.length <= 32) {
|
||||
const h = Buffer.alloc(32);
|
||||
protocolName.copy(h);
|
||||
return h;
|
||||
const h = Buffer.alloc(32)
|
||||
protocolName.copy(h)
|
||||
return h
|
||||
} else {
|
||||
return this.getHash(protocolName, Buffer.alloc(0));
|
||||
return this.getHash(protocolName, Buffer.alloc(0))
|
||||
}
|
||||
}
|
||||
|
||||
protected split(ss: SymmetricState) {
|
||||
const [ tempk1, tempk2 ] = getHkdf(ss.ck, Buffer.alloc(0));
|
||||
const cs1 = this.initializeKey(tempk1);
|
||||
const cs2 = this.initializeKey(tempk2);
|
||||
protected split (ss: SymmetricState): {cs1: CipherState, cs2: CipherState} {
|
||||
const [tempk1, tempk2] = getHkdf(ss.ck, Buffer.alloc(0))
|
||||
const cs1 = this.initializeKey(tempk1)
|
||||
const cs2 = this.initializeKey(tempk2)
|
||||
|
||||
return { cs1, cs2 };
|
||||
return { cs1, cs2 }
|
||||
}
|
||||
|
||||
protected writeMessageRegular(cs: CipherState, payload: bytes): MessageBuffer {
|
||||
const ciphertext = this.encryptWithAd(cs, Buffer.alloc(0), payload);
|
||||
const ne = this.createEmptyKey();
|
||||
const ns = Buffer.alloc(0);
|
||||
protected writeMessageRegular (cs: CipherState, payload: bytes): MessageBuffer {
|
||||
const ciphertext = this.encryptWithAd(cs, Buffer.alloc(0), payload)
|
||||
const ne = this.createEmptyKey()
|
||||
const ns = Buffer.alloc(0)
|
||||
|
||||
return { ne, ns, ciphertext };
|
||||
return { ne, ns, ciphertext }
|
||||
}
|
||||
|
||||
protected readMessageRegular(cs: CipherState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
|
||||
return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext);
|
||||
protected readMessageRegular (cs: CipherState, message: MessageBuffer): {plaintext: bytes, valid: boolean} {
|
||||
return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext)
|
||||
}
|
||||
}
|
||||
|
@ -1,157 +1,156 @@
|
||||
import {Buffer} from "buffer";
|
||||
import {CipherState, HandshakeState, MessageBuffer, NoiseSession} from "../@types/handshake";
|
||||
import {bytes, bytes32} from "../@types/basic";
|
||||
import {generateKeypair, isValidPublicKey} from "../utils";
|
||||
import {AbstractHandshake} from "./abstract-handshake";
|
||||
import {KeyPair} from "../@types/libp2p";
|
||||
|
||||
import { Buffer } from 'buffer'
|
||||
import { CipherState, HandshakeState, MessageBuffer, NoiseSession } from '../@types/handshake'
|
||||
import { bytes, bytes32 } from '../@types/basic'
|
||||
import { generateKeypair, isValidPublicKey } from '../utils'
|
||||
import { AbstractHandshake } from './abstract-handshake'
|
||||
import { KeyPair } from '../@types/libp2p'
|
||||
|
||||
export class IK extends AbstractHandshake {
|
||||
public initSession(initiator: boolean, prologue: bytes32, s: KeyPair, rs: bytes32): NoiseSession {
|
||||
const psk = this.createEmptyKey();
|
||||
public initSession (initiator: boolean, prologue: bytes32, s: KeyPair, rs: bytes32): NoiseSession {
|
||||
const psk = this.createEmptyKey()
|
||||
|
||||
let hs;
|
||||
let hs
|
||||
if (initiator) {
|
||||
hs = this.initializeInitiator(prologue, s, rs, psk);
|
||||
hs = this.initializeInitiator(prologue, s, rs, psk)
|
||||
} else {
|
||||
hs = this.initializeResponder(prologue, s, rs, psk);
|
||||
hs = this.initializeResponder(prologue, s, rs, psk)
|
||||
}
|
||||
|
||||
return {
|
||||
hs,
|
||||
i: initiator,
|
||||
mc: 0,
|
||||
};
|
||||
mc: 0
|
||||
}
|
||||
}
|
||||
|
||||
public sendMessage(session: NoiseSession, message: bytes): MessageBuffer {
|
||||
let messageBuffer: MessageBuffer;
|
||||
public sendMessage (session: NoiseSession, message: bytes): MessageBuffer {
|
||||
let messageBuffer: MessageBuffer
|
||||
if (session.mc === 0) {
|
||||
messageBuffer = this.writeMessageA(session.hs, message);
|
||||
messageBuffer = this.writeMessageA(session.hs, message)
|
||||
} else if (session.mc === 1) {
|
||||
const { messageBuffer: mb, h, cs1, cs2 } = this.writeMessageB(session.hs, message);
|
||||
messageBuffer = mb;
|
||||
session.h = h;
|
||||
session.cs1 = cs1;
|
||||
session.cs2 = cs2;
|
||||
const { messageBuffer: mb, h, cs1, cs2 } = this.writeMessageB(session.hs, message)
|
||||
messageBuffer = mb
|
||||
session.h = h
|
||||
session.cs1 = cs1
|
||||
session.cs2 = cs2
|
||||
} else if (session.mc > 1) {
|
||||
if (session.i) {
|
||||
if (!session.cs1) {
|
||||
throw new Error("CS1 (cipher state) is not defined")
|
||||
throw new Error('CS1 (cipher state) is not defined')
|
||||
}
|
||||
|
||||
messageBuffer = this.writeMessageRegular(session.cs1, message);
|
||||
messageBuffer = this.writeMessageRegular(session.cs1, message)
|
||||
} else {
|
||||
if (!session.cs2) {
|
||||
throw new Error("CS2 (cipher state) is not defined")
|
||||
throw new Error('CS2 (cipher state) is not defined')
|
||||
}
|
||||
|
||||
messageBuffer = this.writeMessageRegular(session.cs2, message);
|
||||
messageBuffer = this.writeMessageRegular(session.cs2, message)
|
||||
}
|
||||
} else {
|
||||
throw new Error("Session invalid.")
|
||||
throw new Error('Session invalid.')
|
||||
}
|
||||
|
||||
session.mc++;
|
||||
return messageBuffer;
|
||||
session.mc++
|
||||
return messageBuffer
|
||||
}
|
||||
|
||||
public recvMessage(session: NoiseSession, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
|
||||
let plaintext = Buffer.alloc(0), valid = false;
|
||||
public recvMessage (session: NoiseSession, message: MessageBuffer): {plaintext: bytes, valid: boolean} {
|
||||
let plaintext = Buffer.alloc(0); let valid = false
|
||||
if (session.mc === 0) {
|
||||
({plaintext, valid} = this.readMessageA(session.hs, message));
|
||||
({ plaintext, valid } = this.readMessageA(session.hs, message))
|
||||
}
|
||||
if (session.mc === 1) {
|
||||
const { plaintext: pt, valid: v, h, cs1, cs2 } = this.readMessageB(session.hs, message);
|
||||
plaintext = pt;
|
||||
valid = v;
|
||||
session.h = h;
|
||||
session.cs1 = cs1;
|
||||
session.cs2 = cs2;
|
||||
const { plaintext: pt, valid: v, h, cs1, cs2 } = this.readMessageB(session.hs, message)
|
||||
plaintext = pt
|
||||
valid = v
|
||||
session.h = h
|
||||
session.cs1 = cs1
|
||||
session.cs2 = cs2
|
||||
}
|
||||
session.mc++;
|
||||
return {plaintext, valid};
|
||||
session.mc++
|
||||
return { plaintext, valid }
|
||||
}
|
||||
|
||||
private writeMessageA(hs: HandshakeState, payload: bytes): MessageBuffer {
|
||||
hs.e = generateKeypair();
|
||||
const ne = hs.e.publicKey;
|
||||
this.mixHash(hs.ss, ne);
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs));
|
||||
const spk = Buffer.from(hs.s.publicKey);
|
||||
const ns = this.encryptAndHash(hs.ss, spk);
|
||||
private writeMessageA (hs: HandshakeState, payload: bytes): MessageBuffer {
|
||||
hs.e = generateKeypair()
|
||||
const ne = hs.e.publicKey
|
||||
this.mixHash(hs.ss, ne)
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs))
|
||||
const spk = Buffer.from(hs.s.publicKey)
|
||||
const ns = this.encryptAndHash(hs.ss, spk)
|
||||
|
||||
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs));
|
||||
const ciphertext = this.encryptAndHash(hs.ss, payload);
|
||||
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs))
|
||||
const ciphertext = this.encryptAndHash(hs.ss, payload)
|
||||
|
||||
return { ne, ns, ciphertext };
|
||||
return { ne, ns, ciphertext }
|
||||
}
|
||||
|
||||
private writeMessageB(hs: HandshakeState, payload: bytes) {
|
||||
hs.e = generateKeypair();
|
||||
const ne = hs.e.publicKey;
|
||||
this.mixHash(hs.ss, ne);
|
||||
private writeMessageB (hs: HandshakeState, payload: bytes) {
|
||||
hs.e = generateKeypair()
|
||||
const ne = hs.e.publicKey
|
||||
this.mixHash(hs.ss, ne)
|
||||
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re));
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs));
|
||||
const ciphertext = this.encryptAndHash(hs.ss, payload);
|
||||
const ns = this.createEmptyKey();
|
||||
const messageBuffer: MessageBuffer = {ne, ns, ciphertext};
|
||||
const { cs1, cs2 } = this.split(hs.ss);
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re))
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs))
|
||||
const ciphertext = this.encryptAndHash(hs.ss, payload)
|
||||
const ns = this.createEmptyKey()
|
||||
const messageBuffer: MessageBuffer = { ne, ns, ciphertext }
|
||||
const { cs1, cs2 } = this.split(hs.ss)
|
||||
|
||||
return { messageBuffer, cs1, cs2, h: hs.ss.h }
|
||||
}
|
||||
|
||||
private readMessageA(hs: HandshakeState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
|
||||
private readMessageA (hs: HandshakeState, message: MessageBuffer): {plaintext: bytes, valid: boolean} {
|
||||
if (isValidPublicKey(message.ne)) {
|
||||
hs.re = message.ne;
|
||||
hs.re = message.ne
|
||||
}
|
||||
|
||||
this.mixHash(hs.ss, hs.re);
|
||||
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re));
|
||||
const {plaintext: ns, valid: valid1} = this.decryptAndHash(hs.ss, message.ns);
|
||||
this.mixHash(hs.ss, hs.re)
|
||||
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re))
|
||||
const { plaintext: ns, valid: valid1 } = this.decryptAndHash(hs.ss, message.ns)
|
||||
if (valid1 && ns.length === 32 && isValidPublicKey(ns)) {
|
||||
hs.rs = ns;
|
||||
hs.rs = ns
|
||||
}
|
||||
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs));
|
||||
const {plaintext, valid: valid2} = this.decryptAndHash(hs.ss, message.ciphertext);
|
||||
return {plaintext, valid: (valid1 && valid2)};
|
||||
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs))
|
||||
const { plaintext, valid: valid2 } = this.decryptAndHash(hs.ss, message.ciphertext)
|
||||
return { plaintext, valid: (valid1 && valid2) }
|
||||
}
|
||||
|
||||
private readMessageB(hs: HandshakeState, message: MessageBuffer): {h: bytes; plaintext: bytes; valid: boolean; cs1: CipherState; cs2: CipherState} {
|
||||
private readMessageB (hs: HandshakeState, message: MessageBuffer): {h: bytes, plaintext: bytes, valid: boolean, cs1: CipherState, cs2: CipherState} {
|
||||
if (isValidPublicKey(message.ne)) {
|
||||
hs.re = message.ne;
|
||||
hs.re = message.ne
|
||||
}
|
||||
|
||||
this.mixHash(hs.ss, hs.re);
|
||||
this.mixHash(hs.ss, hs.re)
|
||||
if (!hs.e) {
|
||||
throw new Error("Handshake state should contain ephemeral key by now.");
|
||||
throw new Error('Handshake state should contain ephemeral key by now.')
|
||||
}
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re));
|
||||
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re));
|
||||
const {plaintext, valid} = this.decryptAndHash(hs.ss, message.ciphertext);
|
||||
const { cs1, cs2 } = this.split(hs.ss);
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re))
|
||||
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re))
|
||||
const { plaintext, valid } = this.decryptAndHash(hs.ss, message.ciphertext)
|
||||
const { cs1, cs2 } = this.split(hs.ss)
|
||||
|
||||
return { h: hs.ss.h, valid, plaintext, cs1, cs2 };
|
||||
return { h: hs.ss.h, valid, plaintext, cs1, cs2 }
|
||||
}
|
||||
|
||||
private initializeInitiator(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
|
||||
const name = "Noise_IK_25519_ChaChaPoly_SHA256";
|
||||
const ss = this.initializeSymmetric(name);
|
||||
this.mixHash(ss, prologue);
|
||||
this.mixHash(ss, rs);
|
||||
const re = Buffer.alloc(32);
|
||||
private initializeInitiator (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
|
||||
const name = 'Noise_IK_25519_ChaChaPoly_SHA256'
|
||||
const ss = this.initializeSymmetric(name)
|
||||
this.mixHash(ss, prologue)
|
||||
this.mixHash(ss, rs)
|
||||
const re = Buffer.alloc(32)
|
||||
|
||||
return { ss, s, rs, re, psk };
|
||||
return { ss, s, rs, re, psk }
|
||||
}
|
||||
|
||||
private initializeResponder(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
|
||||
const name = "Noise_IK_25519_ChaChaPoly_SHA256";
|
||||
const ss = this.initializeSymmetric(name);
|
||||
this.mixHash(ss, prologue);
|
||||
this.mixHash(ss, s.publicKey);
|
||||
const re = Buffer.alloc(32);
|
||||
private initializeResponder (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
|
||||
const name = 'Noise_IK_25519_ChaChaPoly_SHA256'
|
||||
const ss = this.initializeSymmetric(name)
|
||||
this.mixHash(ss, prologue)
|
||||
this.mixHash(ss, s.publicKey)
|
||||
const re = Buffer.alloc(32)
|
||||
|
||||
return { ss, s, rs, re, psk };
|
||||
return { ss, s, rs, re, psk }
|
||||
}
|
||||
}
|
||||
|
@ -1,186 +1,185 @@
|
||||
import { Buffer } from 'buffer';
|
||||
import { Buffer } from 'buffer'
|
||||
import { bytes32, bytes } from '../@types/basic'
|
||||
import { KeyPair } from '../@types/libp2p'
|
||||
import {generateKeypair, isValidPublicKey} from '../utils';
|
||||
import {CipherState, HandshakeState, MessageBuffer, NoiseSession} from "../@types/handshake";
|
||||
import {AbstractHandshake} from "./abstract-handshake";
|
||||
|
||||
import { generateKeypair, isValidPublicKey } from '../utils'
|
||||
import { CipherState, HandshakeState, MessageBuffer, NoiseSession } from '../@types/handshake'
|
||||
import { AbstractHandshake } from './abstract-handshake'
|
||||
|
||||
export class XX extends AbstractHandshake {
|
||||
private initializeInitiator(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
|
||||
const name = "Noise_XX_25519_ChaChaPoly_SHA256";
|
||||
const ss = this.initializeSymmetric(name);
|
||||
this.mixHash(ss, prologue);
|
||||
const re = Buffer.alloc(32);
|
||||
private initializeInitiator (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
|
||||
const name = 'Noise_XX_25519_ChaChaPoly_SHA256'
|
||||
const ss = this.initializeSymmetric(name)
|
||||
this.mixHash(ss, prologue)
|
||||
const re = Buffer.alloc(32)
|
||||
|
||||
return { ss, s, rs, psk, re };
|
||||
return { ss, s, rs, psk, re }
|
||||
}
|
||||
|
||||
private initializeResponder(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
|
||||
const name = "Noise_XX_25519_ChaChaPoly_SHA256";
|
||||
const ss = this.initializeSymmetric(name);
|
||||
this.mixHash(ss, prologue);
|
||||
const re = Buffer.alloc(32);
|
||||
private initializeResponder (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
|
||||
const name = 'Noise_XX_25519_ChaChaPoly_SHA256'
|
||||
const ss = this.initializeSymmetric(name)
|
||||
this.mixHash(ss, prologue)
|
||||
const re = Buffer.alloc(32)
|
||||
|
||||
return { ss, s, rs, psk, re };
|
||||
return { ss, s, rs, psk, re }
|
||||
}
|
||||
|
||||
private writeMessageA(hs: HandshakeState, payload: bytes, e?: KeyPair): MessageBuffer {
|
||||
const ns = Buffer.alloc(0);
|
||||
private writeMessageA (hs: HandshakeState, payload: bytes, e?: KeyPair): MessageBuffer {
|
||||
const ns = Buffer.alloc(0)
|
||||
|
||||
if (e) {
|
||||
hs.e = e;
|
||||
if (e !== undefined) {
|
||||
hs.e = e
|
||||
} else {
|
||||
hs.e = generateKeypair();
|
||||
hs.e = generateKeypair()
|
||||
}
|
||||
|
||||
const ne = hs.e.publicKey;
|
||||
const ne = hs.e.publicKey
|
||||
|
||||
this.mixHash(hs.ss, ne);
|
||||
const ciphertext = this.encryptAndHash(hs.ss, payload);
|
||||
this.mixHash(hs.ss, ne)
|
||||
const ciphertext = this.encryptAndHash(hs.ss, payload)
|
||||
|
||||
return {ne, ns, ciphertext};
|
||||
return { ne, ns, ciphertext }
|
||||
}
|
||||
|
||||
private writeMessageB(hs: HandshakeState, payload: bytes): MessageBuffer {
|
||||
hs.e = generateKeypair();
|
||||
const ne = hs.e.publicKey;
|
||||
this.mixHash(hs.ss, ne);
|
||||
private writeMessageB (hs: HandshakeState, payload: bytes): MessageBuffer {
|
||||
hs.e = generateKeypair()
|
||||
const ne = hs.e.publicKey
|
||||
this.mixHash(hs.ss, ne)
|
||||
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re));
|
||||
const spk = Buffer.from(hs.s.publicKey);
|
||||
const ns = this.encryptAndHash(hs.ss, spk);
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re))
|
||||
const spk = Buffer.from(hs.s.publicKey)
|
||||
const ns = this.encryptAndHash(hs.ss, spk)
|
||||
|
||||
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re));
|
||||
const ciphertext = this.encryptAndHash(hs.ss, payload);
|
||||
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re))
|
||||
const ciphertext = this.encryptAndHash(hs.ss, payload)
|
||||
|
||||
return { ne, ns, ciphertext };
|
||||
return { ne, ns, ciphertext }
|
||||
}
|
||||
|
||||
private writeMessageC(hs: HandshakeState, payload: bytes) {
|
||||
const spk = Buffer.from(hs.s.publicKey);
|
||||
const ns = this.encryptAndHash(hs.ss, spk);
|
||||
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re));
|
||||
const ciphertext = this.encryptAndHash(hs.ss, payload);
|
||||
const ne = this.createEmptyKey();
|
||||
const messageBuffer: MessageBuffer = {ne, ns, ciphertext};
|
||||
const { cs1, cs2 } = this.split(hs.ss);
|
||||
private writeMessageC (hs: HandshakeState, payload: bytes) {
|
||||
const spk = Buffer.from(hs.s.publicKey)
|
||||
const ns = this.encryptAndHash(hs.ss, spk)
|
||||
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re))
|
||||
const ciphertext = this.encryptAndHash(hs.ss, payload)
|
||||
const ne = this.createEmptyKey()
|
||||
const messageBuffer: MessageBuffer = { ne, ns, ciphertext }
|
||||
const { cs1, cs2 } = this.split(hs.ss)
|
||||
|
||||
return { h: hs.ss.h, messageBuffer, cs1, cs2 };
|
||||
return { h: hs.ss.h, messageBuffer, cs1, cs2 }
|
||||
}
|
||||
|
||||
private readMessageA(hs: HandshakeState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
|
||||
private readMessageA (hs: HandshakeState, message: MessageBuffer): {plaintext: bytes, valid: boolean} {
|
||||
if (isValidPublicKey(message.ne)) {
|
||||
hs.re = message.ne;
|
||||
hs.re = message.ne
|
||||
}
|
||||
|
||||
this.mixHash(hs.ss, hs.re);
|
||||
return this.decryptAndHash(hs.ss, message.ciphertext);
|
||||
this.mixHash(hs.ss, hs.re)
|
||||
return this.decryptAndHash(hs.ss, message.ciphertext)
|
||||
}
|
||||
|
||||
private readMessageB(hs: HandshakeState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
|
||||
private readMessageB (hs: HandshakeState, message: MessageBuffer): {plaintext: bytes, valid: boolean} {
|
||||
if (isValidPublicKey(message.ne)) {
|
||||
hs.re = message.ne;
|
||||
hs.re = message.ne
|
||||
}
|
||||
|
||||
this.mixHash(hs.ss, hs.re);
|
||||
this.mixHash(hs.ss, hs.re)
|
||||
if (!hs.e) {
|
||||
throw new Error("Handshake state `e` param is missing.");
|
||||
throw new Error('Handshake state `e` param is missing.')
|
||||
}
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re));
|
||||
const {plaintext: ns, valid: valid1} = this.decryptAndHash(hs.ss, message.ns);
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re))
|
||||
const { plaintext: ns, valid: valid1 } = this.decryptAndHash(hs.ss, message.ns)
|
||||
if (valid1 && ns.length === 32 && isValidPublicKey(ns)) {
|
||||
hs.rs = ns;
|
||||
hs.rs = ns
|
||||
}
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs));
|
||||
const {plaintext, valid: valid2} = this.decryptAndHash(hs.ss, message.ciphertext);
|
||||
return {plaintext, valid: (valid1 && valid2)};
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs))
|
||||
const { plaintext, valid: valid2 } = this.decryptAndHash(hs.ss, message.ciphertext)
|
||||
return { plaintext, valid: (valid1 && valid2) }
|
||||
}
|
||||
|
||||
private readMessageC(hs: HandshakeState, message: MessageBuffer): {h: bytes; plaintext: bytes; valid: boolean; cs1: CipherState; cs2: CipherState} {
|
||||
const {plaintext: ns, valid: valid1} = this.decryptAndHash(hs.ss, message.ns);
|
||||
private readMessageC (hs: HandshakeState, message: MessageBuffer): {h: bytes, plaintext: bytes, valid: boolean, cs1: CipherState, cs2: CipherState} {
|
||||
const { plaintext: ns, valid: valid1 } = this.decryptAndHash(hs.ss, message.ns)
|
||||
if (valid1 && ns.length === 32 && isValidPublicKey(ns)) {
|
||||
hs.rs = ns;
|
||||
hs.rs = ns
|
||||
}
|
||||
if (!hs.e) {
|
||||
throw new Error("Handshake state `e` param is missing.");
|
||||
throw new Error('Handshake state `e` param is missing.')
|
||||
}
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs));
|
||||
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs))
|
||||
|
||||
const {plaintext, valid: valid2} = this.decryptAndHash(hs.ss, message.ciphertext);
|
||||
const { cs1, cs2 } = this.split(hs.ss);
|
||||
const { plaintext, valid: valid2 } = this.decryptAndHash(hs.ss, message.ciphertext)
|
||||
const { cs1, cs2 } = this.split(hs.ss)
|
||||
|
||||
return { h: hs.ss.h, plaintext, valid: (valid1 && valid2), cs1, cs2 };
|
||||
return { h: hs.ss.h, plaintext, valid: (valid1 && valid2), cs1, cs2 }
|
||||
}
|
||||
|
||||
public initSession(initiator: boolean, prologue: bytes32, s: KeyPair): NoiseSession {
|
||||
const psk = this.createEmptyKey();
|
||||
const rs = Buffer.alloc(32); // no static key yet
|
||||
let hs;
|
||||
public initSession (initiator: boolean, prologue: bytes32, s: KeyPair): NoiseSession {
|
||||
const psk = this.createEmptyKey()
|
||||
const rs = Buffer.alloc(32) // no static key yet
|
||||
let hs
|
||||
|
||||
if (initiator) {
|
||||
hs = this.initializeInitiator(prologue, s, rs, psk);
|
||||
hs = this.initializeInitiator(prologue, s, rs, psk)
|
||||
} else {
|
||||
hs = this.initializeResponder(prologue, s, rs, psk);
|
||||
hs = this.initializeResponder(prologue, s, rs, psk)
|
||||
}
|
||||
|
||||
return {
|
||||
hs,
|
||||
i: initiator,
|
||||
mc: 0,
|
||||
};
|
||||
mc: 0
|
||||
}
|
||||
}
|
||||
|
||||
public sendMessage(session: NoiseSession, message: bytes, ephemeral?: KeyPair): MessageBuffer {
|
||||
let messageBuffer: MessageBuffer;
|
||||
public sendMessage (session: NoiseSession, message: bytes, ephemeral?: KeyPair): MessageBuffer {
|
||||
let messageBuffer: MessageBuffer
|
||||
if (session.mc === 0) {
|
||||
messageBuffer = this.writeMessageA(session.hs, message, ephemeral);
|
||||
messageBuffer = this.writeMessageA(session.hs, message, ephemeral)
|
||||
} else if (session.mc === 1) {
|
||||
messageBuffer = this.writeMessageB(session.hs, message);
|
||||
messageBuffer = this.writeMessageB(session.hs, message)
|
||||
} else if (session.mc === 2) {
|
||||
const { h, messageBuffer: resultingBuffer, cs1, cs2 } = this.writeMessageC(session.hs, message);
|
||||
messageBuffer = resultingBuffer;
|
||||
session.h = h;
|
||||
session.cs1 = cs1;
|
||||
session.cs2 = cs2;
|
||||
const { h, messageBuffer: resultingBuffer, cs1, cs2 } = this.writeMessageC(session.hs, message)
|
||||
messageBuffer = resultingBuffer
|
||||
session.h = h
|
||||
session.cs1 = cs1
|
||||
session.cs2 = cs2
|
||||
} else if (session.mc > 2) {
|
||||
if (session.i) {
|
||||
if (!session.cs1) {
|
||||
throw new Error("CS1 (cipher state) is not defined")
|
||||
throw new Error('CS1 (cipher state) is not defined')
|
||||
}
|
||||
|
||||
messageBuffer = this.writeMessageRegular(session.cs1, message);
|
||||
messageBuffer = this.writeMessageRegular(session.cs1, message)
|
||||
} else {
|
||||
if (!session.cs2) {
|
||||
throw new Error("CS2 (cipher state) is not defined")
|
||||
throw new Error('CS2 (cipher state) is not defined')
|
||||
}
|
||||
|
||||
messageBuffer = this.writeMessageRegular(session.cs2, message);
|
||||
messageBuffer = this.writeMessageRegular(session.cs2, message)
|
||||
}
|
||||
} else {
|
||||
throw new Error("Session invalid.")
|
||||
throw new Error('Session invalid.')
|
||||
}
|
||||
|
||||
session.mc++;
|
||||
return messageBuffer;
|
||||
session.mc++
|
||||
return messageBuffer
|
||||
}
|
||||
|
||||
public recvMessage(session: NoiseSession, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
|
||||
let plaintext: bytes = Buffer.alloc(0);
|
||||
let valid = false;
|
||||
public recvMessage (session: NoiseSession, message: MessageBuffer): {plaintext: bytes, valid: boolean} {
|
||||
let plaintext: bytes = Buffer.alloc(0)
|
||||
let valid = false
|
||||
if (session.mc === 0) {
|
||||
({plaintext, valid} = this.readMessageA(session.hs, message));
|
||||
({ plaintext, valid } = this.readMessageA(session.hs, message))
|
||||
} else if (session.mc === 1) {
|
||||
({plaintext, valid} = this.readMessageB(session.hs, message));
|
||||
({ plaintext, valid } = this.readMessageB(session.hs, message))
|
||||
} else if (session.mc === 2) {
|
||||
const { h, plaintext: resultingPlaintext, valid: resultingValid, cs1, cs2 } = this.readMessageC(session.hs, message);
|
||||
plaintext = resultingPlaintext;
|
||||
valid = resultingValid;
|
||||
session.h = h;
|
||||
session.cs1 = cs1;
|
||||
session.cs2 = cs2;
|
||||
const { h, plaintext: resultingPlaintext, valid: resultingValid, cs1, cs2 } = this.readMessageC(session.hs, message)
|
||||
plaintext = resultingPlaintext
|
||||
valid = resultingValid
|
||||
session.h = h
|
||||
session.cs1 = cs1
|
||||
session.cs2 = cs2
|
||||
}
|
||||
session.mc++;
|
||||
return {plaintext, valid};
|
||||
session.mc++
|
||||
return { plaintext, valid }
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import {Noise} from "./noise";
|
||||
export * from "./noise";
|
||||
import { Noise } from './noise'
|
||||
export * from './noise'
|
||||
|
||||
/**
|
||||
* Default configuration, it will generate new noise static key and enable noise pipes (IK handshake).
|
||||
*/
|
||||
export const NOISE = new Noise();
|
||||
export const NOISE = new Noise()
|
||||
|
@ -1,30 +1,29 @@
|
||||
import {bytes, bytes32} from "./@types/basic";
|
||||
import PeerId from "peer-id";
|
||||
import { bytes32 } from './@types/basic'
|
||||
import PeerId from 'peer-id'
|
||||
|
||||
/**
|
||||
* Storage for static keys of previously connected peers.
|
||||
*/
|
||||
class Keycache {
|
||||
private storage = new Map<bytes, bytes32>();
|
||||
private readonly storage = new Map<Uint8Array, bytes32>()
|
||||
|
||||
public store(peerId: PeerId, key: bytes32): void {
|
||||
this.storage.set(peerId.id, key);
|
||||
public store (peerId: PeerId, key: bytes32): void {
|
||||
this.storage.set(peerId.id, key)
|
||||
}
|
||||
|
||||
public load(peerId?: PeerId): bytes32 | null {
|
||||
if(!peerId) {
|
||||
return null;
|
||||
public load (peerId?: PeerId): bytes32 | null {
|
||||
if (!peerId) {
|
||||
return null
|
||||
}
|
||||
return this.storage.get(peerId.id) || null;
|
||||
return this.storage.get(peerId.id) ?? null
|
||||
}
|
||||
|
||||
public resetStorage(): void {
|
||||
this.storage.clear();
|
||||
public resetStorage (): void {
|
||||
this.storage.clear()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
const KeyCache = new Keycache();
|
||||
const KeyCache = new Keycache()
|
||||
export {
|
||||
KeyCache,
|
||||
KeyCache
|
||||
}
|
||||
|
@ -1,47 +1,44 @@
|
||||
import debug from "debug";
|
||||
import {DUMP_SESSION_KEYS} from './constants';
|
||||
import { KeyPair } from "./@types/libp2p";
|
||||
import { NoiseSession, SymmetricState } from "./@types/handshake";
|
||||
import debug from 'debug'
|
||||
import { DUMP_SESSION_KEYS } from './constants'
|
||||
import { KeyPair } from './@types/libp2p'
|
||||
import { NoiseSession } from './@types/handshake'
|
||||
|
||||
export const logger = debug('libp2p:noise');
|
||||
export const logger = debug('libp2p:noise')
|
||||
|
||||
let keyLogger;
|
||||
if(DUMP_SESSION_KEYS){
|
||||
let keyLogger
|
||||
if (DUMP_SESSION_KEYS) {
|
||||
keyLogger = logger
|
||||
}
|
||||
else {
|
||||
keyLogger = () => {}
|
||||
} else {
|
||||
keyLogger = () => { /* do nothing */ }
|
||||
}
|
||||
|
||||
export function logLocalStaticKeys(s: KeyPair): void {
|
||||
export function logLocalStaticKeys (s: KeyPair): void {
|
||||
keyLogger(`LOCAL_STATIC_PUBLIC_KEY ${s.publicKey.toString('hex')}`)
|
||||
keyLogger(`LOCAL_STATIC_PRIVATE_KEY ${s.privateKey.toString('hex')}`)
|
||||
}
|
||||
|
||||
export function logLocalEphemeralKeys(e: KeyPair|undefined): void {
|
||||
if(e){
|
||||
export function logLocalEphemeralKeys (e: KeyPair|undefined): void {
|
||||
if (e) {
|
||||
keyLogger(`LOCAL_PUBLIC_EPHEMERAL_KEY ${e.publicKey.toString('hex')}`)
|
||||
keyLogger(`LOCAL_PRIVATE_EPHEMERAL_KEY ${e.privateKey.toString('hex')}`)
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
keyLogger('Missing local ephemeral keys.')
|
||||
}
|
||||
}
|
||||
|
||||
export function logRemoteStaticKey(rs: Buffer): void {
|
||||
export function logRemoteStaticKey (rs: Buffer): void {
|
||||
keyLogger(`REMOTE_STATIC_PUBLIC_KEY ${rs.toString('hex')}`)
|
||||
}
|
||||
|
||||
export function logRemoteEphemeralKey(re: Buffer): void {
|
||||
export function logRemoteEphemeralKey (re: Buffer): void {
|
||||
keyLogger(`REMOTE_EPHEMERAL_PUBLIC_KEY ${re.toString('hex')}`)
|
||||
}
|
||||
|
||||
export function logCipherState(session: NoiseSession): void {
|
||||
if(session.cs1 && session.cs2){
|
||||
export function logCipherState (session: NoiseSession): void {
|
||||
if (session.cs1 && session.cs2) {
|
||||
keyLogger(`CIPHER_STATE_1 ${session.cs1.n} ${session.cs1.k.toString('hex')}`)
|
||||
keyLogger(`CIPHER_STATE_2 ${session.cs2.n} ${session.cs2.k.toString('hex')}`)
|
||||
}
|
||||
else{
|
||||
} else {
|
||||
keyLogger('Missing cipher state.')
|
||||
}
|
||||
}
|
||||
|
266
src/noise.ts
266
src/noise.ts
@ -1,72 +1,73 @@
|
||||
import {box} from 'tweetnacl';
|
||||
import {Buffer} from "buffer";
|
||||
import Wrap from 'it-pb-rpc';
|
||||
import DuplexPair from 'it-pair/duplex';
|
||||
import ensureBuffer from 'it-buffer';
|
||||
import pipe from 'it-pipe';
|
||||
import {encode, decode} from 'it-length-prefixed';
|
||||
import x25519 from 'bcrypto/lib/js/x25519'
|
||||
import { Buffer } from 'buffer'
|
||||
import Wrap from 'it-pb-rpc'
|
||||
import DuplexPair from 'it-pair/duplex'
|
||||
import ensureBuffer from 'it-buffer'
|
||||
import pipe from 'it-pipe'
|
||||
import { encode, decode } from 'it-length-prefixed'
|
||||
|
||||
import {XXHandshake} from "./handshake-xx";
|
||||
import {IKHandshake} from "./handshake-ik";
|
||||
import {XXFallbackHandshake} from "./handshake-xx-fallback";
|
||||
import {generateKeypair, getPayload} from "./utils";
|
||||
import {uint16BEDecode, uint16BEEncode} from "./encoder";
|
||||
import {decryptStream, encryptStream} from "./crypto";
|
||||
import {bytes} from "./@types/basic";
|
||||
import {INoiseConnection, KeyPair, SecureOutbound} from "./@types/libp2p";
|
||||
import {Duplex} from "it-pair";
|
||||
import {IHandshake} from "./@types/handshake-interface";
|
||||
import {KeyCache} from "./keycache";
|
||||
import {logger} from "./logger";
|
||||
import PeerId from "peer-id";
|
||||
import {NOISE_MSG_MAX_LENGTH_BYTES} from "./constants";
|
||||
import { XXHandshake } from './handshake-xx'
|
||||
import { IKHandshake } from './handshake-ik'
|
||||
import { XXFallbackHandshake } from './handshake-xx-fallback'
|
||||
import { generateKeypair, getPayload } from './utils'
|
||||
import { uint16BEDecode, uint16BEEncode } from './encoder'
|
||||
import { decryptStream, encryptStream } from './crypto'
|
||||
import { bytes } from './@types/basic'
|
||||
import { INoiseConnection, KeyPair, SecureOutbound } from './@types/libp2p'
|
||||
import { Duplex } from 'it-pair'
|
||||
import { IHandshake } from './@types/handshake-interface'
|
||||
import { KeyCache } from './keycache'
|
||||
import { logger } from './logger'
|
||||
import PeerId from 'peer-id'
|
||||
import { NOISE_MSG_MAX_LENGTH_BYTES } from './constants'
|
||||
|
||||
export type WrappedConnection = ReturnType<typeof Wrap>;
|
||||
export type WrappedConnection = ReturnType<typeof Wrap>
|
||||
|
||||
type HandshakeParams = {
|
||||
connection: WrappedConnection;
|
||||
isInitiator: boolean;
|
||||
localPeer: PeerId;
|
||||
remotePeer?: PeerId;
|
||||
};
|
||||
interface HandshakeParams {
|
||||
connection: WrappedConnection
|
||||
isInitiator: boolean
|
||||
localPeer: PeerId
|
||||
remotePeer?: PeerId
|
||||
}
|
||||
|
||||
export class Noise implements INoiseConnection {
|
||||
public protocol = "/noise";
|
||||
public protocol = '/noise'
|
||||
|
||||
private readonly prologue = Buffer.alloc(0);
|
||||
private readonly staticKeys: KeyPair;
|
||||
private readonly earlyData?: bytes;
|
||||
private useNoisePipes: boolean;
|
||||
private readonly prologue = Buffer.alloc(0)
|
||||
private readonly staticKeys: KeyPair
|
||||
private readonly earlyData?: bytes
|
||||
private readonly useNoisePipes: boolean
|
||||
|
||||
/**
|
||||
*
|
||||
* @param staticNoiseKey x25519 private key, reuse for faster handshakes
|
||||
* @param earlyData
|
||||
* @param {bytes} staticNoiseKey - x25519 private key, reuse for faster handshakes
|
||||
* @param {bytes} earlyData
|
||||
*/
|
||||
constructor(staticNoiseKey?: bytes, earlyData?: bytes) {
|
||||
this.earlyData = earlyData || Buffer.alloc(0);
|
||||
//disabled until properly specked
|
||||
this.useNoisePipes = false;
|
||||
constructor (staticNoiseKey?: bytes, earlyData?: bytes) {
|
||||
this.earlyData = earlyData ?? Buffer.alloc(0)
|
||||
// disabled until properly specked
|
||||
this.useNoisePipes = false
|
||||
|
||||
if (staticNoiseKey) {
|
||||
const publicKey = Buffer.from(box.keyPair.fromSecretKey(staticNoiseKey).publicKey);
|
||||
const publicKey = x25519.publicKeyCreate(staticNoiseKey)
|
||||
this.staticKeys = {
|
||||
privateKey: staticNoiseKey,
|
||||
publicKey,
|
||||
publicKey
|
||||
}
|
||||
} else {
|
||||
this.staticKeys = generateKeypair();
|
||||
this.staticKeys = generateKeypair()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt outgoing data to the remote party (handshake as initiator)
|
||||
*
|
||||
* @param {PeerId} localPeer - PeerId of the receiving peer
|
||||
* @param connection - streaming iterable duplex that will be encrypted
|
||||
* @param {any} connection - streaming iterable duplex that will be encrypted
|
||||
* @param {PeerId} remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer.
|
||||
* @returns {Promise<SecureOutbound>}
|
||||
*/
|
||||
public async secureOutbound(localPeer: PeerId, connection: any, remotePeer: PeerId): Promise<SecureOutbound> {
|
||||
public async secureOutbound (localPeer: PeerId, connection: any, remotePeer: PeerId): Promise<SecureOutbound> {
|
||||
const wrappedConnection = Wrap(
|
||||
connection,
|
||||
{
|
||||
@ -74,172 +75,173 @@ export class Noise implements INoiseConnection {
|
||||
lengthDecoder: uint16BEDecode,
|
||||
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
|
||||
}
|
||||
);
|
||||
)
|
||||
const handshake = await this.performHandshake({
|
||||
connection: wrappedConnection,
|
||||
isInitiator: true,
|
||||
localPeer,
|
||||
remotePeer,
|
||||
});
|
||||
const conn = await this.createSecureConnection(wrappedConnection, handshake);
|
||||
|
||||
return {
|
||||
conn,
|
||||
remoteEarlyData: handshake.remoteEarlyData,
|
||||
remotePeer: handshake.remotePeer,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt incoming data (handshake as responder).
|
||||
* @param {PeerId} localPeer - PeerId of the receiving peer.
|
||||
* @param connection - streaming iterable duplex that will be encryption.
|
||||
* @param {PeerId} remotePeer - optional PeerId of the initiating peer, if known. This may only exist during transport upgrades.
|
||||
* @returns {Promise<SecureOutbound>}
|
||||
*/
|
||||
public async secureInbound(localPeer: PeerId, connection: any, remotePeer?: PeerId): Promise<SecureOutbound> {
|
||||
const wrappedConnection = Wrap(
|
||||
connection,
|
||||
{
|
||||
lengthEncoder: uint16BEEncode,
|
||||
lengthDecoder: uint16BEDecode,
|
||||
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
|
||||
}
|
||||
);
|
||||
const handshake = await this.performHandshake({
|
||||
connection: wrappedConnection,
|
||||
isInitiator: false,
|
||||
localPeer,
|
||||
remotePeer
|
||||
});
|
||||
const conn = await this.createSecureConnection(wrappedConnection, handshake);
|
||||
})
|
||||
const conn = await this.createSecureConnection(wrappedConnection, handshake)
|
||||
|
||||
return {
|
||||
conn,
|
||||
remoteEarlyData: handshake.remoteEarlyData,
|
||||
remotePeer: handshake.remotePeer
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrypt incoming data (handshake as responder).
|
||||
*
|
||||
* @param {PeerId} localPeer - PeerId of the receiving peer.
|
||||
* @param {any} connection - streaming iterable duplex that will be encryption.
|
||||
* @param {PeerId} remotePeer - optional PeerId of the initiating peer, if known. This may only exist during transport upgrades.
|
||||
* @returns {Promise<SecureOutbound>}
|
||||
*/
|
||||
public async secureInbound (localPeer: PeerId, connection: any, remotePeer?: PeerId): Promise<SecureOutbound> {
|
||||
const wrappedConnection = Wrap(
|
||||
connection,
|
||||
{
|
||||
lengthEncoder: uint16BEEncode,
|
||||
lengthDecoder: uint16BEDecode,
|
||||
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
|
||||
}
|
||||
)
|
||||
const handshake = await this.performHandshake({
|
||||
connection: wrappedConnection,
|
||||
isInitiator: false,
|
||||
localPeer,
|
||||
remotePeer
|
||||
})
|
||||
const conn = await this.createSecureConnection(wrappedConnection, handshake)
|
||||
|
||||
return {
|
||||
conn,
|
||||
remoteEarlyData: handshake.remoteEarlyData,
|
||||
remotePeer: handshake.remotePeer
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If Noise pipes supported, tries IK handshake first with XX as fallback if it fails.
|
||||
* If noise pipes disabled or remote peer static key is unknown, use XX.
|
||||
* @param params
|
||||
*
|
||||
* @param {HandshakeParams} params
|
||||
*/
|
||||
private async performHandshake(params: HandshakeParams): Promise<IHandshake> {
|
||||
const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData);
|
||||
let tryIK = this.useNoisePipes;
|
||||
if(params.isInitiator && KeyCache.load(params.remotePeer) === null) {
|
||||
//if we are initiator and remote static key is unknown, don't try IK
|
||||
tryIK = false;
|
||||
private async performHandshake (params: HandshakeParams): Promise<IHandshake> {
|
||||
const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData)
|
||||
let tryIK = this.useNoisePipes
|
||||
if (params.isInitiator && KeyCache.load(params.remotePeer) === null) {
|
||||
// if we are initiator and remote static key is unknown, don't try IK
|
||||
tryIK = false
|
||||
}
|
||||
// Try IK if acting as responder or initiator that has remote's static key.
|
||||
if (tryIK) {
|
||||
// Try IK first
|
||||
const { remotePeer, connection, isInitiator } = params;
|
||||
const { remotePeer, connection, isInitiator } = params
|
||||
const ikHandshake = new IKHandshake(
|
||||
isInitiator,
|
||||
payload,
|
||||
this.prologue,
|
||||
this.staticKeys,
|
||||
connection,
|
||||
//safe to cast as we did checks
|
||||
KeyCache.load(params.remotePeer) || Buffer.alloc(32),
|
||||
remotePeer as PeerId,
|
||||
);
|
||||
// safe to cast as we did checks
|
||||
KeyCache.load(params.remotePeer) ?? Buffer.alloc(32),
|
||||
remotePeer as PeerId
|
||||
)
|
||||
|
||||
try {
|
||||
return await this.performIKHandshake(ikHandshake);
|
||||
return await this.performIKHandshake(ikHandshake)
|
||||
} catch (e) {
|
||||
// IK failed, go to XX fallback
|
||||
let ephemeralKeys;
|
||||
let ephemeralKeys
|
||||
if (params.isInitiator) {
|
||||
ephemeralKeys = ikHandshake.getLocalEphemeralKeys();
|
||||
ephemeralKeys = ikHandshake.getLocalEphemeralKeys()
|
||||
}
|
||||
return await this.performXXFallbackHandshake(params, payload, e.initialMsg, ephemeralKeys);
|
||||
return await this.performXXFallbackHandshake(params, payload, e.initialMsg, ephemeralKeys)
|
||||
}
|
||||
} else {
|
||||
// run XX handshake
|
||||
return await this.performXXHandshake(params, payload);
|
||||
return await this.performXXHandshake(params, payload)
|
||||
}
|
||||
}
|
||||
|
||||
private async performXXFallbackHandshake(
|
||||
private async performXXFallbackHandshake (
|
||||
params: HandshakeParams,
|
||||
payload: bytes,
|
||||
initialMsg: bytes,
|
||||
ephemeralKeys?: KeyPair,
|
||||
ephemeralKeys?: KeyPair
|
||||
): Promise<XXFallbackHandshake> {
|
||||
const { isInitiator, remotePeer, connection } = params;
|
||||
const { isInitiator, remotePeer, connection } = params
|
||||
const handshake =
|
||||
new XXFallbackHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, initialMsg, remotePeer, ephemeralKeys);
|
||||
new XXFallbackHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, initialMsg, remotePeer, ephemeralKeys)
|
||||
|
||||
try {
|
||||
await handshake.propose();
|
||||
await handshake.exchange();
|
||||
await handshake.finish();
|
||||
await handshake.propose()
|
||||
await handshake.exchange()
|
||||
await handshake.finish()
|
||||
} catch (e) {
|
||||
logger(e);
|
||||
throw new Error(`Error occurred during XX Fallback handshake: ${e.message}`);
|
||||
logger(e)
|
||||
const err = e as Error
|
||||
throw new Error(`Error occurred during XX Fallback handshake: ${err.message}`)
|
||||
}
|
||||
|
||||
return handshake;
|
||||
return handshake
|
||||
}
|
||||
|
||||
private async performXXHandshake(
|
||||
private async performXXHandshake (
|
||||
params: HandshakeParams,
|
||||
payload: bytes,
|
||||
payload: bytes
|
||||
): Promise<XXHandshake> {
|
||||
const { isInitiator, remotePeer, connection } = params;
|
||||
const handshake = new XXHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer);
|
||||
const { isInitiator, remotePeer, connection } = params
|
||||
const handshake = new XXHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer)
|
||||
|
||||
try {
|
||||
await handshake.propose();
|
||||
await handshake.exchange();
|
||||
await handshake.finish();
|
||||
await handshake.propose()
|
||||
await handshake.exchange()
|
||||
await handshake.finish()
|
||||
|
||||
if (this.useNoisePipes && handshake.remotePeer) {
|
||||
KeyCache.store(handshake.remotePeer, handshake.getRemoteStaticKey());
|
||||
KeyCache.store(handshake.remotePeer, handshake.getRemoteStaticKey())
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(`Error occurred during XX handshake: ${e.message}`);
|
||||
const err = e as Error
|
||||
throw new Error(`Error occurred during XX handshake: ${err.message}`)
|
||||
}
|
||||
|
||||
return handshake;
|
||||
return handshake
|
||||
}
|
||||
|
||||
private async performIKHandshake(
|
||||
handshake: IKHandshake,
|
||||
private async performIKHandshake (
|
||||
handshake: IKHandshake
|
||||
): Promise<IKHandshake> {
|
||||
await handshake.stage0()
|
||||
await handshake.stage1()
|
||||
|
||||
await handshake.stage0();
|
||||
await handshake.stage1();
|
||||
|
||||
return handshake;
|
||||
return handshake
|
||||
}
|
||||
|
||||
private async createSecureConnection(
|
||||
private async createSecureConnection (
|
||||
connection: WrappedConnection,
|
||||
handshake: IHandshake,
|
||||
handshake: IHandshake
|
||||
): Promise<Duplex> {
|
||||
// Create encryption box/unbox wrapper
|
||||
const [secure, user] = DuplexPair();
|
||||
const network = connection.unwrap();
|
||||
const [secure, user] = DuplexPair()
|
||||
const network = connection.unwrap()
|
||||
|
||||
pipe(
|
||||
await pipe(
|
||||
secure, // write to wrapper
|
||||
ensureBuffer, // ensure any type of data is converted to buffer
|
||||
encryptStream(handshake), // data is encrypted
|
||||
encode({ lengthEncoder: uint16BEEncode }), // prefix with message length
|
||||
network, // send to the remote peer
|
||||
decode({ lengthDecoder: uint16BEDecode}), // read message length prefix
|
||||
decode({ lengthDecoder: uint16BEDecode }), // read message length prefix
|
||||
ensureBuffer, // ensure any type of data is converted to buffer
|
||||
decryptStream(handshake), // decrypt the incoming data
|
||||
secure // pipe to the wrapper
|
||||
);
|
||||
)
|
||||
|
||||
return user;
|
||||
return user
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,32 +1,24 @@
|
||||
/*eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars*/
|
||||
(function(global, factory) { /* global define, require, module */
|
||||
|
||||
/* AMD */ if (typeof define === 'function' && define.amd)
|
||||
define(["protobufjs/minimal"], factory);
|
||||
|
||||
/* CommonJS */ else if (typeof require === 'function' && typeof module === 'object' && module && module.exports)
|
||||
module.exports = factory(require("protobufjs/minimal"));
|
||||
|
||||
})(this, function($protobuf) {
|
||||
"use strict";
|
||||
/* eslint-disable block-scoped-var, id-length, no-control-regex, no-magic-numbers, no-prototype-builtins, no-redeclare, no-shadow, no-var, sort-vars */
|
||||
(function (global, factory) { /* global define, require, module */
|
||||
/* AMD */ if (typeof define === 'function' && define.amd) { define(['protobufjs/minimal'], factory) }
|
||||
|
||||
/* CommonJS */ else if (typeof require === 'function' && typeof module === 'object' && module && module.exports) { module.exports = factory(require('protobufjs/minimal')) }
|
||||
})(this, function ($protobuf) {
|
||||
// Common aliases
|
||||
var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
|
||||
var $Reader = $protobuf.Reader; var $Writer = $protobuf.Writer; var $util = $protobuf.util
|
||||
|
||||
// Exported root namespace
|
||||
var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {});
|
||||
|
||||
$root.pb = (function() {
|
||||
var $root = $protobuf.roots.default || ($protobuf.roots.default = {})
|
||||
|
||||
$root.pb = (function () {
|
||||
/**
|
||||
* Namespace pb.
|
||||
* @exports pb
|
||||
* @namespace
|
||||
*/
|
||||
var pb = {};
|
||||
|
||||
pb.NoiseHandshakePayload = (function() {
|
||||
var pb = {}
|
||||
|
||||
pb.NoiseHandshakePayload = (function () {
|
||||
/**
|
||||
* Properties of a NoiseHandshakePayload.
|
||||
* @memberof pb
|
||||
@ -44,11 +36,12 @@
|
||||
* @constructor
|
||||
* @param {pb.INoiseHandshakePayload=} [properties] Properties to set
|
||||
*/
|
||||
function NoiseHandshakePayload(properties) {
|
||||
if (properties)
|
||||
for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i)
|
||||
if (properties[keys[i]] != null)
|
||||
this[keys[i]] = properties[keys[i]];
|
||||
function NoiseHandshakePayload (properties) {
|
||||
if (properties) {
|
||||
for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) {
|
||||
if (properties[keys[i]] != null) { this[keys[i]] = properties[keys[i]] }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -57,7 +50,7 @@
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
* @instance
|
||||
*/
|
||||
NoiseHandshakePayload.prototype.identityKey = $util.newBuffer([]);
|
||||
NoiseHandshakePayload.prototype.identityKey = $util.newBuffer([])
|
||||
|
||||
/**
|
||||
* NoiseHandshakePayload identitySig.
|
||||
@ -65,7 +58,7 @@
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
* @instance
|
||||
*/
|
||||
NoiseHandshakePayload.prototype.identitySig = $util.newBuffer([]);
|
||||
NoiseHandshakePayload.prototype.identitySig = $util.newBuffer([])
|
||||
|
||||
/**
|
||||
* NoiseHandshakePayload data.
|
||||
@ -73,7 +66,7 @@
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
* @instance
|
||||
*/
|
||||
NoiseHandshakePayload.prototype.data = $util.newBuffer([]);
|
||||
NoiseHandshakePayload.prototype.data = $util.newBuffer([])
|
||||
|
||||
/**
|
||||
* Creates a new NoiseHandshakePayload instance using the specified properties.
|
||||
@ -83,9 +76,9 @@
|
||||
* @param {pb.INoiseHandshakePayload=} [properties] Properties to set
|
||||
* @returns {pb.NoiseHandshakePayload} NoiseHandshakePayload instance
|
||||
*/
|
||||
NoiseHandshakePayload.create = function create(properties) {
|
||||
return new NoiseHandshakePayload(properties);
|
||||
};
|
||||
NoiseHandshakePayload.create = function create (properties) {
|
||||
return new NoiseHandshakePayload(properties)
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the specified NoiseHandshakePayload message. Does not implicitly {@link pb.NoiseHandshakePayload.verify|verify} messages.
|
||||
@ -96,17 +89,13 @@
|
||||
* @param {$protobuf.Writer} [writer] Writer to encode to
|
||||
* @returns {$protobuf.Writer} Writer
|
||||
*/
|
||||
NoiseHandshakePayload.encode = function encode(message, writer) {
|
||||
if (!writer)
|
||||
writer = $Writer.create();
|
||||
if (message.identityKey != null && message.hasOwnProperty("identityKey"))
|
||||
writer.uint32(/* id 1, wireType 2 =*/10).bytes(message.identityKey);
|
||||
if (message.identitySig != null && message.hasOwnProperty("identitySig"))
|
||||
writer.uint32(/* id 2, wireType 2 =*/18).bytes(message.identitySig);
|
||||
if (message.data != null && message.hasOwnProperty("data"))
|
||||
writer.uint32(/* id 3, wireType 2 =*/26).bytes(message.data);
|
||||
return writer;
|
||||
};
|
||||
NoiseHandshakePayload.encode = function encode (message, writer) {
|
||||
if (!writer) { writer = $Writer.create() }
|
||||
if (message.identityKey != null && message.hasOwnProperty('identityKey')) { writer.uint32(/* id 1, wireType 2 = */10).bytes(message.identityKey) }
|
||||
if (message.identitySig != null && message.hasOwnProperty('identitySig')) { writer.uint32(/* id 2, wireType 2 = */18).bytes(message.identitySig) }
|
||||
if (message.data != null && message.hasOwnProperty('data')) { writer.uint32(/* id 3, wireType 2 = */26).bytes(message.data) }
|
||||
return writer
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the specified NoiseHandshakePayload message, length delimited. Does not implicitly {@link pb.NoiseHandshakePayload.verify|verify} messages.
|
||||
@ -117,9 +106,9 @@
|
||||
* @param {$protobuf.Writer} [writer] Writer to encode to
|
||||
* @returns {$protobuf.Writer} Writer
|
||||
*/
|
||||
NoiseHandshakePayload.encodeDelimited = function encodeDelimited(message, writer) {
|
||||
return this.encode(message, writer).ldelim();
|
||||
};
|
||||
NoiseHandshakePayload.encodeDelimited = function encodeDelimited (message, writer) {
|
||||
return this.encode(message, writer).ldelim()
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a NoiseHandshakePayload message from the specified reader or buffer.
|
||||
@ -132,29 +121,28 @@
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
NoiseHandshakePayload.decode = function decode(reader, length) {
|
||||
if (!(reader instanceof $Reader))
|
||||
reader = $Reader.create(reader);
|
||||
var end = length === undefined ? reader.len : reader.pos + length, message = new $root.pb.NoiseHandshakePayload();
|
||||
NoiseHandshakePayload.decode = function decode (reader, length) {
|
||||
if (!(reader instanceof $Reader)) { reader = $Reader.create(reader) }
|
||||
var end = length === undefined ? reader.len : reader.pos + length; var message = new $root.pb.NoiseHandshakePayload()
|
||||
while (reader.pos < end) {
|
||||
var tag = reader.uint32();
|
||||
var tag = reader.uint32()
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
message.identityKey = reader.bytes();
|
||||
break;
|
||||
message.identityKey = reader.bytes()
|
||||
break
|
||||
case 2:
|
||||
message.identitySig = reader.bytes();
|
||||
break;
|
||||
message.identitySig = reader.bytes()
|
||||
break
|
||||
case 3:
|
||||
message.data = reader.bytes();
|
||||
break;
|
||||
message.data = reader.bytes()
|
||||
break
|
||||
default:
|
||||
reader.skipType(tag & 7);
|
||||
break;
|
||||
reader.skipType(tag & 7)
|
||||
break
|
||||
}
|
||||
}
|
||||
return message;
|
||||
};
|
||||
return message
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a NoiseHandshakePayload message from the specified reader or buffer, length delimited.
|
||||
@ -166,11 +154,10 @@
|
||||
* @throws {Error} If the payload is not a reader or valid buffer
|
||||
* @throws {$protobuf.util.ProtocolError} If required fields are missing
|
||||
*/
|
||||
NoiseHandshakePayload.decodeDelimited = function decodeDelimited(reader) {
|
||||
if (!(reader instanceof $Reader))
|
||||
reader = new $Reader(reader);
|
||||
return this.decode(reader, reader.uint32());
|
||||
};
|
||||
NoiseHandshakePayload.decodeDelimited = function decodeDelimited (reader) {
|
||||
if (!(reader instanceof $Reader)) { reader = new $Reader(reader) }
|
||||
return this.decode(reader, reader.uint32())
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies a NoiseHandshakePayload message.
|
||||
@ -180,20 +167,19 @@
|
||||
* @param {Object.<string,*>} message Plain object to verify
|
||||
* @returns {string|null} `null` if valid, otherwise the reason why it is not
|
||||
*/
|
||||
NoiseHandshakePayload.verify = function verify(message) {
|
||||
if (typeof message !== "object" || message === null)
|
||||
return "object expected";
|
||||
if (message.identityKey != null && message.hasOwnProperty("identityKey"))
|
||||
if (!(message.identityKey && typeof message.identityKey.length === "number" || $util.isString(message.identityKey)))
|
||||
return "identityKey: buffer expected";
|
||||
if (message.identitySig != null && message.hasOwnProperty("identitySig"))
|
||||
if (!(message.identitySig && typeof message.identitySig.length === "number" || $util.isString(message.identitySig)))
|
||||
return "identitySig: buffer expected";
|
||||
if (message.data != null && message.hasOwnProperty("data"))
|
||||
if (!(message.data && typeof message.data.length === "number" || $util.isString(message.data)))
|
||||
return "data: buffer expected";
|
||||
return null;
|
||||
};
|
||||
NoiseHandshakePayload.verify = function verify (message) {
|
||||
if (typeof message !== 'object' || message === null) { return 'object expected' }
|
||||
if (message.identityKey != null && message.hasOwnProperty('identityKey')) {
|
||||
if (!(message.identityKey && typeof message.identityKey.length === 'number' || $util.isString(message.identityKey))) { return 'identityKey: buffer expected' }
|
||||
}
|
||||
if (message.identitySig != null && message.hasOwnProperty('identitySig')) {
|
||||
if (!(message.identitySig && typeof message.identitySig.length === 'number' || $util.isString(message.identitySig))) { return 'identitySig: buffer expected' }
|
||||
}
|
||||
if (message.data != null && message.hasOwnProperty('data')) {
|
||||
if (!(message.data && typeof message.data.length === 'number' || $util.isString(message.data))) { return 'data: buffer expected' }
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a NoiseHandshakePayload message from a plain object. Also converts values to their respective internal types.
|
||||
@ -203,27 +189,20 @@
|
||||
* @param {Object.<string,*>} object Plain object
|
||||
* @returns {pb.NoiseHandshakePayload} NoiseHandshakePayload
|
||||
*/
|
||||
NoiseHandshakePayload.fromObject = function fromObject(object) {
|
||||
if (object instanceof $root.pb.NoiseHandshakePayload)
|
||||
return object;
|
||||
var message = new $root.pb.NoiseHandshakePayload();
|
||||
if (object.identityKey != null)
|
||||
if (typeof object.identityKey === "string")
|
||||
$util.base64.decode(object.identityKey, message.identityKey = $util.newBuffer($util.base64.length(object.identityKey)), 0);
|
||||
else if (object.identityKey.length)
|
||||
message.identityKey = object.identityKey;
|
||||
if (object.identitySig != null)
|
||||
if (typeof object.identitySig === "string")
|
||||
$util.base64.decode(object.identitySig, message.identitySig = $util.newBuffer($util.base64.length(object.identitySig)), 0);
|
||||
else if (object.identitySig.length)
|
||||
message.identitySig = object.identitySig;
|
||||
if (object.data != null)
|
||||
if (typeof object.data === "string")
|
||||
$util.base64.decode(object.data, message.data = $util.newBuffer($util.base64.length(object.data)), 0);
|
||||
else if (object.data.length)
|
||||
message.data = object.data;
|
||||
return message;
|
||||
};
|
||||
NoiseHandshakePayload.fromObject = function fromObject (object) {
|
||||
if (object instanceof $root.pb.NoiseHandshakePayload) { return object }
|
||||
var message = new $root.pb.NoiseHandshakePayload()
|
||||
if (object.identityKey != null) {
|
||||
if (typeof object.identityKey === 'string') { $util.base64.decode(object.identityKey, message.identityKey = $util.newBuffer($util.base64.length(object.identityKey)), 0) } else if (object.identityKey.length) { message.identityKey = object.identityKey }
|
||||
}
|
||||
if (object.identitySig != null) {
|
||||
if (typeof object.identitySig === 'string') { $util.base64.decode(object.identitySig, message.identitySig = $util.newBuffer($util.base64.length(object.identitySig)), 0) } else if (object.identitySig.length) { message.identitySig = object.identitySig }
|
||||
}
|
||||
if (object.data != null) {
|
||||
if (typeof object.data === 'string') { $util.base64.decode(object.data, message.data = $util.newBuffer($util.base64.length(object.data)), 0) } else if (object.data.length) { message.data = object.data }
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a plain object from a NoiseHandshakePayload message. Also converts values to other types if specified.
|
||||
@ -234,41 +213,28 @@
|
||||
* @param {$protobuf.IConversionOptions} [options] Conversion options
|
||||
* @returns {Object.<string,*>} Plain object
|
||||
*/
|
||||
NoiseHandshakePayload.toObject = function toObject(message, options) {
|
||||
if (!options)
|
||||
options = {};
|
||||
var object = {};
|
||||
NoiseHandshakePayload.toObject = function toObject (message, options) {
|
||||
if (!options) { options = {} }
|
||||
var object = {}
|
||||
if (options.defaults) {
|
||||
if (options.bytes === String)
|
||||
object.identityKey = "";
|
||||
else {
|
||||
object.identityKey = [];
|
||||
if (options.bytes !== Array)
|
||||
object.identityKey = $util.newBuffer(object.identityKey);
|
||||
if (options.bytes === String) { object.identityKey = '' } else {
|
||||
object.identityKey = []
|
||||
if (options.bytes !== Array) { object.identityKey = $util.newBuffer(object.identityKey) }
|
||||
}
|
||||
if (options.bytes === String)
|
||||
object.identitySig = "";
|
||||
else {
|
||||
object.identitySig = [];
|
||||
if (options.bytes !== Array)
|
||||
object.identitySig = $util.newBuffer(object.identitySig);
|
||||
if (options.bytes === String) { object.identitySig = '' } else {
|
||||
object.identitySig = []
|
||||
if (options.bytes !== Array) { object.identitySig = $util.newBuffer(object.identitySig) }
|
||||
}
|
||||
if (options.bytes === String)
|
||||
object.data = "";
|
||||
else {
|
||||
object.data = [];
|
||||
if (options.bytes !== Array)
|
||||
object.data = $util.newBuffer(object.data);
|
||||
if (options.bytes === String) { object.data = '' } else {
|
||||
object.data = []
|
||||
if (options.bytes !== Array) { object.data = $util.newBuffer(object.data) }
|
||||
}
|
||||
}
|
||||
if (message.identityKey != null && message.hasOwnProperty("identityKey"))
|
||||
object.identityKey = options.bytes === String ? $util.base64.encode(message.identityKey, 0, message.identityKey.length) : options.bytes === Array ? Array.prototype.slice.call(message.identityKey) : message.identityKey;
|
||||
if (message.identitySig != null && message.hasOwnProperty("identitySig"))
|
||||
object.identitySig = options.bytes === String ? $util.base64.encode(message.identitySig, 0, message.identitySig.length) : options.bytes === Array ? Array.prototype.slice.call(message.identitySig) : message.identitySig;
|
||||
if (message.data != null && message.hasOwnProperty("data"))
|
||||
object.data = options.bytes === String ? $util.base64.encode(message.data, 0, message.data.length) : options.bytes === Array ? Array.prototype.slice.call(message.data) : message.data;
|
||||
return object;
|
||||
};
|
||||
if (message.identityKey != null && message.hasOwnProperty('identityKey')) { object.identityKey = options.bytes === String ? $util.base64.encode(message.identityKey, 0, message.identityKey.length) : options.bytes === Array ? Array.prototype.slice.call(message.identityKey) : message.identityKey }
|
||||
if (message.identitySig != null && message.hasOwnProperty('identitySig')) { object.identitySig = options.bytes === String ? $util.base64.encode(message.identitySig, 0, message.identitySig.length) : options.bytes === Array ? Array.prototype.slice.call(message.identitySig) : message.identitySig }
|
||||
if (message.data != null && message.hasOwnProperty('data')) { object.data = options.bytes === String ? $util.base64.encode(message.data, 0, message.data.length) : options.bytes === Array ? Array.prototype.slice.call(message.data) : message.data }
|
||||
return object
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this NoiseHandshakePayload to JSON.
|
||||
@ -277,15 +243,15 @@
|
||||
* @instance
|
||||
* @returns {Object.<string,*>} JSON object
|
||||
*/
|
||||
NoiseHandshakePayload.prototype.toJSON = function toJSON() {
|
||||
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
|
||||
};
|
||||
NoiseHandshakePayload.prototype.toJSON = function toJSON () {
|
||||
return this.constructor.toObject(this, $protobuf.util.toJSONOptions)
|
||||
}
|
||||
|
||||
return NoiseHandshakePayload;
|
||||
})();
|
||||
return NoiseHandshakePayload
|
||||
})()
|
||||
|
||||
return pb;
|
||||
})();
|
||||
return pb
|
||||
})()
|
||||
|
||||
return $root;
|
||||
});
|
||||
return $root
|
||||
})
|
||||
|
126
src/utils.ts
126
src/utils.ts
@ -1,119 +1,119 @@
|
||||
import hkdf from 'futoin-hkdf';
|
||||
import {box} from 'tweetnacl';
|
||||
import {Buffer} from "buffer";
|
||||
import PeerId from "peer-id";
|
||||
import {keys} from 'libp2p-crypto';
|
||||
import {KeyPair} from "./@types/libp2p";
|
||||
import {bytes, bytes32} from "./@types/basic";
|
||||
import {Hkdf, INoisePayload} from "./@types/handshake";
|
||||
import {pb} from "./proto/payload";
|
||||
import HKDF from 'bcrypto/lib/hkdf'
|
||||
import x25519 from 'bcrypto/lib/js/x25519'
|
||||
import SHA256 from 'bcrypto/lib/js/sha256'
|
||||
import { Buffer } from 'buffer'
|
||||
import PeerId from 'peer-id'
|
||||
import { keys } from 'libp2p-crypto'
|
||||
import { KeyPair } from './@types/libp2p'
|
||||
import { bytes, bytes32 } from './@types/basic'
|
||||
import { Hkdf, INoisePayload } from './@types/handshake'
|
||||
import { pb } from './proto/payload'
|
||||
import uint8ArrayEquals from 'uint8arrays/equals'
|
||||
|
||||
const NoiseHandshakePayloadProto = pb.NoiseHandshakePayload;
|
||||
const NoiseHandshakePayloadProto = pb.NoiseHandshakePayload
|
||||
|
||||
export function generateKeypair(): KeyPair {
|
||||
const keyPair = box.keyPair();
|
||||
const publicKey = Buffer.from(keyPair.publicKey);
|
||||
const privateKey = Buffer.from(keyPair.secretKey);
|
||||
export function generateKeypair (): KeyPair {
|
||||
const privateKey = x25519.privateKeyGenerate()
|
||||
const publicKey = x25519.publicKeyCreate(privateKey)
|
||||
|
||||
return {
|
||||
publicKey,
|
||||
privateKey,
|
||||
privateKey
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPayload(
|
||||
export async function getPayload (
|
||||
localPeer: PeerId,
|
||||
staticPublicKey: bytes,
|
||||
earlyData?: bytes,
|
||||
earlyData?: bytes
|
||||
): Promise<bytes> {
|
||||
const signedPayload = await signPayload(localPeer, getHandshakePayload(staticPublicKey));
|
||||
const earlyDataPayload = earlyData || Buffer.alloc(0);
|
||||
const signedPayload = await signPayload(localPeer, getHandshakePayload(staticPublicKey))
|
||||
const earlyDataPayload = earlyData ?? Buffer.alloc(0)
|
||||
|
||||
return await createHandshakePayload(
|
||||
return createHandshakePayload(
|
||||
localPeer.marshalPubKey(),
|
||||
signedPayload,
|
||||
earlyDataPayload
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function createHandshakePayload(
|
||||
libp2pPublicKey: bytes,
|
||||
signedPayload: bytes,
|
||||
earlyData?: bytes,
|
||||
): Promise<bytes> {
|
||||
|
||||
export function createHandshakePayload (
|
||||
libp2pPublicKey: Uint8Array,
|
||||
signedPayload: Uint8Array,
|
||||
earlyData?: Uint8Array
|
||||
): bytes {
|
||||
const payloadInit = NoiseHandshakePayloadProto.create({
|
||||
identityKey: libp2pPublicKey,
|
||||
identityKey: Buffer.from(libp2pPublicKey),
|
||||
identitySig: signedPayload,
|
||||
data: earlyData || null,
|
||||
});
|
||||
data: earlyData ?? null
|
||||
})
|
||||
|
||||
return Buffer.from(NoiseHandshakePayloadProto.encode(payloadInit).finish());
|
||||
return Buffer.from(NoiseHandshakePayloadProto.encode(payloadInit).finish())
|
||||
}
|
||||
|
||||
|
||||
export async function signPayload(peerId: PeerId, payload: bytes): Promise<bytes> {
|
||||
return peerId.privKey.sign(payload);
|
||||
export async function signPayload (peerId: PeerId, payload: bytes): Promise<bytes> {
|
||||
return Buffer.from(await peerId.privKey.sign(payload))
|
||||
}
|
||||
|
||||
export async function getPeerIdFromPayload(payload: pb.INoiseHandshakePayload): Promise<PeerId> {
|
||||
return await PeerId.createFromPubKey(Buffer.from(payload.identityKey as Uint8Array));
|
||||
export async function getPeerIdFromPayload (payload: pb.INoiseHandshakePayload): Promise<PeerId> {
|
||||
return await PeerId.createFromPubKey(Buffer.from(payload.identityKey as Uint8Array))
|
||||
}
|
||||
|
||||
export async function decodePayload(payload: bytes|Uint8Array): Promise<pb.INoiseHandshakePayload> {
|
||||
export function decodePayload (payload: bytes|Uint8Array): pb.INoiseHandshakePayload {
|
||||
return NoiseHandshakePayloadProto.toObject(
|
||||
NoiseHandshakePayloadProto.decode(Buffer.from(payload))
|
||||
) as INoisePayload;
|
||||
) as INoisePayload
|
||||
}
|
||||
|
||||
export function getHandshakePayload(publicKey: bytes): bytes {
|
||||
return Buffer.concat([Buffer.from("noise-libp2p-static-key:"), publicKey]);
|
||||
export function getHandshakePayload (publicKey: bytes): bytes {
|
||||
return Buffer.concat([Buffer.from('noise-libp2p-static-key:'), publicKey])
|
||||
}
|
||||
|
||||
async function isValidPeerId(peerId: bytes, publicKeyProtobuf: bytes) {
|
||||
const generatedPeerId = await PeerId.createFromPubKey(publicKeyProtobuf);
|
||||
return generatedPeerId.id.equals(peerId);
|
||||
async function isValidPeerId (peerId: Uint8Array, publicKeyProtobuf: bytes) {
|
||||
const generatedPeerId = await PeerId.createFromPubKey(publicKeyProtobuf)
|
||||
return uint8ArrayEquals(generatedPeerId.id, peerId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies signed payload, throws on any irregularities.
|
||||
*
|
||||
* @param {bytes} noiseStaticKey - owner's noise static key
|
||||
* @param {bytes} payload - decoded payload
|
||||
* @param {PeerId} remotePeer - owner's libp2p peer ID
|
||||
* @returns {Promise<PeerId>} - peer ID of payload owner
|
||||
*/
|
||||
export async function verifySignedPayload(
|
||||
export async function verifySignedPayload (
|
||||
noiseStaticKey: bytes,
|
||||
payload: pb.INoiseHandshakePayload,
|
||||
remotePeer: PeerId
|
||||
): Promise<PeerId> {
|
||||
const identityKey = Buffer.from(payload.identityKey as Uint8Array);
|
||||
const identityKey = Buffer.from(payload.identityKey as Uint8Array)
|
||||
if (!(await isValidPeerId(remotePeer.id, identityKey))) {
|
||||
throw new Error("Peer ID doesn't match libp2p public key.");
|
||||
throw new Error("Peer ID doesn't match libp2p public key.")
|
||||
}
|
||||
const generatedPayload = getHandshakePayload(noiseStaticKey);
|
||||
const generatedPayload = getHandshakePayload(noiseStaticKey)
|
||||
// Unmarshaling from PublicKey protobuf
|
||||
const publicKey = keys.unmarshalPublicKey(identityKey);
|
||||
if (!publicKey.verify(generatedPayload, payload.identitySig as Buffer)) {
|
||||
throw new Error("Static key doesn't match to peer that signed payload!");
|
||||
const publicKey = keys.unmarshalPublicKey(identityKey)
|
||||
// TODO remove this after libp2p-crypto ships proper types
|
||||
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||
if (!payload.identitySig || !publicKey.verify(generatedPayload, Buffer.from(payload.identitySig))) {
|
||||
throw new Error("Static key doesn't match to peer that signed payload!")
|
||||
}
|
||||
return remotePeer;
|
||||
return await PeerId.createFromPubKey(identityKey)
|
||||
}
|
||||
|
||||
export function getHkdf(ck: bytes32, ikm: bytes): Hkdf {
|
||||
const okm = hkdf(ikm, 96, {salt: ck, hash: 'SHA-256'});
|
||||
export function getHkdf (ck: bytes32, ikm: bytes): Hkdf {
|
||||
const info = Buffer.alloc(0)
|
||||
const prk = HKDF.extract(SHA256, ikm, ck)
|
||||
const okm = HKDF.expand(SHA256, prk, info, 96)
|
||||
|
||||
const k1 = okm.slice(0, 32);
|
||||
const k2 = okm.slice(32, 64);
|
||||
const k3 = okm.slice(64, 96);
|
||||
const k1 = okm.slice(0, 32)
|
||||
const k2 = okm.slice(32, 64)
|
||||
const k3 = okm.slice(64, 96)
|
||||
|
||||
return [k1, k2, k3];
|
||||
return [k1, k2, k3]
|
||||
}
|
||||
|
||||
export function isValidPublicKey(pk: bytes): boolean {
|
||||
if(pk.length !== 32 || pk.equals(Buffer.alloc(32))){
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
export function isValidPublicKey (pk: bytes): boolean {
|
||||
return x25519.publicKeyVerify(pk.slice(0, 32))
|
||||
}
|
||||
|
2
test/fixtures/node-globals.js
vendored
Normal file
2
test/fixtures/node-globals.js
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
// @ts-nocheck
|
||||
export const { Buffer } = require('buffer')
|
20
test/fixtures/peer.ts
vendored
20
test/fixtures/peer.ts
vendored
@ -1,4 +1,4 @@
|
||||
import PeerId from 'peer-id';
|
||||
import PeerId from 'peer-id'
|
||||
|
||||
// ed25519 keys
|
||||
const peers = [{
|
||||
@ -17,20 +17,20 @@ const peers = [{
|
||||
id: '12D3KooWPCofiCjhdtezP4eMnqBjjutFZNHjV39F5LWNrCvaLnzT',
|
||||
privKey: 'CAESYLhUut01XPu+yIPbtZ3WnxOd26FYuTMRn/BbdFYsZE2KxueKRlo9yIAxmFReoNFUKztUU4G2aUiTbqDQaA6i0MDG54pGWj3IgDGYVF6g0VQrO1RTgbZpSJNuoNBoDqLQwA==',
|
||||
pubKey: 'CAESIMbnikZaPciAMZhUXqDRVCs7VFOBtmlIk26g0GgOotDA'
|
||||
}];
|
||||
}]
|
||||
|
||||
export async function createPeerIdsFromFixtures (length) {
|
||||
return Promise.all(
|
||||
Array.from({ length }).map((_, i) => PeerId.createFromJSON(peers[i]))
|
||||
export async function createPeerIdsFromFixtures (length: number): Promise<PeerId[]> {
|
||||
return await Promise.all(
|
||||
Array.from({ length }).map(async (_, i) => await PeerId.createFromJSON(peers[i]))
|
||||
)
|
||||
}
|
||||
|
||||
export async function createPeerIds (length) {
|
||||
const peerIds: any[] = [];
|
||||
export async function createPeerIds (length: number): Promise<PeerId[]> {
|
||||
const peerIds: PeerId[] = []
|
||||
for (let i = 0; i < length; i++) {
|
||||
const id = await PeerId.create({ keyType: 'ed25519', bits: 256 });
|
||||
peerIds.push(id);
|
||||
const id = await PeerId.create({ keyType: 'Ed25519', bits: 256 })
|
||||
peerIds.push(id)
|
||||
}
|
||||
|
||||
return peerIds;
|
||||
return peerIds
|
||||
}
|
||||
|
65
test/handshakes/ik.spec.ts
Normal file
65
test/handshakes/ik.spec.ts
Normal file
@ -0,0 +1,65 @@
|
||||
import { Buffer } from 'buffer'
|
||||
import { IK } from '../../src/handshakes/ik'
|
||||
import { KeyPair } from '../../src/@types/libp2p'
|
||||
import { createHandshakePayload, generateKeypair, getHandshakePayload } from '../../src/utils'
|
||||
import { assert, expect } from 'chai'
|
||||
import { generateEd25519Keys } from '../utils'
|
||||
|
||||
describe('IK handshake', () => {
|
||||
const prologue = Buffer.alloc(0)
|
||||
|
||||
it('Test complete IK handshake', async () => {
|
||||
try {
|
||||
const ikI = new IK()
|
||||
const ikR = new IK()
|
||||
|
||||
// Generate static noise keys
|
||||
const kpInitiator: KeyPair = await generateKeypair()
|
||||
const kpResponder: KeyPair = await generateKeypair()
|
||||
|
||||
// Generate libp2p keys
|
||||
const libp2pInitKeys = await generateEd25519Keys()
|
||||
const libp2pRespKeys = await generateEd25519Keys()
|
||||
|
||||
// Create sessions
|
||||
const initiatorSession = await ikI.initSession(true, prologue, kpInitiator, kpResponder.publicKey)
|
||||
const responderSession = await ikR.initSession(false, prologue, kpResponder, Buffer.alloc(32))
|
||||
|
||||
/* Stage 0 */
|
||||
|
||||
// initiator creates payload
|
||||
const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInitiator.publicKey))
|
||||
libp2pInitKeys.marshal().slice(0, 32)
|
||||
const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64)
|
||||
const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload)
|
||||
|
||||
// initiator sends message
|
||||
const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc])
|
||||
const messageBuffer = ikI.sendMessage(initiatorSession, message)
|
||||
|
||||
expect(messageBuffer.ne.length).not.equal(0)
|
||||
|
||||
// responder receives message
|
||||
ikR.recvMessage(responderSession, messageBuffer)
|
||||
|
||||
/* Stage 1 */
|
||||
|
||||
// responder creates payload
|
||||
libp2pRespKeys.marshal().slice(0, 32)
|
||||
const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64)
|
||||
const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResponder.publicKey))
|
||||
const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, respSignedPayload)
|
||||
|
||||
const message1 = Buffer.concat([message, payloadRespEnc])
|
||||
const messageBuffer2 = ikR.sendMessage(responderSession, message1)
|
||||
|
||||
// initiator receives message
|
||||
ikI.recvMessage(initiatorSession, messageBuffer2)
|
||||
|
||||
assert(initiatorSession?.cs1?.k.equals(responderSession?.cs1?.k ?? new Uint8Array()))
|
||||
assert(initiatorSession?.cs2?.k.equals(responderSession?.cs2?.k ?? new Uint8Array()))
|
||||
} catch (e) {
|
||||
return assert(false, e.message)
|
||||
}
|
||||
})
|
||||
})
|
@ -1,67 +0,0 @@
|
||||
import {Buffer} from "buffer";
|
||||
import {IK} from "../../src/handshakes/ik";
|
||||
import {KeyPair} from "../../src/@types/libp2p";
|
||||
import {createHandshakePayload, generateKeypair, getHandshakePayload} from "../../src/utils";
|
||||
import {assert, expect} from "chai";
|
||||
import {generateEd25519Keys} from "../utils";
|
||||
|
||||
describe("IK handshake", () => {
|
||||
const prologue = Buffer.alloc(0);
|
||||
|
||||
it("Test complete IK handshake", async () => {
|
||||
try {
|
||||
const ikI = new IK();
|
||||
const ikR = new IK();
|
||||
|
||||
// Generate static noise keys
|
||||
const kpInitiator: KeyPair = await generateKeypair();
|
||||
const kpResponder: KeyPair = await generateKeypair();
|
||||
|
||||
// Generate libp2p keys
|
||||
const libp2pInitKeys = await generateEd25519Keys();
|
||||
const libp2pRespKeys = await generateEd25519Keys();
|
||||
|
||||
// Create sessions
|
||||
const initiatorSession = await ikI.initSession(true, prologue, kpInitiator, kpResponder.publicKey);
|
||||
const responderSession = await ikR.initSession(false, prologue, kpResponder, Buffer.alloc(32));
|
||||
|
||||
/* Stage 0 */
|
||||
|
||||
// initiator creates payload
|
||||
const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInitiator.publicKey));
|
||||
const libp2pInitPrivKey = libp2pInitKeys.marshal().slice(0, 32);
|
||||
const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64);
|
||||
const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload);
|
||||
|
||||
// initiator sends message
|
||||
const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc]);
|
||||
const messageBuffer = ikI.sendMessage(initiatorSession, message);
|
||||
|
||||
expect(messageBuffer.ne.length).not.equal(0);
|
||||
|
||||
// responder receives message
|
||||
const plaintext = ikR.recvMessage(responderSession, messageBuffer);
|
||||
|
||||
/* Stage 1 */
|
||||
|
||||
// responder creates payload
|
||||
const libp2pRespPrivKey = libp2pRespKeys.marshal().slice(0, 32);
|
||||
const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64);
|
||||
const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResponder.publicKey));
|
||||
const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, respSignedPayload);
|
||||
|
||||
const message1 = Buffer.concat([message, payloadRespEnc]);
|
||||
const messageBuffer2 = ikR.sendMessage(responderSession, message1);
|
||||
|
||||
// initiator receives message
|
||||
const plaintext2 = ikI.recvMessage(initiatorSession, messageBuffer2);
|
||||
|
||||
assert(initiatorSession.cs1.k.equals(responderSession.cs1.k));
|
||||
assert(initiatorSession.cs2.k.equals(responderSession.cs2.k));
|
||||
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
return assert(false, e.message);
|
||||
}
|
||||
});
|
||||
});
|
143
test/handshakes/xx.spec.ts
Normal file
143
test/handshakes/xx.spec.ts
Normal file
@ -0,0 +1,143 @@
|
||||
import { expect, assert } from 'chai'
|
||||
import { Buffer } from 'buffer'
|
||||
|
||||
import { XX } from '../../src/handshakes/xx'
|
||||
import { KeyPair } from '../../src/@types/libp2p'
|
||||
import { generateEd25519Keys } from '../utils'
|
||||
import { createHandshakePayload, generateKeypair, getHandshakePayload, getHkdf } from '../../src/utils'
|
||||
|
||||
describe('XX Handshake', () => {
|
||||
const prologue = Buffer.alloc(0)
|
||||
|
||||
it('Test creating new XX session', async () => {
|
||||
try {
|
||||
const xx = new XX()
|
||||
|
||||
const kpInitiator: KeyPair = await generateKeypair()
|
||||
await generateKeypair()
|
||||
|
||||
await xx.initSession(true, prologue, kpInitiator)
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
it('Test get HKDF', () => {
|
||||
const ckBytes = Buffer.from('4e6f6973655f58585f32353531395f58436861436861506f6c795f53484132353600000000000000000000000000000000000000000000000000000000000000', 'hex')
|
||||
const ikm = Buffer.from('a3eae50ea37a47e8a7aa0c7cd8e16528670536dcd538cebfd724fb68ce44f1910ad898860666227d4e8dd50d22a9a64d1c0a6f47ace092510161e9e442953da3', 'hex')
|
||||
const ck = Buffer.alloc(32)
|
||||
ckBytes.copy(ck)
|
||||
|
||||
const [k1, k2, k3] = getHkdf(ck, ikm)
|
||||
expect(k1.toString('hex')).to.equal('cc5659adff12714982f806e2477a8d5ddd071def4c29bb38777b7e37046f6914')
|
||||
expect(k2.toString('hex')).to.equal('a16ada915e551ab623f38be674bb4ef15d428ae9d80688899c9ef9b62ef208fa')
|
||||
expect(k3.toString('hex')).to.equal('ff67bf9727e31b06efc203907e6786667d2c7a74ac412b4d31a80ba3fd766f68')
|
||||
})
|
||||
|
||||
async function doHandshake (xx) {
|
||||
const kpInit = await generateKeypair()
|
||||
const kpResp = await generateKeypair()
|
||||
|
||||
// initiator setup
|
||||
const libp2pInitKeys = await generateEd25519Keys()
|
||||
const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInit.publicKey))
|
||||
|
||||
// responder setup
|
||||
const libp2pRespKeys = await generateEd25519Keys()
|
||||
const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResp.publicKey))
|
||||
|
||||
// initiator: new XX noise session
|
||||
const nsInit = xx.initSession(true, prologue, kpInit)
|
||||
// responder: new XX noise session
|
||||
const nsResp = xx.initSession(false, prologue, kpResp)
|
||||
|
||||
/* STAGE 0 */
|
||||
|
||||
// initiator creates payload
|
||||
libp2pInitKeys.marshal().slice(0, 32)
|
||||
const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64)
|
||||
|
||||
const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload)
|
||||
|
||||
// initiator sends message
|
||||
const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc])
|
||||
const messageBuffer = xx.sendMessage(nsInit, message)
|
||||
|
||||
expect(messageBuffer.ne.length).not.equal(0)
|
||||
|
||||
// responder receives message
|
||||
xx.recvMessage(nsResp, messageBuffer)
|
||||
|
||||
/* STAGE 1 */
|
||||
|
||||
// responder creates payload
|
||||
libp2pRespKeys.marshal().slice(0, 32)
|
||||
const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64)
|
||||
const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, respSignedPayload)
|
||||
|
||||
const message1 = Buffer.concat([message, payloadRespEnc])
|
||||
const messageBuffer2 = xx.sendMessage(nsResp, message1)
|
||||
|
||||
expect(messageBuffer2.ne.length).not.equal(0)
|
||||
expect(messageBuffer2.ns.length).not.equal(0)
|
||||
|
||||
// initiator receive payload
|
||||
xx.recvMessage(nsInit, messageBuffer2)
|
||||
|
||||
/* STAGE 2 */
|
||||
|
||||
// initiator send message
|
||||
const messageBuffer3 = xx.sendMessage(nsInit, Buffer.alloc(0))
|
||||
|
||||
// responder receive message
|
||||
xx.recvMessage(nsResp, messageBuffer3)
|
||||
|
||||
assert(nsInit.cs1.k.equals(nsResp.cs1.k))
|
||||
assert(nsInit.cs2.k.equals(nsResp.cs2.k))
|
||||
|
||||
return { nsInit, nsResp }
|
||||
}
|
||||
|
||||
it('Test handshake', async () => {
|
||||
try {
|
||||
const xx = new XX()
|
||||
await doHandshake(xx)
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
it('Test symmetric encrypt and decrypt', async () => {
|
||||
try {
|
||||
const xx = new XX()
|
||||
const { nsInit, nsResp } = await doHandshake(xx)
|
||||
const ad = Buffer.from('authenticated')
|
||||
const message = Buffer.from('HelloCrypto')
|
||||
|
||||
const ciphertext = xx.encryptWithAd(nsInit.cs1, ad, message)
|
||||
assert(!Buffer.from('HelloCrypto').equals(ciphertext), 'Encrypted message should not be same as plaintext.')
|
||||
const { plaintext: decrypted, valid } = xx.decryptWithAd(nsResp.cs1, ad, ciphertext)
|
||||
|
||||
assert(Buffer.from('HelloCrypto').equals(decrypted), 'Decrypted text not equal to original message.')
|
||||
assert(valid)
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
it('Test multiple messages encryption and decryption', async () => {
|
||||
const xx = new XX()
|
||||
const { nsInit, nsResp } = await doHandshake(xx)
|
||||
const ad = Buffer.from('authenticated')
|
||||
const message = Buffer.from('ethereum1')
|
||||
|
||||
const encrypted = xx.encryptWithAd(nsInit.cs1, ad, message)
|
||||
const { plaintext: decrypted } = xx.decryptWithAd(nsResp.cs1, ad, encrypted)
|
||||
assert.equal('ethereum1', decrypted.toString('utf8'), 'Decrypted text not equal to original message.')
|
||||
|
||||
const message2 = Buffer.from('ethereum2')
|
||||
const encrypted2 = xx.encryptWithAd(nsInit.cs1, ad, message2)
|
||||
const { plaintext: decrypted2 } = xx.decryptWithAd(nsResp.cs1, ad, encrypted2)
|
||||
assert.equal('ethereum2', decrypted2.toString('utf-8'), 'Decrypted text not equal to original message.')
|
||||
})
|
||||
})
|
@ -1,143 +0,0 @@
|
||||
import { expect, assert } from "chai";
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
import { XX } from "../../src/handshakes/xx";
|
||||
import { KeyPair } from "../../src/@types/libp2p";
|
||||
import { generateEd25519Keys } from "../utils";
|
||||
import {createHandshakePayload, generateKeypair, getHandshakePayload, getHkdf} from "../../src/utils";
|
||||
|
||||
describe("XX Handshake", () => {
|
||||
const prologue = Buffer.alloc(0);
|
||||
|
||||
it("Test creating new XX session", async () => {
|
||||
try {
|
||||
const xx = new XX();
|
||||
|
||||
const kpInitiator: KeyPair = await generateKeypair();
|
||||
const kpResponder: KeyPair = await generateKeypair();
|
||||
|
||||
const session = await xx.initSession(true, prologue, kpInitiator);
|
||||
} catch (e) {
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
it("Test get HKDF", async () => {
|
||||
const ckBytes = Buffer.from('4e6f6973655f58585f32353531395f58436861436861506f6c795f53484132353600000000000000000000000000000000000000000000000000000000000000', 'hex');
|
||||
const ikm = Buffer.from('a3eae50ea37a47e8a7aa0c7cd8e16528670536dcd538cebfd724fb68ce44f1910ad898860666227d4e8dd50d22a9a64d1c0a6f47ace092510161e9e442953da3', 'hex');
|
||||
const ck = Buffer.alloc(32);
|
||||
ckBytes.copy(ck);
|
||||
|
||||
const [k1, k2, k3] = getHkdf(ck, ikm);
|
||||
expect(k1.toString('hex')).to.equal('cc5659adff12714982f806e2477a8d5ddd071def4c29bb38777b7e37046f6914');
|
||||
expect(k2.toString('hex')).to.equal('a16ada915e551ab623f38be674bb4ef15d428ae9d80688899c9ef9b62ef208fa');
|
||||
expect(k3.toString('hex')).to.equal('ff67bf9727e31b06efc203907e6786667d2c7a74ac412b4d31a80ba3fd766f68');
|
||||
});
|
||||
|
||||
async function doHandshake(xx) {
|
||||
const kpInit = await generateKeypair();
|
||||
const kpResp = await generateKeypair();
|
||||
|
||||
// initiator setup
|
||||
const libp2pInitKeys = await generateEd25519Keys();
|
||||
const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInit.publicKey));
|
||||
|
||||
// responder setup
|
||||
const libp2pRespKeys = await generateEd25519Keys();
|
||||
const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResp.publicKey));
|
||||
|
||||
// initiator: new XX noise session
|
||||
const nsInit = xx.initSession(true, prologue, kpInit);
|
||||
// responder: new XX noise session
|
||||
const nsResp = xx.initSession(false, prologue, kpResp);
|
||||
|
||||
/* STAGE 0 */
|
||||
|
||||
// initiator creates payload
|
||||
const libp2pInitPrivKey = libp2pInitKeys.marshal().slice(0, 32);
|
||||
const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64);
|
||||
|
||||
const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload);
|
||||
|
||||
// initiator sends message
|
||||
const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc]);
|
||||
const messageBuffer = xx.sendMessage(nsInit, message);
|
||||
|
||||
expect(messageBuffer.ne.length).not.equal(0);
|
||||
|
||||
// responder receives message
|
||||
xx.recvMessage(nsResp, messageBuffer);
|
||||
|
||||
/* STAGE 1 */
|
||||
|
||||
// responder creates payload
|
||||
const libp2pRespPrivKey = libp2pRespKeys.marshal().slice(0, 32);
|
||||
const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64);
|
||||
const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, respSignedPayload);
|
||||
|
||||
const message1 = Buffer.concat([message, payloadRespEnc]);
|
||||
const messageBuffer2 = xx.sendMessage(nsResp, message1);
|
||||
|
||||
expect(messageBuffer2.ne.length).not.equal(0);
|
||||
expect(messageBuffer2.ns.length).not.equal(0);
|
||||
|
||||
// initiator receive payload
|
||||
xx.recvMessage(nsInit, messageBuffer2);
|
||||
|
||||
/* STAGE 2 */
|
||||
|
||||
// initiator send message
|
||||
const messageBuffer3 = xx.sendMessage(nsInit, Buffer.alloc(0));
|
||||
|
||||
// responder receive message
|
||||
xx.recvMessage(nsResp, messageBuffer3);
|
||||
|
||||
assert(nsInit.cs1.k.equals(nsResp.cs1.k));
|
||||
assert(nsInit.cs2.k.equals(nsResp.cs2.k));
|
||||
|
||||
return { nsInit, nsResp };
|
||||
}
|
||||
|
||||
it("Test handshake", async () => {
|
||||
try {
|
||||
const xx = new XX();
|
||||
await doHandshake(xx);
|
||||
} catch (e) {
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
it("Test symmetric encrypt and decrypt", async () => {
|
||||
try {
|
||||
const xx = new XX();
|
||||
const { nsInit, nsResp } = await doHandshake(xx);
|
||||
const ad = Buffer.from("authenticated");
|
||||
const message = Buffer.from("HelloCrypto");
|
||||
|
||||
const ciphertext = xx.encryptWithAd(nsInit.cs1, ad, message);
|
||||
assert(!Buffer.from("HelloCrypto").equals(ciphertext), "Encrypted message should not be same as plaintext.");
|
||||
const {plaintext: decrypted, valid} = xx.decryptWithAd(nsResp.cs1, ad, ciphertext);
|
||||
|
||||
assert(Buffer.from("HelloCrypto").equals(decrypted), "Decrypted text not equal to original message.");
|
||||
assert(valid);
|
||||
} catch (e) {
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
it("Test multiple messages encryption and decryption", async () => {
|
||||
const xx = new XX();
|
||||
const { nsInit, nsResp } = await doHandshake(xx);
|
||||
const ad = Buffer.from("authenticated");
|
||||
const message = Buffer.from("ethereum1");
|
||||
|
||||
const encrypted = xx.encryptWithAd(nsInit.cs1, ad, message);
|
||||
const {plaintext: decrypted} = xx.decryptWithAd(nsResp.cs1, ad, encrypted);
|
||||
assert.equal("ethereum1", decrypted.toString("utf8"), "Decrypted text not equal to original message.");
|
||||
|
||||
const message2 = Buffer.from("ethereum2");
|
||||
const encrypted2 = xx.encryptWithAd(nsInit.cs1, ad, message2);
|
||||
const {plaintext: decrypted2} = xx.decryptWithAd(nsResp.cs1, ad, encrypted2);
|
||||
assert.equal("ethereum2", decrypted2.toString("utf-8"), "Decrypted text not equal to original message.");
|
||||
});
|
||||
});
|
79
test/ik-handshake.spec.ts
Normal file
79
test/ik-handshake.spec.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import Wrap from 'it-pb-rpc'
|
||||
import Duplex from 'it-pair/duplex'
|
||||
import { Buffer } from 'buffer'
|
||||
import { assert, expect } from 'chai'
|
||||
|
||||
import { createPeerIdsFromFixtures } from './fixtures/peer'
|
||||
import { generateKeypair, getPayload } from '../src/utils'
|
||||
import { IKHandshake } from '../src/handshake-ik'
|
||||
|
||||
describe('IK Handshake', () => {
|
||||
let peerA, peerB
|
||||
|
||||
before(async () => {
|
||||
[peerA, peerB] = await createPeerIdsFromFixtures(3)
|
||||
})
|
||||
|
||||
it('should finish both stages as initiator and responder', async () => {
|
||||
try {
|
||||
const duplex = Duplex()
|
||||
const connectionFrom = Wrap(duplex[0])
|
||||
const connectionTo = Wrap(duplex[1])
|
||||
|
||||
const prologue = Buffer.alloc(0)
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const staticKeysResponder = generateKeypair()
|
||||
|
||||
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
|
||||
const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, staticKeysResponder.publicKey, peerB)
|
||||
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
|
||||
const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey)
|
||||
|
||||
await handshakeInit.stage0()
|
||||
await handshakeResp.stage0()
|
||||
|
||||
await handshakeResp.stage1()
|
||||
await handshakeInit.stage1()
|
||||
|
||||
// Test shared key
|
||||
if (handshakeInit.session.cs1 && handshakeResp.session.cs1 && handshakeInit.session.cs2 && handshakeResp.session.cs2) {
|
||||
assert(handshakeInit.session.cs1.k.equals(handshakeResp.session.cs1.k))
|
||||
assert(handshakeInit.session.cs2.k.equals(handshakeResp.session.cs2.k))
|
||||
} else {
|
||||
assert(false)
|
||||
}
|
||||
|
||||
// Test encryption and decryption
|
||||
const encrypted = handshakeInit.encrypt(Buffer.from('encryptthis'), handshakeInit.session)
|
||||
const { plaintext: decrypted } = handshakeResp.decrypt(encrypted, handshakeResp.session)
|
||||
assert(decrypted.equals(Buffer.from('encryptthis')))
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw error if responder's static key changed", async () => {
|
||||
try {
|
||||
const duplex = Duplex()
|
||||
const connectionFrom = Wrap(duplex[0])
|
||||
const connectionTo = Wrap(duplex[1])
|
||||
|
||||
const prologue = Buffer.alloc(0)
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const staticKeysResponder = generateKeypair()
|
||||
const oldScammyKeys = generateKeypair()
|
||||
|
||||
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
|
||||
const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, oldScammyKeys.publicKey, peerB)
|
||||
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
|
||||
const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey)
|
||||
|
||||
await handshakeInit.stage0()
|
||||
await handshakeResp.stage0()
|
||||
} catch (e) {
|
||||
expect(e.message).to.include("Error occurred while verifying initiator's signed payload")
|
||||
}
|
||||
})
|
||||
})
|
@ -1,80 +0,0 @@
|
||||
import Wrap from "it-pb-rpc";
|
||||
import Duplex from 'it-pair/duplex';
|
||||
import {Buffer} from "buffer";
|
||||
import {assert, expect} from "chai";
|
||||
|
||||
import {createPeerIdsFromFixtures} from "./fixtures/peer";
|
||||
import {generateKeypair, getPayload} from "../src/utils";
|
||||
import {IKHandshake} from "../src/handshake-ik";
|
||||
|
||||
describe("IK Handshake", () => {
|
||||
let peerA, peerB, fakePeer;
|
||||
|
||||
before(async () => {
|
||||
[peerA, peerB, fakePeer] = await createPeerIdsFromFixtures(3);
|
||||
});
|
||||
|
||||
it("should finish both stages as initiator and responder", async() => {
|
||||
try {
|
||||
const duplex = Duplex();
|
||||
const connectionFrom = Wrap(duplex[0]);
|
||||
const connectionTo = Wrap(duplex[1]);
|
||||
|
||||
const prologue = Buffer.alloc(0);
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const staticKeysResponder = generateKeypair();
|
||||
|
||||
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey);
|
||||
const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, staticKeysResponder.publicKey, peerB);
|
||||
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
|
||||
const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey);
|
||||
|
||||
await handshakeInit.stage0();
|
||||
await handshakeResp.stage0();
|
||||
|
||||
await handshakeResp.stage1();
|
||||
await handshakeInit.stage1();
|
||||
|
||||
// Test shared key
|
||||
if (handshakeInit.session.cs1 && handshakeResp.session.cs1 && handshakeInit.session.cs2 && handshakeResp.session.cs2) {
|
||||
assert(handshakeInit.session.cs1.k.equals(handshakeResp.session.cs1.k));
|
||||
assert(handshakeInit.session.cs2.k.equals(handshakeResp.session.cs2.k));
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// Test encryption and decryption
|
||||
const encrypted = handshakeInit.encrypt(Buffer.from("encryptthis"), handshakeInit.session);
|
||||
const {plaintext: decrypted} = handshakeResp.decrypt(encrypted, handshakeResp.session);
|
||||
assert(decrypted.equals(Buffer.from("encryptthis")));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
it("should throw error if responder's static key changed", async() => {
|
||||
try {
|
||||
const duplex = Duplex();
|
||||
const connectionFrom = Wrap(duplex[0]);
|
||||
const connectionTo = Wrap(duplex[1]);
|
||||
|
||||
const prologue = Buffer.alloc(0);
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const staticKeysResponder = generateKeypair();
|
||||
const oldScammyKeys = generateKeypair();
|
||||
|
||||
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey);
|
||||
const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, oldScammyKeys.publicKey, peerB);
|
||||
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
|
||||
const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey);
|
||||
|
||||
await handshakeInit.stage0();
|
||||
await handshakeResp.stage0();
|
||||
} catch (e) {
|
||||
expect(e.message).to.include("Error occurred while verifying initiator's signed payload");
|
||||
}
|
||||
});
|
||||
});
|
11
test/index.spec.ts
Normal file
11
test/index.spec.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { expect } from 'chai'
|
||||
import { Noise } from '../src'
|
||||
|
||||
describe('Index', () => {
|
||||
it('should expose class with tag and required functions', () => {
|
||||
const noise = new Noise()
|
||||
expect(noise.protocol).to.equal('/noise')
|
||||
expect(typeof (noise.secureInbound)).to.equal('function')
|
||||
expect(typeof (noise.secureOutbound)).to.equal('function')
|
||||
})
|
||||
})
|
@ -1,11 +0,0 @@
|
||||
import { expect } from "chai";
|
||||
import { Noise } from "../src";
|
||||
|
||||
describe("Index", () => {
|
||||
it("should expose class with tag and required functions", () => {
|
||||
const noise = new Noise();
|
||||
expect(noise.protocol).to.equal('/noise');
|
||||
expect(typeof(noise.secureInbound)).to.equal('function');
|
||||
expect(typeof(noise.secureOutbound)).to.equal('function');
|
||||
})
|
||||
});
|
35
test/keycache.spec.ts
Normal file
35
test/keycache.spec.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { assert } from 'chai'
|
||||
import { KeyCache } from '../src/keycache'
|
||||
import { createPeerIds, createPeerIdsFromFixtures } from './fixtures/peer'
|
||||
import uint8ArrayEquals from 'uint8arrays/equals'
|
||||
|
||||
describe('KeyCache', () => {
|
||||
let peerA
|
||||
|
||||
before(async () => {
|
||||
[peerA] = await createPeerIdsFromFixtures(2)
|
||||
})
|
||||
|
||||
it('should store and load same key successfully', async () => {
|
||||
try {
|
||||
const key = Buffer.from('this is id 007')
|
||||
await KeyCache.store(peerA, key)
|
||||
const result = await KeyCache.load(peerA)
|
||||
assert(result !== null && uint8ArrayEquals(result, key), 'Stored and loaded key are not the same')
|
||||
} catch (e) {
|
||||
const err = e as Error
|
||||
assert(false, `Test failed - ${err.message}`)
|
||||
}
|
||||
})
|
||||
|
||||
it('should return undefined if key not found', async () => {
|
||||
try {
|
||||
const [newPeer] = await createPeerIds(1)
|
||||
const result = await KeyCache.load(newPeer)
|
||||
assert(result === null)
|
||||
} catch (e) {
|
||||
const err = e as Error
|
||||
assert(false, `Test failed - ${err.message}`)
|
||||
}
|
||||
})
|
||||
})
|
@ -1,34 +0,0 @@
|
||||
import { expect, assert } from "chai";
|
||||
import { KeyCache } from "../src/keycache";
|
||||
import {createPeerIds, createPeerIdsFromFixtures} from "./fixtures/peer";
|
||||
|
||||
describe("KeyCache", () => {
|
||||
let peerA, peerB;
|
||||
|
||||
before(async () => {
|
||||
[peerA, peerB] = await createPeerIdsFromFixtures(2);
|
||||
});
|
||||
|
||||
it("should store and load same key successfully", async() => {
|
||||
try {
|
||||
const key = Buffer.from("this is id 007");
|
||||
await KeyCache.store(peerA, key);
|
||||
const result = await KeyCache.load(peerA);
|
||||
assert(result.equals(key), "Stored and loaded key are not the same");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, `Test failed - ${e.message}`)
|
||||
}
|
||||
});
|
||||
|
||||
it("should return undefined if key not found", async() => {
|
||||
try {
|
||||
const [newPeer] = await createPeerIds(1);
|
||||
const result = await KeyCache.load(newPeer);
|
||||
assert(!result);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, `Test failed - ${e.message}`)
|
||||
}
|
||||
});
|
||||
});
|
367
test/noise.spec.ts
Normal file
367
test/noise.spec.ts
Normal file
@ -0,0 +1,367 @@
|
||||
import { assert, expect } from 'chai'
|
||||
import DuplexPair from 'it-pair/duplex'
|
||||
import { createPeerIdsFromFixtures } from './fixtures/peer'
|
||||
import Wrap from 'it-pb-rpc'
|
||||
import sinon from 'sinon'
|
||||
import BufferList from 'bl'
|
||||
import { randomBytes } from 'libp2p-crypto'
|
||||
import { Buffer } from 'buffer'
|
||||
import uint8ArrayEquals from 'uint8arrays/equals'
|
||||
|
||||
import { Noise } from '../src'
|
||||
import { XXHandshake } from '../src/handshake-xx'
|
||||
import { createHandshakePayload, generateKeypair, getHandshakePayload, getPayload, signPayload } from '../src/utils'
|
||||
import { decode0, decode2, encode1, uint16BEDecode, uint16BEEncode } from '../src/encoder'
|
||||
import { XX } from '../src/handshakes/xx'
|
||||
import { getKeyPairFromPeerId } from './utils'
|
||||
import { KeyCache } from '../src/keycache'
|
||||
import { NOISE_MSG_MAX_LENGTH_BYTES } from '../src/constants'
|
||||
|
||||
describe('Noise', () => {
|
||||
let remotePeer, localPeer
|
||||
const sandbox = sinon.createSandbox()
|
||||
|
||||
before(async () => {
|
||||
[localPeer, remotePeer] = await createPeerIdsFromFixtures(2)
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
it('should communicate through encrypted streams without noise pipes', async () => {
|
||||
try {
|
||||
const noiseInit = new Noise(undefined, undefined)
|
||||
const noiseResp = new Noise(undefined, undefined)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
|
||||
])
|
||||
const wrappedInbound = Wrap(inbound.conn)
|
||||
const wrappedOutbound = Wrap(outbound.conn)
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from('test'))
|
||||
const response = await wrappedInbound.readLP()
|
||||
expect(response.toString()).equal('test')
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
it('should test that secureOutbound is spec compliant', async () => {
|
||||
const noiseInit = new Noise(undefined, undefined)
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
|
||||
const [outbound, { wrapped, handshake }] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
(async () => {
|
||||
const wrapped = Wrap(
|
||||
inboundConnection,
|
||||
{
|
||||
lengthEncoder: uint16BEEncode,
|
||||
lengthDecoder: uint16BEDecode,
|
||||
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
|
||||
}
|
||||
)
|
||||
const prologue = Buffer.alloc(0)
|
||||
const staticKeys = generateKeypair()
|
||||
const xx = new XX()
|
||||
|
||||
const payload = await getPayload(remotePeer, staticKeys.publicKey)
|
||||
const handshake = new XXHandshake(false, payload, prologue, staticKeys, wrapped, localPeer, xx)
|
||||
|
||||
let receivedMessageBuffer = decode0((await wrapped.readLP()).slice())
|
||||
// The first handshake message contains the initiator's ephemeral public key
|
||||
expect(receivedMessageBuffer.ne.length).equal(32)
|
||||
xx.recvMessage(handshake.session, receivedMessageBuffer)
|
||||
|
||||
// Stage 1
|
||||
const { publicKey: libp2pPubKey } = getKeyPairFromPeerId(remotePeer)
|
||||
const signedPayload = await signPayload(remotePeer, getHandshakePayload(staticKeys.publicKey))
|
||||
const handshakePayload = await createHandshakePayload(libp2pPubKey, signedPayload)
|
||||
|
||||
const messageBuffer = xx.sendMessage(handshake.session, handshakePayload)
|
||||
wrapped.writeLP(encode1(messageBuffer))
|
||||
|
||||
// Stage 2 - finish handshake
|
||||
receivedMessageBuffer = decode2((await wrapped.readLP()).slice())
|
||||
xx.recvMessage(handshake.session, receivedMessageBuffer)
|
||||
return { wrapped, handshake }
|
||||
})()
|
||||
])
|
||||
|
||||
try {
|
||||
const wrappedOutbound = Wrap(outbound.conn)
|
||||
wrappedOutbound.write(new BufferList([Buffer.from('test')]))
|
||||
|
||||
// Check that noise message is prefixed with 16-bit big-endian unsigned integer
|
||||
const receivedEncryptedPayload = (await wrapped.read()).slice()
|
||||
const dataLength = receivedEncryptedPayload.readInt16BE(0)
|
||||
const data = receivedEncryptedPayload.slice(2, dataLength + 2)
|
||||
const { plaintext: decrypted, valid } = handshake.decrypt(data, handshake.session)
|
||||
// Decrypted data should match
|
||||
assert(decrypted.equals(Buffer.from('test')))
|
||||
assert(valid)
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
it('should test large payloads', async function () {
|
||||
this.timeout(10000)
|
||||
try {
|
||||
const noiseInit = new Noise(undefined, undefined)
|
||||
const noiseResp = new Noise(undefined, undefined)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
|
||||
])
|
||||
const wrappedInbound = Wrap(inbound.conn)
|
||||
const wrappedOutbound = Wrap(outbound.conn)
|
||||
|
||||
const largePlaintext = randomBytes(100000)
|
||||
wrappedOutbound.writeLP(Buffer.from(largePlaintext))
|
||||
const response = await wrappedInbound.read(100000)
|
||||
|
||||
expect(response.length).equals(largePlaintext.length)
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
it.skip('should communicate through encrypted streams with noise pipes', async () => {
|
||||
try {
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey)
|
||||
const staticKeysResponder = generateKeypair()
|
||||
const noiseResp = new Noise(staticKeysResponder.privateKey)
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey)
|
||||
|
||||
// @ts-expect-error
|
||||
const xxSpy = sandbox.spy(noiseInit, 'performXXHandshake')
|
||||
// @ts-expect-error
|
||||
const xxFallbackSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake')
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
|
||||
])
|
||||
const wrappedInbound = Wrap(inbound.conn)
|
||||
const wrappedOutbound = Wrap(outbound.conn)
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from('test v2'))
|
||||
const response = await wrappedInbound.readLP()
|
||||
expect(response.toString()).equal('test v2')
|
||||
|
||||
assert(xxSpy.notCalled)
|
||||
assert(xxFallbackSpy.notCalled)
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
it.skip('IK -> XX fallback: initiator has invalid remote static key', async () => {
|
||||
try {
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey)
|
||||
const noiseResp = new Noise()
|
||||
// @ts-expect-error
|
||||
const xxSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake')
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.resetStorage()
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
|
||||
KeyCache.store(remotePeer, generateKeypair().publicKey)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
|
||||
])
|
||||
|
||||
const wrappedInbound = Wrap(inbound.conn)
|
||||
const wrappedOutbound = Wrap(outbound.conn)
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from('test fallback'))
|
||||
const response = await wrappedInbound.readLP()
|
||||
expect(response.toString()).equal('test fallback')
|
||||
|
||||
assert(xxSpy.calledOnce, 'XX Fallback method was never called.')
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
// this didn't work before but we didn't verify decryption
|
||||
it.skip('IK -> XX fallback: responder has disabled noise pipes', async () => {
|
||||
try {
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey)
|
||||
|
||||
const staticKeysResponder = generateKeypair()
|
||||
const noiseResp = new Noise(staticKeysResponder.privateKey, undefined)
|
||||
// @ts-expect-error
|
||||
const xxSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake')
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
|
||||
])
|
||||
|
||||
const wrappedInbound = Wrap(inbound.conn)
|
||||
const wrappedOutbound = Wrap(outbound.conn)
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from('test fallback'))
|
||||
const response = await wrappedInbound.readLP()
|
||||
expect(response.toString()).equal('test fallback')
|
||||
|
||||
assert(xxSpy.calledOnce, 'XX Fallback method was never called.')
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
it.skip('Initiator starts with XX (pipes disabled), responder has enabled noise pipes', async () => {
|
||||
try {
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey, undefined)
|
||||
const staticKeysResponder = generateKeypair()
|
||||
|
||||
const noiseResp = new Noise(staticKeysResponder.privateKey)
|
||||
// @ts-expect-error
|
||||
const xxInitSpy = sandbox.spy(noiseInit, 'performXXHandshake')
|
||||
// @ts-expect-error
|
||||
const xxRespSpy = sandbox.spy(noiseResp, 'performXXFallbackHandshake')
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
|
||||
])
|
||||
|
||||
const wrappedInbound = Wrap(inbound.conn)
|
||||
const wrappedOutbound = Wrap(outbound.conn)
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from('test fallback'))
|
||||
const response = await wrappedInbound.readLP()
|
||||
expect(response.toString()).equal('test fallback')
|
||||
|
||||
assert(xxInitSpy.calledOnce, 'XX method was never called.')
|
||||
assert(xxRespSpy.calledOnce, 'XX Fallback method was never called.')
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
it.skip('IK: responder has no remote static key', async () => {
|
||||
try {
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey)
|
||||
const staticKeysResponder = generateKeypair()
|
||||
|
||||
const noiseResp = new Noise(staticKeysResponder.privateKey)
|
||||
// @ts-expect-error
|
||||
const ikInitSpy = sandbox.spy(noiseInit, 'performIKHandshake')
|
||||
// @ts-expect-error
|
||||
const xxFallbackInitSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake')
|
||||
// @ts-expect-error
|
||||
const ikRespSpy = sandbox.spy(noiseResp, 'performIKHandshake')
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.resetStorage()
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
|
||||
])
|
||||
|
||||
const wrappedInbound = Wrap(inbound.conn)
|
||||
const wrappedOutbound = Wrap(outbound.conn)
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from('test fallback'))
|
||||
const response = await wrappedInbound.readLP()
|
||||
expect(response.toString()).equal('test fallback')
|
||||
|
||||
assert(ikInitSpy.calledOnce, 'IK handshake was not called.')
|
||||
assert(ikRespSpy.calledOnce, 'IK handshake was not called.')
|
||||
assert(xxFallbackInitSpy.notCalled, 'XX Fallback method was called.')
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
it('should working without remote peer provided in incoming connection', async () => {
|
||||
try {
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey)
|
||||
const staticKeysResponder = generateKeypair()
|
||||
const noiseResp = new Noise(staticKeysResponder.privateKey)
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection)
|
||||
])
|
||||
const wrappedInbound = Wrap(inbound.conn)
|
||||
const wrappedOutbound = Wrap(outbound.conn)
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from('test v2'))
|
||||
const response = await wrappedInbound.readLP()
|
||||
expect(response.toString()).equal('test v2')
|
||||
|
||||
assert(uint8ArrayEquals(inbound.remotePeer.marshalPubKey(), localPeer.marshalPubKey()))
|
||||
assert(uint8ArrayEquals(outbound.remotePeer.marshalPubKey(), remotePeer.marshalPubKey()))
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
it('should accept and return early data from remote peer', async () => {
|
||||
try {
|
||||
const localPeerEarlyData = Buffer.from('early data')
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey, localPeerEarlyData)
|
||||
const staticKeysResponder = generateKeypair()
|
||||
const noiseResp = new Noise(staticKeysResponder.privateKey)
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection)
|
||||
])
|
||||
|
||||
assert(inbound.remoteEarlyData.equals(localPeerEarlyData))
|
||||
assert(outbound.remoteEarlyData.equals(Buffer.alloc(0)))
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
})
|
@ -1,365 +0,0 @@
|
||||
import {assert, expect} from "chai";
|
||||
import DuplexPair from 'it-pair/duplex';
|
||||
import {Noise} from "../src";
|
||||
import {createPeerIdsFromFixtures} from "./fixtures/peer";
|
||||
import Wrap from "it-pb-rpc";
|
||||
import sinon from "sinon";
|
||||
import {randomBytes} from 'libp2p-crypto';
|
||||
import {XXHandshake} from "../src/handshake-xx";
|
||||
import {createHandshakePayload, generateKeypair, getHandshakePayload, getPayload, signPayload} from "../src/utils";
|
||||
import {decode0, decode2, encode1, uint16BEDecode, uint16BEEncode} from "../src/encoder";
|
||||
import {XX} from "../src/handshakes/xx";
|
||||
import {Buffer} from "buffer";
|
||||
import {getKeyPairFromPeerId} from "./utils";
|
||||
import {KeyCache} from "../src/keycache";
|
||||
import {NOISE_MSG_MAX_LENGTH_BYTES} from "../src/constants";
|
||||
import BufferList from "bl";
|
||||
|
||||
describe("Noise", () => {
|
||||
let remotePeer, localPeer;
|
||||
let sandbox = sinon.createSandbox();
|
||||
|
||||
before(async () => {
|
||||
[localPeer, remotePeer] = await createPeerIdsFromFixtures(2);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
it("should communicate through encrypted streams without noise pipes", async() => {
|
||||
try {
|
||||
const noiseInit = new Noise(undefined, undefined, false);
|
||||
const noiseResp = new Noise(undefined, undefined, false);
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
|
||||
]);
|
||||
const wrappedInbound = Wrap(inbound.conn);
|
||||
const wrappedOutbound = Wrap(outbound.conn);
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from("test"));
|
||||
const response = await wrappedInbound.readLP();
|
||||
expect(response.toString()).equal("test");
|
||||
} catch (e) {
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
it("should test that secureOutbound is spec compliant", async() => {
|
||||
const noiseInit = new Noise(undefined, undefined, false);
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
|
||||
const [outbound, { wrapped, handshake }] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
(async () => {
|
||||
const wrapped = Wrap(
|
||||
inboundConnection,
|
||||
{
|
||||
lengthEncoder: uint16BEEncode,
|
||||
lengthDecoder: uint16BEDecode,
|
||||
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
|
||||
}
|
||||
);
|
||||
const prologue = Buffer.alloc(0);
|
||||
const staticKeys = generateKeypair();
|
||||
const xx = new XX();
|
||||
|
||||
const payload = await getPayload(remotePeer, staticKeys.publicKey);
|
||||
const handshake = new XXHandshake(false, payload, prologue, staticKeys, wrapped, localPeer, xx);
|
||||
|
||||
let receivedMessageBuffer = decode0((await wrapped.readLP()).slice());
|
||||
// The first handshake message contains the initiator's ephemeral public key
|
||||
expect(receivedMessageBuffer.ne.length).equal(32);
|
||||
xx.recvMessage(handshake.session, receivedMessageBuffer);
|
||||
|
||||
// Stage 1
|
||||
const { publicKey: libp2pPubKey } = getKeyPairFromPeerId(remotePeer);
|
||||
const signedPayload = await signPayload(remotePeer, getHandshakePayload(staticKeys.publicKey));
|
||||
const handshakePayload = await createHandshakePayload(libp2pPubKey, signedPayload);
|
||||
|
||||
const messageBuffer = xx.sendMessage(handshake.session, handshakePayload);
|
||||
wrapped.writeLP(encode1(messageBuffer));
|
||||
|
||||
// Stage 2 - finish handshake
|
||||
receivedMessageBuffer = decode2((await wrapped.readLP()).slice());
|
||||
xx.recvMessage(handshake.session, receivedMessageBuffer);
|
||||
return {wrapped, handshake};
|
||||
})(),
|
||||
]);
|
||||
|
||||
try {
|
||||
const wrappedOutbound = Wrap(outbound.conn);
|
||||
wrappedOutbound.write(new BufferList([Buffer.from("test")]));
|
||||
|
||||
// Check that noise message is prefixed with 16-bit big-endian unsigned integer
|
||||
const receivedEncryptedPayload = (await wrapped.read()).slice();
|
||||
const dataLength = receivedEncryptedPayload.readInt16BE(0);
|
||||
const data = receivedEncryptedPayload.slice(2, dataLength + 2);
|
||||
const {plaintext: decrypted, valid} = handshake.decrypt(data, handshake.session);
|
||||
// Decrypted data should match
|
||||
assert(decrypted.equals(Buffer.from("test")));
|
||||
assert(valid);
|
||||
} catch (e) {
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it("should test large payloads", async function() {
|
||||
this.timeout(10000);
|
||||
try {
|
||||
const noiseInit = new Noise(undefined, undefined, false);
|
||||
const noiseResp = new Noise(undefined, undefined, false);
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
|
||||
]);
|
||||
const wrappedInbound = Wrap(inbound.conn);
|
||||
const wrappedOutbound = Wrap(outbound.conn);
|
||||
|
||||
const largePlaintext = randomBytes(100000);
|
||||
wrappedOutbound.writeLP(largePlaintext);
|
||||
const response = await wrappedInbound.read(100000);
|
||||
|
||||
expect(response.length).equals(largePlaintext.length);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
it.skip("should communicate through encrypted streams with noise pipes", async() => {
|
||||
try {
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey);
|
||||
const staticKeysResponder = generateKeypair();
|
||||
const noiseResp = new Noise(staticKeysResponder.privateKey);
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
|
||||
|
||||
const xxSpy = sandbox.spy(noiseInit, "performXXHandshake");
|
||||
const xxFallbackSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
|
||||
]);
|
||||
const wrappedInbound = Wrap(inbound.conn);
|
||||
const wrappedOutbound = Wrap(outbound.conn);
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from("test v2"));
|
||||
const response = await wrappedInbound.readLP();
|
||||
expect(response.toString()).equal("test v2");
|
||||
|
||||
assert(xxSpy.notCalled);
|
||||
assert(xxFallbackSpy.notCalled);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
it.skip("IK -> XX fallback: initiator has invalid remote static key", async() => {
|
||||
try {
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey);
|
||||
const noiseResp = new Noise();
|
||||
const xxSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.resetStorage();
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
|
||||
KeyCache.store(remotePeer, generateKeypair().publicKey);
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
|
||||
]);
|
||||
|
||||
const wrappedInbound = Wrap(inbound.conn);
|
||||
const wrappedOutbound = Wrap(outbound.conn);
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from("test fallback"));
|
||||
const response = await wrappedInbound.readLP();
|
||||
expect(response.toString()).equal("test fallback");
|
||||
|
||||
assert(xxSpy.calledOnce, "XX Fallback method was never called.");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
//this didn't work before but we didn't verify decryption
|
||||
it.skip("IK -> XX fallback: responder has disabled noise pipes", async() => {
|
||||
try {
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey);
|
||||
|
||||
const staticKeysResponder = generateKeypair();
|
||||
const noiseResp = new Noise(staticKeysResponder.privateKey, undefined, false);
|
||||
const xxSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
|
||||
]);
|
||||
|
||||
const wrappedInbound = Wrap(inbound.conn);
|
||||
const wrappedOutbound = Wrap(outbound.conn);
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from("test fallback"));
|
||||
const response = await wrappedInbound.readLP();
|
||||
expect(response.toString()).equal("test fallback");
|
||||
|
||||
assert(xxSpy.calledOnce, "XX Fallback method was never called.");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
it.skip("Initiator starts with XX (pipes disabled), responder has enabled noise pipes", async() => {
|
||||
try {
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey, undefined, false);
|
||||
const staticKeysResponder = generateKeypair();
|
||||
|
||||
const noiseResp = new Noise(staticKeysResponder.privateKey);
|
||||
const xxInitSpy = sandbox.spy(noiseInit, "performXXHandshake");
|
||||
const xxRespSpy = sandbox.spy(noiseResp, "performXXFallbackHandshake");
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
|
||||
]);
|
||||
|
||||
const wrappedInbound = Wrap(inbound.conn);
|
||||
const wrappedOutbound = Wrap(outbound.conn);
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from("test fallback"));
|
||||
const response = await wrappedInbound.readLP();
|
||||
expect(response.toString()).equal("test fallback");
|
||||
|
||||
assert(xxInitSpy.calledOnce, "XX method was never called.");
|
||||
assert(xxRespSpy.calledOnce, "XX Fallback method was never called.");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
it.skip("IK: responder has no remote static key", async() => {
|
||||
try {
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey);
|
||||
const staticKeysResponder = generateKeypair();
|
||||
|
||||
const noiseResp = new Noise(staticKeysResponder.privateKey);
|
||||
const ikInitSpy = sandbox.spy(noiseInit, "performIKHandshake");
|
||||
const xxFallbackInitSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
|
||||
const ikRespSpy = sandbox.spy(noiseResp, "performIKHandshake");
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.resetStorage();
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
|
||||
]);
|
||||
|
||||
const wrappedInbound = Wrap(inbound.conn);
|
||||
const wrappedOutbound = Wrap(outbound.conn);
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from("test fallback"));
|
||||
const response = await wrappedInbound.readLP();
|
||||
expect(response.toString()).equal("test fallback");
|
||||
|
||||
assert(ikInitSpy.calledOnce, "IK handshake was not called.");
|
||||
assert(ikRespSpy.calledOnce, "IK handshake was not called.");
|
||||
assert(xxFallbackInitSpy.notCalled, "XX Fallback method was called.");
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
it("should working without remote peer provided in incoming connection", async() => {
|
||||
try {
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey);
|
||||
const staticKeysResponder = generateKeypair();
|
||||
const noiseResp = new Noise(staticKeysResponder.privateKey);
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection),
|
||||
]);
|
||||
const wrappedInbound = Wrap(inbound.conn);
|
||||
const wrappedOutbound = Wrap(outbound.conn);
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from("test v2"));
|
||||
const response = await wrappedInbound.readLP();
|
||||
expect(response.toString()).equal("test v2");
|
||||
|
||||
assert(inbound.remotePeer.marshalPubKey().equals(localPeer.marshalPubKey()));
|
||||
assert(outbound.remotePeer.marshalPubKey().equals(remotePeer.marshalPubKey()));
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
it("should accept and return early data from remote peer", async() => {
|
||||
try {
|
||||
const localPeerEarlyData = Buffer.from('early data')
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey, localPeerEarlyData);
|
||||
const staticKeysResponder = generateKeypair();
|
||||
const noiseResp = new Noise(staticKeysResponder.privateKey);
|
||||
|
||||
// Prepare key cache for noise pipes
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection),
|
||||
]);
|
||||
|
||||
assert(inbound.remoteEarlyData.equals(localPeerEarlyData))
|
||||
assert(outbound.remoteEarlyData.equals(Buffer.alloc(0)))
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
});
|
@ -1,13 +1,14 @@
|
||||
import {keys} from 'libp2p-crypto';
|
||||
import {KeyPair, PeerId} from "../src/@types/libp2p";
|
||||
import { keys, PrivateKey } from 'libp2p-crypto'
|
||||
import { KeyPair } from '../src/@types/libp2p'
|
||||
import PeerId from 'peer-id'
|
||||
|
||||
export async function generateEd25519Keys() {
|
||||
return await keys.generateKeyPair('ed25519');
|
||||
export async function generateEd25519Keys (): Promise<PrivateKey> {
|
||||
return await keys.generateKeyPair('Ed25519', 32)
|
||||
}
|
||||
|
||||
export function getKeyPairFromPeerId(peerId: PeerId): KeyPair {
|
||||
export function getKeyPairFromPeerId (peerId: PeerId): KeyPair {
|
||||
return {
|
||||
privateKey: peerId.privKey.marshal().slice(0, 32),
|
||||
publicKey: peerId.marshalPubKey(),
|
||||
privateKey: Buffer.from(peerId.privKey.marshal().slice(0, 32)),
|
||||
publicKey: Buffer.from(peerId.marshalPubKey())
|
||||
}
|
||||
}
|
||||
|
76
test/xx-fallback-handshake.spec.ts
Normal file
76
test/xx-fallback-handshake.spec.ts
Normal file
@ -0,0 +1,76 @@
|
||||
import Wrap from 'it-pb-rpc'
|
||||
import { Buffer } from 'buffer'
|
||||
import Duplex from 'it-pair/duplex'
|
||||
|
||||
import {
|
||||
generateKeypair,
|
||||
getPayload
|
||||
} from '../src/utils'
|
||||
import { XXFallbackHandshake } from '../src/handshake-xx-fallback'
|
||||
import { createPeerIdsFromFixtures } from './fixtures/peer'
|
||||
import { assert } from 'chai'
|
||||
import { encode0 } from '../src/encoder'
|
||||
|
||||
describe('XX Fallback Handshake', () => {
|
||||
let peerA, peerB
|
||||
|
||||
before(async () => {
|
||||
[peerA, peerB] = await createPeerIdsFromFixtures(2)
|
||||
})
|
||||
|
||||
it('should test that both parties can fallback to XX and finish handshake', async () => {
|
||||
try {
|
||||
const duplex = Duplex()
|
||||
const connectionFrom = Wrap(duplex[0])
|
||||
const connectionTo = Wrap(duplex[1])
|
||||
|
||||
const prologue = Buffer.alloc(0)
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const staticKeysResponder = generateKeypair()
|
||||
const ephemeralKeys = generateKeypair()
|
||||
|
||||
// Initial msg for responder is IK first message from initiator
|
||||
const handshakePayload = await getPayload(peerA, staticKeysInitiator.publicKey)
|
||||
const initialMsgR = encode0({
|
||||
ne: ephemeralKeys.publicKey,
|
||||
ns: Buffer.alloc(0),
|
||||
ciphertext: handshakePayload
|
||||
})
|
||||
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
|
||||
const handshakeResp =
|
||||
new XXFallbackHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, initialMsgR, peerA)
|
||||
|
||||
await handshakeResp.propose()
|
||||
await handshakeResp.exchange()
|
||||
|
||||
// Initial message for initiator is XX Message B from responder
|
||||
// This is the point where initiator falls back from IK
|
||||
const initialMsgI = await connectionFrom.readLP()
|
||||
const handshakeInit =
|
||||
new XXFallbackHandshake(true, handshakePayload, prologue, staticKeysInitiator, connectionFrom, initialMsgI.slice(0), peerB, ephemeralKeys)
|
||||
|
||||
await handshakeInit.propose()
|
||||
await handshakeInit.exchange()
|
||||
|
||||
await handshakeInit.finish()
|
||||
await handshakeResp.finish()
|
||||
|
||||
const sessionInitator = handshakeInit.session
|
||||
const sessionResponder = handshakeResp.session
|
||||
|
||||
// Test shared key
|
||||
if (sessionInitator.cs1 !== undefined &&
|
||||
sessionResponder.cs1 !== undefined &&
|
||||
sessionInitator.cs2 !== undefined &&
|
||||
sessionResponder.cs2 !== undefined) {
|
||||
assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k))
|
||||
assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k))
|
||||
} else {
|
||||
assert(false)
|
||||
}
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
})
|
@ -1,74 +0,0 @@
|
||||
import Wrap from "it-pb-rpc";
|
||||
import {Buffer} from "buffer";
|
||||
import Duplex from 'it-pair/duplex';
|
||||
|
||||
import {
|
||||
generateKeypair,
|
||||
getPayload,
|
||||
} from "../src/utils";
|
||||
import {XXFallbackHandshake} from "../src/handshake-xx-fallback";
|
||||
import {createPeerIdsFromFixtures} from "./fixtures/peer";
|
||||
import {assert} from "chai";
|
||||
import {decode1, encode0, encode1} from "../src/encoder";
|
||||
|
||||
describe("XX Fallback Handshake", () => {
|
||||
let peerA, peerB, fakePeer;
|
||||
|
||||
before(async () => {
|
||||
[peerA, peerB] = await createPeerIdsFromFixtures(2);
|
||||
});
|
||||
|
||||
it("should test that both parties can fallback to XX and finish handshake", async () => {
|
||||
try {
|
||||
const duplex = Duplex();
|
||||
const connectionFrom = Wrap(duplex[0]);
|
||||
const connectionTo = Wrap(duplex[1]);
|
||||
|
||||
const prologue = Buffer.alloc(0);
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const staticKeysResponder = generateKeypair();
|
||||
const ephemeralKeys = generateKeypair();
|
||||
|
||||
// Initial msg for responder is IK first message from initiator
|
||||
const handshakePayload = await getPayload(peerA, staticKeysInitiator.publicKey);
|
||||
const initialMsgR = encode0({
|
||||
ne: ephemeralKeys.publicKey,
|
||||
ns: Buffer.alloc(0),
|
||||
ciphertext: handshakePayload,
|
||||
});
|
||||
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
|
||||
const handshakeResp =
|
||||
new XXFallbackHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, initialMsgR, peerA);
|
||||
|
||||
await handshakeResp.propose();
|
||||
await handshakeResp.exchange();
|
||||
|
||||
// Initial message for initiator is XX Message B from responder
|
||||
// This is the point where initiator falls back from IK
|
||||
const initialMsgI = await connectionFrom.readLP();
|
||||
const handshakeInit =
|
||||
new XXFallbackHandshake(true, handshakePayload, prologue, staticKeysInitiator, connectionFrom, initialMsgI, peerB, ephemeralKeys);
|
||||
|
||||
await handshakeInit.propose();
|
||||
await handshakeInit.exchange();
|
||||
|
||||
await handshakeInit.finish();
|
||||
await handshakeResp.finish();
|
||||
|
||||
const sessionInitator = handshakeInit.session;
|
||||
const sessionResponder = handshakeResp.session;
|
||||
|
||||
// Test shared key
|
||||
if (sessionInitator.cs1 && sessionResponder.cs1 && sessionInitator.cs2 && sessionResponder.cs2) {
|
||||
assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k));
|
||||
assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k));
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
})
|
120
test/xx-handshake.spec.ts
Normal file
120
test/xx-handshake.spec.ts
Normal file
@ -0,0 +1,120 @@
|
||||
import { assert, expect } from 'chai'
|
||||
import Duplex from 'it-pair/duplex'
|
||||
import { Buffer } from 'buffer'
|
||||
import Wrap from 'it-pb-rpc'
|
||||
import { XXHandshake } from '../src/handshake-xx'
|
||||
import { generateKeypair, getPayload } from '../src/utils'
|
||||
import { createPeerIdsFromFixtures } from './fixtures/peer'
|
||||
|
||||
describe('XX Handshake', () => {
|
||||
let peerA, peerB, fakePeer
|
||||
|
||||
before(async () => {
|
||||
[peerA, peerB, fakePeer] = await createPeerIdsFromFixtures(3)
|
||||
})
|
||||
|
||||
it('should propose, exchange and finish handshake', async () => {
|
||||
try {
|
||||
const duplex = Duplex()
|
||||
const connectionFrom = Wrap(duplex[0])
|
||||
const connectionTo = Wrap(duplex[1])
|
||||
|
||||
const prologue = Buffer.alloc(0)
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const staticKeysResponder = generateKeypair()
|
||||
|
||||
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
|
||||
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB)
|
||||
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
|
||||
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA)
|
||||
|
||||
await handshakeInitator.propose()
|
||||
await handshakeResponder.propose()
|
||||
|
||||
await handshakeResponder.exchange()
|
||||
await handshakeInitator.exchange()
|
||||
|
||||
await handshakeInitator.finish()
|
||||
await handshakeResponder.finish()
|
||||
|
||||
const sessionInitator = handshakeInitator.session
|
||||
const sessionResponder = handshakeResponder.session
|
||||
|
||||
// Test shared key
|
||||
if (sessionInitator.cs1 && sessionResponder.cs1 && sessionInitator.cs2 && sessionResponder.cs2) {
|
||||
assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k))
|
||||
assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k))
|
||||
} else {
|
||||
assert(false)
|
||||
}
|
||||
|
||||
// Test encryption and decryption
|
||||
const encrypted = handshakeInitator.encrypt(Buffer.from('encryptthis'), handshakeInitator.session)
|
||||
const { plaintext: decrypted, valid } = handshakeResponder.decrypt(encrypted, handshakeResponder.session)
|
||||
assert(decrypted.equals(Buffer.from('encryptthis')))
|
||||
assert(valid)
|
||||
} catch (e) {
|
||||
assert(false, e.message)
|
||||
}
|
||||
})
|
||||
|
||||
it('Initiator should fail to exchange handshake if given wrong public key in payload', async () => {
|
||||
try {
|
||||
const duplex = Duplex()
|
||||
const connectionFrom = Wrap(duplex[0])
|
||||
const connectionTo = Wrap(duplex[1])
|
||||
|
||||
const prologue = Buffer.alloc(0)
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const staticKeysResponder = generateKeypair()
|
||||
|
||||
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
|
||||
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, fakePeer)
|
||||
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
|
||||
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA)
|
||||
|
||||
await handshakeInitator.propose()
|
||||
await handshakeResponder.propose()
|
||||
|
||||
await handshakeResponder.exchange()
|
||||
await handshakeInitator.exchange()
|
||||
|
||||
assert(false, 'Should throw exception')
|
||||
} catch (e) {
|
||||
expect(e.message).equals("Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key.")
|
||||
}
|
||||
})
|
||||
|
||||
it('Responder should fail to exchange handshake if given wrong public key in payload', async () => {
|
||||
try {
|
||||
const duplex = Duplex()
|
||||
const connectionFrom = Wrap(duplex[0])
|
||||
const connectionTo = Wrap(duplex[1])
|
||||
|
||||
const prologue = Buffer.alloc(0)
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const staticKeysResponder = generateKeypair()
|
||||
|
||||
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
|
||||
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB)
|
||||
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
|
||||
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, fakePeer)
|
||||
|
||||
await handshakeInitator.propose()
|
||||
await handshakeResponder.propose()
|
||||
|
||||
await handshakeResponder.exchange()
|
||||
await handshakeInitator.exchange()
|
||||
|
||||
await handshakeInitator.finish()
|
||||
await handshakeResponder.finish()
|
||||
|
||||
assert(false, 'Should throw exception')
|
||||
} catch (e) {
|
||||
expect(e.message).equals("Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key.")
|
||||
}
|
||||
})
|
||||
})
|
@ -1,121 +0,0 @@
|
||||
import {assert, expect} from "chai";
|
||||
import Duplex from 'it-pair/duplex';
|
||||
import {Buffer} from "buffer";
|
||||
import Wrap from "it-pb-rpc";
|
||||
import {XXHandshake} from "../src/handshake-xx";
|
||||
import {generateKeypair, getPayload} from "../src/utils";
|
||||
import {createPeerIdsFromFixtures} from "./fixtures/peer";
|
||||
|
||||
|
||||
describe("XX Handshake", () => {
|
||||
let peerA, peerB, fakePeer;
|
||||
|
||||
before(async () => {
|
||||
[peerA, peerB, fakePeer] = await createPeerIdsFromFixtures(3);
|
||||
});
|
||||
|
||||
it("should propose, exchange and finish handshake", async() => {
|
||||
try {
|
||||
const duplex = Duplex();
|
||||
const connectionFrom = Wrap(duplex[0]);
|
||||
const connectionTo = Wrap(duplex[1]);
|
||||
|
||||
const prologue = Buffer.alloc(0);
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const staticKeysResponder = generateKeypair();
|
||||
|
||||
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey);
|
||||
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB);
|
||||
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
|
||||
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA);
|
||||
|
||||
await handshakeInitator.propose();
|
||||
await handshakeResponder.propose();
|
||||
|
||||
await handshakeResponder.exchange();
|
||||
await handshakeInitator.exchange();
|
||||
|
||||
await handshakeInitator.finish();
|
||||
await handshakeResponder.finish();
|
||||
|
||||
const sessionInitator = handshakeInitator.session;
|
||||
const sessionResponder = handshakeResponder.session;
|
||||
|
||||
// Test shared key
|
||||
if (sessionInitator.cs1 && sessionResponder.cs1 && sessionInitator.cs2 && sessionResponder.cs2) {
|
||||
assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k));
|
||||
assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k));
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
|
||||
// Test encryption and decryption
|
||||
const encrypted = handshakeInitator.encrypt(Buffer.from("encryptthis"), handshakeInitator.session);
|
||||
const {plaintext: decrypted, valid} = handshakeResponder.decrypt(encrypted, handshakeResponder.session);
|
||||
assert(decrypted.equals(Buffer.from("encryptthis")));
|
||||
assert(valid);
|
||||
} catch (e) {
|
||||
assert(false, e.message);
|
||||
}
|
||||
});
|
||||
|
||||
it("Initiator should fail to exchange handshake if given wrong public key in payload", async() => {
|
||||
try {
|
||||
const duplex = Duplex();
|
||||
const connectionFrom = Wrap(duplex[0]);
|
||||
const connectionTo = Wrap(duplex[1]);
|
||||
|
||||
const prologue = Buffer.alloc(0);
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const staticKeysResponder = generateKeypair();
|
||||
|
||||
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey);
|
||||
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, fakePeer);
|
||||
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
|
||||
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA);
|
||||
|
||||
await handshakeInitator.propose();
|
||||
await handshakeResponder.propose();
|
||||
|
||||
await handshakeResponder.exchange();
|
||||
await handshakeInitator.exchange();
|
||||
|
||||
assert(false, "Should throw exception");
|
||||
} catch (e) {
|
||||
expect(e.message).equals("Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key.")
|
||||
}
|
||||
});
|
||||
|
||||
it("Responder should fail to exchange handshake if given wrong public key in payload", async() => {
|
||||
try {
|
||||
const duplex = Duplex();
|
||||
const connectionFrom = Wrap(duplex[0]);
|
||||
const connectionTo = Wrap(duplex[1]);
|
||||
|
||||
const prologue = Buffer.alloc(0);
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const staticKeysResponder = generateKeypair();
|
||||
|
||||
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey);
|
||||
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB);
|
||||
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
|
||||
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, fakePeer);
|
||||
|
||||
await handshakeInitator.propose();
|
||||
await handshakeResponder.propose();
|
||||
|
||||
await handshakeResponder.exchange();
|
||||
await handshakeInitator.exchange();
|
||||
|
||||
await handshakeInitator.finish();
|
||||
await handshakeResponder.finish();
|
||||
|
||||
assert(false, "Should throw exception");
|
||||
} catch (e) {
|
||||
expect(e.message).equals("Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key.")
|
||||
}
|
||||
});
|
||||
});
|
@ -1,8 +1,13 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"outDir": "dist",
|
||||
"incremental": true,
|
||||
"composite": true,
|
||||
"target": "ES2018",
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"allowJs": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": false,
|
||||
@ -12,9 +17,12 @@
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/src/**/*.ts"
|
||||
"**/test/**/*.ts",
|
||||
"**/src/**/*.ts",
|
||||
"**/src/**/*.js"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
|
@ -1,24 +0,0 @@
|
||||
|
||||
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
|
||||
|
||||
module.exports = {
|
||||
entry: "./src/index.ts",
|
||||
mode: "production",
|
||||
output: {
|
||||
filename: "../bundle/bundle.js"
|
||||
},
|
||||
node: {
|
||||
fs: "empty"
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".js"],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{test: /\.ts$/, exclude: [/node_modules/], use: {loader: "babel-loader", options: require("./babel.web.config")}}
|
||||
],
|
||||
},
|
||||
plugins: [
|
||||
new BundleAnalyzerPlugin()
|
||||
]
|
||||
};
|
@ -1,19 +0,0 @@
|
||||
module.exports = {
|
||||
entry: "./src/index.ts",
|
||||
mode: "production",
|
||||
output: {
|
||||
filename: "dist/bundle.js"
|
||||
},
|
||||
node: {
|
||||
fs: "empty"
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".js"],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{test: /\.ts$/, use: {loader: "ts-loader", options: {transpileOnly: true}}}
|
||||
],
|
||||
},
|
||||
|
||||
};
|
Loading…
x
Reference in New Issue
Block a user