Create XX fallback flow

This commit is contained in:
Belma Gutlic
2020-01-05 19:00:16 +01:00
parent a514ededa1
commit 4a9b814e5a
6 changed files with 194 additions and 33 deletions

View File

@@ -54,7 +54,11 @@ export class Handshake implements HandshakeInterface {
return this.ik.encryptWithAd(cs, Buffer.alloc(0), plaintext);
}
public getRemoteEphemeralKeys(): KeyPair | undefined {
public getRemoteEphemeralKeys(): KeyPair {
if (!this.session.hs.e) {
throw new Error("Ephemeral keys do not exist.");
}
return this.session.hs.e;
}

View File

@@ -0,0 +1,89 @@
import { Buffer } from "buffer";
import { Handshake as XXHandshake } from "./handshake-xx";
import { XXHandshake as XX } from "./handshakes/xx";
import { KeyPair, PeerId } from "./@types/libp2p";
import { bytes, bytes32 } from "./@types/basic";
import {
createHandshakePayload,
getHandshakePayload,
signEarlyDataPayload,
signPayload,
verifySignedPayload,
} from "./utils";
import { logger } from "./logger";
import { decodeMessageBuffer, encodeMessageBuffer } from "./encoder";
import { WrappedConnection } from "./noise";
export class Handshake extends XXHandshake {
private ephemeralKeys: KeyPair;
private initialMsg: bytes;
constructor(
isInitiator: boolean,
libp2pPrivateKey: bytes,
libp2pPublicKey: bytes,
prologue: bytes32,
staticKeys: KeyPair,
connection: WrappedConnection,
remotePeer: PeerId,
ephemeralKeys: KeyPair,
initialMsg: bytes,
handshake?: XX,
) {
super(isInitiator, libp2pPrivateKey, libp2pPublicKey, prologue, staticKeys, connection, remotePeer, handshake);
this.ephemeralKeys = ephemeralKeys;
this.initialMsg = initialMsg;
}
// stage 0
public async propose(): Promise<void> {
if (this.isInitiator) {
logger("XX Fallback Stage 0 - Initiator starting to send first message.");
const messageBuffer = this.xx.sendMessage(this.session, Buffer.alloc(0), this.ephemeralKeys);
this.connection.writeLP(encodeMessageBuffer(messageBuffer));
logger("XX Fallback Stage 0 - Initiator finished sending first message.");
} else {
logger("XX Fallback Stage 0 - Responder waiting to receive first message...");
const receivedMessageBuffer = this.xx.decode0(this.initialMsg);
this.xx.recvMessage(this.session, {
ne: receivedMessageBuffer.ne,
ns: Buffer.alloc(0),
ciphertext: Buffer.alloc(0),
});
logger("XX Fallback Stage 0 - Responder received first message.");
}
}
// stage 1
public async exchange(): Promise<void> {
if (this.isInitiator) {
logger('XX Fallback Stage 1 - Initiator waiting to receive first message from responder...');
const receivedMessageBuffer = this.xx.decode1(this.initialMsg);
const plaintext = this.xx.recvMessage(this.session, receivedMessageBuffer);
logger('XX Fallback Stage 1 - Initiator received the message. Got remote\'s static key.');
// logger("Initiator going to check remote's signature...");
// try {
// await verifySignedPayload(receivedMessageBuffer.ns, plaintext, this.remotePeer.id);
// } catch (e) {
// throw new Error(`Error occurred while verifying signed payload: ${e.message}`);
// }
// logger("All good with the signature!");
} else {
logger('Stage 1 - Responder sending out first message with signed payload and static key.');
const signedPayload = signPayload(this.libp2pPrivateKey, getHandshakePayload(this.staticKeys.publicKey));
const signedEarlyDataPayload = signEarlyDataPayload(this.libp2pPrivateKey, Buffer.alloc(0));
const handshakePayload = await createHandshakePayload(
this.libp2pPublicKey,
this.libp2pPrivateKey,
signedPayload,
signedEarlyDataPayload,
);
const messageBuffer = this.xx.sendMessage(this.session, handshakePayload);
this.connection.writeLP(encodeMessageBuffer(messageBuffer));
logger('Stage 1 - Responder sent the second handshake message with signed payload.')
}
}
}

View File

@@ -20,13 +20,14 @@ export class Handshake implements HandshakeInterface {
public isInitiator: boolean;
public session: NoiseSession;
private libp2pPrivateKey: bytes;
private libp2pPublicKey: bytes;
protected connection: WrappedConnection;
protected xx: XXHandshake;
protected libp2pPrivateKey: bytes;
protected libp2pPublicKey: bytes;
private prologue: bytes32;
private staticKeys: KeyPair;
private connection: WrappedConnection;
protected staticKeys: KeyPair;
private remotePeer: PeerId;
private xx: XXHandshake;
constructor(
isInitiator: boolean,
@@ -36,7 +37,6 @@ export class Handshake implements HandshakeInterface {
staticKeys: KeyPair,
connection: WrappedConnection,
remotePeer: PeerId,
ephemeralKeys?: KeyPair,
handshake?: XXHandshake,
) {
this.isInitiator = isInitiator;

View File

@@ -199,4 +199,28 @@ export class XXHandshake extends AbstractHandshake {
session.mc = session.mc.add(new BN(1));
return plaintext;
}
public decode0(input: bytes): MessageBuffer {
if (input.length < 32) {
throw new Error("Cannot decode stage 0 MessageBuffer: length less than 32 bytes.");
}
return {
ne: input.slice(0, 32),
ciphertext: input.slice(32, input.length),
ns: Buffer.alloc(0),
}
}
public decode1(input: bytes): MessageBuffer {
if (input.length < 96) {
throw new Error("Cannot decode stage 0 MessageBuffer: length less than 96 bytes.");
}
return {
ne: input.slice(0, 32),
ns: input.slice(32, 80),
ciphertext: input.slice(80, input.length),
}
}
}

View File

@@ -8,6 +8,7 @@ import lp from 'it-length-prefixed';
import { Handshake as XX } from "./handshake-xx";
import { Handshake as IK } from "./handshake-ik";
import { Handshake as XXFallback } from "./handshake-xx-fallback";
import { generateKeypair } from "./utils";
import { uint16BEDecode, uint16BEEncode } from "./encoder";
import { decryptStream, encryptStream } from "./crypto";
@@ -19,6 +20,13 @@ import {HandshakeInterface} from "./@types/handshake-interface";
export type WrappedConnection = ReturnType<typeof Wrap>;
type HandshakeParams = {
connection: WrappedConnection;
isInitiator: boolean;
libp2pPublicKey: bytes;
remotePeer: PeerId;
};
export class Noise implements NoiseConnection {
public protocol = "/noise";
@@ -52,7 +60,12 @@ export class Noise implements NoiseConnection {
public async secureOutbound(localPeer: PeerId, connection: any, remotePeer: PeerId): Promise<SecureOutbound> {
const wrappedConnection = Wrap(connection);
const libp2pPublicKey = localPeer.marshalPubKey();
const handshake = await this.performHandshake(wrappedConnection, true, libp2pPublicKey, remotePeer);
const handshake = await this.performHandshake({
connection: wrappedConnection,
isInitiator: true,
libp2pPublicKey,
remotePeer,
});
const conn = await this.createSecureConnection(wrappedConnection, handshake);
return {
@@ -71,7 +84,12 @@ export class Noise implements NoiseConnection {
public async secureInbound(localPeer: PeerId, connection: any, remotePeer: PeerId): Promise<SecureOutbound> {
const wrappedConnection = Wrap(connection);
const libp2pPublicKey = localPeer.marshalPubKey();
const handshake = await this.performHandshake(wrappedConnection, false, libp2pPublicKey, remotePeer);
const handshake = await this.performHandshake({
connection: wrappedConnection,
isInitiator: false,
libp2pPublicKey,
remotePeer
});
const conn = await this.createSecureConnection(wrappedConnection, handshake);
return {
@@ -88,46 +106,72 @@ export class Noise implements NoiseConnection {
* @param libp2pPublicKey
* @param remotePeer
*/
private async performHandshake(
connection: WrappedConnection,
isInitiator: boolean,
libp2pPublicKey: bytes,
remotePeer: PeerId,
): Promise<HandshakeInterface> {
private async performHandshake(params: HandshakeParams): Promise<HandshakeInterface> {
// TODO: Implement noise pipes
const IKhandshake = new IK(isInitiator, this.privateKey, libp2pPublicKey, this.prologue, this.staticKeys, connection, remotePeer);
if(true) {
// XX fallback
const ephemeralKeys = IKhandshake.getRemoteEphemeralKeys();
return await this.performXXHandshake(connection, isInitiator, libp2pPublicKey, remotePeer, ephemeralKeys);
if (false) {
let IKhandshake;
try {
IKhandshake = await this.performIKHandshake(params);
return IKhandshake;
} catch (e) {
// XX fallback
const ephemeralKeys = IKhandshake.getRemoteEphemeralKeys();
return await this.performXXFallbackHandshake(params, ephemeralKeys, e.initialMsg);
}
} else {
return await this.performXXHandshake(connection, isInitiator, libp2pPublicKey, remotePeer);
return await this.performXXHandshake(params);
}
}
private async performXXHandshake(
connection: WrappedConnection,
isInitiator: boolean,
libp2pPublicKey: bytes,
remotePeer: PeerId,
ephemeralKeys?: KeyPair,
): Promise<HandshakeInterface> {
const handshake = new XX(isInitiator, this.privateKey, libp2pPublicKey, this.prologue, this.staticKeys, connection, remotePeer, ephemeralKeys);
private async performXXFallbackHandshake(
params: HandshakeParams,
ephemeralKeys: KeyPair,
initialMsg: bytes,
): Promise<XXFallback> {
const { isInitiator, libp2pPublicKey, remotePeer, connection } = params;
const handshake =
new XXFallback(isInitiator, this.privateKey, libp2pPublicKey, this.prologue, this.staticKeys, connection, remotePeer, ephemeralKeys, initialMsg);
try {
await handshake.propose();
await handshake.exchange();
await handshake.finish(this.earlyData);
} catch (e) {
throw new Error(`Error occurred during handshake: ${e.message}`);
throw new Error(`Error occurred during XX Fallback handshake: ${e.message}`);
}
return handshake;
}
private async performXXHandshake(
params: HandshakeParams,
): Promise<XX> {
const { isInitiator, libp2pPublicKey, remotePeer, connection } = params;
const handshake = new XX(isInitiator, this.privateKey, libp2pPublicKey, this.prologue, this.staticKeys, connection, remotePeer);
try {
await handshake.propose();
await handshake.exchange();
await handshake.finish(this.earlyData);
} catch (e) {
throw new Error(`Error occurred during XX handshake: ${e.message}`);
}
return handshake;
}
private async performIKHandshake(
params: HandshakeParams,
): Promise<IK> {
const { isInitiator, libp2pPublicKey, remotePeer, connection } = params;
const handshake = new IK(params.isInitiator, this.privateKey, params.libp2pPublicKey, this.prologue, this.staticKeys, params.connection, remotePeer);
// TODO
return handshake;
}
private async createSecureConnection(
connection: WrappedConnection,
handshake: HandshakeInterface,

View File

@@ -61,7 +61,7 @@ describe("Noise", () => {
const xx = new XXHandshake();
const { privateKey: libp2pPrivKey, publicKey: libp2pPubKey } = getKeyPairFromPeerId(remotePeer);
const handshake = new Handshake(false, libp2pPrivKey, libp2pPubKey, prologue, staticKeys, wrapped, localPeer, undefined, xx);
const handshake = new Handshake(false, libp2pPrivKey, libp2pPubKey, prologue, staticKeys, wrapped, localPeer, xx);
let receivedMessageBuffer = decodeMessageBuffer((await wrapped.readLP()).slice());
// The first handshake message contains the initiator's ephemeral public key