Merge pull request #67 from NodeFactoryIo/mpetrunic/lint-fix

Fix lint errors and tests
This commit is contained in:
Marin Petrunić 2020-06-23 17:36:45 +02:00 committed by GitHub
commit e01d8e293a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 1504 additions and 1548 deletions

2
.nvmrc
View File

@ -1 +1 @@
12.4.0 12

View File

@ -5,6 +5,9 @@ stages:
- test - test
- cov - cov
env:
- YARN_GPG=no
node_js: node_js:
- '12' - '12'
- '14' - '14'
@ -15,7 +18,9 @@ os:
- windows - windows
script: npx nyc -s yarn run test:node --bail 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: jobs:
include: include:
@ -23,18 +28,19 @@ jobs:
script: script:
- yarn aegir dep-check - yarn aegir dep-check
- yarn run lint - yarn run lint
- yarn run build
- stage: test - stage: test
name: chrome name: chrome
addons: addons:
chrome: stable chrome: stable
script: npx aegir test -t browser -t webworker --ts script: npx aegir test -t browser -t webworker --node true --ts
- stage: test - stage: test
name: firefox name: firefox
addons: addons:
firefox: latest 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 - stage: test
name: electron-main name: electron-main
@ -46,7 +52,7 @@ jobs:
name: electron-renderer name: electron-renderer
os: osx os: osx
script: script:
- npx aegir test -t electron-renderer --bail --ts - npx aegir test -t electron-renderer --node true --bail --ts
notifications: notifications:
email: false email: false

View File

@ -2,8 +2,10 @@
"name": "libp2p-noise", "name": "libp2p-noise",
"version": "1.1.2", "version": "1.1.2",
"main": "dist/src/index.js", "main": "dist/src/index.js",
"types": "dist/src/index.d.ts",
"files": [ "files": [
"dist" "dist",
"src"
], ],
"repository": "git@github.com:NodeFactoryIo/js-libp2p-noise.git", "repository": "git@github.com:NodeFactoryIo/js-libp2p-noise.git",
"author": "NodeFactory <info@nodefactory.io>", "author": "NodeFactory <info@nodefactory.io>",
@ -16,17 +18,19 @@
"scripts": { "scripts": {
"build": "aegir build --ts", "build": "aegir build --ts",
"lint": "aegir lint --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: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" "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": { "devDependencies": {
"@types/bl": "^2.1.0", "@types/bl": "^2.1.0",
"@types/chai": "^4.2.4", "@types/chai": "^4.2.4",
"@types/mocha": "^5.2.7", "@types/mocha": "^5.2.7",
"aegir": "ipfs/aegir#feat/cosmiconfig", "aegir": "25.0.0",
"chai": "^4.2.0", "chai": "^4.2.0",
"karma-mocha-webworker": "^1.3.0",
"mocha": "^6.2.2", "mocha": "^6.2.2",
"sinon": "^8.1.0" "sinon": "^8.1.0"
}, },
@ -47,6 +51,12 @@
"bn.js": "4.4.0" "bn.js": "4.4.0"
}, },
"eslintConfig": { "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"
]
} }
} }

View File

@ -1,7 +1,6 @@
declare module "it-length-prefixed" { declare module 'it-length-prefixed' {
/* eslint-disable @typescript-eslint/interface-name-prefix */ import BufferList from 'bl'
import BufferList from "bl"; import { Buffer } from 'buffer'
import {Buffer} from "buffer"
interface LengthDecoderFunction { interface LengthDecoderFunction {
(data: Buffer | BufferList): number; (data: Buffer | BufferList): number;
@ -9,7 +8,7 @@ declare module "it-length-prefixed" {
} }
interface LengthEncoderFunction { interface LengthEncoderFunction {
(value: Buffer, target: number, offset: number): number|Buffer; (value: number, target: Buffer, offset: number): number|Buffer;
bytes: number; bytes: number;
} }
@ -33,7 +32,7 @@ declare module "it-length-prefixed" {
MAX_DATA_LENGTH: number; MAX_DATA_LENGTH: number;
} }
export const encode: Encoder; export const encode: Encoder
export const decode: Decoder; export const decode: Decoder
} }

View File

@ -1,6 +1,4 @@
export const NOISE_MSG_MAX_LENGTH_BYTES = 65535; 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 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 DUMP_SESSION_KEYS = process.env.DUMP_SESSION_KEYS

View File

@ -1,49 +1,48 @@
import { Buffer } from "buffer"; import { Buffer } from 'buffer'
import {IHandshake} from "./@types/handshake-interface"; import { IHandshake } from './@types/handshake-interface'
import {NOISE_MSG_MAX_LENGTH_BYTES, NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG} from "./constants"; import { NOISE_MSG_MAX_LENGTH_BYTES, NOISE_MSG_MAX_LENGTH_BYTES_WITHOUT_TAG } from './constants'
interface IReturnEncryptionWrapper { interface IReturnEncryptionWrapper {
(source: Iterable<Uint8Array>): AsyncIterableIterator<Uint8Array>; (source: Iterable<Uint8Array>): AsyncIterableIterator<Uint8Array>;
} }
// Returns generator that encrypts payload from the user // Returns generator that encrypts payload from the user
export function encryptStream(handshake: IHandshake): IReturnEncryptionWrapper { export function encryptStream (handshake: IHandshake): IReturnEncryptionWrapper {
return async function * (source) { return async function * (source) {
for await (const chunk of 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) { 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) { if (end > chunkBuffer.length) {
end = chunkBuffer.length; end = chunkBuffer.length
} }
const data = handshake.encrypt(chunkBuffer.slice(i, end), handshake.session); const data = handshake.encrypt(chunkBuffer.slice(i, end), handshake.session)
yield data; yield data
} }
} }
} }
} }
// Decrypt received payload to the user // Decrypt received payload to the user
export function decryptStream(handshake: IHandshake): IReturnEncryptionWrapper { export function decryptStream (handshake: IHandshake): IReturnEncryptionWrapper {
return async function * (source) { return async function * (source) {
for await (const chunk of 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) { 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) { if (end > chunkBuffer.length) {
end = chunkBuffer.length; end = chunkBuffer.length
} }
const chunk = chunkBuffer.slice(i, end); const chunk = chunkBuffer.slice(i, end)
const {plaintext: decrypted, valid} = await handshake.decrypt(chunk, handshake.session); const { plaintext: decrypted, valid } = await handshake.decrypt(chunk, handshake.session)
if(!valid) { if (!valid) {
throw new Error("Failed to validate decrypted chunk"); throw new Error('Failed to validate decrypted chunk')
} }
yield decrypted; yield decrypted
} }
} }
} }

View File

