diff --git a/package.json b/package.json index 43db885..def4896 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,6 @@ "@libp2p/interface-keys": "^1.0.2", "@libp2p/interface-peer-id": "^1.0.2", "@libp2p/logger": "^2.0.0", - "@libp2p/peer-collections": "^2.0.0", "@libp2p/peer-id": "^1.1.8", "@stablelib/chacha20poly1305": "^1.0.1", "@stablelib/hkdf": "^1.0.1", diff --git a/src/errors.ts b/src/errors.ts deleted file mode 100644 index 80c4cae..0000000 --- a/src/errors.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Uint8ArrayList } from 'uint8arraylist' - -export class FailedIKError extends Error { - public initialMsg: string|Uint8ArrayList|Uint8Array - - constructor (initialMsg: string|Uint8ArrayList|Uint8Array, message?: string) { - super(message) - - this.initialMsg = initialMsg - this.name = 'FailedIKhandshake' - } -} diff --git a/src/handshake-ik.ts b/src/handshake-ik.ts deleted file mode 100644 index e48a8af..0000000 --- a/src/handshake-ik.ts +++ /dev/null @@ -1,158 +0,0 @@ -import type { PeerId } from '@libp2p/interface-peer-id' -import type { ProtobufStream } from 'it-pb-stream' -import type { CipherState, NoiseSession } from './@types/handshake.js' -import type { bytes, bytes32 } from './@types/basic.js' -import type { KeyPair } from './@types/libp2p.js' -import type { IHandshake } from './@types/handshake-interface.js' -import type { ICryptoInterface } from './crypto.js' -import { IK } from './handshakes/ik.js' -import { decode0, decode1, encode0, encode1 } from './encoder.js' -import { FailedIKError } from './errors.js' -import { - logger, - logLocalStaticKeys, - logRemoteStaticKey, - logLocalEphemeralKeys, - logRemoteEphemeralKey, - logCipherState -} from './logger.js' -import { decodePayload, getPeerIdFromPayload, verifySignedPayload } from './utils.js' - -export class IKHandshake implements IHandshake { - public isInitiator: boolean - public session: NoiseSession - public remotePeer!: PeerId - public remoteEarlyData: Uint8Array - - private readonly payload: bytes - private readonly prologue: bytes32 - private readonly staticKeypair: KeyPair - private readonly connection: ProtobufStream - private readonly ik: IK - - constructor ( - isInitiator: boolean, - payload: bytes, - prologue: bytes32, - crypto: ICryptoInterface, - staticKeypair: KeyPair, - connection: ProtobufStream, - remoteStaticKey: bytes, - remotePeer?: PeerId, - handshake?: IK - ) { - this.isInitiator = isInitiator - this.payload = payload - this.prologue = prologue - this.staticKeypair = staticKeypair - this.connection = connection - if (remotePeer) { - this.remotePeer = remotePeer - } - this.ik = handshake ?? new IK(crypto) - this.session = this.ik.initSession(this.isInitiator, this.prologue, this.staticKeypair, remoteStaticKey) - this.remoteEarlyData = new Uint8Array() - } - - public async stage0 (): Promise { - logLocalStaticKeys(this.session.hs.s) - logRemoteStaticKey(this.session.hs.rs) - if (this.isInitiator) { - logger('IK Stage 0 - Initiator sending message...') - const messageBuffer = this.ik.sendMessage(this.session, this.payload) - this.connection.writeLP(encode1(messageBuffer)) - logger('IK Stage 0 - Initiator sent message.') - logLocalEphemeralKeys(this.session.hs.e) - } else { - logger('IK Stage 0 - Responder receiving message...') - const receivedMsg = await this.connection.readLP() - try { - const receivedMessageBuffer = decode1(receivedMsg.slice()) - const { plaintext, valid } = this.ik.recvMessage(this.session, receivedMessageBuffer) - if (!valid) { - throw new Error('ik handshake stage 0 decryption validation fail') - } - logger('IK Stage 0 - Responder got message, going to verify payload.') - const decodedPayload = decodePayload(plaintext) - this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload) - await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer) - this.setRemoteEarlyData(decodedPayload.data) - logger('IK Stage 0 - Responder successfully verified payload!') - logRemoteEphemeralKey(this.session.hs.re) - } catch (e) { - const err = e as Error - logger('Responder breaking up with IK handshake in stage 0.') - - throw new FailedIKError(receivedMsg.slice(), `Error occurred while verifying initiator's signed payload: ${err.message}`) - } - } - } - - public async stage1 (): Promise { - if (this.isInitiator) { - logger('IK Stage 1 - Initiator receiving message...') - const receivedMsg = (await this.connection.readLP()).slice() - const receivedMessageBuffer = decode0(receivedMsg) - const { plaintext, valid } = this.ik.recvMessage(this.session, receivedMessageBuffer) - logger('IK Stage 1 - Initiator got message, going to verify payload.') - try { - if (!valid) { - throw new Error('ik stage 1 decryption validation fail') - } - const decodedPayload = decodePayload(plaintext) - this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload) - await verifySignedPayload(receivedMessageBuffer.ns.slice(0, 32), decodedPayload, this.remotePeer) - this.setRemoteEarlyData(decodedPayload.data) - logger('IK Stage 1 - Initiator successfully verified payload!') - logRemoteEphemeralKey(this.session.hs.re) - } catch (e) { - const err = e as Error - logger('Initiator breaking up with IK handshake in stage 1.') - throw new FailedIKError(receivedMsg, `Error occurred while verifying responder's signed payload: ${err.message}`) - } - } else { - logger('IK Stage 1 - Responder sending message...') - const messageBuffer = this.ik.sendMessage(this.session, this.payload) - this.connection.writeLP(encode0(messageBuffer)) - logger('IK Stage 1 - Responder sent message...') - logLocalEphemeralKeys(this.session.hs.e) - } - logCipherState(this.session) - } - - public decrypt (ciphertext: Uint8Array, session: NoiseSession): {plaintext: bytes, valid: boolean} { - const cs = this.getCS(session, false) - return this.ik.decryptWithAd(cs, new Uint8Array(0), ciphertext) - } - - public encrypt (plaintext: Uint8Array, session: NoiseSession): bytes { - const cs = this.getCS(session) - return this.ik.encryptWithAd(cs, new Uint8Array(0), plaintext) - } - - public getLocalEphemeralKeys (): KeyPair { - if (!this.session.hs.e) { - throw new Error('Ephemeral keys do not exist.') - } - - return this.session.hs.e - } - - private getCS (session: NoiseSession, encryption = true): CipherState { - if (!session.cs1 || !session.cs2) { - throw new Error('Handshake not completed properly, cipher state does not exist.') - } - - if (this.isInitiator) { - return encryption ? session.cs1 : session.cs2 - } else { - return encryption ? session.cs2 : session.cs1 - } - } - - private setRemoteEarlyData (data: Uint8Array|null|undefined): void { - if (data) { - this.remoteEarlyData = data - } - } -} diff --git a/src/handshake-xx-fallback.ts b/src/handshake-xx-fallback.ts deleted file mode 100644 index 9e00d58..0000000 --- a/src/handshake-xx-fallback.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { PeerId } from '@libp2p/interface-peer-id' -import type { ProtobufStream } from 'it-pb-stream' -import type { bytes, bytes32 } from './@types/basic.js' -import type { KeyPair } from './@types/libp2p.js' -import { XXHandshake } from './handshake-xx.js' -import type { XX } from './handshakes/xx.js' -import type { ICryptoInterface } from './crypto.js' -import { decode0, decode1 } from './encoder.js' -import { logger, logLocalEphemeralKeys, logRemoteEphemeralKey, logRemoteStaticKey } from './logger.js' -import { decodePayload, getPeerIdFromPayload, verifySignedPayload } from './utils.js' - -export class XXFallbackHandshake extends XXHandshake { - private readonly ephemeralKeys?: KeyPair - private readonly initialMsg: bytes - - constructor ( - isInitiator: boolean, - payload: bytes, - prologue: bytes32, - crypto: ICryptoInterface, - staticKeypair: KeyPair, - connection: ProtobufStream, - initialMsg: bytes, - remotePeer?: PeerId, - ephemeralKeys?: KeyPair, - handshake?: XX - ) { - super(isInitiator, payload, prologue, crypto, staticKeypair, connection, remotePeer, handshake) - if (ephemeralKeys) { - this.ephemeralKeys = ephemeralKeys - } - this.initialMsg = initialMsg - } - - // stage 0 - // eslint-disable-next-line require-await - public async propose (): Promise { - if (this.isInitiator) { - this.xx.sendMessage(this.session, new Uint8Array(0), this.ephemeralKeys) - logger('XX Fallback Stage 0 - Initialized state as the first message was sent by initiator.') - logLocalEphemeralKeys(this.session.hs.e) - } else { - logger('XX Fallback Stage 0 - Responder decoding initial msg from IK.') - const receivedMessageBuffer = decode0(this.initialMsg) - const { valid } = this.xx.recvMessage(this.session, { - ne: receivedMessageBuffer.ne, - ns: new Uint8Array(0), - ciphertext: new Uint8Array(0) - }) - if (!valid) { - throw new Error('xx fallback stage 0 decryption validation fail') - } - logger('XX Fallback Stage 0 - Responder used received message from IK.') - logRemoteEphemeralKey(this.session.hs.re) - } - } - - // stage 1 - public async exchange (): Promise { - if (this.isInitiator) { - const receivedMessageBuffer = decode1(this.initialMsg) - const { plaintext, valid } = this.xx.recvMessage(this.session, receivedMessageBuffer) - if (!valid) { - throw new Error('xx fallback stage 1 decryption validation fail') - } - logger('XX Fallback Stage 1 - Initiator used received message from IK.') - logRemoteEphemeralKey(this.session.hs.re) - logRemoteStaticKey(this.session.hs.rs) - - logger("Initiator going to check remote's signature...") - try { - const decodedPayload = decodePayload(plaintext) - this.remotePeer = this.remotePeer || await getPeerIdFromPayload(decodedPayload) - await verifySignedPayload(this.session.hs.rs, decodedPayload, this.remotePeer) - this.setRemoteEarlyData(decodedPayload.data) - } catch (e) { - const err = e as Error - throw new Error(`Error occurred while verifying signed payload from responder: ${err.message}`) - } - logger('All good with the signature!') - } else { - logger('XX Fallback Stage 1 - Responder start') - await super.exchange() - logger('XX Fallback Stage 1 - Responder end') - } - } -} diff --git a/src/handshakes/ik.ts b/src/handshakes/ik.ts deleted file mode 100644 index 2ec938f..0000000 --- a/src/handshakes/ik.ts +++ /dev/null @@ -1,155 +0,0 @@ -import type { bytes, bytes32 } from '../@types/basic.js' -import type { CipherState, HandshakeState, MessageBuffer, NoiseSession } from '../@types/handshake.js' -import type { KeyPair } from '../@types/libp2p.js' -import { isValidPublicKey } from '../utils.js' -import { AbstractHandshake } from './abstract-handshake.js' - -export class IK extends AbstractHandshake { - public initSession (initiator: boolean, prologue: bytes32, s: KeyPair, rs: bytes32): NoiseSession { - const psk = this.createEmptyKey() - - let hs - if (initiator) { - hs = this.initializeInitiator(prologue, s, rs, psk) - } else { - hs = this.initializeResponder(prologue, s, rs, psk) - } - - return { - hs, - i: initiator, - mc: 0 - } - } - - public sendMessage (session: NoiseSession, message: bytes): MessageBuffer { - let messageBuffer: MessageBuffer - if (session.mc === 0) { - messageBuffer = this.writeMessageA(session.hs, message) - } else if (session.mc === 1) { - const { messageBuffer: mb, h, cs1, cs2 } = this.writeMessageB(session.hs, message) - messageBuffer = mb - session.h = h - session.cs1 = cs1 - session.cs2 = cs2 - } else if (session.mc > 1) { - if (session.i) { - if (!session.cs1) { - throw new Error('CS1 (cipher state) is not defined') - } - - messageBuffer = this.writeMessageRegular(session.cs1, message) - } else { - if (!session.cs2) { - throw new Error('CS2 (cipher state) is not defined') - } - - messageBuffer = this.writeMessageRegular(session.cs2, message) - } - } else { - throw new Error('Session invalid.') - } - - session.mc++ - return messageBuffer - } - - public recvMessage (session: NoiseSession, message: MessageBuffer): {plaintext: bytes, valid: boolean} { - let plaintext = new Uint8Array(0); let valid = false - if (session.mc === 0) { - ({ plaintext, valid } = this.readMessageA(session.hs, message)) - } - if (session.mc === 1) { - const { plaintext: pt, valid: v, h, cs1, cs2 } = this.readMessageB(session.hs, message) - plaintext = pt - valid = v - session.h = h - session.cs1 = cs1 - session.cs2 = cs2 - } - session.mc++ - return { plaintext, valid } - } - - private writeMessageA (hs: HandshakeState, payload: bytes): MessageBuffer { - hs.e = this.crypto.generateX25519KeyPair() - const ne = hs.e.publicKey - this.mixHash(hs.ss, ne) - this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs)) - const spk = hs.s.publicKey - const ns = this.encryptAndHash(hs.ss, spk) - - this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs)) - const ciphertext = this.encryptAndHash(hs.ss, payload) - - return { ne, ns, ciphertext } - } - - private writeMessageB (hs: HandshakeState, payload: bytes): { messageBuffer: MessageBuffer, cs1: CipherState, cs2: CipherState, h: bytes} { - hs.e = this.crypto.generateX25519KeyPair() - const ne = hs.e.publicKey - this.mixHash(hs.ss, ne) - - this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re)) - this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.rs)) - const ciphertext = this.encryptAndHash(hs.ss, payload) - const ns = this.createEmptyKey() - const messageBuffer: MessageBuffer = { ne, ns, ciphertext } - const { cs1, cs2 } = this.split(hs.ss) - - return { messageBuffer, cs1, cs2, h: hs.ss.h } - } - - private readMessageA (hs: HandshakeState, message: MessageBuffer): {plaintext: bytes, valid: boolean} { - if (isValidPublicKey(message.ne)) { - hs.re = message.ne - } - - this.mixHash(hs.ss, hs.re) - this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)) - const { plaintext: ns, valid: valid1 } = this.decryptAndHash(hs.ss, message.ns) - if (valid1 && isValidPublicKey(ns)) { - hs.rs = ns - } - this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.rs)) - const { plaintext, valid: valid2 } = this.decryptAndHash(hs.ss, message.ciphertext) - return { plaintext, valid: (valid1 && valid2) } - } - - private readMessageB (hs: HandshakeState, message: MessageBuffer): {h: bytes, plaintext: bytes, valid: boolean, cs1: CipherState, cs2: CipherState} { - if (isValidPublicKey(message.ne)) { - hs.re = message.ne - } - - this.mixHash(hs.ss, hs.re) - if (!hs.e) { - throw new Error('Handshake state should contain ephemeral key by now.') - } - this.mixKey(hs.ss, this.dh(hs.e.privateKey, hs.re)) - this.mixKey(hs.ss, this.dh(hs.s.privateKey, hs.re)) - const { plaintext, valid } = this.decryptAndHash(hs.ss, message.ciphertext) - const { cs1, cs2 } = this.split(hs.ss) - - return { h: hs.ss.h, valid, plaintext, cs1, cs2 } - } - - private initializeInitiator (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState { - const name = 'Noise_IK_25519_ChaChaPoly_SHA256' - const ss = this.initializeSymmetric(name) - this.mixHash(ss, prologue) - this.mixHash(ss, rs) - const re = new Uint8Array(32) - - return { ss, s, rs, re, psk } - } - - private initializeResponder (prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState { - const name = 'Noise_IK_25519_ChaChaPoly_SHA256' - const ss = this.initializeSymmetric(name) - this.mixHash(ss, prologue) - this.mixHash(ss, s.publicKey) - const re = new Uint8Array(32) - - return { ss, s, rs, re, psk } - } -} diff --git a/src/keycache.ts b/src/keycache.ts deleted file mode 100644 index 431bee7..0000000 --- a/src/keycache.ts +++ /dev/null @@ -1,30 +0,0 @@ -import type { PeerId } from '@libp2p/interface-peer-id' -import { PeerMap } from '@libp2p/peer-collections' -import type { bytes32 } from './@types/basic.js' - -/** - * Storage for static keys of previously connected peers. - */ -class Keycache { - private readonly storage = new PeerMap() - - public store (peerId: PeerId, key: bytes32): void { - this.storage.set(peerId, key) - } - - public load (peerId?: PeerId): bytes32 | null { - if (!peerId) { - return null - } - return this.storage.get(peerId) ?? null - } - - public resetStorage (): void { - this.storage.clear() - } -} - -const KeyCache = new Keycache() -export { - KeyCache -} diff --git a/src/noise.ts b/src/noise.ts index 820d98e..5b52b9d 100644 --- a/src/noise.ts +++ b/src/noise.ts @@ -13,12 +13,7 @@ import type { ICryptoInterface } from './crypto.js' import { stablelib } from './crypto/stablelib.js' import { decryptStream, encryptStream } from './crypto/streaming.js' import { uint16BEDecode, uint16BEEncode } from './encoder.js' -import type { FailedIKError } from './errors.js' -import { IKHandshake } from './handshake-ik.js' import { XXHandshake } from './handshake-xx.js' -import { XXFallbackHandshake } from './handshake-xx-fallback.js' -import { KeyCache } from './keycache.js' -import { logger } from './logger.js' import { getPayload } from './utils.js' interface HandshakeParams { @@ -35,7 +30,6 @@ export class Noise implements INoiseConnection { private readonly prologue = new Uint8Array(0) private readonly staticKeys: KeyPair private readonly earlyData?: bytes - private readonly useNoisePipes: boolean /** * @param {bytes} staticNoiseKey - x25519 private key, reuse for faster handshakes @@ -43,8 +37,6 @@ export class Noise implements INoiseConnection { */ constructor (staticNoiseKey?: bytes, earlyData?: bytes, crypto: ICryptoInterface = stablelib) { this.earlyData = earlyData ?? new Uint8Array(0) - // disabled until properly specked - this.useNoisePipes = false this.crypto = crypto if (staticNoiseKey) { @@ -127,77 +119,9 @@ export class Noise implements INoiseConnection { */ private async performHandshake (params: HandshakeParams): Promise { const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData) - let tryIK = this.useNoisePipes - if (params.isInitiator && KeyCache.load(params.remotePeer) === null) { - // if we are initiator and remote static key is unknown, don't try IK - tryIK = false - } - // Try IK if acting as responder or initiator that has remote's static key. - if (tryIK) { - // Try IK first - const { remotePeer, connection, isInitiator } = params - const ikHandshake = new IKHandshake( - isInitiator, - payload, - this.prologue, - this.crypto, - this.staticKeys, - connection, - // safe to cast as we did checks - KeyCache.load(params.remotePeer) ?? new Uint8Array(32), - remotePeer as PeerId - ) - - try { - return await this.performIKHandshake(ikHandshake) - } catch (e) { - const err = e as FailedIKError - // IK failed, go to XX fallback - let ephemeralKeys - if (params.isInitiator) { - ephemeralKeys = ikHandshake.getLocalEphemeralKeys() - } - return await this.performXXFallbackHandshake(params, payload, err.initialMsg as Uint8Array, ephemeralKeys) - } - } else { - // run XX handshake - return await this.performXXHandshake(params, payload) - } - } - - private async performXXFallbackHandshake ( - params: HandshakeParams, - payload: bytes, - initialMsg: bytes, - ephemeralKeys?: KeyPair - ): Promise { - const { isInitiator, remotePeer, connection } = params - const handshake = - new XXFallbackHandshake( - isInitiator, - payload, - this.prologue, - this.crypto, - this.staticKeys, - connection, - initialMsg, - remotePeer, - ephemeralKeys - ) - - try { - await handshake.propose() - await handshake.exchange() - await handshake.finish() - } catch (e) { - const err = e as Error - err.message = `Error occurred during XX Fallback handshake: ${err.message}` - logger(err) - throw err - } - - return handshake + // run XX handshake + return await this.performXXHandshake(params, payload) } private async performXXHandshake ( @@ -219,10 +143,6 @@ export class Noise implements INoiseConnection { await handshake.propose() await handshake.exchange() await handshake.finish() - - if (this.useNoisePipes && handshake.remotePeer) { - KeyCache.store(handshake.remotePeer, handshake.getRemoteStaticKey()) - } } catch (e: unknown) { if (e instanceof Error) { e.message = `Error occurred during XX handshake: ${e.message}` @@ -233,15 +153,6 @@ export class Noise implements INoiseConnection { return handshake } - private async performIKHandshake ( - handshake: IKHandshake - ): Promise { - await handshake.stage0() - await handshake.stage1() - - return handshake - } - private async createSecureConnection ( connection: ProtobufStream, handshake: IHandshake diff --git a/test/handshakes/ik.spec.ts b/test/handshakes/ik.spec.ts deleted file mode 100644 index 1f7f019..0000000 --- a/test/handshakes/ik.spec.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { assert, expect } from 'aegir/chai' -import { Buffer } from 'buffer' -import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { IK } from '../../src/handshakes/ik.js' -import type { KeyPair } from '../../src/@types/libp2p.js' -import { stablelib } from '../../src/crypto/stablelib.js' -import { createHandshakePayload, getHandshakePayload } from '../../src/utils.js' -import { generateEd25519Keys } from '../utils.js' - -describe('IK handshake', () => { - const prologue = Buffer.alloc(0) - - it('Test complete IK handshake', async () => { - try { - const ikI = new IK(stablelib) - const ikR = new IK(stablelib) - - // Generate static noise keys - const kpInitiator: KeyPair = stablelib.generateX25519KeyPair() - const kpResponder: KeyPair = stablelib.generateX25519KeyPair() - - // Generate libp2p keys - const libp2pInitKeys = await generateEd25519Keys() - const libp2pRespKeys = await generateEd25519Keys() - - // Create sessions - const initiatorSession = await ikI.initSession(true, prologue, kpInitiator, kpResponder.publicKey) - const responderSession = await ikR.initSession(false, prologue, kpResponder, Buffer.alloc(32)) - - /* Stage 0 */ - - // initiator creates payload - const initSignedPayload = await libp2pInitKeys.sign(getHandshakePayload(kpInitiator.publicKey)) - libp2pInitKeys.marshal().slice(0, 32) - const libp2pInitPubKey = libp2pInitKeys.marshal().slice(32, 64) - const payloadInitEnc = await createHandshakePayload(libp2pInitPubKey, initSignedPayload) - - // initiator sends message - const message = Buffer.concat([Buffer.alloc(0), payloadInitEnc]) - const messageBuffer = ikI.sendMessage(initiatorSession, message) - - expect(messageBuffer.ne.length).not.equal(0) - - // responder receives message - ikR.recvMessage(responderSession, messageBuffer) - - /* Stage 1 */ - - // responder creates payload - libp2pRespKeys.marshal().slice(0, 32) - const libp2pRespPubKey = libp2pRespKeys.marshal().slice(32, 64) - const respSignedPayload = await libp2pRespKeys.sign(getHandshakePayload(kpResponder.publicKey)) - const payloadRespEnc = await createHandshakePayload(libp2pRespPubKey, respSignedPayload) - - const message1 = Buffer.concat([message, payloadRespEnc]) - const messageBuffer2 = ikR.sendMessage(responderSession, message1) - - // initiator receives message - ikI.recvMessage(initiatorSession, messageBuffer2) - - if (initiatorSession?.cs1?.k != null) { - assert(uint8ArrayEquals(initiatorSession.cs1.k, responderSession?.cs1?.k ?? new Uint8Array())) - } - - if (initiatorSession?.cs2?.k != null) { - assert(uint8ArrayEquals(initiatorSession.cs2.k, responderSession?.cs2?.k ?? new Uint8Array())) - } - } catch (e) { - const err = e as Error - return assert(false, err.message) - } - }) -}) diff --git a/test/ik-handshake.spec.ts b/test/ik-handshake.spec.ts deleted file mode 100644 index 3ee12f7..0000000 --- a/test/ik-handshake.spec.ts +++ /dev/null @@ -1,84 +0,0 @@ -import type { PeerId } from '@libp2p/interface-peer-id' -import { Buffer } from 'buffer' -import { pbStream } from 'it-pb-stream' -import { duplexPair } from 'it-pair/duplex' -import { equals as uint8ArrayEquals } from 'uint8arrays' -import { assert, expect } from 'aegir/chai' -import { stablelib } from '../src/crypto/stablelib.js' -import { IKHandshake } from '../src/handshake-ik.js' -import { getPayload } from '../src/utils.js' -import { createPeerIdsFromFixtures } from './fixtures/peer.js' - -describe('IK Handshake', () => { - let peerA: PeerId, peerB: PeerId - - before(async () => { - [peerA, peerB] = await createPeerIdsFromFixtures(3) - }) - - // IK handshake is not used, no idea why this test isn't passing but it makes no sense to debug until we start using it - it.skip('should finish both stages as initiator and responder', async () => { - try { - const duplex = duplexPair() - const connectionFrom = pbStream(duplex[0]) - const connectionTo = pbStream(duplex[1]) - - const prologue = Buffer.alloc(0) - const staticKeysInitiator = stablelib.generateX25519KeyPair() - const staticKeysResponder = stablelib.generateX25519KeyPair() - - const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey) - const handshakeInit = new IKHandshake(true, initPayload, prologue, stablelib, staticKeysInitiator, connectionFrom, staticKeysResponder.publicKey, peerB) - - const respPayload = await getPayload(peerB, staticKeysResponder.publicKey) - const handshakeResp = new IKHandshake(false, respPayload, prologue, stablelib, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey) - - await handshakeInit.stage0() - await handshakeResp.stage0() - - await handshakeResp.stage1() - await handshakeInit.stage1() - - // Test shared key - if (handshakeInit.session.cs1 && handshakeResp.session.cs1 && handshakeInit.session.cs2 && handshakeResp.session.cs2) { - assert(uint8ArrayEquals(handshakeInit.session.cs1.k, handshakeResp.session.cs1.k)) - assert(uint8ArrayEquals(handshakeInit.session.cs2.k, handshakeResp.session.cs2.k)) - } else { - assert(false) - } - - // Test encryption and decryption - const encrypted = handshakeInit.encrypt(Buffer.from('encryptthis'), handshakeInit.session) - const { plaintext: decrypted } = handshakeResp.decrypt(encrypted, handshakeResp.session) - assert(uint8ArrayEquals(decrypted, Buffer.from('encryptthis'))) - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it("should throw error if responder's static key changed", async () => { - try { - const duplex = duplexPair() - const connectionFrom = pbStream(duplex[0]) - const connectionTo = pbStream(duplex[1]) - - const prologue = Buffer.alloc(0) - const staticKeysInitiator = stablelib.generateX25519KeyPair() - const staticKeysResponder = stablelib.generateX25519KeyPair() - const oldScammyKeys = stablelib.generateX25519KeyPair() - - const initPayload = await getPayload(peerA, staticKeysInitiator.publicKey) - const handshakeInit = new IKHandshake(true, initPayload, prologue, stablelib, staticKeysInitiator, connectionFrom, oldScammyKeys.publicKey, peerB) - - const respPayload = await getPayload(peerB, staticKeysResponder.publicKey) - const handshakeResp = new IKHandshake(false, respPayload, prologue, stablelib, staticKeysResponder, connectionTo, staticKeysInitiator.publicKey) - - await handshakeInit.stage0() - await handshakeResp.stage0() - } catch (e) { - const err = e as Error - expect(err.message).to.include("Error occurred while verifying initiator's signed payload") - } - }) -}) diff --git a/test/keycache.spec.ts b/test/keycache.spec.ts deleted file mode 100644 index 97fbb91..0000000 --- a/test/keycache.spec.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { PeerId } from '@libp2p/interface-peer-id' -import { Buffer } from 'buffer' -import { assert } from 'aegir/chai' -import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { KeyCache } from '../src/keycache.js' -import { createPeerIds, createPeerIdsFromFixtures } from './fixtures/peer.js' - -describe('KeyCache', () => { - let peerA: PeerId - - before(async () => { - [peerA] = await createPeerIdsFromFixtures(2) - }) - - it('should store and load same key successfully', async () => { - try { - const key = Buffer.from('this is id 007') - await KeyCache.store(peerA, key) - const result = await KeyCache.load(peerA) - assert(result !== null && uint8ArrayEquals(result, key), 'Stored and loaded key are not the same') - } catch (e) { - const err = e as Error - assert(false, `Test failed - ${err.message}`) - } - }) - - it('should return undefined if key not found', async () => { - try { - const [newPeer] = await createPeerIds(1) - const result = await KeyCache.load(newPeer) - assert(result === null) - } catch (e) { - const err = e as Error - assert(false, `Test failed - ${err.message}`) - } - }) -}) diff --git a/test/noise.spec.ts b/test/noise.spec.ts index 1d7fde3..6669864 100644 --- a/test/noise.spec.ts +++ b/test/noise.spec.ts @@ -11,7 +11,6 @@ import sinon from 'sinon' import { NOISE_MSG_MAX_LENGTH_BYTES } from '../src/constants.js' import { stablelib } from '../src/crypto/stablelib.js' import { decode0, decode2, encode1, uint16BEDecode, uint16BEEncode } from '../src/encoder.js' -import { KeyCache } from '../src/keycache.js' import { XX } from '../src/handshakes/xx.js' import { XXHandshake } from '../src/handshake-xx.js' import { Noise } from '../src/index.js' @@ -131,188 +130,6 @@ describe('Noise', () => { } }) - it.skip('should communicate through encrypted streams with noise pipes', async () => { - try { - const staticKeysInitiator = stablelib.generateX25519KeyPair() - const noiseInit = new Noise(staticKeysInitiator.privateKey) - const staticKeysResponder = stablelib.generateX25519KeyPair() - const noiseResp = new Noise(staticKeysResponder.privateKey) - - // Prepare key cache for noise pipes - KeyCache.store(localPeer, staticKeysInitiator.publicKey) - KeyCache.store(remotePeer, staticKeysResponder.publicKey) - - // @ts-expect-error - const xxSpy = sandbox.spy(noiseInit, 'performXXHandshake') - // @ts-expect-error - const xxFallbackSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake') - - const [inboundConnection, outboundConnection] = duplexPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection, localPeer) - ]) - const wrappedInbound = pbStream(inbound.conn) - const wrappedOutbound = pbStream(outbound.conn) - - wrappedOutbound.writeLP(Buffer.from('test v2')) - const response = await wrappedInbound.readLP() - expect(uint8ArrayToString(response.slice())).equal('test v2') - - assert(xxSpy.notCalled) - assert(xxFallbackSpy.notCalled) - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it.skip('IK -> XX fallback: initiator has invalid remote static key', async () => { - try { - const staticKeysInitiator = stablelib.generateX25519KeyPair() - const noiseInit = new Noise(staticKeysInitiator.privateKey) - const noiseResp = new Noise() - // @ts-expect-error - const xxSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake') - - // Prepare key cache for noise pipes - KeyCache.resetStorage() - KeyCache.store(localPeer, staticKeysInitiator.publicKey) - KeyCache.store(remotePeer, stablelib.generateX25519KeyPair().publicKey) - - const [inboundConnection, outboundConnection] = duplexPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection, localPeer) - ]) - - const wrappedInbound = pbStream(inbound.conn) - const wrappedOutbound = pbStream(outbound.conn) - - wrappedOutbound.writeLP(Buffer.from('test fallback')) - const response = await wrappedInbound.readLP() - expect(uint8ArrayToString(response.slice())).equal('test fallback') - - assert(xxSpy.calledOnce, 'XX Fallback method was never called.') - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - // this didn't work before but we didn't verify decryption - it.skip('IK -> XX fallback: responder has disabled noise pipes', async () => { - try { - const staticKeysInitiator = stablelib.generateX25519KeyPair() - const noiseInit = new Noise(staticKeysInitiator.privateKey) - - const staticKeysResponder = stablelib.generateX25519KeyPair() - const noiseResp = new Noise(staticKeysResponder.privateKey, undefined) - // @ts-expect-error - const xxSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake') - - // Prepare key cache for noise pipes - KeyCache.store(localPeer, staticKeysInitiator.publicKey) - KeyCache.store(remotePeer, staticKeysResponder.publicKey) - - const [inboundConnection, outboundConnection] = duplexPair() - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection, localPeer) - ]) - - const wrappedInbound = pbStream(inbound.conn) - const wrappedOutbound = pbStream(outbound.conn) - - wrappedOutbound.writeLP(Buffer.from('test fallback')) - const response = await wrappedInbound.readLP() - expect(uint8ArrayToString(response.slice())).equal('test fallback') - - assert(xxSpy.calledOnce, 'XX Fallback method was never called.') - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it.skip('Initiator starts with XX (pipes disabled), responder has enabled noise pipes', async () => { - try { - const staticKeysInitiator = stablelib.generateX25519KeyPair() - const noiseInit = new Noise(staticKeysInitiator.privateKey, undefined) - const staticKeysResponder = stablelib.generateX25519KeyPair() - - const noiseResp = new Noise(staticKeysResponder.privateKey) - // @ts-expect-error - const xxInitSpy = sandbox.spy(noiseInit, 'performXXHandshake') - // @ts-expect-error - const xxRespSpy = sandbox.spy(noiseResp, 'performXXFallbackHandshake') - - // Prepare key cache for noise pipes - KeyCache.store(localPeer, staticKeysInitiator.publicKey) - - const [inboundConnection, outboundConnection] = duplexPair() - - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection, localPeer) - ]) - - const wrappedInbound = pbStream(inbound.conn) - const wrappedOutbound = pbStream(outbound.conn) - - wrappedOutbound.writeLP(Buffer.from('test fallback')) - const response = await wrappedInbound.readLP() - expect(uint8ArrayToString(response.slice())).equal('test fallback') - - assert(xxInitSpy.calledOnce, 'XX method was never called.') - assert(xxRespSpy.calledOnce, 'XX Fallback method was never called.') - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) - - it.skip('IK: responder has no remote static key', async () => { - try { - const staticKeysInitiator = stablelib.generateX25519KeyPair() - const noiseInit = new Noise(staticKeysInitiator.privateKey) - const staticKeysResponder = stablelib.generateX25519KeyPair() - - const noiseResp = new Noise(staticKeysResponder.privateKey) - // @ts-expect-error - const ikInitSpy = sandbox.spy(noiseInit, 'performIKHandshake') - // @ts-expect-error - const xxFallbackInitSpy = sandbox.spy(noiseInit, 'performXXFallbackHandshake') - // @ts-expect-error - const ikRespSpy = sandbox.spy(noiseResp, 'performIKHandshake') - - // Prepare key cache for noise pipes - KeyCache.resetStorage() - KeyCache.store(remotePeer, staticKeysResponder.publicKey) - - const [inboundConnection, outboundConnection] = duplexPair() - - const [outbound, inbound] = await Promise.all([ - noiseInit.secureOutbound(localPeer, outboundConnection, remotePeer), - noiseResp.secureInbound(remotePeer, inboundConnection, localPeer) - ]) - - const wrappedInbound = pbStream(inbound.conn) - const wrappedOutbound = pbStream(outbound.conn) - - wrappedOutbound.writeLP(Buffer.from('test fallback')) - const response = await wrappedInbound.readLP() - expect(uint8ArrayToString(response.slice())).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) { - const err = e as Error - assert(false, err.message) - } - }) - it('should working without remote peer provided in incoming connection', async () => { try { const staticKeysInitiator = stablelib.generateX25519KeyPair() @@ -320,10 +137,6 @@ describe('Noise', () => { const staticKeysResponder = stablelib.generateX25519KeyPair() 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), @@ -357,10 +170,6 @@ describe('Noise', () => { const staticKeysResponder = stablelib.generateX25519KeyPair() 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), diff --git a/test/xx-fallback-handshake.spec.ts b/test/xx-fallback-handshake.spec.ts deleted file mode 100644 index 5f9405c..0000000 --- a/test/xx-fallback-handshake.spec.ts +++ /dev/null @@ -1,76 +0,0 @@ -import type { PeerId } from '@libp2p/interface-peer-id' -import { Buffer } from 'buffer' -import { assert } from 'aegir/chai' -import { pbStream } from 'it-pb-stream' -import { duplexPair } from 'it-pair/duplex' -import { equals as uint8ArrayEquals } from 'uint8arrays/equals' -import { stablelib } from '../src/crypto/stablelib.js' -import { encode0 } from '../src/encoder.js' -import { XXFallbackHandshake } from '../src/handshake-xx-fallback.js' -import { getPayload } from '../src/utils.js' -import { createPeerIdsFromFixtures } from './fixtures/peer.js' - -describe('XX Fallback Handshake', () => { - let peerA: PeerId, peerB: PeerId - - before(async () => { - [peerA, peerB] = await createPeerIdsFromFixtures(2) - }) - - it('should test that both parties can fallback to XX and finish handshake', async () => { - try { - const duplex = duplexPair() - const connectionFrom = pbStream(duplex[0]) - const connectionTo = pbStream(duplex[1]) - - const prologue = Buffer.alloc(0) - const staticKeysInitiator = stablelib.generateX25519KeyPair() - const staticKeysResponder = stablelib.generateX25519KeyPair() - const ephemeralKeys = stablelib.generateX25519KeyPair() - - // Initial msg for responder is IK first message from initiator - const handshakePayload = await getPayload(peerA, staticKeysInitiator.publicKey) - const initialMsgR = encode0({ - ne: ephemeralKeys.publicKey, - ns: Buffer.alloc(0), - ciphertext: handshakePayload - }) - - const respPayload = await getPayload(peerB, staticKeysResponder.publicKey) - const handshakeResp = - new XXFallbackHandshake(false, respPayload, prologue, stablelib, staticKeysResponder, connectionTo, initialMsgR, peerA) - - await handshakeResp.propose() - await handshakeResp.exchange() - - // Initial message for initiator is XX Message B from responder - // This is the point where initiator falls back from IK - const initialMsgI = await connectionFrom.readLP() - const handshakeInit = - new XXFallbackHandshake(true, handshakePayload, prologue, stablelib, staticKeysInitiator, connectionFrom, initialMsgI.slice(0), peerB, ephemeralKeys) - - await handshakeInit.propose() - await handshakeInit.exchange() - - await handshakeInit.finish() - await handshakeResp.finish() - - const sessionInitator = handshakeInit.session - const sessionResponder = handshakeResp.session - - // Test shared key - if (sessionInitator.cs1 !== undefined && - sessionResponder.cs1 !== undefined && - sessionInitator.cs2 !== undefined && - sessionResponder.cs2 !== undefined) { - assert(uint8ArrayEquals(sessionInitator.cs1.k, sessionResponder.cs1.k)) - assert(uint8ArrayEquals(sessionInitator.cs2.k, sessionResponder.cs2.k)) - } else { - assert(false) - } - } catch (e) { - const err = e as Error - assert(false, err.message) - } - }) -})