mirror of
https://github.com/fluencelabs/js-libp2p-noise
synced 2025-04-25 05:02:14 +00:00
Merge pull request #67 from NodeFactoryIo/mpetrunic/lint-fix
Fix lint errors and tests
This commit is contained in:
commit
e01d8e293a
14
.travis.yml
14
.travis.yml
@ -5,6 +5,9 @@ stages:
|
||||
- test
|
||||
- cov
|
||||
|
||||
env:
|
||||
- YARN_GPG=no
|
||||
|
||||
node_js:
|
||||
- '12'
|
||||
- '14'
|
||||
@ -15,7 +18,9 @@ os:
|
||||
- windows
|
||||
|
||||
script: npx nyc -s yarn run test:node --bail
|
||||
after_success: npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov
|
||||
after_success:
|
||||
- npm install -g travis-deploy-once
|
||||
- if [ "$TRAVIS_OS_NAME" = "linux" ]; then travis-deploy-once "npx nyc report --reporter=text-lcov > coverage.lcov && npx codecov"; fi
|
||||
|
||||
jobs:
|
||||
include:
|
||||
@ -23,18 +28,19 @@ jobs:
|
||||
script:
|
||||
- yarn aegir dep-check
|
||||
- yarn run lint
|
||||
- yarn run build
|
||||
|
||||
- stage: test
|
||||
name: chrome
|
||||
addons:
|
||||
chrome: stable
|
||||
script: npx aegir test -t browser -t webworker --ts
|
||||
script: npx aegir test -t browser -t webworker --node true --ts
|
||||
|
||||
- stage: test
|
||||
name: firefox
|
||||
addons:
|
||||
firefox: latest
|
||||
script: npx aegir test -t browser -t webworker --ts -- --browsers FirefoxHeadless
|
||||
script: npx aegir test -t browser -t webworker --ts --node true -- --browsers FirefoxHeadless
|
||||
|
||||
- stage: test
|
||||
name: electron-main
|
||||
@ -46,7 +52,7 @@ jobs:
|
||||
name: electron-renderer
|
||||
os: osx
|
||||
script:
|
||||
- npx aegir test -t electron-renderer --bail --ts
|
||||
- npx aegir test -t electron-renderer --node true --bail --ts
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
|
20
package.json
20
package.json
@ -2,8 +2,10 @@
|
||||
"name": "libp2p-noise",
|
||||
"version": "1.1.2",
|
||||
"main": "dist/src/index.js",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"files": [
|
||||
"dist"
|
||||
"dist",
|
||||
"src"
|
||||
],
|
||||
"repository": "git@github.com:NodeFactoryIo/js-libp2p-noise.git",
|
||||
"author": "NodeFactory <info@nodefactory.io>",
|
||||
@ -16,17 +18,19 @@
|
||||
"scripts": {
|
||||
"build": "aegir build --ts",
|
||||
"lint": "aegir lint --ts",
|
||||
"test": "aegir test --ts",
|
||||
"lint:fix": "aegir lint --ts --fix",
|
||||
"test": "aegir test --ts --node true",
|
||||
"test:node": "aegir test -t node --ts",
|
||||
"test:browser": "aegir test -t browser --ts",
|
||||
"test:browser": "aegir test -t browser --node true --ts",
|
||||
"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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bl": "^2.1.0",
|
||||
"@types/chai": "^4.2.4",
|
||||
"@types/mocha": "^5.2.7",
|
||||
"aegir": "ipfs/aegir#feat/cosmiconfig",
|
||||
"aegir": "25.0.0",
|
||||
"chai": "^4.2.0",
|
||||
"karma-mocha-webworker": "^1.3.0",
|
||||
"mocha": "^6.2.2",
|
||||
"sinon": "^8.1.0"
|
||||
},
|
||||
@ -47,6 +51,12 @@
|
||||
"bn.js": "4.4.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "./node_modules/aegir/src/config/eslintrc-ts.js"
|
||||
"extends": "./node_modules/aegir/src/config/eslintrc-ts.js",
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": "error"
|
||||
},
|
||||
"ignorePatterns": [
|
||||
"src/proto/payload.js"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
13
src/@types/it-length-prefixed/index.d.ts
vendored
13
src/@types/it-length-prefixed/index.d.ts
vendored
@ -1,7 +1,6 @@
|
||||
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;
|
||||
@ -9,7 +8,7 @@ declare module "it-length-prefixed" {
|
||||
}
|
||||
|
||||
interface LengthEncoderFunction {
|
||||
(value: Buffer, target: number, offset: number): number|Buffer;
|
||||
(value: number, target: Buffer, offset: number): number|Buffer;
|
||||
bytes: number;
|
||||
}
|
||||
|
||||
@ -33,7 +32,7 @@ declare module "it-length-prefixed" {
|
||||
MAX_DATA_LENGTH: number;
|
||||
}
|
||||
|
||||
export const encode: Encoder;
|
||||
export const decode: Decoder;
|
||||
export const encode: Encoder
|
||||
export const decode: Decoder
|
||||
|
||||
}
|
||||
|
@ -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>;
|
||||
}
|
||||
|
||||
// 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,22 +1,22 @@
|
||||
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,
|
||||
logger,
|
||||
logLocalStaticKeys,
|
||||
logRemoteStaticKey,
|
||||
logLocalEphemeralKeys,
|
||||
logRemoteEphemeralKey,
|
||||
logRemoteStaticKey,
|
||||
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;
|
||||
@ -30,7 +30,7 @@ export class IKHandshake implements IHandshake {
|
||||
private connection: WrappedConnection;
|
||||
private ik: IK;
|
||||
|
||||
constructor(
|
||||
constructor (
|
||||
isInitiator: boolean,
|
||||
payload: bytes,
|
||||
prologue: bytes32,
|
||||
@ -38,118 +38,118 @@ 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.");
|
||||
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: ${e.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}`);
|
||||
logger('Initiator breaking up with IK handshake in stage 1.')
|
||||
throw new FailedIKError(receivedMsg, `Error occurred while verifying responder's signed payload: ${e.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;
|
||||
|
||||
constructor(
|
||||
constructor (
|
||||
isInitiator: boolean,
|
||||
payload: bytes,
|
||||
prologue: bytes32,
|
||||
@ -22,63 +22,64 @@ 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}`);
|
||||
throw new Error(`Error occurred while verifying signed payload from responder: ${e.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,26 +1,26 @@
|
||||
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";
|
||||
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'
|
||||
|
||||
export class XXHandshake implements IHandshake {
|
||||
public isInitiator: boolean;
|
||||
@ -35,139 +35,139 @@ export class XXHandshake implements IHandshake {
|
||||
|
||||
private 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}`);
|
||||
throw new Error(`Error occurred while verifying signed payload: ${e.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}`);
|
||||
throw new Error(`Error occurred while verifying signed payload: ${e.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,180 +1,179 @@
|
||||
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 { 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 derived = x25519.derive(publicKey, privateKey);
|
||||
const result = Buffer.alloc(32);
|
||||
derived.copy(result);
|
||||
return result;
|
||||
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 {
|
||||
return SHA256.digest(Buffer.from([...a, ...b]));
|
||||
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) as CipherState
|
||||
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;
|
||||
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,5 +1,5 @@
|
||||
import {bytes, bytes32} from "./@types/basic";
|
||||
import PeerId from "peer-id";
|
||||
import { bytes, bytes32 } from './@types/basic'
|
||||
import PeerId from 'peer-id'
|
||||
|
||||
/**
|
||||
* Storage for static keys of previously connected peers.
|
||||
@ -7,24 +7,23 @@ import PeerId from "peer-id";
|
||||
class Keycache {
|
||||
private storage = new Map<bytes, 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 {
|
||||
} 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.')
|
||||
}
|
||||
}
|
||||
|
195
src/noise.ts
195
src/noise.ts
@ -1,25 +1,25 @@
|
||||
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 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>;
|
||||
|
||||
@ -31,7 +31,7 @@ type HandshakeParams = {
|
||||
};
|
||||
|
||||
export class Noise implements INoiseConnection {
|
||||
public protocol = "/noise";
|
||||
public protocol = '/noise';
|
||||
|
||||
private readonly prologue = Buffer.alloc(0);
|
||||
private readonly staticKeys: KeyPair;
|
||||
@ -40,206 +40,209 @@ export class Noise implements INoiseConnection {
|
||||
|
||||
/**
|
||||
*
|
||||
* @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 = x25519.publicKeyCreate(staticNoiseKey);
|
||||
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,
|
||||
{
|
||||
// wrong types in repo
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
lengthEncoder: uint16BEEncode,
|
||||
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);
|
||||
remotePeer
|
||||
})
|
||||
const conn = await this.createSecureConnection(wrappedConnection, handshake)
|
||||
|
||||
return {
|
||||
conn,
|
||||
remoteEarlyData: handshake.remoteEarlyData,
|
||||
remotePeer: handshake.remotePeer,
|
||||
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 {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> {
|
||||
public async secureInbound (localPeer: PeerId, connection: any, remotePeer?: PeerId): Promise<SecureOutbound> {
|
||||
const wrappedConnection = Wrap(
|
||||
connection,
|
||||
{
|
||||
// wrong types in repo
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
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
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
// safe to cast as we did checks
|
||||
KeyCache.load(params.remotePeer) || Buffer.alloc(32),
|
||||
remotePeer as PeerId,
|
||||
);
|
||||
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)
|
||||
throw new Error(`Error occurred during XX Fallback handshake: ${e.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}`);
|
||||
throw new Error(`Error occurred during XX handshake: ${e.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,33 +1,25 @@
|
||||
/*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 */
|
||||
/* 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) }
|
||||
|
||||
/* 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; var $Writer = $protobuf.Writer; var $util = $protobuf.util
|
||||
|
||||
/* CommonJS */ else if (typeof require === 'function' && typeof module === 'object' && module && module.exports)
|
||||
module.exports = factory(require("protobufjs/minimal"));
|
||||
// Exported root namespace
|
||||
var $root = $protobuf.roots.default || ($protobuf.roots.default = {})
|
||||
|
||||
})(this, function($protobuf) {
|
||||
"use strict";
|
||||
|
||||
// Common aliases
|
||||
var $Reader = $protobuf.Reader, $Writer = $protobuf.Writer, $util = $protobuf.util;
|
||||
|
||||
// Exported root namespace
|
||||
var $root = $protobuf.roots["default"] || ($protobuf.roots["default"] = {});
|
||||
|
||||
$root.pb = (function() {
|
||||
|
||||
/**
|
||||
$root.pb = (function () {
|
||||
/**
|
||||
* Namespace pb.
|
||||
* @exports pb
|
||||
* @namespace
|
||||
*/
|
||||
var pb = {};
|
||||
|
||||
pb.NoiseHandshakePayload = (function() {
|
||||
|
||||
/**
|
||||
var pb = {}
|
||||
|
||||
pb.NoiseHandshakePayload = (function () {
|
||||
/**
|
||||
* Properties of a NoiseHandshakePayload.
|
||||
* @memberof pb
|
||||
* @interface INoiseHandshakePayload
|
||||
@ -35,8 +27,8 @@
|
||||
* @property {Uint8Array|null} [identitySig] NoiseHandshakePayload identitySig
|
||||
* @property {Uint8Array|null} [data] NoiseHandshakePayload data
|
||||
*/
|
||||
|
||||
/**
|
||||
|
||||
/**
|
||||
* Constructs a new NoiseHandshakePayload.
|
||||
* @memberof pb
|
||||
* @classdesc Represents a NoiseHandshakePayload.
|
||||
@ -44,38 +36,39 @@
|
||||
* @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]] }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* NoiseHandshakePayload identityKey.
|
||||
* @member {Uint8Array} identityKey
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
* @instance
|
||||
*/
|
||||
NoiseHandshakePayload.prototype.identityKey = $util.newBuffer([]);
|
||||
|
||||
/**
|
||||
NoiseHandshakePayload.prototype.identityKey = $util.newBuffer([])
|
||||
|
||||
/**
|
||||
* NoiseHandshakePayload identitySig.
|
||||
* @member {Uint8Array} identitySig
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
* @instance
|
||||
*/
|
||||
NoiseHandshakePayload.prototype.identitySig = $util.newBuffer([]);
|
||||
|
||||
/**
|
||||
NoiseHandshakePayload.prototype.identitySig = $util.newBuffer([])
|
||||
|
||||
/**
|
||||
* NoiseHandshakePayload data.
|
||||
* @member {Uint8Array} data
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
* @instance
|
||||
*/
|
||||
NoiseHandshakePayload.prototype.data = $util.newBuffer([]);
|
||||
|
||||
/**
|
||||
NoiseHandshakePayload.prototype.data = $util.newBuffer([])
|
||||
|
||||
/**
|
||||
* Creates a new NoiseHandshakePayload instance using the specified properties.
|
||||
* @function create
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
@ -83,11 +76,11 @@
|
||||
* @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.
|
||||
* @function encode
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
@ -96,19 +89,15 @@
|
||||
* @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.
|
||||
* @function encodeDelimited
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
@ -117,11 +106,11 @@
|
||||
* @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.
|
||||
* @function decode
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
@ -132,31 +121,30 @@
|
||||
* @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();
|
||||
while (reader.pos < end) {
|
||||
var tag = reader.uint32();
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
message.identityKey = reader.bytes();
|
||||
break;
|
||||
case 2:
|
||||
message.identitySig = reader.bytes();
|
||||
break;
|
||||
case 3:
|
||||
message.data = reader.bytes();
|
||||
break;
|
||||
default:
|
||||
reader.skipType(tag & 7);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return message;
|
||||
};
|
||||
|
||||
/**
|
||||
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()
|
||||
switch (tag >>> 3) {
|
||||
case 1:
|
||||
message.identityKey = reader.bytes()
|
||||
break
|
||||
case 2:
|
||||
message.identitySig = reader.bytes()
|
||||
break
|
||||
case 3:
|
||||
message.data = reader.bytes()
|
||||
break
|
||||
default:
|
||||
reader.skipType(tag & 7)
|
||||
break
|
||||
}
|
||||
}
|
||||
return message
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes a NoiseHandshakePayload message from the specified reader or buffer, length delimited.
|
||||
* @function decodeDelimited
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
@ -166,13 +154,12 @@
|
||||
* @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.
|
||||
* @function verify
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
@ -180,22 +167,21 @@
|
||||
* @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.
|
||||
* @function fromObject
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
@ -203,29 +189,22 @@
|
||||
* @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.
|
||||
* @function toObject
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
@ -234,58 +213,45 @@
|
||||
* @param {$protobuf.IConversionOptions} [options] Conversion options
|
||||
* @returns {Object.<string,*>} Plain 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.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 (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;
|
||||
};
|
||||
|
||||
/**
|
||||
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.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 (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.
|
||||
* @function toJSON
|
||||
* @memberof pb.NoiseHandshakePayload
|
||||
* @instance
|
||||
* @returns {Object.<string,*>} JSON object
|
||||
*/
|
||||
NoiseHandshakePayload.prototype.toJSON = function toJSON() {
|
||||
return this.constructor.toObject(this, $protobuf.util.toJSONOptions);
|
||||
};
|
||||
|
||||
return NoiseHandshakePayload;
|
||||
})();
|
||||
|
||||
return pb;
|
||||
})();
|
||||
NoiseHandshakePayload.prototype.toJSON = function toJSON () {
|
||||
return this.constructor.toObject(this, $protobuf.util.toJSONOptions)
|
||||
}
|
||||
|
||||
return $root;
|
||||
});
|
||||
return NoiseHandshakePayload
|
||||
})()
|
||||
|
||||
return pb
|
||||
})()
|
||||
|
||||
return $root
|
||||
})
|
||||
|
110
src/utils.ts
110
src/utils.ts
@ -1,78 +1,76 @@
|
||||
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 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'
|
||||
|
||||
const NoiseHandshakePayloadProto = pb.NoiseHandshakePayload;
|
||||
const NoiseHandshakePayloadProto = pb.NoiseHandshakePayload
|
||||
|
||||
export function generateKeypair(): KeyPair {
|
||||
const privateKey = x25519.privateKeyGenerate();
|
||||
const publicKey = x25519.publicKeyCreate(privateKey);
|
||||
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(
|
||||
localPeer.marshalPubKey(),
|
||||
signedPayload,
|
||||
earlyDataPayload
|
||||
);
|
||||
)
|
||||
}
|
||||
|
||||
export async function createHandshakePayload(
|
||||
export function createHandshakePayload (
|
||||
libp2pPublicKey: bytes,
|
||||
signedPayload: bytes,
|
||||
earlyData?: bytes,
|
||||
): Promise<bytes> {
|
||||
|
||||
earlyData?: bytes
|
||||
): bytes {
|
||||
const payloadInit = NoiseHandshakePayloadProto.create({
|
||||
identityKey: 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 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: bytes, publicKeyProtobuf: bytes) {
|
||||
const generatedPeerId = await PeerId.createFromPubKey(publicKeyProtobuf)
|
||||
return generatedPeerId.id.equals(peerId)
|
||||
}
|
||||
|
||||
/**
|
||||
@ -82,36 +80,36 @@ async function isValidPeerId(peerId: bytes, publicKeyProtobuf: bytes) {
|
||||
* @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);
|
||||
const publicKey = keys.unmarshalPublicKey(identityKey)
|
||||
if (!payload.identitySig || !publicKey.verify(generatedPayload, Buffer.from(payload.identitySig))) {
|
||||
throw new Error("Static key doesn't match to peer that signed payload!");
|
||||
throw new Error("Static key doesn't match to peer that signed payload!")
|
||||
}
|
||||
return PeerId.createFromPubKey(identityKey);
|
||||
return PeerId.createFromPubKey(identityKey)
|
||||
}
|
||||
|
||||
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);
|
||||
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 {
|
||||
return x25519.publicKeyVerify(pk.slice(0, 32));
|
||||
export function isValidPublicKey (pk: bytes): boolean {
|
||||
return x25519.publicKeyVerify(pk.slice(0, 32))
|
||||
}
|
||||
|
18
test/fixtures/peer.ts
vendored
18
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(
|
||||
export async function createPeerIdsFromFixtures (length:number): Promise<PeerId[]> {
|
||||
return await Promise.all(
|
||||
Array.from({ length }).map((_, i) => 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
|
||||
}
|
||||
|
@ -1,67 +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";
|
||||
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);
|
||||
describe('IK handshake', () => {
|
||||
const prologue = Buffer.alloc(0)
|
||||
|
||||
it("Test complete IK handshake", async () => {
|
||||
it('Test complete IK handshake', async () => {
|
||||
try {
|
||||
const ikI = new IK();
|
||||
const ikR = new IK();
|
||||
const ikI = new IK()
|
||||
const ikR = new IK()
|
||||
|
||||
// Generate static noise keys
|
||||
const kpInitiator: KeyPair = await generateKeypair();
|
||||
const kpResponder: KeyPair = await generateKeypair();
|
||||
const kpInitiator: KeyPair = await generateKeypair()
|
||||
const kpResponder: KeyPair = await generateKeypair()
|
||||
|
||||
// Generate libp2p keys
|
||||
const libp2pInitKeys = await generateEd25519Keys();
|
||||
const libp2pRespKeys = await generateEd25519Keys();
|
||||
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));
|
||||
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);
|
||||
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);
|
||||
const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc])
|
||||
const messageBuffer = ikI.sendMessage(initiatorSession, message)
|
||||
|
||||
expect(messageBuffer.ne.length).not.equal(0);
|
||||
expect(messageBuffer.ne.length).not.equal(0)
|
||||
|
||||
// responder receives message
|
||||
const plaintext = ikR.recvMessage(responderSession, messageBuffer);
|
||||
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);
|
||||
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);
|
||||
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));
|
||||
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) {
|
||||
console.error(e);
|
||||
return assert(false, e.message);
|
||||
return assert(false, e.message)
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
@ -1,143 +1,143 @@
|
||||
import { expect, assert } from "chai";
|
||||
import { Buffer } from 'buffer';
|
||||
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";
|
||||
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);
|
||||
describe('XX Handshake', () => {
|
||||
const prologue = Buffer.alloc(0)
|
||||
|
||||
it("Test creating new XX session", async () => {
|
||||
it('Test creating new XX session', async () => {
|
||||
try {
|
||||
const xx = new XX();
|
||||
const xx = new XX()
|
||||
|
||||
const kpInitiator: KeyPair = await generateKeypair();
|
||||
const kpResponder: KeyPair = await generateKeypair();
|
||||
const kpInitiator: KeyPair = await generateKeypair()
|
||||
await generateKeypair()
|
||||
|
||||
const session = await xx.initSession(true, prologue, kpInitiator);
|
||||
await xx.initSession(true, prologue, kpInitiator)
|
||||
} catch (e) {
|
||||
assert(false, e.message);
|
||||
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);
|
||||
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');
|
||||
});
|
||||
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();
|
||||
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));
|
||||
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));
|
||||
const libp2pRespKeys = await generateEd25519Keys()
|
||||
const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResp.publicKey))
|
||||
|
||||
// initiator: new XX noise session
|
||||
const nsInit = xx.initSession(true, prologue, kpInit);
|
||||
const nsInit = xx.initSession(true, prologue, kpInit)
|
||||
// responder: new XX noise session
|
||||
const nsResp = xx.initSession(false, prologue, kpResp);
|
||||
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);
|
||||
libp2pInitKeys.marshal().slice(0, 32)
|
||||
const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64)
|
||||
|
||||
const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload);
|
||||
const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload)
|
||||
|
||||
// initiator sends message
|
||||
const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc]);
|
||||
const messageBuffer = xx.sendMessage(nsInit, message);
|
||||
const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc])
|
||||
const messageBuffer = xx.sendMessage(nsInit, message)
|
||||
|
||||
expect(messageBuffer.ne.length).not.equal(0);
|
||||
expect(messageBuffer.ne.length).not.equal(0)
|
||||
|
||||
// responder receives message
|
||||
xx.recvMessage(nsResp, messageBuffer);
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
expect(messageBuffer2.ne.length).not.equal(0)
|
||||
expect(messageBuffer2.ns.length).not.equal(0)
|
||||
|
||||
// initiator receive payload
|
||||
xx.recvMessage(nsInit, messageBuffer2);
|
||||
xx.recvMessage(nsInit, messageBuffer2)
|
||||
|
||||
/* STAGE 2 */
|
||||
|
||||
// initiator send message
|
||||
const messageBuffer3 = xx.sendMessage(nsInit, Buffer.alloc(0));
|
||||
const messageBuffer3 = xx.sendMessage(nsInit, Buffer.alloc(0))
|
||||
|
||||
// responder receive message
|
||||
xx.recvMessage(nsResp, messageBuffer3);
|
||||
xx.recvMessage(nsResp, messageBuffer3)
|
||||
|
||||
assert(nsInit.cs1.k.equals(nsResp.cs1.k));
|
||||
assert(nsInit.cs2.k.equals(nsResp.cs2.k));
|
||||
assert(nsInit.cs1.k.equals(nsResp.cs1.k))
|
||||
assert(nsInit.cs2.k.equals(nsResp.cs2.k))
|
||||
|
||||
return { nsInit, nsResp };
|
||||
return { nsInit, nsResp }
|
||||
}
|
||||
|
||||
it("Test handshake", async () => {
|
||||
it('Test handshake', async () => {
|
||||
try {
|
||||
const xx = new XX();
|
||||
await doHandshake(xx);
|
||||
const xx = new XX()
|
||||
await doHandshake(xx)
|
||||
} catch (e) {
|
||||
assert(false, e.message);
|
||||
assert(false, e.message)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it("Test symmetric encrypt and decrypt", async () => {
|
||||
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 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);
|
||||
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);
|
||||
assert(Buffer.from('HelloCrypto').equals(decrypted), 'Decrypted text not equal to original message.')
|
||||
assert(valid)
|
||||
} catch (e) {
|
||||
assert(false, e.message);
|
||||
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");
|
||||
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 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.");
|
||||
});
|
||||
});
|
||||
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,80 +1,79 @@
|
||||
import Wrap from "it-pb-rpc";
|
||||
import Duplex from 'it-pair/duplex';
|
||||
import {Buffer} from "buffer";
|
||||
import {assert, expect} from "chai";
|
||||
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";
|
||||
import { createPeerIdsFromFixtures } from './fixtures/peer'
|
||||
import { generateKeypair, getPayload } from '../src/utils'
|
||||
import { IKHandshake } from '../src/handshake-ik'
|
||||
|
||||
describe("IK Handshake", () => {
|
||||
let peerA, peerB, fakePeer;
|
||||
describe('IK Handshake', () => {
|
||||
let peerA, peerB
|
||||
|
||||
before(async () => {
|
||||
[peerA, peerB, fakePeer] = await createPeerIdsFromFixtures(3);
|
||||
});
|
||||
[peerA, peerB] = await createPeerIdsFromFixtures(3)
|
||||
})
|
||||
|
||||
it("should finish both stages as initiator and responder", async() => {
|
||||
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 duplex = Duplex()
|
||||
const connectionFrom = Wrap(duplex[0])
|
||||
const connectionTo = Wrap(duplex[1])
|
||||
|
||||
const prologue = Buffer.alloc(0);
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const staticKeysResponder = generateKeypair();
|
||||
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 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);
|
||||
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 handshakeInit.stage0()
|
||||
await handshakeResp.stage0()
|
||||
|
||||
await handshakeResp.stage1();
|
||||
await handshakeInit.stage1();
|
||||
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));
|
||||
assert(handshakeInit.session.cs1.k.equals(handshakeResp.session.cs1.k))
|
||||
assert(handshakeInit.session.cs2.k.equals(handshakeResp.session.cs2.k))
|
||||
} else {
|
||||
assert(false);
|
||||
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")));
|
||||
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);
|
||||
assert(false, e.message)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it("should throw error if responder's static key changed", async() => {
|
||||
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 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 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 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);
|
||||
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 handshakeInit.stage0()
|
||||
await handshakeResp.stage0()
|
||||
} catch (e) {
|
||||
expect(e.message).to.include("Error occurred while verifying initiator's signed payload");
|
||||
expect(e.message).to.include("Error occurred while verifying initiator's signed payload")
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { expect } from "chai";
|
||||
import { Noise } from "../src";
|
||||
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');
|
||||
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,34 +1,32 @@
|
||||
import { expect, assert } from "chai";
|
||||
import { KeyCache } from "../src/keycache";
|
||||
import {createPeerIds, createPeerIdsFromFixtures} from "./fixtures/peer";
|
||||
import { assert } from 'chai'
|
||||
import { KeyCache } from '../src/keycache'
|
||||
import { createPeerIds, createPeerIdsFromFixtures } from './fixtures/peer'
|
||||
|
||||
describe("KeyCache", () => {
|
||||
let peerA, peerB;
|
||||
describe('KeyCache', () => {
|
||||
let peerA
|
||||
|
||||
before(async () => {
|
||||
[peerA, peerB] = await createPeerIdsFromFixtures(2);
|
||||
});
|
||||
[peerA] = await createPeerIdsFromFixtures(2)
|
||||
})
|
||||
|
||||
it("should store and load same key successfully", async() => {
|
||||
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");
|
||||
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() => {
|
||||
it('should return undefined if key not found', async () => {
|
||||
try {
|
||||
const [newPeer] = await createPeerIds(1);
|
||||
const result = await KeyCache.load(newPeer);
|
||||
assert(!result);
|
||||
const [newPeer] = await createPeerIds(1)
|
||||
const result = await KeyCache.load(newPeer)
|
||||
assert(!result)
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, `Test failed - ${e.message}`)
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
@ -1,57 +1,57 @@
|
||||
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 { 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 {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";
|
||||
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;
|
||||
let sandbox = sinon.createSandbox();
|
||||
describe('Noise', () => {
|
||||
let remotePeer, localPeer
|
||||
const sandbox = sinon.createSandbox()
|
||||
|
||||
before(async () => {
|
||||
[localPeer, remotePeer] = await createPeerIdsFromFixtures(2);
|
||||
});
|
||||
[localPeer, remotePeer] = await createPeerIdsFromFixtures(2)
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
sandbox.restore();
|
||||
});
|
||||
afterEach(function () {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
it("should communicate through encrypted streams without noise pipes", async() => {
|
||||
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 noiseInit = new Noise(undefined, undefined)
|
||||
const noiseResp = new Noise(undefined, undefined)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
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);
|
||||
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");
|
||||
wrappedOutbound.writeLP(Buffer.from('test'))
|
||||
const response = await wrappedInbound.readLP()
|
||||
expect(response.toString()).equal('test')
|
||||
} catch (e) {
|
||||
assert(false, e.message);
|
||||
assert(false, e.message)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it("should test that secureOutbound is spec compliant", async() => {
|
||||
const noiseInit = new Noise(undefined, undefined, false);
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
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),
|
||||
@ -63,304 +63,295 @@ describe("Noise", () => {
|
||||
lengthDecoder: uint16BEDecode,
|
||||
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
|
||||
}
|
||||
);
|
||||
const prologue = Buffer.alloc(0);
|
||||
const staticKeys = generateKeypair();
|
||||
const xx = new XX();
|
||||
)
|
||||
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);
|
||||
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());
|
||||
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);
|
||||
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 { 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));
|
||||
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};
|
||||
})(),
|
||||
]);
|
||||
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")]));
|
||||
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);
|
||||
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);
|
||||
assert(decrypted.equals(Buffer.from('test')))
|
||||
assert(valid)
|
||||
} catch (e) {
|
||||
assert(false, e.message);
|
||||
assert(false, e.message)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
it("should test large payloads", async function() {
|
||||
this.timeout(10000);
|
||||
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 noiseInit = new Noise(undefined, undefined)
|
||||
const noiseResp = new Noise(undefined, undefined)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
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);
|
||||
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);
|
||||
const largePlaintext = randomBytes(100000)
|
||||
wrappedOutbound.writeLP(largePlaintext)
|
||||
const response = await wrappedInbound.read(100000)
|
||||
|
||||
expect(response.length).equals(largePlaintext.length);
|
||||
expect(response.length).equals(largePlaintext.length)
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
assert(false, e.message);
|
||||
assert(false, e.message)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it.skip("should communicate through encrypted streams with noise pipes", async() => {
|
||||
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);
|
||||
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);
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey)
|
||||
|
||||
const xxSpy = sandbox.spy(noiseInit, "performXXHandshake");
|
||||
const xxFallbackSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
|
||||
const xxSpy = sandbox.spy(noiseInit, 'performXXHandshake')
|
||||
const xxFallbackSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake')
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
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);
|
||||
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");
|
||||
wrappedOutbound.writeLP(Buffer.from('test v2'))
|
||||
const response = await wrappedInbound.readLP()
|
||||
expect(response.toString()).equal('test v2')
|
||||
|
||||
assert(xxSpy.notCalled);
|
||||
assert(xxFallbackSpy.notCalled);
|
||||
assert(xxSpy.notCalled)
|
||||
assert(xxFallbackSpy.notCalled)
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, e.message);
|
||||
assert(false, e.message)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it.skip("IK -> XX fallback: initiator has invalid remote static key", async() => {
|
||||
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");
|
||||
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);
|
||||
KeyCache.resetStorage()
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
|
||||
KeyCache.store(remotePeer, generateKeypair().publicKey)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
|
||||
]);
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
|
||||
])
|
||||
|
||||
const wrappedInbound = Wrap(inbound.conn);
|
||||
const wrappedOutbound = Wrap(outbound.conn);
|
||||
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");
|
||||
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.");
|
||||
assert(xxSpy.calledOnce, 'XX Fallback method was never called.')
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, e.message);
|
||||
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() => {
|
||||
// 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, undefined, false);
|
||||
const staticKeysResponder = generateKeypair();
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey)
|
||||
|
||||
const noiseResp = new Noise(staticKeysResponder.privateKey);
|
||||
const xxInitSpy = sandbox.spy(noiseInit, "performXXHandshake");
|
||||
const xxRespSpy = sandbox.spy(noiseResp, "performXXFallbackHandshake");
|
||||
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);
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
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),
|
||||
]);
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
|
||||
])
|
||||
|
||||
const wrappedInbound = Wrap(inbound.conn);
|
||||
const wrappedOutbound = Wrap(outbound.conn);
|
||||
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");
|
||||
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.");
|
||||
assert(xxSpy.calledOnce, 'XX Fallback method was never called.')
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, e.message);
|
||||
assert(false, e.message)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it.skip("IK: responder has no remote static key", async() => {
|
||||
it.skip('Initiator starts with XX (pipes disabled), responder has enabled noise pipes', async () => {
|
||||
try {
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey);
|
||||
const staticKeysResponder = generateKeypair();
|
||||
const staticKeysInitiator = generateKeypair()
|
||||
const noiseInit = new Noise(staticKeysInitiator.privateKey, undefined, false)
|
||||
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");
|
||||
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.resetStorage();
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
|
||||
]);
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
|
||||
])
|
||||
|
||||
const wrappedInbound = Wrap(inbound.conn);
|
||||
const wrappedOutbound = Wrap(outbound.conn);
|
||||
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");
|
||||
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.");
|
||||
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);
|
||||
assert(false, e.message)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it("should working without remote peer provided in incoming connection", async() => {
|
||||
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 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.store(localPeer, staticKeysInitiator.publicKey);
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
|
||||
KeyCache.resetStorage()
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
|
||||
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);
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
|
||||
])
|
||||
|
||||
wrappedOutbound.writeLP(Buffer.from("test v2"));
|
||||
const response = await wrappedInbound.readLP();
|
||||
expect(response.toString()).equal("test v2");
|
||||
const wrappedInbound = Wrap(inbound.conn)
|
||||
const wrappedOutbound = Wrap(outbound.conn)
|
||||
|
||||
assert(inbound.remotePeer.marshalPubKey().equals(localPeer.marshalPubKey()));
|
||||
assert(outbound.remotePeer.marshalPubKey().equals(remotePeer.marshalPubKey()));
|
||||
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);
|
||||
assert(false, e.message)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it("should accept and return early data from remote peer", async() => {
|
||||
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) {
|
||||
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);
|
||||
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);
|
||||
KeyCache.store(localPeer, staticKeysInitiator.publicKey)
|
||||
KeyCache.store(remotePeer, staticKeysResponder.publicKey)
|
||||
|
||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||
const [inboundConnection, outboundConnection] = DuplexPair()
|
||||
const [outbound, inbound] = await Promise.all([
|
||||
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
|
||||
noiseResp.secureInbound(remotePeer, inboundConnection),
|
||||
]);
|
||||
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);
|
||||
assert(false, e.message)
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
@ -1,14 +1,14 @@
|
||||
import {keys} from 'libp2p-crypto';
|
||||
import {KeyPair} from "../src/@types/libp2p";
|
||||
import PeerId from "peer-id";
|
||||
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(),
|
||||
publicKey: peerId.marshalPubKey()
|
||||
}
|
||||
}
|
||||
|
@ -1,74 +1,73 @@
|
||||
import Wrap from "it-pb-rpc";
|
||||
import {Buffer} from "buffer";
|
||||
import Duplex from 'it-pair/duplex';
|
||||
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";
|
||||
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, fakePeer;
|
||||
describe('XX Fallback Handshake', () => {
|
||||
let peerA, peerB
|
||||
|
||||
before(async () => {
|
||||
[peerA, peerB] = await createPeerIdsFromFixtures(2);
|
||||
});
|
||||
[peerA, peerB] = await createPeerIdsFromFixtures(2)
|
||||
})
|
||||
|
||||
it("should test that both parties can fallback to XX and finish handshake", async () => {
|
||||
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 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();
|
||||
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 handshakePayload = await getPayload(peerA, staticKeysInitiator.publicKey)
|
||||
const initialMsgR = encode0({
|
||||
ne: ephemeralKeys.publicKey,
|
||||
ns: Buffer.alloc(0),
|
||||
ciphertext: handshakePayload,
|
||||
});
|
||||
ciphertext: handshakePayload
|
||||
})
|
||||
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey);
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
|
||||
const handshakeResp =
|
||||
new XXFallbackHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, initialMsgR, peerA);
|
||||
new XXFallbackHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, initialMsgR, peerA)
|
||||
|
||||
await handshakeResp.propose();
|
||||
await handshakeResp.exchange();
|
||||
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 initialMsgI = await connectionFrom.readLP()
|
||||
const handshakeInit =
|
||||
new XXFallbackHandshake(true, handshakePayload, prologue, staticKeysInitiator, connectionFrom, initialMsgI, peerB, ephemeralKeys);
|
||||
new XXFallbackHandshake(true, handshakePayload, prologue, staticKeysInitiator, connectionFrom, initialMsgI, peerB, ephemeralKeys)
|
||||
|
||||
await handshakeInit.propose();
|
||||
await handshakeInit.exchange();
|
||||
await handshakeInit.propose()
|
||||
await handshakeInit.exchange()
|
||||
|
||||
await handshakeInit.finish();
|
||||
await handshakeResp.finish();
|
||||
await handshakeInit.finish()
|
||||
await handshakeResp.finish()
|
||||
|
||||
const sessionInitator = handshakeInit.session;
|
||||
const sessionResponder = handshakeResp.session;
|
||||
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));
|
||||
assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k))
|
||||
assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k))
|
||||
} else {
|
||||
assert(false);
|
||||
assert(false)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
assert(false, e.message);
|
||||
assert(false, e.message)
|
||||
}
|
||||
});
|
||||
})
|
||||
})
|
||||
|
@ -1,121 +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";
|
||||
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;
|
||||
describe('XX Handshake', () => {
|
||||
let peerA, peerB, fakePeer
|
||||
|
||||
before(async () => {
|
||||
[peerA, peerB, fakePeer] = await createPeerIdsFromFixtures(3);
|
||||
});
|
||||
[peerA, peerB, fakePeer] = await createPeerIdsFromFixtures(3)
|
||||
})
|
||||
|
||||
it("should propose, exchange and finish handshake", async() => {
|
||||
it('should propose, exchange and finish handshake', async () => {
|
||||
try {
|
||||
const duplex = Duplex();
|
||||
const connectionFrom = Wrap(duplex[0]);
|
||||
const connectionTo = Wrap(duplex[1]);
|
||||
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 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 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);
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
|
||||
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA)
|
||||
|
||||
await handshakeInitator.propose();
|
||||
await handshakeResponder.propose();
|
||||
await handshakeInitator.propose()
|
||||
await handshakeResponder.propose()
|
||||
|
||||
await handshakeResponder.exchange();
|
||||
await handshakeInitator.exchange();
|
||||
await handshakeResponder.exchange()
|
||||
await handshakeInitator.exchange()
|
||||
|
||||
await handshakeInitator.finish();
|
||||
await handshakeResponder.finish();
|
||||
await handshakeInitator.finish()
|
||||
await handshakeResponder.finish()
|
||||
|
||||
const sessionInitator = handshakeInitator.session;
|
||||
const sessionResponder = handshakeResponder.session;
|
||||
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));
|
||||
assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k))
|
||||
assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k))
|
||||
} else {
|
||||
assert(false);
|
||||
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);
|
||||
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);
|
||||
assert(false, e.message)
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
it("Initiator should fail to exchange handshake if given wrong public key in payload", async() => {
|
||||
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 duplex = Duplex()
|
||||
const connectionFrom = Wrap(duplex[0])
|
||||
const connectionTo = Wrap(duplex[1])
|
||||
|
||||
const prologue = Buffer.alloc(0);
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const staticKeysResponder = generateKeypair();
|
||||
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 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);
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
|
||||
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA)
|
||||
|
||||
await handshakeInitator.propose();
|
||||
await handshakeResponder.propose();
|
||||
await handshakeInitator.propose()
|
||||
await handshakeResponder.propose()
|
||||
|
||||
await handshakeResponder.exchange();
|
||||
await handshakeInitator.exchange();
|
||||
await handshakeResponder.exchange()
|
||||
await handshakeInitator.exchange()
|
||||
|
||||
assert(false, "Should throw exception");
|
||||
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() => {
|
||||
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 duplex = Duplex()
|
||||
const connectionFrom = Wrap(duplex[0])
|
||||
const connectionTo = Wrap(duplex[1])
|
||||
|
||||
const prologue = Buffer.alloc(0);
|
||||
const staticKeysInitiator = generateKeypair();
|
||||
const staticKeysResponder = generateKeypair();
|
||||
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 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);
|
||||
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
|
||||
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, fakePeer)
|
||||
|
||||
await handshakeInitator.propose();
|
||||
await handshakeResponder.propose();
|
||||
await handshakeInitator.propose()
|
||||
await handshakeResponder.propose()
|
||||
|
||||
await handshakeResponder.exchange();
|
||||
await handshakeInitator.exchange();
|
||||
await handshakeResponder.exchange()
|
||||
await handshakeInitator.exchange()
|
||||
|
||||
await handshakeInitator.finish();
|
||||
await handshakeResponder.finish();
|
||||
await handshakeInitator.finish()
|
||||
await handshakeResponder.finish()
|
||||
|
||||
assert(false, "Should throw exception");
|
||||
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.")
|
||||
}
|
||||
});
|
||||
});
|
||||
})
|
||||
})
|
||||
|
@ -4,6 +4,7 @@
|
||||
"module": "commonjs",
|
||||
"strict": true,
|
||||
"allowJs": true,
|
||||
"sourceMap": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"noImplicitAny": false,
|
||||
|
13
yarn.lock
13
yarn.lock
@ -1803,9 +1803,10 @@ add-stream@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa"
|
||||
integrity sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=
|
||||
|
||||
aegir@ipfs/aegir#feat/cosmiconfig:
|
||||
version "24.0.0"
|
||||
resolved "https://codeload.github.com/ipfs/aegir/tar.gz/15d895f22055023a78d704c8beabbd45c35a8f61"
|
||||
aegir@25.0.0:
|
||||
version "25.0.0"
|
||||
resolved "https://registry.yarnpkg.com/aegir/-/aegir-25.0.0.tgz#9275c4dba0717355ee20c1c35cb27f474fadcca9"
|
||||
integrity sha512-VP1ACVfnUj/k/kwc/W74IOFKVurPsN3BmWxtbxVpr7hLkOoi3/OYBaaROeVndmUiOHihye1PLQUAMpHROgRpeg==
|
||||
dependencies:
|
||||
"@babel/cli" "^7.10.1"
|
||||
"@babel/core" "^7.10.2"
|
||||
@ -1849,7 +1850,6 @@ aegir@ipfs/aegir#feat/cosmiconfig:
|
||||
eslint-plugin-node "^11.0.0"
|
||||
eslint-plugin-promise "^4.2.1"
|
||||
eslint-plugin-standard "^4.0.1"
|
||||
esm "^3.2.25"
|
||||
execa "^4.0.0"
|
||||
extract-zip "^2.0.1"
|
||||
findup-sync "^4.0.0"
|
||||
@ -4595,11 +4595,6 @@ eslint@^6.3.0:
|
||||
text-table "^0.2.0"
|
||||
v8-compile-cache "^2.0.3"
|
||||
|
||||
esm@^3.2.25:
|
||||
version "3.2.25"
|
||||
resolved "https://registry.yarnpkg.com/esm/-/esm-3.2.25.tgz#342c18c29d56157688ba5ce31f8431fbb795cc10"
|
||||
integrity sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==
|
||||
|
||||
espree@^6.1.2:
|
||||
version "6.2.1"
|
||||
resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"
|
||||
|
Loading…
x
Reference in New Issue
Block a user