@ -1,66 +1,67 @@
import {Buffer} from "buffer"; import { Buffer } from 'buffer'
import {bytes} from "./@types/basic"; import { bytes } from './@types/basic'
import {MessageBuffer} from "./@types/handshake"; import { MessageBuffer } from './@types/handshake'
import BufferList from 'bl'
export const uint16BEEncode = (value, target, offset) => { export const uint16BEEncode = (value: number, target: Buffer, offset: number): Buffer => {
target = target || Buffer.allocUnsafe(2); target = target || Buffer.allocUnsafe(2)
target.writeUInt16BE(value, offset); target.writeUInt16BE(value, offset)
return target; return target
}; }
uint16BEEncode.bytes = 2; uint16BEEncode.bytes = 2
export const uint16BEDecode = data => { export const uint16BEDecode = (data: Buffer | BufferList): number => {
if (data.length < 2) throw RangeError('Could not decode int16BE'); if (data.length < 2) throw RangeError('Could not decode int16BE')
return data.readUInt16BE(0); return data.readUInt16BE(0)
}; }
uint16BEDecode.bytes = 2; uint16BEDecode.bytes = 2
// Note: IK and XX encoder usage is opposite (XX uses in stages encode0 where IK uses encode1) // Note: IK and XX encoder usage is opposite (XX uses in stages encode0 where IK uses encode1)
export function encode0(message: MessageBuffer): bytes { export function encode0 (message: MessageBuffer): bytes {
return Buffer.concat([message.ne, message.ciphertext]); return Buffer.concat([message.ne, message.ciphertext])
} }
export function encode1(message: MessageBuffer): bytes { export function encode1 (message: MessageBuffer): bytes {
return Buffer.concat([message.ne, message.ns, message.ciphertext]); return Buffer.concat([message.ne, message.ns, message.ciphertext])
} }
export function encode2(message: MessageBuffer): bytes { export function encode2 (message: MessageBuffer): bytes {
return Buffer.concat([message.ns, message.ciphertext]); return Buffer.concat([message.ns, message.ciphertext])
} }
export function decode0(input: bytes): MessageBuffer { export function decode0 (input: bytes): MessageBuffer {
if (input.length < 32) { 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 { return {
ne: input.slice(0, 32), ne: input.slice(0, 32),
ciphertext: input.slice(32, input.length), 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) { 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 { return {
ne: input.slice(0, 32), ne: input.slice(0, 32),
ns: input.slice(32, 80), 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) { 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 { return {
ne: Buffer.alloc(0), ne: Buffer.alloc(0),
ns: input.slice(0, 48), ns: input.slice(0, 48),
ciphertext: input.slice(48, input.length), ciphertext: input.slice(48, input.length)
} }
} }

View File

@ -1,10 +1,12 @@
import BufferList from 'bl'
export class FailedIKError extends Error { export class FailedIKError extends Error {
public initialMsg; public initialMsg: string|BufferList|Buffer;
constructor(initialMsg, message?: string) { constructor (initialMsg: string|BufferList|Buffer, message?: string) {
super(message); super(message)
this.initialMsg = initialMsg; this.initialMsg = initialMsg
this.name = "FailedIKhandshake"; this.name = 'FailedIKhandshake'
} }
}; }

View File

@ -1,13 +1,13 @@
import {WrappedConnection} from "./noise"; import { WrappedConnection } from './noise'
import {IK} from "./handshakes/ik"; import { IK } from './handshakes/ik'
import {NoiseSession} from "./@types/handshake"; import { NoiseSession } from './@types/handshake'
import {bytes, bytes32} from "./@types/basic"; import { bytes, bytes32 } from './@types/basic'
import {KeyPair} from "./@types/libp2p"; import { KeyPair } from './@types/libp2p'
import {IHandshake} from "./@types/handshake-interface"; import { IHandshake } from './@types/handshake-interface'
import {Buffer} from "buffer"; import { Buffer } from 'buffer'
import {decode0, decode1, encode0, encode1} from "./encoder"; import { decode0, decode1, encode0, encode1 } from './encoder'
import {decodePayload, getPeerIdFromPayload, verifySignedPayload} from "./utils"; import { decodePayload, getPeerIdFromPayload, verifySignedPayload } from './utils'
import {FailedIKError} from "./errors"; import { FailedIKError } from './errors'
import { import {
logger, logger,
logLocalStaticKeys, logLocalStaticKeys,
@ -15,8 +15,8 @@ import {
logLocalEphemeralKeys, logLocalEphemeralKeys,
logRemoteEphemeralKey, logRemoteEphemeralKey,
logCipherState logCipherState
} from "./logger"; } from './logger'
import PeerId from "peer-id"; import PeerId from 'peer-id'
export class IKHandshake implements IHandshake { export class IKHandshake implements IHandshake {
public isInitiator: boolean; public isInitiator: boolean;
@ -30,7 +30,7 @@ export class IKHandshake implements IHandshake {
private connection: WrappedConnection; private connection: WrappedConnection;
private ik: IK; private ik: IK;
constructor( constructor (
isInitiator: boolean, isInitiator: boolean,
payload: bytes, payload: bytes,
prologue: bytes32, prologue: bytes32,
@ -38,118 +38,118 @@ export class IKHandshake implements IHandshake {
connection: WrappedConnection, connection: WrappedConnection,
remoteStaticKey: bytes, remoteStaticKey: bytes,
remotePeer?: PeerId, remotePeer?: PeerId,
handshake?: IK, handshake?: IK
) { ) {
this.isInitiator = isInitiator; this.isInitiator = isInitiator
this.payload = Buffer.from(payload); this.payload = Buffer.from(payload)
this.prologue = prologue; this.prologue = prologue
this.staticKeypair = staticKeypair; this.staticKeypair = staticKeypair
this.connection = connection; this.connection = connection
if(remotePeer) { if (remotePeer) {
this.remotePeer = remotePeer; this.remotePeer = remotePeer
} }
this.ik = handshake || new IK(); this.ik = handshake || new IK()
this.session = this.ik.initSession(this.isInitiator, this.prologue, this.staticKeypair, remoteStaticKey); this.session = this.ik.initSession(this.isInitiator, this.prologue, this.staticKeypair, remoteStaticKey)
this.remoteEarlyData = Buffer.alloc(0) this.remoteEarlyData = Buffer.alloc(0)
} }
public async stage0(): Promise<void> { public async stage0 (): Promise<void> {
logLocalStaticKeys(this.session.hs.s) logLocalStaticKeys(this.session.hs.s)
logRemoteStaticKey(this.session.hs.rs) logRemoteStaticKey(this.session.hs.rs)
if (this.isInitiator) { if (this.isInitiator) {
logger("IK Stage 0 - Initiator sending message..."); logger('IK Stage 0 - Initiator sending message...')
const messageBuffer = this.ik.sendMessage(this.session, this.payload); const messageBuffer = this.ik.sendMessage(this.session, this.payload)
this.connection.writeLP(encode1(messageBuffer)); this.connection.writeLP(encode1(messageBuffer))
logger("IK Stage 0 - Initiator sent message."); logger('IK Stage 0 - Initiator sent message.')
logLocalEphemeralKeys(this.session.hs.e) logLocalEphemeralKeys(this.session.hs.e)
} else { } else {
logger("IK Stage 0 - Responder receiving message..."); logger('IK Stage 0 - Responder receiving message...')
const receivedMsg = await this.connection.readLP(); const receivedMsg = await this.connection.readLP()
try { try {
const receivedMessageBuffer = decode1(receivedMsg.slice()); const receivedMessageBuffer = decode1(receivedMsg.slice())
const {plaintext, valid} = this.ik.recvMessage(this.session, receivedMessageBuffer); const { plaintext, valid } = this.ik.recvMessage(this.session, receivedMessageBuffer)
if(!valid) { if (!valid) {
throw new Error("ik handshake stage 0 decryption validation fail"); throw new Error('ik handshake stage 0 decryption validation fail')
} }
logger("IK Stage 0 - Responder got message, going to verify payload."); logger('IK Stage 0 - Responder got message, going to verify payload.')
const decodedPayload = await decodePayload(plaintext); const decodedPayload = await decodePayload(plaintext)
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload); this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer); await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer)
this.setRemoteEarlyData(decodedPayload.data); this.setRemoteEarlyData(decodedPayload.data)
logger("IK Stage 0 - Responder successfully verified payload!"); logger('IK Stage 0 - Responder successfully verified payload!')
logRemoteEphemeralKey(this.session.hs.re) logRemoteEphemeralKey(this.session.hs.re)
} catch (e) { } 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) { if (this.isInitiator) {
logger("IK Stage 1 - Initiator receiving message..."); logger('IK Stage 1 - Initiator receiving message...')
const receivedMsg = (await this.connection.readLP()).slice(); const receivedMsg = (await this.connection.readLP()).slice()
const receivedMessageBuffer = decode0(Buffer.from(receivedMsg)); const receivedMessageBuffer = decode0(Buffer.from(receivedMsg))
const {plaintext, valid} = this.ik.recvMessage(this.session, receivedMessageBuffer); 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 got message, going to verify payload.')
try { try {
if(!valid) { if (!valid) {
throw new Error("ik stage 1 decryption validation fail"); throw new Error('ik stage 1 decryption validation fail')
} }
const decodedPayload = await decodePayload(plaintext); const decodedPayload = await decodePayload(plaintext)
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload); this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
await verifySignedPayload(receivedMessageBuffer.ns.slice(0, 32), decodedPayload, this.remotePeer); await verifySignedPayload(receivedMessageBuffer.ns.slice(0, 32), decodedPayload, this.remotePeer)
this.setRemoteEarlyData(decodedPayload.data); this.setRemoteEarlyData(decodedPayload.data)
logger("IK Stage 1 - Initiator successfully verified payload!"); logger('IK Stage 1 - Initiator successfully verified payload!')
logRemoteEphemeralKey(this.session.hs.re) logRemoteEphemeralKey(this.session.hs.re)
} catch (e) { } catch (e) {
logger("Initiator breaking up with IK handshake in stage 1."); logger('Initiator breaking up with IK handshake in stage 1.')
throw new FailedIKError(receivedMsg, `Error occurred while verifying responder's signed payload: ${e.message}`); throw new FailedIKError(receivedMsg, `Error occurred while verifying responder's signed payload: ${e.message}`)
} }
} else { } else {
logger("IK Stage 1 - Responder sending message..."); logger('IK Stage 1 - Responder sending message...')
const messageBuffer = this.ik.sendMessage(this.session, this.payload); const messageBuffer = this.ik.sendMessage(this.session, this.payload)
this.connection.writeLP(encode0(messageBuffer)); this.connection.writeLP(encode0(messageBuffer))
logger("IK Stage 1 - Responder sent message..."); logger('IK Stage 1 - Responder sent message...')
logLocalEphemeralKeys(this.session.hs.e) logLocalEphemeralKeys(this.session.hs.e)
} }
logCipherState(this.session) logCipherState(this.session)
} }
public decrypt(ciphertext: bytes, session: NoiseSession): {plaintext: bytes; valid: boolean} { public decrypt (ciphertext: bytes, session: NoiseSession): {plaintext: bytes; valid: boolean} {
const cs = this.getCS(session, false); const cs = this.getCS(session, false)
return this.ik.decryptWithAd(cs, Buffer.alloc(0), ciphertext); return this.ik.decryptWithAd(cs, Buffer.alloc(0), ciphertext)
} }
public encrypt(plaintext: Buffer, session: NoiseSession): Buffer { public encrypt (plaintext: Buffer, session: NoiseSession): Buffer {
const cs = this.getCS(session); const cs = this.getCS(session)
return this.ik.encryptWithAd(cs, Buffer.alloc(0), plaintext); return this.ik.encryptWithAd(cs, Buffer.alloc(0), plaintext)
} }
public getLocalEphemeralKeys(): KeyPair { public getLocalEphemeralKeys (): KeyPair {
if (!this.session.hs.e) { 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) { 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) { if (this.isInitiator) {
return encryption ? session.cs1 : session.cs2; return encryption ? session.cs1 : session.cs2
} else { } else {
return encryption ? session.cs2 : session.cs1; return encryption ? session.cs2 : session.cs1
} }
} }
private setRemoteEarlyData(data: Uint8Array|null|undefined): void { private setRemoteEarlyData (data: Uint8Array|null|undefined): void {
if(data){ if (data) {
this.remoteEarlyData = Buffer.from(data.buffer, data.byteOffset, data.length); this.remoteEarlyData = Buffer.from(data.buffer, data.byteOffset, data.length)
} }
} }
} }

View File

@ -1,19 +1,19 @@
import {Buffer} from "buffer"; import { Buffer } from 'buffer'
import {XXHandshake} from "./handshake-xx"; import { XXHandshake } from './handshake-xx'
import {XX} from "./handshakes/xx"; import { XX } from './handshakes/xx'
import {KeyPair} from "./@types/libp2p"; import { KeyPair } from './@types/libp2p'
import {bytes, bytes32} from "./@types/basic"; import { bytes, bytes32 } from './@types/basic'
import {decodePayload, getPeerIdFromPayload, verifySignedPayload} from "./utils"; import { decodePayload, getPeerIdFromPayload, verifySignedPayload } from './utils'
import {logger, logLocalEphemeralKeys, logRemoteEphemeralKey, logRemoteStaticKey} from "./logger"; import { logger, logLocalEphemeralKeys, logRemoteEphemeralKey, logRemoteStaticKey } from './logger'
import {WrappedConnection} from "./noise"; import { WrappedConnection } from './noise'
import {decode0, decode1} from "./encoder"; import { decode0, decode1 } from './encoder'
import PeerId from "peer-id"; import PeerId from 'peer-id'
export class XXFallbackHandshake extends XXHandshake { export class XXFallbackHandshake extends XXHandshake {
private ephemeralKeys?: KeyPair; private ephemeralKeys?: KeyPair;
private initialMsg: bytes; private initialMsg: bytes;
constructor( constructor (
isInitiator: boolean, isInitiator: boolean,
payload: bytes, payload: bytes,
prologue: bytes32, prologue: bytes32,
@ -22,63 +22,64 @@ export class XXFallbackHandshake extends XXHandshake {
initialMsg: bytes, initialMsg: bytes,
remotePeer?: PeerId, remotePeer?: PeerId,
ephemeralKeys?: KeyPair, ephemeralKeys?: KeyPair,
handshake?: XX, handshake?: XX
) { ) {
super(isInitiator, payload, prologue, staticKeypair, connection, remotePeer, handshake); super(isInitiator, payload, prologue, staticKeypair, connection, remotePeer, handshake)
if (ephemeralKeys) { if (ephemeralKeys) {
this.ephemeralKeys = ephemeralKeys; this.ephemeralKeys = ephemeralKeys
} }
this.initialMsg = initialMsg; this.initialMsg = initialMsg
} }
// stage 0 // stage 0
public async propose(): Promise<void> { // eslint-disable-next-line require-await
public async propose (): Promise<void> {
if (this.isInitiator) { if (this.isInitiator) {
this.xx.sendMessage(this.session, Buffer.alloc(0), this.ephemeralKeys); 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."); logger('XX Fallback Stage 0 - Initialized state as the first message was sent by initiator.')
logLocalEphemeralKeys(this.session.hs.e) logLocalEphemeralKeys(this.session.hs.e)
} else { } else {
logger("XX Fallback Stage 0 - Responder decoding initial msg from IK."); logger('XX Fallback Stage 0 - Responder decoding initial msg from IK.')
const receivedMessageBuffer = decode0(this.initialMsg); const receivedMessageBuffer = decode0(this.initialMsg)
const {valid} = this.xx.recvMessage(this.session, { const { valid } = this.xx.recvMessage(this.session, {
ne: receivedMessageBuffer.ne, ne: receivedMessageBuffer.ne,
ns: Buffer.alloc(0), ns: Buffer.alloc(0),
ciphertext: Buffer.alloc(0), ciphertext: Buffer.alloc(0)
}); })
if(!valid) { if (!valid) {
throw new Error("xx fallback stage 0 decryption validation fail"); 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) logRemoteEphemeralKey(this.session.hs.re)
} }
} }
// stage 1 // stage 1
public async exchange(): Promise<void> { public async exchange (): Promise<void> {
if (this.isInitiator) { if (this.isInitiator) {
const receivedMessageBuffer = decode1(this.initialMsg); const receivedMessageBuffer = decode1(this.initialMsg)
const {plaintext, valid} = this.xx.recvMessage(this.session, receivedMessageBuffer); const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
if(!valid) { if (!valid) {
throw new Error("xx fallback stage 1 decryption validation fail"); 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) logRemoteEphemeralKey(this.session.hs.re)
logRemoteStaticKey(this.session.hs.rs) logRemoteStaticKey(this.session.hs.rs)
logger("Initiator going to check remote's signature..."); logger("Initiator going to check remote's signature...")
try { try {
const decodedPayload = await decodePayload(plaintext); const decodedPayload = await decodePayload(plaintext)
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload); this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer); await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer)
this.setRemoteEarlyData(decodedPayload.data) this.setRemoteEarlyData(decodedPayload.data)
} catch (e) { } 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 { } else {
logger("XX Fallback Stage 1 - Responder start"); logger('XX Fallback Stage 1 - Responder start')
await super.exchange(); await super.exchange()
logger("XX Fallback Stage 1 - Responder end"); logger('XX Fallback Stage 1 - Responder end')
} }
} }
} }

View File

@ -1,26 +1,26 @@
import { Buffer } from "buffer"; import { Buffer } from 'buffer'
import { XX } from "./handshakes/xx"; import { XX } from './handshakes/xx'
import { KeyPair } from "./@types/libp2p"; import { KeyPair } from './@types/libp2p'
import { bytes, bytes32 } from "./@types/basic"; import { bytes, bytes32 } from './@types/basic'
import { NoiseSession } from "./@types/handshake"; import { NoiseSession } from './@types/handshake'
import {IHandshake} from "./@types/handshake-interface"; import { IHandshake } from './@types/handshake-interface'
import { import {
decodePayload, decodePayload,
getPeerIdFromPayload, getPeerIdFromPayload,
verifySignedPayload, verifySignedPayload
} from "./utils"; } from './utils'
import { import {
logger, logger,
logLocalStaticKeys, logLocalStaticKeys,
logLocalEphemeralKeys, logLocalEphemeralKeys,
logRemoteEphemeralKey, logRemoteEphemeralKey,
logRemoteStaticKey, logRemoteStaticKey,
logCipherState, logCipherState
} from "./logger"; } from './logger'
import {decode0, decode1, decode2, encode0, encode1, encode2} from "./encoder"; import { decode0, decode1, decode2, encode0, encode1, encode2 } from './encoder'
import { WrappedConnection } from "./noise"; import { WrappedConnection } from './noise'
import PeerId from "peer-id"; import PeerId from 'peer-id'
export class XXHandshake implements IHandshake { export class XXHandshake implements IHandshake {
public isInitiator: boolean; public isInitiator: boolean;
@ -35,139 +35,139 @@ export class XXHandshake implements IHandshake {
private prologue: bytes32; private prologue: bytes32;
constructor( constructor (
isInitiator: boolean, isInitiator: boolean,
payload: bytes, payload: bytes,
prologue: bytes32, prologue: bytes32,
staticKeypair: KeyPair, staticKeypair: KeyPair,
connection: WrappedConnection, connection: WrappedConnection,
remotePeer?: PeerId, remotePeer?: PeerId,
handshake?: XX, handshake?: XX
) { ) {
this.isInitiator = isInitiator; this.isInitiator = isInitiator
this.payload = payload; this.payload = payload
this.prologue = prologue; this.prologue = prologue
this.staticKeypair = staticKeypair; this.staticKeypair = staticKeypair
this.connection = connection; this.connection = connection
if(remotePeer) { if (remotePeer) {
this.remotePeer = remotePeer; this.remotePeer = remotePeer
} }
this.xx = handshake || new XX(); this.xx = handshake || new XX()
this.session = this.xx.initSession(this.isInitiator, this.prologue, this.staticKeypair); this.session = this.xx.initSession(this.isInitiator, this.prologue, this.staticKeypair)
this.remoteEarlyData = Buffer.alloc(0) this.remoteEarlyData = Buffer.alloc(0)
} }
// stage 0 // stage 0
public async propose(): Promise<void> { public async propose (): Promise<void> {
logLocalStaticKeys(this.session.hs.s) logLocalStaticKeys(this.session.hs.s)
if (this.isInitiator) { if (this.isInitiator) {
logger("Stage 0 - Initiator starting to send first message."); logger('Stage 0 - Initiator starting to send first message.')
const messageBuffer = this.xx.sendMessage(this.session, Buffer.alloc(0)); const messageBuffer = this.xx.sendMessage(this.session, Buffer.alloc(0))
this.connection.writeLP(encode0(messageBuffer)); this.connection.writeLP(encode0(messageBuffer))
logger("Stage 0 - Initiator finished sending first message."); logger('Stage 0 - Initiator finished sending first message.')
logLocalEphemeralKeys(this.session.hs.e) logLocalEphemeralKeys(this.session.hs.e)
} else { } else {
logger("Stage 0 - Responder waiting to receive first message..."); logger('Stage 0 - Responder waiting to receive first message...')
const receivedMessageBuffer = decode0((await this.connection.readLP()).slice()); const receivedMessageBuffer = decode0((await this.connection.readLP()).slice())
const {valid} = this.xx.recvMessage(this.session, receivedMessageBuffer); const { valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
if(!valid) { if (!valid) {
throw new Error("xx handshake stage 0 validation fail"); 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) logRemoteEphemeralKey(this.session.hs.re)
} }
} }
// stage 1 // stage 1
public async exchange(): Promise<void> { public async exchange (): Promise<void> {
if (this.isInitiator) { if (this.isInitiator) {
logger('Stage 1 - Initiator waiting to receive first message from responder...'); logger('Stage 1 - Initiator waiting to receive first message from responder...')
const receivedMessageBuffer = decode1((await this.connection.readLP()).slice()); const receivedMessageBuffer = decode1((await this.connection.readLP()).slice())
const {plaintext, valid} = this.xx.recvMessage(this.session, receivedMessageBuffer); const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
if(!valid) { if (!valid) {
throw new Error("xx handshake stage 1 validation fail"); 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) logRemoteEphemeralKey(this.session.hs.re)
logRemoteStaticKey(this.session.hs.rs) logRemoteStaticKey(this.session.hs.rs)
logger("Initiator going to check remote's signature..."); logger("Initiator going to check remote's signature...")
try { try {
const decodedPayload = await decodePayload(plaintext); const decodedPayload = await decodePayload(plaintext)
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload); this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
this.remotePeer = await verifySignedPayload(receivedMessageBuffer.ns, decodedPayload, this.remotePeer); this.remotePeer = await verifySignedPayload(receivedMessageBuffer.ns, decodedPayload, this.remotePeer)
this.setRemoteEarlyData(decodedPayload.data) this.setRemoteEarlyData(decodedPayload.data)
} catch (e) { } 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 { } else {
logger('Stage 1 - Responder sending out first message with signed payload and static key.'); logger('Stage 1 - Responder sending out first message with signed payload and static key.')
const messageBuffer = this.xx.sendMessage(this.session, this.payload); const messageBuffer = this.xx.sendMessage(this.session, this.payload)
this.connection.writeLP(encode1(messageBuffer)); this.connection.writeLP(encode1(messageBuffer))
logger('Stage 1 - Responder sent the second handshake message with signed payload.') logger('Stage 1 - Responder sent the second handshake message with signed payload.')
logLocalEphemeralKeys(this.session.hs.e) logLocalEphemeralKeys(this.session.hs.e)
} }
} }
// stage 2 // stage 2
public async finish(): Promise<void> { public async finish (): Promise<void> {
if (this.isInitiator) { if (this.isInitiator) {
logger('Stage 2 - Initiator sending third handshake message.'); logger('Stage 2 - Initiator sending third handshake message.')
const messageBuffer = this.xx.sendMessage(this.session, this.payload); const messageBuffer = this.xx.sendMessage(this.session, this.payload)
this.connection.writeLP(encode2(messageBuffer)); this.connection.writeLP(encode2(messageBuffer))
logger('Stage 2 - Initiator sent message with signed payload.'); logger('Stage 2 - Initiator sent message with signed payload.')
} else { } else {
logger('Stage 2 - Responder waiting for third handshake message...'); logger('Stage 2 - Responder waiting for third handshake message...')
const receivedMessageBuffer = decode2((await this.connection.readLP()).slice()); const receivedMessageBuffer = decode2((await this.connection.readLP()).slice())
const {plaintext, valid} = this.xx.recvMessage(this.session, receivedMessageBuffer); const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer)
if(!valid) { if (!valid) {
throw new Error("xx handshake stage 2 validation fail"); 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 { try {
const decodedPayload = await decodePayload(plaintext); const decodedPayload = await decodePayload(plaintext)
this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload); this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload)
await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer); await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer)
this.setRemoteEarlyData(decodedPayload.data) this.setRemoteEarlyData(decodedPayload.data)
} catch (e) { } 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) logCipherState(this.session)
} }
public encrypt(plaintext: bytes, session: NoiseSession): bytes { public encrypt (plaintext: bytes, session: NoiseSession): bytes {
const cs = this.getCS(session); 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} { public decrypt (ciphertext: bytes, session: NoiseSession): {plaintext: bytes; valid: boolean} {
const cs = this.getCS(session, false); const cs = this.getCS(session, false)
return this.xx.decryptWithAd(cs, Buffer.alloc(0), ciphertext); return this.xx.decryptWithAd(cs, Buffer.alloc(0), ciphertext)
} }
public getRemoteStaticKey(): bytes { public getRemoteStaticKey (): bytes {
return this.session.hs.rs; return this.session.hs.rs
} }
private getCS(session: NoiseSession, encryption = true) { private getCS (session: NoiseSession, encryption = true) {
if (!session.cs1 || !session.cs2) { 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) { if (this.isInitiator) {
return encryption ? session.cs1 : session.cs2; return encryption ? session.cs1 : session.cs2
} else { } else {
return encryption ? session.cs2 : session.cs1; return encryption ? session.cs2 : session.cs1
} }
} }
protected setRemoteEarlyData(data: Uint8Array|null|undefined): void { protected setRemoteEarlyData (data: Uint8Array|null|undefined): void {
if(data){ if (data) {
this.remoteEarlyData = Buffer.from(data.buffer, data.byteOffset, data.length); this.remoteEarlyData = Buffer.from(data.buffer, data.byteOffset, data.length)
} }
} }
} }

View File

@ -1,180 +1,179 @@
import {Buffer} from "buffer"; import { Buffer } from 'buffer'
import AEAD from 'bcrypto/lib/js/aead'; import AEAD from 'bcrypto/lib/js/aead'
import x25519 from 'bcrypto/lib/js/x25519'; import x25519 from 'bcrypto/lib/js/x25519'
import SHA256 from 'bcrypto/lib/js/sha256'; import SHA256 from 'bcrypto/lib/js/sha256'
import {bytes, bytes32, uint32} from "../@types/basic"; import { bytes, bytes32, uint32 } from '../@types/basic'
import {CipherState, MessageBuffer, SymmetricState} from "../@types/handshake"; import { CipherState, MessageBuffer, SymmetricState } from '../@types/handshake'
import {getHkdf} from "../utils"; import { getHkdf } from '../utils'
import {logger} from "../logger"; import { logger } from '../logger'
export const MIN_NONCE = 0; export const MIN_NONCE = 0
export abstract class AbstractHandshake { export abstract class AbstractHandshake {
public encryptWithAd(cs: CipherState, ad: bytes, plaintext: bytes): bytes { public encryptWithAd (cs: CipherState, ad: bytes, plaintext: bytes): bytes {
const e = this.encrypt(cs.k, cs.n, ad, plaintext); const e = this.encrypt(cs.k, cs.n, ad, plaintext)
this.setNonce(cs, this.incrementNonce(cs.n)); this.setNonce(cs, this.incrementNonce(cs.n))
return e; return e
} }
public decryptWithAd(cs: CipherState, ad: bytes, ciphertext: bytes): {plaintext: bytes; valid: boolean} { public decryptWithAd (cs: CipherState, ad: bytes, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
const {plaintext, valid} = this.decrypt(cs.k, cs.n, ad, ciphertext); const { plaintext, valid } = this.decrypt(cs.k, cs.n, ad, ciphertext)
this.setNonce(cs, this.incrementNonce(cs.n)); this.setNonce(cs, this.incrementNonce(cs.n))
return {plaintext, valid}; return { plaintext, valid }
} }
// Cipher state related // Cipher state related
protected hasKey(cs: CipherState): boolean { protected hasKey (cs: CipherState): boolean {
return !this.isEmptyKey(cs.k); return !this.isEmptyKey(cs.k)
} }
protected setNonce(cs: CipherState, nonce: uint32): void { protected setNonce (cs: CipherState, nonce: uint32): void {
cs.n = nonce; cs.n = nonce
} }
protected createEmptyKey(): bytes32 { protected createEmptyKey (): bytes32 {
return Buffer.alloc(32); return Buffer.alloc(32)
} }
protected isEmptyKey(k: bytes32): boolean { protected isEmptyKey (k: bytes32): boolean {
const emptyKey = this.createEmptyKey(); const emptyKey = this.createEmptyKey()
return emptyKey.equals(k); return emptyKey.equals(k)
} }
protected incrementNonce(n: uint32): uint32 { protected incrementNonce (n: uint32): uint32 {
return n + 1; return n + 1
} }
protected nonceToBytes(n: uint32): bytes { protected nonceToBytes (n: uint32): bytes {
const nonce = Buffer.alloc(12); const nonce = Buffer.alloc(12)
nonce.writeUInt32LE(n, 4); nonce.writeUInt32LE(n, 4)
return nonce; return nonce
} }
protected encrypt(k: bytes32, n: uint32, ad: bytes, plaintext: bytes): bytes { protected encrypt (k: bytes32, n: uint32, ad: bytes, plaintext: bytes): bytes {
const nonce = this.nonceToBytes(n); const nonce = this.nonceToBytes(n)
const ctx = new AEAD(); const ctx = new AEAD()
plaintext = Buffer.from(plaintext); plaintext = Buffer.from(plaintext)
ctx.init(k, nonce); ctx.init(k, nonce)
ctx.aad(ad); ctx.aad(ad)
ctx.encrypt(plaintext); ctx.encrypt(plaintext)
// Encryption is done on the sent reference // 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 { protected encryptAndHash (ss: SymmetricState, plaintext: bytes): bytes {
let ciphertext; let ciphertext
if (this.hasKey(ss.cs)) { if (this.hasKey(ss.cs)) {
ciphertext = this.encryptWithAd(ss.cs, ss.h, plaintext); ciphertext = this.encryptWithAd(ss.cs, ss.h, plaintext)
} else { } else {
ciphertext = plaintext; ciphertext = plaintext
} }
this.mixHash(ss, ciphertext); this.mixHash(ss, ciphertext)
return ciphertext; return ciphertext
} }
protected decrypt(k: bytes32, n: uint32, ad: bytes, ciphertext: bytes): {plaintext: bytes; valid: boolean} { protected decrypt (k: bytes32, n: uint32, ad: bytes, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
const nonce = this.nonceToBytes(n); const nonce = this.nonceToBytes(n)
const ctx = new AEAD(); const ctx = new AEAD()
ciphertext = Buffer.from(ciphertext); ciphertext = Buffer.from(ciphertext)
const tag = ciphertext.slice(ciphertext.length - 16); const tag = ciphertext.slice(ciphertext.length - 16)
ciphertext = ciphertext.slice(0, ciphertext.length - 16); ciphertext = ciphertext.slice(0, ciphertext.length - 16)
ctx.init(k, nonce); ctx.init(k, nonce)
ctx.aad(ad); ctx.aad(ad)
ctx.decrypt(ciphertext); ctx.decrypt(ciphertext)
// Decryption is done on the sent reference // 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} { protected decryptAndHash (ss: SymmetricState, ciphertext: bytes): {plaintext: bytes; valid: boolean} {
let plaintext: bytes, valid = true; let plaintext: bytes; let valid = true
if (this.hasKey(ss.cs)) { if (this.hasKey(ss.cs)) {
({plaintext, valid} = this.decryptWithAd(ss.cs, ss.h, ciphertext)); ({ plaintext, valid } = this.decryptWithAd(ss.cs, ss.h, ciphertext))
} else { } else {
plaintext = ciphertext; plaintext = ciphertext
} }
this.mixHash(ss, ciphertext); this.mixHash(ss, ciphertext)
return {plaintext, valid}; return { plaintext, valid }
} }
protected dh(privateKey: bytes32, publicKey: bytes32): bytes32 { protected dh (privateKey: bytes32, publicKey: bytes32): bytes32 {
try { try {
const derived = x25519.derive(publicKey, privateKey); const derived = x25519.derive(publicKey, privateKey)
const result = Buffer.alloc(32); const result = Buffer.alloc(32)
derived.copy(result); derived.copy(result)
return result; return result
} catch (e) { } catch (e) {
logger(e.message); logger(e.message)
return Buffer.alloc(32); return Buffer.alloc(32)
} }
} }
protected mixHash(ss: SymmetricState, data: bytes): void { protected mixHash (ss: SymmetricState, data: bytes): void {
ss.h = this.getHash(ss.h, data); ss.h = this.getHash(ss.h, data)
} }
protected getHash(a: bytes, b: bytes): bytes32 { protected getHash (a: bytes, b: bytes): bytes32 {
return SHA256.digest(Buffer.from([...a, ...b])); return SHA256.digest(Buffer.from([...a, ...b]))
} }
protected mixKey(ss: SymmetricState, ikm: bytes32): void { protected mixKey (ss: SymmetricState, ikm: bytes32): void {
const [ ck, tempK ] = getHkdf(ss.ck, ikm); const [ck, tempK] = getHkdf(ss.ck, ikm)
ss.cs = this.initializeKey(tempK) as CipherState; ss.cs = this.initializeKey(tempK) as CipherState
ss.ck = ck; ss.ck = ck
} }
protected initializeKey(k: bytes32): CipherState { protected initializeKey (k: bytes32): CipherState {
const n = MIN_NONCE; const n = MIN_NONCE
return { k, n }; return { k, n }
} }
// Symmetric state related // Symmetric state related
protected initializeSymmetric(protocolName: string): SymmetricState { protected initializeSymmetric (protocolName: string): SymmetricState {
const protocolNameBytes: bytes = Buffer.from(protocolName, 'utf-8'); const protocolNameBytes: bytes = Buffer.from(protocolName, 'utf-8')
const h = this.hashProtocolName(protocolNameBytes); const h = this.hashProtocolName(protocolNameBytes)
const ck = h; const ck = h
const key = this.createEmptyKey(); const key = this.createEmptyKey()
const cs: CipherState = this.initializeKey(key); 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) { if (protocolName.length <= 32) {
const h = Buffer.alloc(32); const h = Buffer.alloc(32)
protocolName.copy(h); protocolName.copy(h)
return h; return h
} else { } else {
return this.getHash(protocolName, Buffer.alloc(0)); return this.getHash(protocolName, Buffer.alloc(0))
} }
} }
protected split(ss: SymmetricState) { protected split (ss: SymmetricState): {cs1: CipherState, cs2: CipherState} {
const [ tempk1, tempk2 ] = getHkdf(ss.ck, Buffer.alloc(0)); const [tempk1, tempk2] = getHkdf(ss.ck, Buffer.alloc(0))
const cs1 = this.initializeKey(tempk1); const cs1 = this.initializeKey(tempk1)
const cs2 = this.initializeKey(tempk2); const cs2 = this.initializeKey(tempk2)
return { cs1, cs2 }; return { cs1, cs2 }
} }
protected writeMessageRegular(cs: CipherState, payload: bytes): MessageBuffer { protected writeMessageRegular (cs: CipherState, payload: bytes): MessageBuffer {
const ciphertext = this.encryptWithAd(cs, Buffer.alloc(0), payload); const ciphertext = this.encryptWithAd(cs, Buffer.alloc(0), payload)
const ne = this.createEmptyKey(); const ne = this.createEmptyKey()
const ns = Buffer.alloc(0); const ns = Buffer.alloc(0)
return { ne, ns, ciphertext }; return { ne, ns, ciphertext }
} }
protected readMessageRegular(cs: CipherState, message: MessageBuffer): {plaintext: bytes; valid: boolean} { protected readMessageRegular (cs: CipherState, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext); return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext)
} }
} }

View File

@ -1,157 +1,156 @@
import {Buffer} from "buffer"; import { Buffer } from 'buffer'
import {CipherState, HandshakeState, MessageBuffer, NoiseSession} from "../@types/handshake"; import { CipherState, HandshakeState, MessageBuffer, NoiseSession } from '../@types/handshake'
import {bytes, bytes32} from "../@types/basic"; import { bytes, bytes32 } from '../@types/basic'
import {generateKeypair, isValidPublicKey} from "../utils"; import { generateKeypair, isValidPublicKey } from '../utils'
import {AbstractHandshake} from "./abstract-handshake"; import { AbstractHandshake } from './abstract-handshake'
import {KeyPair} from "../@types/libp2p"; import { KeyPair } from '../@types/libp2p'
export class IK extends AbstractHandshake { export class IK extends AbstractHandshake {
public initSession(initiator: boolean, prologue: bytes32, s: KeyPair, rs: bytes32): NoiseSession { public initSession (initiator: boolean, prologue: bytes32, s: KeyPair, rs: bytes32): NoiseSession {
const psk = this.createEmptyKey(); const psk = this.createEmptyKey()
let hs; let hs
if (initiator) { if (initiator) {
hs = this.initializeInitiator(prologue, s, rs, psk); hs = this.initializeInitiator(prologue, s, rs, psk)
} else { } else {
hs = this.initializeResponder(prologue, s, rs, psk); hs = this.initializeResponder(prologue, s, rs, psk)
} }
return { return {
hs, hs,
i: initiator, i: initiator,
mc: 0, mc: 0
}; }
} }
public sendMessage(session: NoiseSession, message: bytes): MessageBuffer { public sendMessage (session: NoiseSession, message: bytes): MessageBuffer {
let messageBuffer: MessageBuffer; let messageBuffer: MessageBuffer
if (session.mc === 0) { if (session.mc === 0) {
messageBuffer = this.writeMessageA(session.hs, message); messageBuffer = this.writeMessageA(session.hs, message)
} else if (session.mc === 1) { } else if (session.mc === 1) {
const { messageBuffer: mb, h, cs1, cs2 } = this.writeMessageB(session.hs, message); const { messageBuffer: mb, h, cs1, cs2 } = this.writeMessageB(session.hs, message)
messageBuffer = mb; messageBuffer = mb
session.h = h; session.h = h
session.cs1 = cs1; session.cs1 = cs1
session.cs2 = cs2; session.cs2 = cs2
} else if (session.mc > 1) { } else if (session.mc > 1) {
if (session.i) { if (session.i) {
if (!session.cs1) { 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 { } else {
if (!session.cs2) { 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 { } else {
throw new Error("Session invalid.") throw new Error('Session invalid.')
} }
session.mc++; session.mc++
return messageBuffer; return messageBuffer
} }
public recvMessage(session: NoiseSession, message: MessageBuffer): {plaintext: bytes; valid: boolean} { public recvMessage (session: NoiseSession, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
let plaintext = Buffer.alloc(0), valid = false; let plaintext = Buffer.alloc(0); let valid = false
if (session.mc === 0) { if (session.mc === 0) {
({plaintext, valid} = this.readMessageA(session.hs, message)); ({ plaintext, valid } = this.readMessageA(session.hs, message))
} }
if (session.mc === 1) { if (session.mc === 1) {
const { plaintext: pt, valid: v, h, cs1, cs2 } = this.readMessageB(session.hs, message); const { plaintext: pt, valid: v, h, cs1, cs2 } = this.readMessageB(session.hs, message)
plaintext = pt; plaintext = pt
valid = v; valid = v
session.h = h; session.h = h
session.cs1 = cs1; session.cs1 = cs1
session.cs2 = cs2; session.cs2 = cs2
} }
session.mc++; session.mc++
return {plaintext, valid}; return { plaintext, valid }
} }
private writeMessageA(hs: HandshakeState, payload: bytes): MessageBuffer { private writeMessageA (hs: HandshakeState, payload: bytes): MessageBuffer {
hs.e = generateKeypair(); hs.e = generateKeypair()
const ne = hs.e.publicKey; const ne = hs.e.publicKey
this.mixHash(hs.ss, ne); this.mixHash(hs.ss, ne)
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs)); this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs))
const spk = Buffer.from(hs.s.publicKey); const spk = Buffer.from(hs.s.publicKey)
const ns = this.encryptAndHash(hs.ss, spk); const ns = this.encryptAndHash(hs.ss, spk)
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs)); this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs))
const ciphertext = this.encryptAndHash(hs.ss, payload); const ciphertext = this.encryptAndHash(hs.ss, payload)
return { ne, ns, ciphertext }; return { ne, ns, ciphertext }
} }
private writeMessageB(hs: HandshakeState, payload: bytes) { private writeMessageB (hs: HandshakeState, payload: bytes) {
hs.e = generateKeypair(); hs.e = generateKeypair()
const ne = hs.e.publicKey; const ne = hs.e.publicKey
this.mixHash(hs.ss, ne); 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.re))
this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs)); this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs))
const ciphertext = this.encryptAndHash(hs.ss, payload); const ciphertext = this.encryptAndHash(hs.ss, payload)
const ns = this.createEmptyKey(); const ns = this.createEmptyKey()
const messageBuffer: MessageBuffer = {ne, ns, ciphertext}; const messageBuffer: MessageBuffer = { ne, ns, ciphertext }
const { cs1, cs2 } = this.split(hs.ss); const { cs1, cs2 } = this.split(hs.ss)
return { messageBuffer, cs1, cs2, h: hs.ss.h } 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)) { if (isValidPublicKey(message.ne)) {
hs.re = message.ne; hs.re = message.ne
} }
this.mixHash(hs.ss, hs.re); this.mixHash(hs.ss, hs.re)
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)); this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re))
const {plaintext: ns, valid: valid1} = this.decryptAndHash(hs.ss, message.ns); const { plaintext: ns, valid: valid1 } = this.decryptAndHash(hs.ss, message.ns)
if (valid1 && ns.length === 32 && isValidPublicKey(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)); this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs))
const {plaintext, valid: valid2} = this.decryptAndHash(hs.ss, message.ciphertext); const { plaintext, valid: valid2 } = this.decryptAndHash(hs.ss, message.ciphertext)
return {plaintext, valid: (valid1 && valid2)}; 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)) { 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) { 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.e.privateKey, hs.re))
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)); this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re))
const {plaintext, valid} = this.decryptAndHash(hs.ss, message.ciphertext); const { plaintext, valid } = this.decryptAndHash(hs.ss, message.ciphertext)
const { cs1, cs2 } = this.split(hs.ss); 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 { private initializeInitiator (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = "Noise_IK_25519_ChaChaPoly_SHA256"; const name = 'Noise_IK_25519_ChaChaPoly_SHA256'
const ss = this.initializeSymmetric(name); const ss = this.initializeSymmetric(name)
this.mixHash(ss, prologue); this.mixHash(ss, prologue)
this.mixHash(ss, rs); this.mixHash(ss, rs)
const re = Buffer.alloc(32); 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 { private initializeResponder (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = "Noise_IK_25519_ChaChaPoly_SHA256"; const name = 'Noise_IK_25519_ChaChaPoly_SHA256'
const ss = this.initializeSymmetric(name); const ss = this.initializeSymmetric(name)
this.mixHash(ss, prologue); this.mixHash(ss, prologue)
this.mixHash(ss, s.publicKey); this.mixHash(ss, s.publicKey)
const re = Buffer.alloc(32); const re = Buffer.alloc(32)
return { ss, s, rs, re, psk }; return { ss, s, rs, re, psk }
} }
} }

