mirror of
https://github.com/fluencelabs/js-libp2p-noise
synced 2025-04-25 12:32:28 +00:00
Merge pull request #18 from NodeFactoryIo/feature/noise-pipes
Noise pipes
This commit is contained in:
commit
25e2e96c5a
@ -24,6 +24,7 @@ export class IKHandshake implements IHandshake {
|
|||||||
staticKeypair: KeyPair,
|
staticKeypair: KeyPair,
|
||||||
connection: WrappedConnection,
|
connection: WrappedConnection,
|
||||||
remotePeer: PeerId,
|
remotePeer: PeerId,
|
||||||
|
remoteStaticKey: bytes,
|
||||||
handshake?: IK,
|
handshake?: IK,
|
||||||
) {
|
) {
|
||||||
this.isInitiator = isInitiator;
|
this.isInitiator = isInitiator;
|
||||||
@ -34,11 +35,7 @@ export class IKHandshake implements IHandshake {
|
|||||||
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);
|
||||||
// Dummy data
|
|
||||||
// TODO: Load remote static keys if found
|
|
||||||
const remoteStaticKeys = this.staticKeypair;
|
|
||||||
this.session = this.ik.initSession(this.isInitiator, this.prologue, this.staticKeypair, remoteStaticKeys.publicKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public decrypt(ciphertext: Buffer, session: NoiseSession): Buffer {
|
public decrypt(ciphertext: Buffer, session: NoiseSession): Buffer {
|
||||||
|
@ -114,6 +114,10 @@ export class XXHandshake implements IHandshake {
|
|||||||
return this.xx.decryptWithAd(cs, Buffer.alloc(0), ciphertext);
|
return this.xx.decryptWithAd(cs, Buffer.alloc(0), ciphertext);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getRemoteStaticKey(): bytes {
|
||||||
|
return this.session.hs.rs;
|
||||||
|
}
|
||||||
|
|
||||||
private getCS(session: NoiseSession, encryption = true) {
|
private getCS(session: NoiseSession, encryption = true) {
|
||||||
if (!session.cs1 || !session.cs2) {
|
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.");
|
||||||
|
@ -145,7 +145,6 @@ export class XX extends AbstractHandshake {
|
|||||||
session.h = h;
|
session.h = h;
|
||||||
session.cs1 = cs1;
|
session.cs1 = cs1;
|
||||||
session.cs2 = cs2;
|
session.cs2 = cs2;
|
||||||
delete session.hs;
|
|
||||||
} else if (session.mc.gtn(2)) {
|
} else if (session.mc.gtn(2)) {
|
||||||
if (session.i) {
|
if (session.i) {
|
||||||
if (!session.cs1) {
|
if (!session.cs1) {
|
||||||
|
27
src/keycache.ts
Normal file
27
src/keycache.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import {PeerId} from "./@types/libp2p";
|
||||||
|
import {bytes, bytes32} from "./@types/basic";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage for static keys of previously connected peers.
|
||||||
|
*/
|
||||||
|
class Keycache {
|
||||||
|
private storage = new Map<bytes, bytes32>();
|
||||||
|
|
||||||
|
public async store(peerId: PeerId, key: bytes32): Promise<void> {
|
||||||
|
this.storage.set(peerId.id, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async load(peerId: PeerId): Promise<bytes32|null> {
|
||||||
|
return this.storage.get(peerId.id) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetStorage(): void {
|
||||||
|
this.storage.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const KeyCache = new Keycache();
|
||||||
|
export {
|
||||||
|
KeyCache,
|
||||||
|
}
|
37
src/noise.ts
37
src/noise.ts
@ -16,6 +16,8 @@ import { bytes } from "./@types/basic";
|
|||||||
import { INoiseConnection, PeerId, KeyPair, SecureOutbound } from "./@types/libp2p";
|
import { INoiseConnection, PeerId, KeyPair, SecureOutbound } from "./@types/libp2p";
|
||||||
import { Duplex } from "./@types/it-pair";
|
import { Duplex } from "./@types/it-pair";
|
||||||
import {IHandshake} from "./@types/handshake-interface";
|
import {IHandshake} from "./@types/handshake-interface";
|
||||||
|
import {KeyCache} from "./keycache";
|
||||||
|
import {logger} from "./logger";
|
||||||
|
|
||||||
export type WrappedConnection = ReturnType<typeof Wrap>;
|
export type WrappedConnection = ReturnType<typeof Wrap>;
|
||||||
|
|
||||||
@ -32,9 +34,11 @@ export class Noise implements INoiseConnection {
|
|||||||
private readonly prologue = Buffer.from(this.protocol);
|
private readonly prologue = Buffer.from(this.protocol);
|
||||||
private readonly staticKeys: KeyPair;
|
private readonly staticKeys: KeyPair;
|
||||||
private readonly earlyData?: bytes;
|
private readonly earlyData?: bytes;
|
||||||
|
private useNoisePipes: boolean;
|
||||||
|
|
||||||
constructor(staticNoiseKey?: bytes, earlyData?: bytes) {
|
constructor(staticNoiseKey?: bytes, earlyData?: bytes, useNoisePipes = true) {
|
||||||
this.earlyData = earlyData || Buffer.alloc(0);
|
this.earlyData = earlyData || Buffer.alloc(0);
|
||||||
|
this.useNoisePipes = useNoisePipes;
|
||||||
|
|
||||||
if (staticNoiseKey) {
|
if (staticNoiseKey) {
|
||||||
const publicKey = x25519.publicKeyCreate(staticNoiseKey); // TODO: verify this
|
const publicKey = x25519.publicKeyCreate(staticNoiseKey); // TODO: verify this
|
||||||
@ -95,27 +99,35 @@ export class Noise implements INoiseConnection {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 remote peer static key is unknown, use XX.
|
* If noise pipes disabled or remote peer static key is unknown, use XX.
|
||||||
* @param connection
|
* @param connection
|
||||||
* @param isInitiator
|
* @param isInitiator
|
||||||
* @param libp2pPublicKey
|
* @param libp2pPublicKey
|
||||||
* @param remotePeer
|
* @param remotePeer
|
||||||
*/
|
*/
|
||||||
private async performHandshake(params: HandshakeParams): Promise<IHandshake> {
|
private async performHandshake(params: HandshakeParams): Promise<IHandshake> {
|
||||||
// TODO: Implement noise pipes
|
|
||||||
const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData);
|
const payload = await getPayload(params.localPeer, this.staticKeys.publicKey, this.earlyData);
|
||||||
|
|
||||||
if (false) {
|
let foundRemoteStaticKey: bytes|null = null;
|
||||||
let IKhandshake;
|
if (this.useNoisePipes && params.isInitiator) {
|
||||||
|
logger("Initiator using noise pipes. Going to load cached static key...");
|
||||||
|
foundRemoteStaticKey = await KeyCache.load(params.remotePeer);
|
||||||
|
logger(`Static key has been found: ${!!foundRemoteStaticKey}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundRemoteStaticKey) {
|
||||||
|
// Try IK first
|
||||||
|
const { remotePeer, connection, isInitiator } = params;
|
||||||
|
const IKhandshake = new IKHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer, foundRemoteStaticKey);
|
||||||
try {
|
try {
|
||||||
IKhandshake = await this.performIKHandshake(params, payload);
|
return await this.performIKHandshake(IKhandshake, payload);
|
||||||
return IKhandshake;
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// XX fallback
|
// IK failed, go to XX fallback
|
||||||
const ephemeralKeys = IKhandshake.getRemoteEphemeralKeys();
|
const ephemeralKeys = IKhandshake.getRemoteEphemeralKeys();
|
||||||
return await this.performXXFallbackHandshake(params, payload, ephemeralKeys, e.initialMsg);
|
return await this.performXXFallbackHandshake(params, payload, ephemeralKeys, e.initialMsg);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Noise pipes not supported, use XX
|
||||||
return await this.performXXHandshake(params, payload);
|
return await this.performXXHandshake(params, payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -152,6 +164,10 @@ export class Noise implements INoiseConnection {
|
|||||||
await handshake.propose();
|
await handshake.propose();
|
||||||
await handshake.exchange();
|
await handshake.exchange();
|
||||||
await handshake.finish();
|
await handshake.finish();
|
||||||
|
|
||||||
|
if (this.useNoisePipes) {
|
||||||
|
await KeyCache.store(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}`);
|
||||||
}
|
}
|
||||||
@ -160,12 +176,9 @@ export class Noise implements INoiseConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async performIKHandshake(
|
private async performIKHandshake(
|
||||||
params: HandshakeParams,
|
handshake: IKHandshake,
|
||||||
payload: bytes,
|
payload: bytes,
|
||||||
): Promise<IKHandshake> {
|
): Promise<IKHandshake> {
|
||||||
const { isInitiator, remotePeer, connection } = params;
|
|
||||||
const handshake = new IKHandshake(isInitiator, payload, this.prologue, this.staticKeys, connection, remotePeer);
|
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
|
||||||
return handshake;
|
return handshake;
|
||||||
|
34
test/keycache.test.ts
Normal file
34
test/keycache.test.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { expect, assert } from "chai";
|
||||||
|
import { KeyCache } from "../src/keycache";
|
||||||
|
import {createPeerIds, createPeerIdsFromFixtures} from "./fixtures/peer";
|
||||||
|
|
||||||
|
describe("KeyCache", () => {
|
||||||
|
let peerA, peerB;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
[peerA, peerB] = await createPeerIdsFromFixtures(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should store and load same key successfully", async() => {
|
||||||
|
try {
|
||||||
|
const key = Buffer.from("this is id 007");
|
||||||
|
await KeyCache.store(peerA, key);
|
||||||
|
const result = await KeyCache.load(peerA);
|
||||||
|
assert(result.equals(key), "Stored and loaded key are not the same");
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
assert(false, `Test failed - ${e.message}`)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should return undefined if key not found", async() => {
|
||||||
|
try {
|
||||||
|
const [newPeer] = await createPeerIds(1);
|
||||||
|
const result = await KeyCache.load(newPeer);
|
||||||
|
assert(!result);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
assert(false, `Test failed - ${e.message}`)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
@ -26,8 +26,8 @@ describe("Noise", () => {
|
|||||||
|
|
||||||
it("should communicate through encrypted streams", async() => {
|
it("should communicate through encrypted streams", async() => {
|
||||||
try {
|
try {
|
||||||
const noiseInit = new Noise();
|
const noiseInit = new Noise(undefined, undefined, false);
|
||||||
const noiseResp = new Noise();
|
const noiseResp = new Noise(undefined, undefined, false);
|
||||||
|
|
||||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||||
const [outbound, inbound] = await Promise.all([
|
const [outbound, inbound] = await Promise.all([
|
||||||
@ -46,7 +46,7 @@ describe("Noise", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it("should test that secureOutbound is spec compliant", async() => {
|
it("should test that secureOutbound is spec compliant", async() => {
|
||||||
const noiseInit = new Noise();
|
const noiseInit = new Noise(undefined, undefined, false);
|
||||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||||
|
|
||||||
const [outbound, { wrapped, handshake }] = await Promise.all([
|
const [outbound, { wrapped, handshake }] = await Promise.all([
|
||||||
@ -99,8 +99,8 @@ describe("Noise", () => {
|
|||||||
|
|
||||||
it("should test large payloads", async() => {
|
it("should test large payloads", async() => {
|
||||||
try {
|
try {
|
||||||
const noiseInit = new Noise();
|
const noiseInit = new Noise(undefined, undefined, false);
|
||||||
const noiseResp = new Noise();
|
const noiseResp = new Noise(undefined, undefined, false);
|
||||||
|
|
||||||
const [inboundConnection, outboundConnection] = DuplexPair();
|
const [inboundConnection, outboundConnection] = DuplexPair();
|
||||||
const [outbound, inbound] = await Promise.all([
|
const [outbound, inbound] = await Promise.all([
|
||||||
|
Loading…
x
Reference in New Issue
Block a user