View File

@ -1,186 +1,185 @@
import { Buffer } from 'buffer'; import { Buffer } from 'buffer'
import { bytes32, bytes } from '../@types/basic' import { bytes32, bytes } from '../@types/basic'
import { KeyPair } from '../@types/libp2p' import { KeyPair } from '../@types/libp2p'
import {generateKeypair, isValidPublicKey} from '../utils'; import { generateKeypair, isValidPublicKey } from '../utils'
import {CipherState, HandshakeState, MessageBuffer, NoiseSession} from "../@types/handshake"; import { CipherState, HandshakeState, MessageBuffer, NoiseSession } from '../@types/handshake'
import {AbstractHandshake} from "./abstract-handshake"; import { AbstractHandshake } from './abstract-handshake'
export class XX extends AbstractHandshake { export class XX extends AbstractHandshake {
private initializeInitiator(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState { private initializeInitiator (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = "Noise_XX_25519_ChaChaPoly_SHA256"; const name = 'Noise_XX_25519_ChaChaPoly_SHA256'
const ss = this.initializeSymmetric(name); const ss = this.initializeSymmetric(name)
this.mixHash(ss, prologue); this.mixHash(ss, prologue)
const re = Buffer.alloc(32); 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 { private initializeResponder (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = "Noise_XX_25519_ChaChaPoly_SHA256"; const name = 'Noise_XX_25519_ChaChaPoly_SHA256'
const ss = this.initializeSymmetric(name); const ss = this.initializeSymmetric(name)
this.mixHash(ss, prologue); this.mixHash(ss, prologue)
const re = Buffer.alloc(32); 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 { private writeMessageA (hs: HandshakeState, payload: bytes, e?: KeyPair): MessageBuffer {
const ns = Buffer.alloc(0); const ns = Buffer.alloc(0)
if (e) { if (e) {
hs.e = e; hs.e = e
} else { } else {
hs.e = generateKeypair(); hs.e = generateKeypair()
} }
const ne = hs.e.publicKey; const ne = hs.e.publicKey
this.mixHash(hs.ss, ne); this.mixHash(hs.ss, ne)
const ciphertext = this.encryptAndHash(hs.ss, payload); const ciphertext = this.encryptAndHash(hs.ss, payload)
return {ne, ns, ciphertext}; return { ne, ns, ciphertext }
} }
private writeMessageB(hs: HandshakeState, payload: bytes): MessageBuffer { private writeMessageB (hs: HandshakeState, payload: bytes): MessageBuffer {
hs.e = generateKeypair(); hs.e = generateKeypair()
const ne = hs.e.publicKey; const ne = hs.e.publicKey
this.mixHash(hs.ss, ne); 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.re))
const spk = Buffer.from(hs.s.publicKey); const spk = Buffer.from(hs.s.publicKey)
const ns = this.encryptAndHash(hs.ss, spk); const ns = this.encryptAndHash(hs.ss, spk)
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)); this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re))
const ciphertext = this.encryptAndHash(hs.ss, payload); const ciphertext = this.encryptAndHash(hs.ss, payload)
return { ne, ns, ciphertext }; return { ne, ns, ciphertext }
} }
private writeMessageC(hs: HandshakeState, payload: bytes) { private writeMessageC (hs: HandshakeState, payload: bytes) {
const spk = Buffer.from(hs.s.publicKey); const spk = Buffer.from(hs.s.publicKey)
const ns = this.encryptAndHash(hs.ss, spk); const ns = this.encryptAndHash(hs.ss, spk)
this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)); this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re))
const ciphertext = this.encryptAndHash(hs.ss, payload); const ciphertext = this.encryptAndHash(hs.ss, payload)
const ne = this.createEmptyKey(); const ne = this.createEmptyKey()
const messageBuffer: MessageBuffer = {ne, ns, ciphertext}; const messageBuffer: MessageBuffer = { ne, ns, ciphertext }
const { cs1, cs2 } = this.split(hs.ss); 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)) { if (isValidPublicKey(message.ne)) {
hs.re = message.ne; hs.re = message.ne
} }
this.mixHash(hs.ss, hs.re); this.mixHash(hs.ss, hs.re)
return this.decryptAndHash(hs.ss, message.ciphertext); 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)) { 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) { 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)); this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re))
const {plaintext: ns, valid: valid1} = this.decryptAndHash(hs.ss, message.ns); const { plaintext: ns, valid: valid1 } = this.decryptAndHash(hs.ss, message.ns)
if (valid1 && ns.length === 32 && isValidPublicKey(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)); this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs))
const {plaintext, valid: valid2} = this.decryptAndHash(hs.ss, message.ciphertext); const { plaintext, valid: valid2 } = this.decryptAndHash(hs.ss, message.ciphertext)
return {plaintext, valid: (valid1 && valid2)}; return { plaintext, valid: (valid1 && valid2) }
} }
private readMessageC(hs: HandshakeState, message: MessageBuffer): {h: bytes; plaintext: bytes; valid: boolean; cs1: CipherState; cs2: CipherState} { 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); const { plaintext: ns, valid: valid1 } = this.decryptAndHash(hs.ss, message.ns)
if (valid1 && ns.length === 32 && isValidPublicKey(ns)) { if (valid1 && ns.length === 32 && isValidPublicKey(ns)) {
hs.rs = ns; hs.rs = ns
} }
if (!hs.e) { 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 { plaintext, valid: valid2 } = this.decryptAndHash(hs.ss, message.ciphertext)
const { cs1, cs2 } = this.split(hs.ss); 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 { public initSession (initiator: boolean, prologue: bytes32, s: KeyPair): NoiseSession {
const psk = this.createEmptyKey(); const psk = this.createEmptyKey()
const rs = Buffer.alloc(32); // no static key yet const rs = Buffer.alloc(32) // no static key yet
let hs; let hs
if (initiator) { if (initiator) {
hs = this.initializeInitiator(prologue, s, rs, psk); hs = this.initializeInitiator(prologue, s, rs, psk)
} else { } else {
hs = this.initializeResponder(prologue, s, rs, psk); hs = this.initializeResponder(prologue, s, rs, psk)
} }
return { return {
hs, hs,
i: initiator, i: initiator,
mc: 0, mc: 0
}; }
} }
public sendMessage(session: NoiseSession, message: bytes, ephemeral?: KeyPair): MessageBuffer { public sendMessage (session: NoiseSession, message: bytes, ephemeral?: KeyPair): MessageBuffer {
let messageBuffer: MessageBuffer; let messageBuffer: MessageBuffer
if (session.mc === 0) { if (session.mc === 0) {
messageBuffer = this.writeMessageA(session.hs, message, ephemeral); messageBuffer = this.writeMessageA(session.hs, message, ephemeral)
} else if (session.mc === 1) { } else if (session.mc === 1) {
messageBuffer = this.writeMessageB(session.hs, message); messageBuffer = this.writeMessageB(session.hs, message)
} else if (session.mc === 2) { } else if (session.mc === 2) {
const { h, messageBuffer: resultingBuffer, cs1, cs2 } = this.writeMessageC(session.hs, message); const { h, messageBuffer: resultingBuffer, cs1, cs2 } = this.writeMessageC(session.hs, message)
messageBuffer = resultingBuffer; messageBuffer = resultingBuffer
session.h = h; session.h = h
session.cs1 = cs1; session.cs1 = cs1
session.cs2 = cs2; session.cs2 = cs2
} else if (session.mc > 2) { } else if (session.mc > 2) {
if (session.i) { if (session.i) {
if (!session.cs1) { 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 { } else {
if (!session.cs2) { 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 { } else {
throw new Error("Session invalid.") throw new Error('Session invalid.')
} }
session.mc++; session.mc++
return messageBuffer; return messageBuffer
} }
public recvMessage(session: NoiseSession, message: MessageBuffer): {plaintext: bytes; valid: boolean} { public recvMessage (session: NoiseSession, message: MessageBuffer): {plaintext: bytes; valid: boolean} {
let plaintext: bytes = Buffer.alloc(0); let plaintext: bytes = Buffer.alloc(0)
let valid = false; let valid = false
if (session.mc === 0) { if (session.mc === 0) {
({plaintext, valid} = this.readMessageA(session.hs, message)); ({ plaintext, valid } = this.readMessageA(session.hs, message))
} else if (session.mc === 1) { } else if (session.mc === 1) {
({plaintext, valid} = this.readMessageB(session.hs, message)); ({ plaintext, valid } = this.readMessageB(session.hs, message))
} else if (session.mc === 2) { } else if (session.mc === 2) {
const { h, plaintext: resultingPlaintext, valid: resultingValid, cs1, cs2 } = this.readMessageC(session.hs, message); const { h, plaintext: resultingPlaintext, valid: resultingValid, cs1, cs2 } = this.readMessageC(session.hs, message)
plaintext = resultingPlaintext; plaintext = resultingPlaintext
valid = resultingValid; valid = resultingValid
session.h = h; session.h = h
session.cs1 = cs1; session.cs1 = cs1
session.cs2 = cs2; session.cs2 = cs2
} }
session.mc++; session.mc++
return {plaintext, valid}; return { plaintext, valid }
} }
} }

View File

@ -1,7 +1,7 @@
import {Noise} from "./noise"; import { Noise } from './noise'
export * from "./noise"; export * from './noise'
/** /**
* Default configuration, it will generate new noise static key and enable noise pipes (IK handshake). * 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()

View File

@ -1,5 +1,5 @@
import {bytes, bytes32} from "./@types/basic"; import { bytes, bytes32 } from './@types/basic'
import PeerId from "peer-id"; import PeerId from 'peer-id'
/** /**
* Storage for static keys of previously connected peers. * Storage for static keys of previously connected peers.
@ -7,24 +7,23 @@ import PeerId from "peer-id";
class Keycache { class Keycache {
private storage = new Map<bytes, bytes32>(); private storage = new Map<bytes, bytes32>();
public store(peerId: PeerId, key: bytes32): void { public store (peerId: PeerId, key: bytes32): void {
this.storage.set(peerId.id, key); this.storage.set(peerId.id, key)
} }
public load(peerId?: PeerId): bytes32 | null { public load (peerId?: PeerId): bytes32 | null {
if(!peerId) { if (!peerId) {
return null; return null
} }
return this.storage.get(peerId.id) || null; return this.storage.get(peerId.id) || null
} }
public resetStorage(): void { public resetStorage (): void {
this.storage.clear(); this.storage.clear()
} }
} }
const KeyCache = new Keycache(); const KeyCache = new Keycache()
export { export {
KeyCache, KeyCache
} }

View File

@ -1,47 +1,44 @@
import debug from "debug"; import debug from 'debug'
import {DUMP_SESSION_KEYS} from './constants'; import { DUMP_SESSION_KEYS } from './constants'
import { KeyPair } from "./@types/libp2p"; import { KeyPair } from './@types/libp2p'
import { NoiseSession, SymmetricState } from "./@types/handshake"; import { NoiseSession } from './@types/handshake'
export const logger = debug('libp2p:noise'); export const logger = debug('libp2p:noise')
let keyLogger; let keyLogger
if(DUMP_SESSION_KEYS){ if (DUMP_SESSION_KEYS) {
keyLogger = logger keyLogger = logger
} } else {
else {
keyLogger = () => { /* do nothing */ } 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_PUBLIC_KEY ${s.publicKey.toString('hex')}`)
keyLogger(`LOCAL_STATIC_PRIVATE_KEY ${s.privateKey.toString('hex')}`) keyLogger(`LOCAL_STATIC_PRIVATE_KEY ${s.privateKey.toString('hex')}`)
} }
export function logLocalEphemeralKeys(e: KeyPair|undefined): void { export function logLocalEphemeralKeys (e: KeyPair|undefined): void {
if(e){ if (e) {
keyLogger(`LOCAL_PUBLIC_EPHEMERAL_KEY ${e.publicKey.toString('hex')}`) keyLogger(`LOCAL_PUBLIC_EPHEMERAL_KEY ${e.publicKey.toString('hex')}`)
keyLogger(`LOCAL_PRIVATE_EPHEMERAL_KEY ${e.privateKey.toString('hex')}`) keyLogger(`LOCAL_PRIVATE_EPHEMERAL_KEY ${e.privateKey.toString('hex')}`)
} } else {
else{
keyLogger('Missing local ephemeral keys.') 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')}`) 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')}`) keyLogger(`REMOTE_EPHEMERAL_PUBLIC_KEY ${re.toString('hex')}`)
} }
export function logCipherState(session: NoiseSession): void { export function logCipherState (session: NoiseSession): void {
if(session.cs1 && session.cs2){ if (session.cs1 && session.cs2) {
keyLogger(`CIPHER_STATE_1 ${session.cs1.n} ${session.cs1.k.toString('hex')}`) keyLogger(`CIPHER_STATE_1 ${session.cs1.n} ${session.cs1.k.toString('hex')}`)
keyLogger(`CIPHER_STATE_2 ${session.cs2.n} ${session.cs2.k.toString('hex')}`) keyLogger(`CIPHER_STATE_2 ${session.cs2.n} ${session.cs2.k.toString('hex')}`)
} } else {
else{
keyLogger('Missing cipher state.') keyLogger('Missing cipher state.')
} }
} }

View File

@ -1,25 +1,25 @@
import x25519 from 'bcrypto/lib/js/x25519'; import x25519 from 'bcrypto/lib/js/x25519'
import {Buffer} from "buffer"; import { Buffer } from 'buffer'
import Wrap from 'it-pb-rpc'; import Wrap from 'it-pb-rpc'
import DuplexPair from 'it-pair/duplex'; import DuplexPair from 'it-pair/duplex'
import ensureBuffer from 'it-buffer'; import ensureBuffer from 'it-buffer'
import pipe from 'it-pipe'; import pipe from 'it-pipe'
import {encode, decode} from 'it-length-prefixed'; import { encode, decode } from 'it-length-prefixed'
import {XXHandshake} from "./handshake-xx"; import { XXHandshake } from './handshake-xx'
import {IKHandshake} from "./handshake-ik"; import { IKHandshake } from './handshake-ik'
import {XXFallbackHandshake} from "./handshake-xx-fallback"; import { XXFallbackHandshake } from './handshake-xx-fallback'
import {generateKeypair, getPayload} from "./utils"; import { generateKeypair, getPayload } from './utils'
import {uint16BEDecode, uint16BEEncode} from "./encoder"; import { uint16BEDecode, uint16BEEncode } from './encoder'
import {decryptStream, encryptStream} from "./crypto"; import { decryptStream, encryptStream } from './crypto'
import {bytes} from "./@types/basic"; import { bytes } from './@types/basic'
import {INoiseConnection, KeyPair, SecureOutbound} from "./@types/libp2p"; import { INoiseConnection, KeyPair, SecureOutbound } from './@types/libp2p'
import {Duplex} from "it-pair"; import { Duplex } from 'it-pair'
import {IHandshake} from "./@types/handshake-interface"; import { IHandshake } from './@types/handshake-interface'
import {KeyCache} from "./keycache"; import { KeyCache } from './keycache'
import {logger} from "./logger"; import { logger } from './logger'
import PeerId from "peer-id"; import PeerId from 'peer-id'
import {NOISE_MSG_MAX_LENGTH_BYTES} from "./constants"; import { NOISE_MSG_MAX_LENGTH_BYTES } from './constants'
export type WrappedConnection = ReturnType<typeof Wrap>; export type WrappedConnection = ReturnType<typeof Wrap>;
@ -31,7 +31,7 @@ type HandshakeParams = {
}; };
export class Noise implements INoiseConnection { export class Noise implements INoiseConnection {
public protocol = "/noise"; public protocol = '/noise';
private readonly prologue = Buffer.alloc(0); private readonly prologue = Buffer.alloc(0);
private readonly staticKeys: KeyPair; private readonly staticKeys: KeyPair;
@ -40,206 +40,209 @@ export class Noise implements INoiseConnection {
/** /**
* *
* @param staticNoiseKey x25519 private key, reuse for faster handshakes * @param {bytes} staticNoiseKey x25519 private key, reuse for faster handshakes
* @param earlyData * @param {bytes} earlyData
*/ */
constructor(staticNoiseKey?: bytes, earlyData?: bytes) { constructor (staticNoiseKey?: bytes, earlyData?: bytes) {
this.earlyData = earlyData || Buffer.alloc(0); this.earlyData = earlyData || Buffer.alloc(0)
//disabled until properly specked // disabled until properly specked
this.useNoisePipes = false; this.useNoisePipes = false
if (staticNoiseKey) { if (staticNoiseKey) {
const publicKey = x25519.publicKeyCreate(staticNoiseKey); const publicKey = x25519.publicKeyCreate(staticNoiseKey)
this.staticKeys = { this.staticKeys = {
privateKey: staticNoiseKey, privateKey: staticNoiseKey,
publicKey, publicKey
} }
} else { } else {
this.staticKeys = generateKeypair(); this.staticKeys = generateKeypair()
} }
} }
/** /**
* Encrypt outgoing data to the remote party (handshake as initiator) * Encrypt outgoing data to the remote party (handshake as initiator)
* @param {PeerId} localPeer - PeerId of the receiving peer * @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. * @param {PeerId} remotePeer - PeerId of the remote peer. Used to validate the integrity of the remote peer.
* @returns {Promise<SecureOutbound>} * @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( const wrappedConnection = Wrap(
connection, connection,
{ {
// wrong types in repo
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
lengthEncoder: uint16BEEncode, lengthEncoder: uint16BEEncode,
lengthDecoder: uint16BEDecode, lengthDecoder: uint16BEDecode,
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
} }
); )
const handshake = await this.performHandshake({ const handshake = await this.performHandshake({
connection: wrappedConnection, connection: wrappedConnection,
isInitiator: true, isInitiator: true,
localPeer, localPeer,
remotePeer, remotePeer
}); })
const conn = await this.createSecureConnection(wrappedConnection, handshake); const conn = await this.createSecureConnection(wrappedConnection, handshake)
return { return {
conn, conn,
remoteEarlyData: handshake.remoteEarlyData, remoteEarlyData: handshake.remoteEarlyData,
remotePeer: handshake.remotePeer, remotePeer: handshake.remotePeer
} }
} }
/** /**
* Decrypt incoming data (handshake as responder). * Decrypt incoming data (handshake as responder).
* @param {PeerId} localPeer - PeerId of the receiving peer. * @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. * @param {PeerId} remotePeer - optional PeerId of the initiating peer, if known. This may only exist during transport upgrades.
* @returns {Promise<SecureOutbound>} * @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( const wrappedConnection = Wrap(
connection, connection,
{ {
// wrong types in repo
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
lengthEncoder: uint16BEEncode, lengthEncoder: uint16BEEncode,
lengthDecoder: uint16BEDecode, lengthDecoder: uint16BEDecode,
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
} }
); )
const handshake = await this.performHandshake({ const handshake = await this.performHandshake({
connection: wrappedConnection, connection: wrappedConnection,
isInitiator: false, isInitiator: false,
localPeer, localPeer,
remotePeer remotePeer
}); })
const conn = await this.createSecureConnection(wrappedConnection, handshake); const conn = await this.createSecureConnection(wrappedConnection, handshake)
return { return {
conn, conn,
remoteEarlyData: handshake.remoteEarlyData, remoteEarlyData: handshake.remoteEarlyData,
remotePeer: handshake.remotePeer remotePeer: handshake.remotePeer
}; }
} }
/** /**
* If Noise pipes supported, tries IK handshake first with XX as fallback if it fails. * 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. * 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> { private async performHandshake (params: HandshakeParams): Promise<IHandshake> {
const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData); const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData)
let tryIK = this.useNoisePipes; let tryIK = this.useNoisePipes
if(params.isInitiator && KeyCache.load(params.remotePeer) === null) { if (params.isInitiator && KeyCache.load(params.remotePeer) === null) {
//if we are initiator and remote static key is unknown, don't try IK // if we are initiator and remote static key is unknown, don't try IK
tryIK = false; tryIK = false
} }
// Try IK if acting as responder or initiator that has remote's static key. // Try IK if acting as responder or initiator that has remote's static key.
if (tryIK) { if (tryIK) {
// Try IK first // Try IK first
const { remotePeer, connection, isInitiator } = params; const { remotePeer, connection, isInitiator } = params
const ikHandshake = new IKHandshake( const ikHandshake = new IKHandshake(
isInitiator, isInitiator,
payload, payload,
this.prologue, this.prologue,
this.staticKeys, this.staticKeys,
connection, connection,
//safe to cast as we did checks // safe to cast as we did checks
KeyCache.load(params.remotePeer) || Buffer.alloc(32), KeyCache.load(params.remotePeer) || Buffer.alloc(32),
remotePeer as PeerId, remotePeer as PeerId
); )
try { try {
return await this.performIKHandshake(ikHandshake); return await this.performIKHandshake(ikHandshake)
} catch (e) { } catch (e) {
// IK failed, go to XX fallback // IK failed, go to XX fallback
let ephemeralKeys; let ephemeralKeys
if (params.isInitiator) { 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 { } else {
// run XX handshake // run XX handshake
return await this.performXXHandshake(params, payload); return await this.performXXHandshake(params, payload)
} }
} }
private async performXXFallbackHandshake( private async performXXFallbackHandshake (
params: HandshakeParams, params: HandshakeParams,
payload: bytes, payload: bytes,
initialMsg: bytes, initialMsg: bytes,
ephemeralKeys?: KeyPair, ephemeralKeys?: KeyPair
): Promise<XXFallbackHandshake> { ): Promise<XXFallbackHandshake> {
const { isInitiator, remotePeer, connection } = params; const { isInitiator, remotePeer, connection } = params
const handshake = 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 { try {
await handshake.propose(); await handshake.propose()
await handshake.exchange(); await handshake.exchange()
await handshake.finish(); await handshake.finish()
} catch (e) { } catch (e) {
logger(e); logger(e)
throw new Error(`Error occurred during XX Fallback handshake: ${e.message}`); throw new Error(`Error occurred during XX Fallback handshake: ${e.message}`)
} }
return handshake; return handshake
} }
private async performXXHandshake( private async performXXHandshake (
params: HandshakeParams, params: HandshakeParams,
payload: bytes, payload: bytes
): Promise<XXHandshake> { ): Promise<XXHandshake> {
const { isInitiator, remotePeer, connection } = params; const { isInitiator, remotePeer, connection } = params
const handshake = new XXHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer); const handshake = new XXHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer)
try { try {
await handshake.propose(); await handshake.propose()
await handshake.exchange(); await handshake.exchange()
await handshake.finish(); await handshake.finish()
if (this.useNoisePipes && handshake.remotePeer) { if (this.useNoisePipes && handshake.remotePeer) {
KeyCache.store(handshake.remotePeer, handshake.getRemoteStaticKey()); KeyCache.store(handshake.remotePeer, handshake.getRemoteStaticKey())
} }
} catch (e) { } 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( private async performIKHandshake (
handshake: IKHandshake, handshake: IKHandshake
): Promise<IKHandshake> { ): Promise<IKHandshake> {
await handshake.stage0()
await handshake.stage1()
await handshake.stage0(); return handshake
await handshake.stage1();
return handshake;
} }
private async createSecureConnection( private async createSecureConnection (
connection: WrappedConnection, connection: WrappedConnection,
handshake: IHandshake, handshake: IHandshake
): Promise<Duplex> { ): Promise<Duplex> {
// Create encryption box/unbox wrapper // Create encryption box/unbox wrapper
const [secure, user] = DuplexPair(); const [secure, user] = DuplexPair()
const network = connection.unwrap(); const network = connection.unwrap()
pipe( await pipe(
secure, // write to wrapper secure, // write to wrapper
ensureBuffer, // ensure any type of data is converted to buffer ensureBuffer, // ensure any type of data is converted to buffer
encryptStream(handshake), // data is encrypted encryptStream(handshake), // data is encrypted
encode({ lengthEncoder: uint16BEEncode }), // prefix with message length encode({ lengthEncoder: uint16BEEncode }), // prefix with message length
network, // send to the remote peer 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 ensureBuffer, // ensure any type of data is converted to buffer
decryptStream(handshake), // decrypt the incoming data decryptStream(handshake), // decrypt the incoming data
secure // pipe to the wrapper secure // pipe to the wrapper
); )
return user; return user
} }
} }

View File

@ -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*/ /* 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 */ (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) /* CommonJS */ else if (typeof require === 'function' && typeof module === 'object' && module && module.exports) { module.exports = factory(require('protobufjs/minimal')) }
define(["protobufjs/minimal"], factory); })(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) // Exported root namespace
module.exports = factory(require("protobufjs/minimal")); var $root = $protobuf.roots.default || ($protobuf.roots.default = {})
})(this, function($protobuf) { $root.pb = (function () {
"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() {
/**
* Namespace pb. * Namespace pb.
* @exports pb * @exports pb
* @namespace * @namespace
*/ */
var pb = {}; var pb = {}
pb.NoiseHandshakePayload = (function() { pb.NoiseHandshakePayload = (function () {
/**
/**
* Properties of a NoiseHandshakePayload. * Properties of a NoiseHandshakePayload.
* @memberof pb * @memberof pb
* @interface INoiseHandshakePayload * @interface INoiseHandshakePayload
@ -36,7 +28,7 @@
* @property {Uint8Array|null} [data] NoiseHandshakePayload data * @property {Uint8Array|null} [data] NoiseHandshakePayload data
*/ */
/** /**
* Constructs a new NoiseHandshakePayload. * Constructs a new NoiseHandshakePayload.
* @memberof pb * @memberof pb
* @classdesc Represents a NoiseHandshakePayload. * @classdesc Represents a NoiseHandshakePayload.
@ -44,38 +36,39 @@
* @constructor * @constructor
* @param {pb.INoiseHandshakePayload=} [properties] Properties to set * @param {pb.INoiseHandshakePayload=} [properties] Properties to set
*/ */
function NoiseHandshakePayload(properties) { function NoiseHandshakePayload (properties) {
if (properties) if (properties) {
for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) for (var keys = Object.keys(properties), i = 0; i < keys.length; ++i) {
if (properties[keys[i]] != null) if (properties[keys[i]] != null) { this[keys[i]] = properties[keys[i]] }
this[keys[i]] = properties[keys[i]]; }
} }
}
/** /**
* NoiseHandshakePayload identityKey. * NoiseHandshakePayload identityKey.
* @member {Uint8Array} identityKey * @member {Uint8Array} identityKey
* @memberof pb.NoiseHandshakePayload * @memberof pb.NoiseHandshakePayload
* @instance * @instance
*/ */
NoiseHandshakePayload.prototype.identityKey = $util.newBuffer([]); NoiseHandshakePayload.prototype.identityKey = $util.newBuffer([])
/** /**
* NoiseHandshakePayload identitySig. * NoiseHandshakePayload identitySig.
* @member {Uint8Array} identitySig * @member {Uint8Array} identitySig
* @memberof pb.NoiseHandshakePayload * @memberof pb.NoiseHandshakePayload
* @instance * @instance
*/ */
NoiseHandshakePayload.prototype.identitySig = $util.newBuffer([]); NoiseHandshakePayload.prototype.identitySig = $util.newBuffer([])
/** /**
* NoiseHandshakePayload data. * NoiseHandshakePayload data.
* @member {Uint8Array} data * @member {Uint8Array} data
* @memberof pb.NoiseHandshakePayload * @memberof pb.NoiseHandshakePayload
* @instance * @instance
*/ */
NoiseHandshakePayload.prototype.data = $util.newBuffer([]); NoiseHandshakePayload.prototype.data = $util.newBuffer([])
/** /**
* Creates a new NoiseHandshakePayload instance using the specified properties. * Creates a new NoiseHandshakePayload instance using the specified properties.
* @function create * @function create
* @memberof pb.NoiseHandshakePayload * @memberof pb.NoiseHandshakePayload
@ -83,11 +76,11 @@
* @param {pb.INoiseHandshakePayload=} [properties] Properties to set * @param {pb.INoiseHandshakePayload=} [properties] Properties to set
* @returns {pb.NoiseHandshakePayload} NoiseHandshakePayload instance * @returns {pb.NoiseHandshakePayload} NoiseHandshakePayload instance
*/ */
NoiseHandshakePayload.create = function create(properties) { NoiseHandshakePayload.create = function create (properties) {
return new NoiseHandshakePayload(properties); return new NoiseHandshakePayload(properties)
}; }
/** /**
* Encodes the specified NoiseHandshakePayload message. Does not implicitly {@link pb.NoiseHandshakePayload.verify|verify} messages. * Encodes the specified NoiseHandshakePayload message. Does not implicitly {@link pb.NoiseHandshakePayload.verify|verify} messages.
* @function encode * @function encode
* @memberof pb.NoiseHandshakePayload * @memberof pb.NoiseHandshakePayload
@ -96,19 +89,15 @@
* @param {$protobuf.Writer} [writer] Writer to encode to * @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer * @returns {$protobuf.Writer} Writer
*/ */
NoiseHandshakePayload.encode = function encode(message, writer) { NoiseHandshakePayload.encode = function encode (message, writer) {
if (!writer) if (!writer) { writer = $Writer.create() }
writer = $Writer.create(); if (message.identityKey != null && message.hasOwnProperty('identityKey')) { writer.uint32(/* id 1, wireType 2 = */10).bytes(message.identityKey) }
if (message.identityKey != null && message.hasOwnProperty("identityKey")) if (message.identitySig != null && message.hasOwnProperty('identitySig')) { writer.uint32(/* id 2, wireType 2 = */18).bytes(message.identitySig) }
writer.uint32(/* id 1, wireType 2 =*/10).bytes(message.identityKey); if (message.data != null && message.hasOwnProperty('data')) { writer.uint32(/* id 3, wireType 2 = */26).bytes(message.data) }
if (message.identitySig != null && message.hasOwnProperty("identitySig")) return writer
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. * Encodes the specified NoiseHandshakePayload message, length delimited. Does not implicitly {@link pb.NoiseHandshakePayload.verify|verify} messages.
* @function encodeDelimited * @function encodeDelimited
* @memberof pb.NoiseHandshakePayload * @memberof pb.NoiseHandshakePayload
@ -117,11 +106,11 @@
* @param {$protobuf.Writer} [writer] Writer to encode to * @param {$protobuf.Writer} [writer] Writer to encode to
* @returns {$protobuf.Writer} Writer * @returns {$protobuf.Writer} Writer
*/ */
NoiseHandshakePayload.encodeDelimited = function encodeDelimited(message, writer) { NoiseHandshakePayload.encodeDelimited = function encodeDelimited (message, writer) {
return this.encode(message, writer).ldelim(); return this.encode(message, writer).ldelim()
}; }
/** /**
* Decodes a NoiseHandshakePayload message from the specified reader or buffer. * Decodes a NoiseHandshakePayload message from the specified reader or buffer.
* @function decode * @function decode
* @memberof pb.NoiseHandshakePayload * @memberof pb.NoiseHandshakePayload
@ -132,31 +121,30 @@
* @throws {Error} If the payload is not a reader or valid buffer * @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing * @throws {$protobuf.util.ProtocolError} If required fields are missing
*/ */
NoiseHandshakePayload.decode = function decode(reader, length) { NoiseHandshakePayload.decode = function decode (reader, length) {
if (!(reader instanceof $Reader)) if (!(reader instanceof $Reader)) { reader = $Reader.create(reader) }
reader = $Reader.create(reader); var end = length === undefined ? reader.len : reader.pos + length; var message = new $root.pb.NoiseHandshakePayload()
var end = length === undefined ? reader.len : reader.pos + length, message = new $root.pb.NoiseHandshakePayload(); while (reader.pos < end) {
while (reader.pos < end) { var tag = reader.uint32()
var tag = reader.uint32(); switch (tag >>> 3) {
switch (tag >>> 3) { case 1:
case 1: message.identityKey = reader.bytes()
message.identityKey = reader.bytes(); break
break; case 2:
case 2: message.identitySig = reader.bytes()
message.identitySig = reader.bytes(); break
break; case 3:
case 3: message.data = reader.bytes()
message.data = reader.bytes(); break
break; default:
default: reader.skipType(tag & 7)
reader.skipType(tag & 7); break
break; }
} }
} return message
return message; }
};
/** /**
* Decodes a NoiseHandshakePayload message from the specified reader or buffer, length delimited. * Decodes a NoiseHandshakePayload message from the specified reader or buffer, length delimited.
* @function decodeDelimited * @function decodeDelimited
* @memberof pb.NoiseHandshakePayload * @memberof pb.NoiseHandshakePayload
@ -166,13 +154,12 @@
* @throws {Error} If the payload is not a reader or valid buffer * @throws {Error} If the payload is not a reader or valid buffer
* @throws {$protobuf.util.ProtocolError} If required fields are missing * @throws {$protobuf.util.ProtocolError} If required fields are missing
*/ */
NoiseHandshakePayload.decodeDelimited = function decodeDelimited(reader) { NoiseHandshakePayload.decodeDelimited = function decodeDelimited (reader) {
if (!(reader instanceof $Reader)) if (!(reader instanceof $Reader)) { reader = new $Reader(reader) }
reader = new $Reader(reader); return this.decode(reader, reader.uint32())
return this.decode(reader, reader.uint32()); }
};
/** /**
* Verifies a NoiseHandshakePayload message. * Verifies a NoiseHandshakePayload message.
* @function verify * @function verify
* @memberof pb.NoiseHandshakePayload * @memberof pb.NoiseHandshakePayload
@ -180,22 +167,21 @@
* @param {Object.<string,*>} message Plain object to verify * @param {Object.<string,*>} message Plain object to verify
* @returns {string|null} `null` if valid, otherwise the reason why it is not * @returns {string|null} `null` if valid, otherwise the reason why it is not
*/ */
NoiseHandshakePayload.verify = function verify(message) { NoiseHandshakePayload.verify = function verify (message) {
if (typeof message !== "object" || message === null) if (typeof message !== 'object' || message === null) { return 'object expected' }
return "object expected"; if (message.identityKey != null && message.hasOwnProperty('identityKey')) {
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.identityKey && typeof message.identityKey.length === "number" || $util.isString(message.identityKey))) }
return "identityKey: buffer expected"; if (message.identitySig != null && message.hasOwnProperty('identitySig')) {
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.identitySig && typeof message.identitySig.length === "number" || $util.isString(message.identitySig))) }
return "identitySig: buffer expected"; if (message.data != null && message.hasOwnProperty('data')) {
if (message.data != null && message.hasOwnProperty("data")) if (!(message.data && typeof message.data.length === 'number' || $util.isString(message.data))) { return 'data: buffer expected' }
if (!(message.data && typeof message.data.length === "number" || $util.isString(message.data))) }
return "data: buffer expected"; return null
return null; }
};
/** /**
* Creates a NoiseHandshakePayload message from a plain object. Also converts values to their respective internal types. * Creates a NoiseHandshakePayload message from a plain object. Also converts values to their respective internal types.
* @function fromObject * @function fromObject
* @memberof pb.NoiseHandshakePayload * @memberof pb.NoiseHandshakePayload
@ -203,29 +189,22 @@
* @param {Object.<string,*>} object Plain object * @param {Object.<string,*>} object Plain object
* @returns {pb.NoiseHandshakePayload} NoiseHandshakePayload * @returns {pb.NoiseHandshakePayload} NoiseHandshakePayload
*/ */
NoiseHandshakePayload.fromObject = function fromObject(object) { NoiseHandshakePayload.fromObject = function fromObject (object) {
if (object instanceof $root.pb.NoiseHandshakePayload) if (object instanceof $root.pb.NoiseHandshakePayload) { return object }
return object; var message = new $root.pb.NoiseHandshakePayload()
var message = new $root.pb.NoiseHandshakePayload(); if (object.identityKey != null) {
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 (typeof object.identityKey === "string") }
$util.base64.decode(object.identityKey, message.identityKey = $util.newBuffer($util.base64.length(object.identityKey)), 0); if (object.identitySig != null) {
else if (object.identityKey.length) 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 }
message.identityKey = object.identityKey; }
if (object.identitySig != null) if (object.data != null) {
if (typeof object.identitySig === "string") 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 }
$util.base64.decode(object.identitySig, message.identitySig = $util.newBuffer($util.base64.length(object.identitySig)), 0); }
else if (object.identitySig.length) return message
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. * Creates a plain object from a NoiseHandshakePayload message. Also converts values to other types if specified.
* @function toObject * @function toObject
* @memberof pb.NoiseHandshakePayload * @memberof pb.NoiseHandshakePayload
@ -234,58 +213,45 @@
* @param {$protobuf.IConversionOptions} [options] Conversion options * @param {$protobuf.IConversionOptions} [options] Conversion options
* @returns {Object.<string,*>} Plain object * @returns {Object.<string,*>} Plain object
*/ */
NoiseHandshakePayload.toObject = function toObject(message, options) { NoiseHandshakePayload.toObject = function toObject (message, options) {
if (!options) if (!options) { options = {} }
options = {}; var object = {}
var object = {}; if (options.defaults) {
if (options.defaults) { if (options.bytes === String) { object.identityKey = '' } else {
if (options.bytes === String) object.identityKey = []
object.identityKey = ""; if (options.bytes !== Array) { object.identityKey = $util.newBuffer(object.identityKey) }
else { }
object.identityKey = []; if (options.bytes === String) { object.identitySig = '' } else {
if (options.bytes !== Array) object.identitySig = []
object.identityKey = $util.newBuffer(object.identityKey); if (options.bytes !== Array) { object.identitySig = $util.newBuffer(object.identitySig) }
} }
if (options.bytes === String) if (options.bytes === String) { object.data = '' } else {
object.identitySig = ""; object.data = []
else { if (options.bytes !== Array) { object.data = $util.newBuffer(object.data) }
object.identitySig = []; }
if (options.bytes !== Array) }
object.identitySig = $util.newBuffer(object.identitySig); 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 (options.bytes === String) 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 }
object.data = ""; return object
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. * Converts this NoiseHandshakePayload to JSON.
* @function toJSON * @function toJSON
* @memberof pb.NoiseHandshakePayload * @memberof pb.NoiseHandshakePayload
* @instance * @instance
* @returns {Object.<string,*>} JSON object * @returns {Object.<string,*>} JSON object
*/ */
NoiseHandshakePayload.prototype.toJSON = function toJSON() { NoiseHandshakePayload.prototype.toJSON = function toJSON () {
return this.constructor.toObject(this, $protobuf.util.toJSONOptions); return this.constructor.toObject(this, $protobuf.util.toJSONOptions)
}; }
return NoiseHandshakePayload; return NoiseHandshakePayload
})(); })()
return pb; return pb
})(); })()
return $root; return $root
}); })

View File

@ -1,78 +1,76 @@
import HKDF from 'bcrypto/lib/hkdf'; import HKDF from 'bcrypto/lib/hkdf'
import x25519 from 'bcrypto/lib/js/x25519'; import x25519 from 'bcrypto/lib/js/x25519'
import SHA256 from 'bcrypto/lib/js/sha256'; import SHA256 from 'bcrypto/lib/js/sha256'
import {Buffer} from "buffer"; import { Buffer } from 'buffer'
import PeerId from "peer-id"; import PeerId from 'peer-id'
import {keys} from 'libp2p-crypto'; import { keys } from 'libp2p-crypto'
import {KeyPair} from "./@types/libp2p"; import { KeyPair } from './@types/libp2p'
import {bytes, bytes32} from "./@types/basic"; import { bytes, bytes32 } from './@types/basic'
import {Hkdf, INoisePayload} from "./@types/handshake"; import { Hkdf, INoisePayload } from './@types/handshake'
import {pb} from "./proto/payload"; import { pb } from './proto/payload'
const NoiseHandshakePayloadProto = pb.NoiseHandshakePayload; const NoiseHandshakePayloadProto = pb.NoiseHandshakePayload
export function generateKeypair(): KeyPair { export function generateKeypair (): KeyPair {
const privateKey = x25519.privateKeyGenerate(); const privateKey = x25519.privateKeyGenerate()
const publicKey = x25519.publicKeyCreate(privateKey); const publicKey = x25519.publicKeyCreate(privateKey)
return { return {
publicKey, publicKey,
privateKey, privateKey
} }
} }
export async function getPayload( export async function getPayload (
localPeer: PeerId, localPeer: PeerId,
staticPublicKey: bytes, staticPublicKey: bytes,
earlyData?: bytes, earlyData?: bytes
): Promise<bytes> { ): Promise<bytes> {
const signedPayload = await signPayload(localPeer, getHandshakePayload(staticPublicKey)); const signedPayload = await signPayload(localPeer, getHandshakePayload(staticPublicKey))
const earlyDataPayload = earlyData || Buffer.alloc(0); const earlyDataPayload = earlyData || Buffer.alloc(0)
return await createHandshakePayload( return await createHandshakePayload(
localPeer.marshalPubKey(), localPeer.marshalPubKey(),
signedPayload, signedPayload,
earlyDataPayload earlyDataPayload
); )
} }
export async function createHandshakePayload( export function createHandshakePayload (
libp2pPublicKey: bytes, libp2pPublicKey: bytes,
signedPayload: bytes, signedPayload: bytes,
earlyData?: bytes, earlyData?: bytes
): Promise<bytes> { ): bytes {
const payloadInit = NoiseHandshakePayloadProto.create({ const payloadInit = NoiseHandshakePayloadProto.create({
identityKey: libp2pPublicKey, identityKey: libp2pPublicKey,
identitySig: signedPayload, 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> {
export async function signPayload(peerId: PeerId, payload: bytes): Promise<bytes> { return await peerId.privKey.sign(payload)
return peerId.privKey.sign(payload);
} }
export async function getPeerIdFromPayload(payload: pb.INoiseHandshakePayload): Promise<PeerId> { export async function getPeerIdFromPayload (payload: pb.INoiseHandshakePayload): Promise<PeerId> {
return await PeerId.createFromPubKey(Buffer.from(payload.identityKey as Uint8Array)); 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( return NoiseHandshakePayloadProto.toObject(
NoiseHandshakePayloadProto.decode(Buffer.from(payload)) NoiseHandshakePayloadProto.decode(Buffer.from(payload))
) as INoisePayload; ) as INoisePayload
} }
export function getHandshakePayload(publicKey: bytes): bytes { export function getHandshakePayload (publicKey: bytes): bytes {
return Buffer.concat([Buffer.from("noise-libp2p-static-key:"), publicKey]); return Buffer.concat([Buffer.from('noise-libp2p-static-key:'), publicKey])
} }
async function isValidPeerId(peerId: bytes, publicKeyProtobuf: bytes) { async function isValidPeerId (peerId: bytes, publicKeyProtobuf: bytes) {
const generatedPeerId = await PeerId.createFromPubKey(publicKeyProtobuf); const generatedPeerId = await PeerId.createFromPubKey(publicKeyProtobuf)
return generatedPeerId.id.equals(peerId); return generatedPeerId.id.equals(peerId)
} }
/** /**
@ -82,36 +80,36 @@ async function isValidPeerId(peerId: bytes, publicKeyProtobuf: bytes) {
* @param {PeerId} remotePeer - owner's libp2p peer ID * @param {PeerId} remotePeer - owner's libp2p peer ID
* @returns {Promise<PeerId>} - peer ID of payload owner * @returns {Promise<PeerId>} - peer ID of payload owner
*/ */
export async function verifySignedPayload( export async function verifySignedPayload (
noiseStaticKey: bytes, noiseStaticKey: bytes,
payload: pb.INoiseHandshakePayload, payload: pb.INoiseHandshakePayload,
remotePeer: PeerId remotePeer: PeerId
): Promise<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))) { 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 // Unmarshaling from PublicKey protobuf
const publicKey = keys.unmarshalPublicKey(identityKey); const publicKey = keys.unmarshalPublicKey(identityKey)
if (!payload.identitySig || !publicKey.verify(generatedPayload, Buffer.from(payload.identitySig))) { 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 { export function getHkdf (ck: bytes32, ikm: bytes): Hkdf {
const info = Buffer.alloc(0); const info = Buffer.alloc(0)
const prk = HKDF.extract(SHA256, ikm, ck); const prk = HKDF.extract(SHA256, ikm, ck)
const okm = HKDF.expand(SHA256, prk, info, 96); const okm = HKDF.expand(SHA256, prk, info, 96)
const k1 = okm.slice(0, 32); const k1 = okm.slice(0, 32)
const k2 = okm.slice(32, 64); const k2 = okm.slice(32, 64)
const k3 = okm.slice(64, 96); const k3 = okm.slice(64, 96)
return [k1, k2, k3]; return [k1, k2, k3]
} }
export function isValidPublicKey(pk: bytes): boolean { export function isValidPublicKey (pk: bytes): boolean {
return x25519.publicKeyVerify(pk.slice(0, 32)); return x25519.publicKeyVerify(pk.slice(0, 32))
} }

18
test/fixtures/peer.ts vendored
View File

@ -1,4 +1,4 @@
import PeerId from 'peer-id'; import PeerId from 'peer-id'
// ed25519 keys // ed25519 keys
const peers = [{ const peers = [{
@ -17,20 +17,20 @@ const peers = [{
id: '12D3KooWPCofiCjhdtezP4eMnqBjjutFZNHjV39F5LWNrCvaLnzT', id: '12D3KooWPCofiCjhdtezP4eMnqBjjutFZNHjV39F5LWNrCvaLnzT',
privKey: 'CAESYLhUut01XPu+yIPbtZ3WnxOd26FYuTMRn/BbdFYsZE2KxueKRlo9yIAxmFReoNFUKztUU4G2aUiTbqDQaA6i0MDG54pGWj3IgDGYVF6g0VQrO1RTgbZpSJNuoNBoDqLQwA==', privKey: 'CAESYLhUut01XPu+yIPbtZ3WnxOd26FYuTMRn/BbdFYsZE2KxueKRlo9yIAxmFReoNFUKztUU4G2aUiTbqDQaA6i0MDG54pGWj3IgDGYVF6g0VQrO1RTgbZpSJNuoNBoDqLQwA==',
pubKey: 'CAESIMbnikZaPciAMZhUXqDRVCs7VFOBtmlIk26g0GgOotDA' pubKey: 'CAESIMbnikZaPciAMZhUXqDRVCs7VFOBtmlIk26g0GgOotDA'
}]; }]
export async function createPeerIdsFromFixtures (length) { export async function createPeerIdsFromFixtures (length:number): Promise<PeerId[]> {
return Promise.all( return await Promise.all(
Array.from({ length }).map((_, i) => PeerId.createFromJSON(peers[i])) Array.from({ length }).map((_, i) => PeerId.createFromJSON(peers[i]))
) )
} }
export async function createPeerIds (length) { export async function createPeerIds (length: number): Promise<PeerId[]> {
const peerIds: any[] = []; const peerIds: PeerId[] = []
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
const id = await PeerId.create({ keyType: 'ed25519', bits: 256 }); const id = await PeerId.create({ keyType: 'Ed25519', bits: 256 })
peerIds.push(id); peerIds.push(id)
} }
return peerIds; return peerIds
} }

View File

@ -1,67 +1,65 @@
import {Buffer} from "buffer"; import { Buffer } from 'buffer'
import {IK} from "../../src/handshakes/ik"; import { IK } from '../../src/handshakes/ik'
import {KeyPair} from "../../src/@types/libp2p"; import { KeyPair } from '../../src/@types/libp2p'
import {createHandshakePayload, generateKeypair, getHandshakePayload} from "../../src/utils"; import { createHandshakePayload, generateKeypair, getHandshakePayload } from '../../src/utils'
import {assert, expect} from "chai"; import { assert, expect } from 'chai'
import {generateEd25519Keys} from "../utils"; import { generateEd25519Keys } from '../utils'
describe("IK handshake", () => { describe('IK handshake', () => {
const prologue = Buffer.alloc(0); const prologue = Buffer.alloc(0)
it("Test complete IK handshake", async () => { it('Test complete IK handshake', async () => {
try { try {
const ikI = new IK(); const ikI = new IK()
const ikR = new IK(); const ikR = new IK()
// Generate static noise keys // Generate static noise keys
const kpInitiator: KeyPair = await generateKeypair(); const kpInitiator: KeyPair = await generateKeypair()
const kpResponder: KeyPair = await generateKeypair(); const kpResponder: KeyPair = await generateKeypair()
// Generate libp2p keys // Generate libp2p keys
const libp2pInitKeys = await generateEd25519Keys(); const libp2pInitKeys = await generateEd25519Keys()
const libp2pRespKeys = await generateEd25519Keys(); const libp2pRespKeys = await generateEd25519Keys()
// Create sessions // Create sessions
const initiatorSession = await ikI.initSession(true, prologue, kpInitiator, kpResponder.publicKey); const initiatorSession = await ikI.initSession(true, prologue, kpInitiator, kpResponder.publicKey)
const responderSession = await ikR.initSession(false, prologue, kpResponder, Buffer.alloc(32)); const responderSession = await ikR.initSession(false, prologue, kpResponder, Buffer.alloc(32))
/* Stage 0 */ /* Stage 0 */
// initiator creates payload // initiator creates payload
const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInitiator.publicKey)); const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInitiator.publicKey))
const libp2pInitPrivKey = libp2pInitKeys.marshal().slice(0, 32); libp2pInitKeys.marshal().slice(0, 32)
const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64); const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64)
const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload); const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload)
// initiator sends message // initiator sends message
const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc]); const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc])
const messageBuffer = ikI.sendMessage(initiatorSession, message); const messageBuffer = ikI.sendMessage(initiatorSession, message)
expect(messageBuffer.ne.length).not.equal(0); expect(messageBuffer.ne.length).not.equal(0)
// responder receives message // responder receives message
const plaintext = ikR.recvMessage(responderSession, messageBuffer); ikR.recvMessage(responderSession, messageBuffer)
/* Stage 1 */ /* Stage 1 */
// responder creates payload // responder creates payload
const libp2pRespPrivKey = libp2pRespKeys.marshal().slice(0, 32); libp2pRespKeys.marshal().slice(0, 32)
const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64); const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64)
const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResponder.publicKey)); const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResponder.publicKey))
const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, respSignedPayload); const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, respSignedPayload)
const message1 = Buffer.concat([message, payloadRespEnc]); const message1 = Buffer.concat([message, payloadRespEnc])
const messageBuffer2 = ikR.sendMessage(responderSession, message1); const messageBuffer2 = ikR.sendMessage(responderSession, message1)
// initiator receives message // initiator receives message
const plaintext2 = ikI.recvMessage(initiatorSession, messageBuffer2); ikI.recvMessage(initiatorSession, messageBuffer2)
assert(initiatorSession.cs1.k.equals(responderSession.cs1.k));
assert(initiatorSession.cs2.k.equals(responderSession.cs2.k));
assert(initiatorSession?.cs1?.k.equals(responderSession?.cs1?.k || new Uint8Array()))
assert(initiatorSession?.cs2?.k.equals(responderSession?.cs2?.k || new Uint8Array()))
} catch (e) { } catch (e) {
console.error(e); return assert(false, e.message)
return assert(false, e.message);
} }
}); })
}); })

View File

@ -1,143 +1,143 @@
import { expect, assert } from "chai"; import { expect, assert } from 'chai'
import { Buffer } from 'buffer'; import { Buffer } from 'buffer'
import { XX } from "../../src/handshakes/xx"; import { XX } from '../../src/handshakes/xx'
import { KeyPair } from "../../src/@types/libp2p"; import { KeyPair } from '../../src/@types/libp2p'
import { generateEd25519Keys } from "../utils"; import { generateEd25519Keys } from '../utils'
import {createHandshakePayload, generateKeypair, getHandshakePayload, getHkdf} from "../../src/utils"; import { createHandshakePayload, generateKeypair, getHandshakePayload, getHkdf } from '../../src/utils'
describe("XX Handshake", () => { describe('XX Handshake', () => {
const prologue = Buffer.alloc(0); const prologue = Buffer.alloc(0)
it("Test creating new XX session", async () => { it('Test creating new XX session', async () => {
try { try {
const xx = new XX(); const xx = new XX()
const kpInitiator: KeyPair = await generateKeypair(); const kpInitiator: KeyPair = await generateKeypair()
const kpResponder: KeyPair = await generateKeypair(); await generateKeypair()
const session = await xx.initSession(true, prologue, kpInitiator); await xx.initSession(true, prologue, kpInitiator)
} catch (e) { } catch (e) {
assert(false, e.message); assert(false, e.message)
} }
}); })
it("Test get HKDF", async () => { it('Test get HKDF', () => {
const ckBytes = Buffer.from('4e6f6973655f58585f32353531395f58436861436861506f6c795f53484132353600000000000000000000000000000000000000000000000000000000000000', 'hex'); const ckBytes = Buffer.from('4e6f6973655f58585f32353531395f58436861436861506f6c795f53484132353600000000000000000000000000000000000000000000000000000000000000', 'hex')
const ikm = Buffer.from('a3eae50ea37a47e8a7aa0c7cd8e16528670536dcd538cebfd724fb68ce44f1910ad898860666227d4e8dd50d22a9a64d1c0a6f47ace092510161e9e442953da3', 'hex'); const ikm = Buffer.from('a3eae50ea37a47e8a7aa0c7cd8e16528670536dcd538cebfd724fb68ce44f1910ad898860666227d4e8dd50d22a9a64d1c0a6f47ace092510161e9e442953da3', 'hex')
const ck = Buffer.alloc(32); const ck = Buffer.alloc(32)
ckBytes.copy(ck); ckBytes.copy(ck)
const [k1, k2, k3] = getHkdf(ck, ikm); const [k1, k2, k3] = getHkdf(ck, ikm)
expect(k1.toString('hex')).to.equal('cc5659adff12714982f806e2477a8d5ddd071def4c29bb38777b7e37046f6914'); expect(k1.toString('hex')).to.equal('cc5659adff12714982f806e2477a8d5ddd071def4c29bb38777b7e37046f6914')
expect(k2.toString('hex')).to.equal('a16ada915e551ab623f38be674bb4ef15d428ae9d80688899c9ef9b62ef208fa'); expect(k2.toString('hex')).to.equal('a16ada915e551ab623f38be674bb4ef15d428ae9d80688899c9ef9b62ef208fa')
expect(k3.toString('hex')).to.equal('ff67bf9727e31b06efc203907e6786667d2c7a74ac412b4d31a80ba3fd766f68'); expect(k3.toString('hex')).to.equal('ff67bf9727e31b06efc203907e6786667d2c7a74ac412b4d31a80ba3fd766f68')
}); })
async function doHandshake(xx) { async function doHandshake (xx) {
const kpInit = await generateKeypair(); const kpInit = await generateKeypair()
const kpResp = await generateKeypair(); const kpResp = await generateKeypair()
// initiator setup // initiator setup
const libp2pInitKeys = await generateEd25519Keys(); const libp2pInitKeys = await generateEd25519Keys()
const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInit.publicKey)); const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInit.publicKey))
// responder setup // responder setup
const libp2pRespKeys = await generateEd25519Keys(); const libp2pRespKeys = await generateEd25519Keys()
const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResp.publicKey)); const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResp.publicKey))
// initiator: new XX noise session // initiator: new XX noise session
const nsInit = xx.initSession(true, prologue, kpInit); const nsInit = xx.initSession(true, prologue, kpInit)
// responder: new XX noise session // responder: new XX noise session
const nsResp = xx.initSession(false, prologue, kpResp); const nsResp = xx.initSession(false, prologue, kpResp)
/* STAGE 0 */ /* STAGE 0 */
// initiator creates payload // initiator creates payload
const libp2pInitPrivKey = libp2pInitKeys.marshal().slice(0, 32); libp2pInitKeys.marshal().slice(0, 32)
const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64); const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64)
const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload); const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload)
// initiator sends message // initiator sends message
const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc]); const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc])
const messageBuffer = xx.sendMessage(nsInit, message); const messageBuffer = xx.sendMessage(nsInit, message)
expect(messageBuffer.ne.length).not.equal(0); expect(messageBuffer.ne.length).not.equal(0)
// responder receives message // responder receives message
xx.recvMessage(nsResp, messageBuffer); xx.recvMessage(nsResp, messageBuffer)
/* STAGE 1 */ /* STAGE 1 */
// responder creates payload // responder creates payload
const libp2pRespPrivKey = libp2pRespKeys.marshal().slice(0, 32); libp2pRespKeys.marshal().slice(0, 32)
const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64); const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64)
const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, respSignedPayload); const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, respSignedPayload)
const message1 = Buffer.concat([message, payloadRespEnc]); const message1 = Buffer.concat([message, payloadRespEnc])
const messageBuffer2 = xx.sendMessage(nsResp, message1); const messageBuffer2 = xx.sendMessage(nsResp, message1)
expect(messageBuffer2.ne.length).not.equal(0); expect(messageBuffer2.ne.length).not.equal(0)
expect(messageBuffer2.ns.length).not.equal(0); expect(messageBuffer2.ns.length).not.equal(0)
// initiator receive payload // initiator receive payload
xx.recvMessage(nsInit, messageBuffer2); xx.recvMessage(nsInit, messageBuffer2)
/* STAGE 2 */ /* STAGE 2 */
// initiator send message // initiator send message
const messageBuffer3 = xx.sendMessage(nsInit, Buffer.alloc(0)); const messageBuffer3 = xx.sendMessage(nsInit, Buffer.alloc(0))
// responder receive message // responder receive message
xx.recvMessage(nsResp, messageBuffer3); xx.recvMessage(nsResp, messageBuffer3)
assert(nsInit.cs1.k.equals(nsResp.cs1.k)); assert(nsInit.cs1.k.equals(nsResp.cs1.k))
assert(nsInit.cs2.k.equals(nsResp.cs2.k)); assert(nsInit.cs2.k.equals(nsResp.cs2.k))
return { nsInit, nsResp }; return { nsInit, nsResp }
} }
it("Test handshake", async () => { it('Test handshake', async () => {
try { try {
const xx = new XX(); const xx = new XX()
await doHandshake(xx); await doHandshake(xx)
} catch (e) { } 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 { try {
const xx = new XX(); const xx = new XX()
const { nsInit, nsResp } = await doHandshake(xx); const { nsInit, nsResp } = await doHandshake(xx)
const ad = Buffer.from("authenticated"); const ad = Buffer.from('authenticated')
const message = Buffer.from("HelloCrypto"); const message = Buffer.from('HelloCrypto')
const ciphertext = xx.encryptWithAd(nsInit.cs1, ad, message); const ciphertext = xx.encryptWithAd(nsInit.cs1, ad, message)
assert(!Buffer.from("HelloCrypto").equals(ciphertext), "Encrypted message should not be same as plaintext."); 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 { plaintext: decrypted, valid } = xx.decryptWithAd(nsResp.cs1, ad, ciphertext)
assert(Buffer.from("HelloCrypto").equals(decrypted), "Decrypted text not equal to original message."); assert(Buffer.from('HelloCrypto').equals(decrypted), 'Decrypted text not equal to original message.')
assert(valid); assert(valid)
} catch (e) { } catch (e) {
assert(false, e.message); assert(false, e.message)
} }
}); })
it("Test multiple messages encryption and decryption", async () => { it('Test multiple messages encryption and decryption', async () => {
const xx = new XX(); const xx = new XX()
const { nsInit, nsResp } = await doHandshake(xx); const { nsInit, nsResp } = await doHandshake(xx)
const ad = Buffer.from("authenticated"); const ad = Buffer.from('authenticated')
const message = Buffer.from("ethereum1"); const message = Buffer.from('ethereum1')
const encrypted = xx.encryptWithAd(nsInit.cs1, ad, message); const encrypted = xx.encryptWithAd(nsInit.cs1, ad, message)
const {plaintext: decrypted} = xx.decryptWithAd(nsResp.cs1, ad, encrypted); const { plaintext: decrypted } = xx.decryptWithAd(nsResp.cs1, ad, encrypted)
assert.equal("ethereum1", decrypted.toString("utf8"), "Decrypted text not equal to original message."); assert.equal('ethereum1', decrypted.toString('utf8'), 'Decrypted text not equal to original message.')
const message2 = Buffer.from("ethereum2"); const message2 = Buffer.from('ethereum2')
const encrypted2 = xx.encryptWithAd(nsInit.cs1, ad, message2); const encrypted2 = xx.encryptWithAd(nsInit.cs1, ad, message2)
const {plaintext: decrypted2} = xx.decryptWithAd(nsResp.cs1, ad, encrypted2); const { plaintext: decrypted2 } = xx.decryptWithAd(nsResp.cs1, ad, encrypted2)
assert.equal("ethereum2", decrypted2.toString("utf-8"), "Decrypted text not equal to original message."); assert.equal('ethereum2', decrypted2.toString('utf-8'), 'Decrypted text not equal to original message.')
}); })
}); })

View File

@ -1,80 +1,79 @@
import Wrap from "it-pb-rpc"; import Wrap from 'it-pb-rpc'
import Duplex from 'it-pair/duplex'; import Duplex from 'it-pair/duplex'
import {Buffer} from "buffer"; import { Buffer } from 'buffer'
import {assert, expect} from "chai"; import { assert, expect } from 'chai'
import {createPeerIdsFromFixtures} from "./fixtures/peer"; import { createPeerIdsFromFixtures } from './fixtures/peer'
import {generateKeypair, getPayload} from "../src/utils"; import { generateKeypair, getPayload } from '../src/utils'
import {IKHandshake} from "../src/handshake-ik"; import { IKHandshake } from '../src/handshake-ik'
describe("IK Handshake", () => { describe('IK Handshake', () => {
let peerA, peerB, fakePeer; let peerA, peerB
before(async () => { 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 { try {
const duplex = Duplex(); const duplex = Duplex()
const connectionFrom = Wrap(duplex[0]); const connectionFrom = Wrap(duplex[0])
const connectionTo = Wrap(duplex[1]); const connectionTo = Wrap(duplex[1])
const prologue = Buffer.alloc(0); const prologue = Buffer.alloc(0)
const staticKeysInitiator = generateKeypair(); const staticKeysInitiator = generateKeypair()
const staticKeysResponder = generateKeypair(); const staticKeysResponder = generateKeypair()
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey); const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, staticKeysResponder.publicKey, peerB); const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, staticKeysResponder.publicKey, peerB)
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey); const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey); const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey)
await handshakeInit.stage0(); await handshakeInit.stage0()
await handshakeResp.stage0(); await handshakeResp.stage0()
await handshakeResp.stage1(); await handshakeResp.stage1()
await handshakeInit.stage1(); await handshakeInit.stage1()
// Test shared key // Test shared key
if (handshakeInit.session.cs1 && handshakeResp.session.cs1 && handshakeInit.session.cs2 && handshakeResp.session.cs2) { 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.cs1.k.equals(handshakeResp.session.cs1.k))
assert(handshakeInit.session.cs2.k.equals(handshakeResp.session.cs2.k)); assert(handshakeInit.session.cs2.k.equals(handshakeResp.session.cs2.k))
} else { } else {
assert(false); assert(false)
} }
// Test encryption and decryption // Test encryption and decryption
const encrypted = handshakeInit.encrypt(Buffer.from("encryptthis"), handshakeInit.session); const encrypted = handshakeInit.encrypt(Buffer.from('encryptthis'), handshakeInit.session)
const {plaintext: decrypted} = handshakeResp.decrypt(encrypted, handshakeResp.session); const { plaintext: decrypted } = handshakeResp.decrypt(encrypted, handshakeResp.session)
assert(decrypted.equals(Buffer.from("encryptthis"))); assert(decrypted.equals(Buffer.from('encryptthis')))
} catch (e) { } 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 { try {
const duplex = Duplex(); const duplex = Duplex()
const connectionFrom = Wrap(duplex[0]); const connectionFrom = Wrap(duplex[0])
const connectionTo = Wrap(duplex[1]); const connectionTo = Wrap(duplex[1])
const prologue = Buffer.alloc(0); const prologue = Buffer.alloc(0)
const staticKeysInitiator = generateKeypair(); const staticKeysInitiator = generateKeypair()
const staticKeysResponder = generateKeypair(); const staticKeysResponder = generateKeypair()
const oldScammyKeys = generateKeypair(); const oldScammyKeys = generateKeypair()
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey); const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, oldScammyKeys.publicKey, peerB); const handshakeInit = new IKHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, oldScammyKeys.publicKey, peerB)
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey); const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey); const handshakeResp = new IKHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey)
await handshakeInit.stage0(); await handshakeInit.stage0()
await handshakeResp.stage0(); await handshakeResp.stage0()
} catch (e) { } 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")
} }
}); })
}); })

View File

@ -1,11 +1,11 @@
import { expect } from "chai"; import { expect } from 'chai'
import { Noise } from "../src"; import { Noise } from '../src'
describe("Index", () => { describe('Index', () => {
it("should expose class with tag and required functions", () => { it('should expose class with tag and required functions', () => {
const noise = new Noise(); const noise = new Noise()
expect(noise.protocol).to.equal('/noise'); expect(noise.protocol).to.equal('/noise')
expect(typeof(noise.secureInbound)).to.equal('function'); expect(typeof (noise.secureInbound)).to.equal('function')
expect(typeof(noise.secureOutbound)).to.equal('function'); expect(typeof (noise.secureOutbound)).to.equal('function')
}) })
}); })

View File

@ -1,34 +1,32 @@
import { expect, assert } from "chai"; import { assert } from 'chai'
import { KeyCache } from "../src/keycache"; import { KeyCache } from '../src/keycache'
import {createPeerIds, createPeerIdsFromFixtures} from "./fixtures/peer"; import { createPeerIds, createPeerIdsFromFixtures } from './fixtures/peer'
describe("KeyCache", () => { describe('KeyCache', () => {
let peerA, peerB; let peerA
before(async () => { 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 { try {
const key = Buffer.from("this is id 007"); const key = Buffer.from('this is id 007')
await KeyCache.store(peerA, key); await KeyCache.store(peerA, key)
const result = await KeyCache.load(peerA); const result = await KeyCache.load(peerA)
assert(result.equals(key), "Stored and loaded key are not the same"); assert(result?.equals(key), 'Stored and loaded key are not the same')
} catch (e) { } catch (e) {
console.error(e);
assert(false, `Test failed - ${e.message}`) 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 { try {
const [newPeer] = await createPeerIds(1); const [newPeer] = await createPeerIds(1)
const result = await KeyCache.load(newPeer); const result = await KeyCache.load(newPeer)
assert(!result); assert(!result)
} catch (e) { } catch (e) {
console.error(e);
assert(false, `Test failed - ${e.message}`) assert(false, `Test failed - ${e.message}`)
} }
}); })
}); })

View File

@ -1,57 +1,57 @@
import {assert, expect} from "chai"; import { assert, expect } from 'chai'
import DuplexPair from 'it-pair/duplex'; import DuplexPair from 'it-pair/duplex'
import {createPeerIdsFromFixtures} from "./fixtures/peer"; import { createPeerIdsFromFixtures } from './fixtures/peer'
import Wrap from "it-pb-rpc"; import Wrap from 'it-pb-rpc'
import sinon from "sinon"; import sinon from 'sinon'
import BufferList from "bl"; import BufferList from 'bl'
import {randomBytes} from 'libp2p-crypto'; import { randomBytes } from 'libp2p-crypto'
import {Buffer} from "buffer"; import { Buffer } from 'buffer'
import {Noise} from "../src"; import { Noise } from '../src'
import {XXHandshake} from "../src/handshake-xx"; import { XXHandshake } from '../src/handshake-xx'
import {createHandshakePayload, generateKeypair, getHandshakePayload, getPayload, signPayload} from "../src/utils"; import { createHandshakePayload, generateKeypair, getHandshakePayload, getPayload, signPayload } from '../src/utils'
import {decode0, decode2, encode1, uint16BEDecode, uint16BEEncode} from "../src/encoder"; import { decode0, decode2, encode1, uint16BEDecode, uint16BEEncode } from '../src/encoder'
import {XX} from "../src/handshakes/xx"; import { XX } from '../src/handshakes/xx'
import {getKeyPairFromPeerId} from "./utils"; import { getKeyPairFromPeerId } from './utils'
import {KeyCache} from "../src/keycache"; import { KeyCache } from '../src/keycache'
import {NOISE_MSG_MAX_LENGTH_BYTES} from "../src/constants"; import { NOISE_MSG_MAX_LENGTH_BYTES } from '../src/constants'
describe("Noise", () => { describe('Noise', () => {
let remotePeer, localPeer; let remotePeer, localPeer
let sandbox = sinon.createSandbox(); const sandbox = sinon.createSandbox()
before(async () => { before(async () => {
[localPeer, remotePeer] = await createPeerIdsFromFixtures(2); [localPeer, remotePeer] = await createPeerIdsFromFixtures(2)
}); })
afterEach(function() { afterEach(function () {
sandbox.restore(); sandbox.restore()
}); })
it("should communicate through encrypted streams without noise pipes", async() => { it('should communicate through encrypted streams without noise pipes', async () => {
try { try {
const noiseInit = new Noise(undefined, undefined, false); const noiseInit = new Noise(undefined, undefined)
const noiseResp = new Noise(undefined, undefined, false); const noiseResp = new Noise(undefined, undefined)
const [inboundConnection, outboundConnection] = DuplexPair(); const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([ const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer), noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
]); ])
const wrappedInbound = Wrap(inbound.conn); const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn); const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.writeLP(Buffer.from("test")); wrappedOutbound.writeLP(Buffer.from('test'))
const response = await wrappedInbound.readLP(); const response = await wrappedInbound.readLP()
expect(response.toString()).equal("test"); expect(response.toString()).equal('test')
} catch (e) { } catch (e) {
assert(false, e.message); assert(false, e.message)
} }
}); })
it("should test that secureOutbound is spec compliant", async() => { it('should test that secureOutbound is spec compliant', async () => {
const noiseInit = new Noise(undefined, undefined, false); const noiseInit = new Noise(undefined, undefined)
const [inboundConnection, outboundConnection] = DuplexPair(); const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, { wrapped, handshake }] = await Promise.all([ const [outbound, { wrapped, handshake }] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
@ -63,304 +63,295 @@ describe("Noise", () => {
lengthDecoder: uint16BEDecode, lengthDecoder: uint16BEDecode,
maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES maxDataLength: NOISE_MSG_MAX_LENGTH_BYTES
} }
); )
const prologue = Buffer.alloc(0); const prologue = Buffer.alloc(0)
const staticKeys = generateKeypair(); const staticKeys = generateKeypair()
const xx = new XX(); const xx = new XX()
const payload = await getPayload(remotePeer, staticKeys.publicKey); const payload = await getPayload(remotePeer, staticKeys.publicKey)
const handshake = new XXHandshake(false, payload, prologue, staticKeys, wrapped, localPeer, xx); 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 // The first handshake message contains the initiator's ephemeral public key
expect(receivedMessageBuffer.ne.length).equal(32); expect(receivedMessageBuffer.ne.length).equal(32)
xx.recvMessage(handshake.session, receivedMessageBuffer); xx.recvMessage(handshake.session, receivedMessageBuffer)
// Stage 1 // Stage 1
const { publicKey: libp2pPubKey } = getKeyPairFromPeerId(remotePeer); const { publicKey: libp2pPubKey } = getKeyPairFromPeerId(remotePeer)
const signedPayload = await signPayload(remotePeer, getHandshakePayload(staticKeys.publicKey)); const signedPayload = await signPayload(remotePeer, getHandshakePayload(staticKeys.publicKey))
const handshakePayload = await createHandshakePayload(libp2pPubKey, signedPayload); const handshakePayload = await createHandshakePayload(libp2pPubKey, signedPayload)
const messageBuffer = xx.sendMessage(handshake.session, handshakePayload); const messageBuffer = xx.sendMessage(handshake.session, handshakePayload)
wrapped.writeLP(encode1(messageBuffer)); wrapped.writeLP(encode1(messageBuffer))
// Stage 2 - finish handshake // Stage 2 - finish handshake
receivedMessageBuffer = decode2((await wrapped.readLP()).slice()); receivedMessageBuffer = decode2((await wrapped.readLP()).slice())
xx.recvMessage(handshake.session, receivedMessageBuffer); xx.recvMessage(handshake.session, receivedMessageBuffer)
return {wrapped, handshake}; return { wrapped, handshake }
})(), })()
]); ])
try { try {
const wrappedOutbound = Wrap(outbound.conn); const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.write(new BufferList([Buffer.from("test")])); wrappedOutbound.write(new BufferList([Buffer.from('test')]))
// Check that noise message is prefixed with 16-bit big-endian unsigned integer // Check that noise message is prefixed with 16-bit big-endian unsigned integer
const receivedEncryptedPayload = (await wrapped.read()).slice(); const receivedEncryptedPayload = (await wrapped.read()).slice()
const dataLength = receivedEncryptedPayload.readInt16BE(0); const dataLength = receivedEncryptedPayload.readInt16BE(0)
const data = receivedEncryptedPayload.slice(2, dataLength + 2); const data = receivedEncryptedPayload.slice(2, dataLength + 2)
const {plaintext: decrypted, valid} = handshake.decrypt(data, handshake.session); const { plaintext: decrypted, valid } = handshake.decrypt(data, handshake.session)
// Decrypted data should match // Decrypted data should match
assert(decrypted.equals(Buffer.from("test"))); assert(decrypted.equals(Buffer.from('test')))
assert(valid); assert(valid)
} catch (e) { } catch (e) {
assert(false, e.message); assert(false, e.message)
} }
}); })
it('should test large payloads', async function () {
it("should test large payloads", async function() { this.timeout(10000)
this.timeout(10000);
try { try {
const noiseInit = new Noise(undefined, undefined, false); const noiseInit = new Noise(undefined, undefined)
const noiseResp = new Noise(undefined, undefined, false); const noiseResp = new Noise(undefined, undefined)
const [inboundConnection, outboundConnection] = DuplexPair(); const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([ const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer), noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
]); ])
const wrappedInbound = Wrap(inbound.conn); const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn); const wrappedOutbound = Wrap(outbound.conn)
const largePlaintext = randomBytes(100000); const largePlaintext = randomBytes(100000)
wrappedOutbound.writeLP(largePlaintext); wrappedOutbound.writeLP(largePlaintext)
const response = await wrappedInbound.read(100000); const response = await wrappedInbound.read(100000)
expect(response.length).equals(largePlaintext.length); expect(response.length).equals(largePlaintext.length)
} catch (e) { } 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 { try {
const staticKeysInitiator = generateKeypair(); const staticKeysInitiator = generateKeypair()
const noiseInit = new Noise(staticKeysInitiator.privateKey); const noiseInit = new Noise(staticKeysInitiator.privateKey)
const staticKeysResponder = generateKeypair(); const staticKeysResponder = generateKeypair()
const noiseResp = new Noise(staticKeysResponder.privateKey); const noiseResp = new Noise(staticKeysResponder.privateKey)
// Prepare key cache for noise pipes // Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey); KeyCache.store(localPeer, staticKeysInitiator.publicKey)
KeyCache.store(remotePeer, staticKeysResponder.publicKey); KeyCache.store(remotePeer, staticKeysResponder.publicKey)
const xxSpy = sandbox.spy(noiseInit, "performXXHandshake"); const xxSpy = sandbox.spy(noiseInit, 'performXXHandshake')
const xxFallbackSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake"); const xxFallbackSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake')
const [inboundConnection, outboundConnection] = DuplexPair(); const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([ const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer), noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
]); ])
const wrappedInbound = Wrap(inbound.conn); const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn); const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.writeLP(Buffer.from("test v2")); wrappedOutbound.writeLP(Buffer.from('test v2'))
const response = await wrappedInbound.readLP(); const response = await wrappedInbound.readLP()
expect(response.toString()).equal("test v2"); expect(response.toString()).equal('test v2')
assert(xxSpy.notCalled); assert(xxSpy.notCalled)
assert(xxFallbackSpy.notCalled); assert(xxFallbackSpy.notCalled)
} catch (e) { } 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 { try {
const staticKeysInitiator = generateKeypair(); const staticKeysInitiator = generateKeypair()
const noiseInit = new Noise(staticKeysInitiator.privateKey); const noiseInit = new Noise(staticKeysInitiator.privateKey)
const noiseResp = new Noise(); const noiseResp = new Noise()
const xxSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake"); const xxSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake')
// Prepare key cache for noise pipes // Prepare key cache for noise pipes
KeyCache.resetStorage(); KeyCache.resetStorage()
KeyCache.store(localPeer, staticKeysInitiator.publicKey); KeyCache.store(localPeer, staticKeysInitiator.publicKey)
KeyCache.store(remotePeer, generateKeypair().publicKey); KeyCache.store(remotePeer, generateKeypair().publicKey)
const [inboundConnection, outboundConnection] = DuplexPair(); const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([ const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer), noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
]); ])
const wrappedInbound = Wrap(inbound.conn); const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn); const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.writeLP(Buffer.from("test fallback")); wrappedOutbound.writeLP(Buffer.from('test fallback'))
const response = await wrappedInbound.readLP(); const response = await wrappedInbound.readLP()
expect(response.toString()).equal("test fallback"); 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) { } catch (e) {
console.error(e); assert(false, e.message)
assert(false, e.message);
} }
}); })
//this didn't work before but we didn't verify decryption // this didn't work before but we didn't verify decryption
it.skip("IK -> XX fallback: responder has disabled noise pipes", async() => { it.skip('IK -> XX fallback: responder has disabled noise pipes', async () => {
try {
const staticKeysInitiator = generateKeypair();
const noiseInit = new Noise(staticKeysInitiator.privateKey);
const staticKeysResponder = generateKeypair();
const noiseResp = new Noise(staticKeysResponder.privateKey, undefined, false);
const xxSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake");
// Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey);
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer),
]);
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test fallback"));
const response = await wrappedInbound.readLP();
expect(response.toString()).equal("test fallback");
assert(xxSpy.calledOnce, "XX Fallback method was never called.");
} catch (e) {
console.error(e);
assert(false, e.message);
}
});
it.skip("Initiator starts with XX (pipes disabled), responder has enabled noise pipes", async() => {
try { try {
const staticKeysInitiator = generateKeypair(); const staticKeysInitiator = generateKeypair()
const noiseInit = new Noise(staticKeysInitiator.privateKey, undefined, false); const noiseInit = new Noise(staticKeysInitiator.privateKey)
const staticKeysResponder = generateKeypair();
const noiseResp = new Noise(staticKeysResponder.privateKey); const staticKeysResponder = generateKeypair()
const xxInitSpy = sandbox.spy(noiseInit, "performXXHandshake"); const noiseResp = new Noise(staticKeysResponder.privateKey, undefined, false)
const xxRespSpy = sandbox.spy(noiseResp, "performXXFallbackHandshake"); const xxSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake')
// Prepare key cache for noise pipes // Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.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([ const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer), noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
]); ])
const wrappedInbound = Wrap(inbound.conn); const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn); const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.writeLP(Buffer.from("test fallback")); wrappedOutbound.writeLP(Buffer.from('test fallback'))
const response = await wrappedInbound.readLP(); const response = await wrappedInbound.readLP()
expect(response.toString()).equal("test fallback"); expect(response.toString()).equal('test fallback')
assert(xxInitSpy.calledOnce, "XX method was never called."); assert(xxSpy.calledOnce, 'XX Fallback method was never called.')
assert(xxRespSpy.calledOnce, "XX Fallback method was never called.");
} catch (e) { } 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 { try {
const staticKeysInitiator = generateKeypair(); const staticKeysInitiator = generateKeypair()
const noiseInit = new Noise(staticKeysInitiator.privateKey); const noiseInit = new Noise(staticKeysInitiator.privateKey, undefined, false)
const staticKeysResponder = generateKeypair(); const staticKeysResponder = generateKeypair()
const noiseResp = new Noise(staticKeysResponder.privateKey); const noiseResp = new Noise(staticKeysResponder.privateKey)
const ikInitSpy = sandbox.spy(noiseInit, "performIKHandshake"); const xxInitSpy = sandbox.spy(noiseInit, 'performXXHandshake')
const xxFallbackInitSpy = sandbox.spy(noiseInit, "performXXFallbackHandshake"); const xxRespSpy = sandbox.spy(noiseResp, 'performXXFallbackHandshake')
const ikRespSpy = sandbox.spy(noiseResp, "performIKHandshake");
// Prepare key cache for noise pipes // Prepare key cache for noise pipes
KeyCache.resetStorage(); KeyCache.store(localPeer, staticKeysInitiator.publicKey)
KeyCache.store(remotePeer, staticKeysResponder.publicKey);
const [inboundConnection, outboundConnection] = DuplexPair(); const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([ const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection, localPeer), noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
]); ])
const wrappedInbound = Wrap(inbound.conn); const wrappedInbound = Wrap(inbound.conn)
const wrappedOutbound = Wrap(outbound.conn); const wrappedOutbound = Wrap(outbound.conn)
wrappedOutbound.writeLP(Buffer.from("test fallback")); wrappedOutbound.writeLP(Buffer.from('test fallback'))
const response = await wrappedInbound.readLP(); const response = await wrappedInbound.readLP()
expect(response.toString()).equal("test fallback"); expect(response.toString()).equal('test fallback')
assert(ikInitSpy.calledOnce, "IK handshake was not called."); assert(xxInitSpy.calledOnce, 'XX method was never called.')
assert(ikRespSpy.calledOnce, "IK handshake was not called."); assert(xxRespSpy.calledOnce, 'XX Fallback method was never called.')
assert(xxFallbackInitSpy.notCalled, "XX Fallback method was called.");
} catch (e) { } 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 { try {
const staticKeysInitiator = generateKeypair(); const staticKeysInitiator = generateKeypair()
const noiseInit = new Noise(staticKeysInitiator.privateKey); const noiseInit = new Noise(staticKeysInitiator.privateKey)
const staticKeysResponder = generateKeypair(); const staticKeysResponder = generateKeypair()
const noiseResp = new Noise(staticKeysResponder.privateKey);
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 // Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey); KeyCache.resetStorage()
KeyCache.store(remotePeer, staticKeysResponder.publicKey); KeyCache.store(remotePeer, staticKeysResponder.publicKey)
const [inboundConnection, outboundConnection] = DuplexPair()
const [inboundConnection, outboundConnection] = DuplexPair();
const [outbound, inbound] = await Promise.all([ const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection), noiseResp.secureInbound(remotePeer, inboundConnection, localPeer)
]); ])
const wrappedInbound = Wrap(inbound.conn);
const wrappedOutbound = Wrap(outbound.conn);
wrappedOutbound.writeLP(Buffer.from("test v2")); const wrappedInbound = Wrap(inbound.conn)
const response = await wrappedInbound.readLP(); const wrappedOutbound = Wrap(outbound.conn)
expect(response.toString()).equal("test v2");
assert(inbound.remotePeer.marshalPubKey().equals(localPeer.marshalPubKey())); wrappedOutbound.writeLP(Buffer.from('test fallback'))
assert(outbound.remotePeer.marshalPubKey().equals(remotePeer.marshalPubKey())); 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) { } 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 { try {
const localPeerEarlyData = Buffer.from('early data') const localPeerEarlyData = Buffer.from('early data')
const staticKeysInitiator = generateKeypair(); const staticKeysInitiator = generateKeypair()
const noiseInit = new Noise(staticKeysInitiator.privateKey, localPeerEarlyData); const noiseInit = new Noise(staticKeysInitiator.privateKey, localPeerEarlyData)
const staticKeysResponder = generateKeypair(); const staticKeysResponder = generateKeypair()
const noiseResp = new Noise(staticKeysResponder.privateKey); const noiseResp = new Noise(staticKeysResponder.privateKey)
// Prepare key cache for noise pipes // Prepare key cache for noise pipes
KeyCache.store(localPeer, staticKeysInitiator.publicKey); KeyCache.store(localPeer, staticKeysInitiator.publicKey)
KeyCache.store(remotePeer, staticKeysResponder.publicKey); KeyCache.store(remotePeer, staticKeysResponder.publicKey)
const [inboundConnection, outboundConnection] = DuplexPair(); const [inboundConnection, outboundConnection] = DuplexPair()
const [outbound, inbound] = await Promise.all([ const [outbound, inbound] = await Promise.all([
noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer),
noiseResp.secureInbound(remotePeer, inboundConnection), noiseResp.secureInbound(remotePeer, inboundConnection)
]); ])
assert(inbound.remoteEarlyData.equals(localPeerEarlyData)) assert(inbound.remoteEarlyData.equals(localPeerEarlyData))
assert(outbound.remoteEarlyData.equals(Buffer.alloc(0))) assert(outbound.remoteEarlyData.equals(Buffer.alloc(0)))
} catch (e) { } catch (e) {
console.error(e); assert(false, e.message)
assert(false, e.message);
} }
}); })
}); })

View File

@ -1,14 +1,14 @@
import {keys} from 'libp2p-crypto'; import { keys, PrivateKey } from 'libp2p-crypto'
import {KeyPair} from "../src/@types/libp2p"; import { KeyPair } from '../src/@types/libp2p'
import PeerId from "peer-id"; import PeerId from 'peer-id'
export async function generateEd25519Keys() { export async function generateEd25519Keys (): Promise<PrivateKey> {
return await keys.generateKeyPair('ed25519'); return await keys.generateKeyPair('Ed25519', 32)
} }
export function getKeyPairFromPeerId(peerId: PeerId): KeyPair { export function getKeyPairFromPeerId (peerId: PeerId): KeyPair {
return { return {
privateKey: peerId.privKey.marshal().slice(0, 32), privateKey: peerId.privKey.marshal().slice(0, 32),
publicKey: peerId.marshalPubKey(), publicKey: peerId.marshalPubKey()
} }
} }

View File

@ -1,74 +1,73 @@
import Wrap from "it-pb-rpc"; import Wrap from 'it-pb-rpc'
import {Buffer} from "buffer"; import { Buffer } from 'buffer'
import Duplex from 'it-pair/duplex'; import Duplex from 'it-pair/duplex'
import { import {
generateKeypair, generateKeypair,
getPayload, getPayload
} from "../src/utils"; } from '../src/utils'
import {XXFallbackHandshake} from "../src/handshake-xx-fallback"; import { XXFallbackHandshake } from '../src/handshake-xx-fallback'
import {createPeerIdsFromFixtures} from "./fixtures/peer"; import { createPeerIdsFromFixtures } from './fixtures/peer'
import {assert} from "chai"; import { assert } from 'chai'
import {decode1, encode0, encode1} from "../src/encoder"; import { encode0 } from '../src/encoder'
describe("XX Fallback Handshake", () => { describe('XX Fallback Handshake', () => {
let peerA, peerB, fakePeer; let peerA, peerB
before(async () => { 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 { try {
const duplex = Duplex(); const duplex = Duplex()
const connectionFrom = Wrap(duplex[0]); const connectionFrom = Wrap(duplex[0])
const connectionTo = Wrap(duplex[1]); const connectionTo = Wrap(duplex[1])
const prologue = Buffer.alloc(0); const prologue = Buffer.alloc(0)
const staticKeysInitiator = generateKeypair(); const staticKeysInitiator = generateKeypair()
const staticKeysResponder = generateKeypair(); const staticKeysResponder = generateKeypair()
const ephemeralKeys = generateKeypair(); const ephemeralKeys = generateKeypair()
// Initial msg for responder is IK first message from initiator // 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({ const initialMsgR = encode0({
ne: ephemeralKeys.publicKey, ne: ephemeralKeys.publicKey,
ns: Buffer.alloc(0), ns: Buffer.alloc(0),
ciphertext: handshakePayload, ciphertext: handshakePayload
}); })
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey); const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
const handshakeResp = 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.propose()
await handshakeResp.exchange(); await handshakeResp.exchange()
// Initial message for initiator is XX Message B from responder // Initial message for initiator is XX Message B from responder
// This is the point where initiator falls back from IK // This is the point where initiator falls back from IK
const initialMsgI = await connectionFrom.readLP(); const initialMsgI = await connectionFrom.readLP()
const handshakeInit = 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.propose()
await handshakeInit.exchange(); await handshakeInit.exchange()
await handshakeInit.finish(); await handshakeInit.finish()
await handshakeResp.finish(); await handshakeResp.finish()
const sessionInitator = handshakeInit.session; const sessionInitator = handshakeInit.session
const sessionResponder = handshakeResp.session; const sessionResponder = handshakeResp.session
// Test shared key // Test shared key
if (sessionInitator.cs1 && sessionResponder.cs1 && sessionInitator.cs2 && sessionResponder.cs2) { if (sessionInitator.cs1 && sessionResponder.cs1 && sessionInitator.cs2 && sessionResponder.cs2) {
assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k)); assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k))
assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k)); assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k))
} else { } else {
assert(false); assert(false)
} }
} catch (e) { } catch (e) {
console.error(e); assert(false, e.message)
assert(false, e.message);
} }
}); })
}) })

View File

@ -1,121 +1,120 @@
import {assert, expect} from "chai"; import { assert, expect } from 'chai'
import Duplex from 'it-pair/duplex'; import Duplex from 'it-pair/duplex'
import {Buffer} from "buffer"; import { Buffer } from 'buffer'
import Wrap from "it-pb-rpc"; import Wrap from 'it-pb-rpc'
import {XXHandshake} from "../src/handshake-xx"; import { XXHandshake } from '../src/handshake-xx'
import {generateKeypair, getPayload} from "../src/utils"; import { generateKeypair, getPayload } from '../src/utils'
import {createPeerIdsFromFixtures} from "./fixtures/peer"; import { createPeerIdsFromFixtures } from './fixtures/peer'
describe('XX Handshake', () => {
describe("XX Handshake", () => { let peerA, peerB, fakePeer
let peerA, peerB, fakePeer;
before(async () => { 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 { try {
const duplex = Duplex(); const duplex = Duplex()
const connectionFrom = Wrap(duplex[0]); const connectionFrom = Wrap(duplex[0])
const connectionTo = Wrap(duplex[1]); const connectionTo = Wrap(duplex[1])
const prologue = Buffer.alloc(0); const prologue = Buffer.alloc(0)
const staticKeysInitiator = generateKeypair(); const staticKeysInitiator = generateKeypair()
const staticKeysResponder = generateKeypair(); const staticKeysResponder = generateKeypair()
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey); const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB); const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB)
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey); const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA); const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA)
await handshakeInitator.propose(); await handshakeInitator.propose()
await handshakeResponder.propose(); await handshakeResponder.propose()
await handshakeResponder.exchange(); await handshakeResponder.exchange()
await handshakeInitator.exchange(); await handshakeInitator.exchange()
await handshakeInitator.finish(); await handshakeInitator.finish()
await handshakeResponder.finish(); await handshakeResponder.finish()
const sessionInitator = handshakeInitator.session; const sessionInitator = handshakeInitator.session
const sessionResponder = handshakeResponder.session; const sessionResponder = handshakeResponder.session
// Test shared key // Test shared key
if (sessionInitator.cs1 && sessionResponder.cs1 && sessionInitator.cs2 && sessionResponder.cs2) { if (sessionInitator.cs1 && sessionResponder.cs1 && sessionInitator.cs2 && sessionResponder.cs2) {
assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k)); assert(sessionInitator.cs1.k.equals(sessionResponder.cs1.k))
assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k)); assert(sessionInitator.cs2.k.equals(sessionResponder.cs2.k))
} else { } else {
assert(false); assert(false)
} }
// Test encryption and decryption // Test encryption and decryption
const encrypted = handshakeInitator.encrypt(Buffer.from("encryptthis"), handshakeInitator.session); const encrypted = handshakeInitator.encrypt(Buffer.from('encryptthis'), handshakeInitator.session)
const {plaintext: decrypted, valid} = handshakeResponder.decrypt(encrypted, handshakeResponder.session); const { plaintext: decrypted, valid } = handshakeResponder.decrypt(encrypted, handshakeResponder.session)
assert(decrypted.equals(Buffer.from("encryptthis"))); assert(decrypted.equals(Buffer.from('encryptthis')))
assert(valid); assert(valid)
} catch (e) { } 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 { try {
const duplex = Duplex(); const duplex = Duplex()
const connectionFrom = Wrap(duplex[0]); const connectionFrom = Wrap(duplex[0])
const connectionTo = Wrap(duplex[1]); const connectionTo = Wrap(duplex[1])
const prologue = Buffer.alloc(0); const prologue = Buffer.alloc(0)
const staticKeysInitiator = generateKeypair(); const staticKeysInitiator = generateKeypair()
const staticKeysResponder = generateKeypair(); const staticKeysResponder = generateKeypair()
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey); const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, fakePeer); const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, fakePeer)
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey); const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA); const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, peerA)
await handshakeInitator.propose(); await handshakeInitator.propose()
await handshakeResponder.propose(); await handshakeResponder.propose()
await handshakeResponder.exchange(); await handshakeResponder.exchange()
await handshakeInitator.exchange(); await handshakeInitator.exchange()
assert(false, "Should throw exception"); assert(false, 'Should throw exception')
} catch (e) { } catch (e) {
expect(e.message).equals("Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key.") 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 { try {
const duplex = Duplex(); const duplex = Duplex()
const connectionFrom = Wrap(duplex[0]); const connectionFrom = Wrap(duplex[0])
const connectionTo = Wrap(duplex[1]); const connectionTo = Wrap(duplex[1])
const prologue = Buffer.alloc(0); const prologue = Buffer.alloc(0)
const staticKeysInitiator = generateKeypair(); const staticKeysInitiator = generateKeypair()
const staticKeysResponder = generateKeypair(); const staticKeysResponder = generateKeypair()
const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey); const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey)
const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB); const handshakeInitator = new XXHandshake(true, initPayload, prologue, staticKeysInitiator, connectionFrom, peerB)
const respPayload = await getPayload(peerB, staticKeysResponder.publicKey); const respPayload = await getPayload(peerB, staticKeysResponder.publicKey)
const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, fakePeer); const handshakeResponder = new XXHandshake(false, respPayload, prologue, staticKeysResponder, connectionTo, fakePeer)
await handshakeInitator.propose(); await handshakeInitator.propose()
await handshakeResponder.propose(); await handshakeResponder.propose()
await handshakeResponder.exchange(); await handshakeResponder.exchange()
await handshakeInitator.exchange(); await handshakeInitator.exchange()
await handshakeInitator.finish(); await handshakeInitator.finish()
await handshakeResponder.finish(); await handshakeResponder.finish()
assert(false, "Should throw exception"); assert(false, 'Should throw exception')
} catch (e) { } catch (e) {
expect(e.message).equals("Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key.") expect(e.message).equals("Error occurred while verifying signed payload: Peer ID doesn't match libp2p public key.")
} }
}); })
}); })

View File

@ -4,6 +4,7 @@
"module": "commonjs", "module": "commonjs",
"strict": true, "strict": true,
"allowJs": true, "allowJs": true,
"sourceMap": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"esModuleInterop": true, "esModuleInterop": true,
"noImplicitAny": false, "noImplicitAny": false,

View File

@ -1803,9 +1803,10 @@ add-stream@^1.0.0:
resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa" resolved "https://registry.yarnpkg.com/add-stream/-/add-stream-1.0.0.tgz#6a7990437ca736d5e1288db92bd3266d5f5cb2aa"
integrity sha1-anmQQ3ynNtXhKI25K9MmbV9csqo= integrity sha1-anmQQ3ynNtXhKI25K9MmbV9csqo=
aegir@ipfs/aegir#feat/cosmiconfig: aegir@25.0.0:
version "24.0.0" version "25.0.0"
resolved "https://codeload.github.com/ipfs/aegir/tar.gz/15d895f22055023a78d704c8beabbd45c35a8f61" resolved "https://registry.yarnpkg.com/aegir/-/aegir-25.0.0.tgz#9275c4dba0717355ee20c1c35cb27f474fadcca9"
integrity sha512-VP1ACVfnUj/k/kwc/W74IOFKVurPsN3BmWxtbxVpr7hLkOoi3/OYBaaROeVndmUiOHihye1PLQUAMpHROgRpeg==
dependencies: dependencies:
"@babel/cli" "^7.10.1" "@babel/cli" "^7.10.1"
"@babel/core" "^7.10.2" "@babel/core" "^7.10.2"
@ -1849,7 +1850,6 @@ aegir@ipfs/aegir#feat/cosmiconfig:
eslint-plugin-node "^11.0.0" eslint-plugin-node "^11.0.0"
eslint-plugin-promise "^4.2.1" eslint-plugin-promise "^4.2.1"
eslint-plugin-standard "^4.0.1" eslint-plugin-standard "^4.0.1"
esm "^3.2.25"
execa "^4.0.0" execa "^4.0.0"
extract-zip "^2.0.1" extract-zip "^2.0.1"
findup-sync "^4.0.0" findup-sync "^4.0.0"
@ -4595,11 +4595,6 @@ eslint@^6.3.0:
text-table "^0.2.0" text-table "^0.2.0"
v8-compile-cache "^2.0.3" 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: espree@^6.1.2:
version "6.2.1" version "6.2.1"
resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a" resolved "https://registry.yarnpkg.com/espree/-/espree-6.2.1.tgz#77fc72e1fd744a2052c20f38a5b575832e82734a"