Update handling keys, refactoring

This commit is contained in:
morrigan
2019-11-28 17:32:46 +01:00
parent d03f4974ba
commit a8ff05cdf1
8 changed files with 194 additions and 151 deletions

View File

@ -9,10 +9,10 @@ export interface KeyPair {
export type PeerId = {
id: bytes,
privKey: {
bytes: bytes,
marshal(): bytes,
},
pubKey: {
bytes: bytes,
marshal(): bytes,
},
};

View File

@ -8,93 +8,98 @@ import {
decodeMessageBuffer,
encodeMessageBuffer,
getHandshakePayload,
logger,
logger, signEarlyDataPayload,
signPayload,
} from "./utils";
import { WrappedConnection } from "./noise";
type handshakeType = "XX";
export class Handshake {
public isInitiator: boolean;
public session: NoiseSession;
private type: handshakeType;
private remotePublicKey: bytes;
private remotePublicKey?: bytes;
private libp2pPrivateKey: bytes;
private libp2pPublicKey: bytes;
private prologue: bytes32;
private staticKeys: KeyPair;
private connection: WrappedConnection;
private xx: XXHandshake;
constructor(
type: handshakeType,
isInitiator: boolean,
remotePublicKey: bytes,
libp2pPrivateKey: bytes,
libp2pPublicKey: bytes,
prologue: bytes32,
staticKeys: KeyPair,
connection: WrappedConnection,
handshake?: XXHandshake,
) {
this.type = type;
this.isInitiator = isInitiator;
this.remotePublicKey = remotePublicKey;
this.libp2pPrivateKey = libp2pPrivateKey;
this.libp2pPublicKey = libp2pPublicKey;
this.prologue = prologue;
this.staticKeys = staticKeys;
this.connection = connection;
this.xx = handshake || new XXHandshake();
this.session = this.xx.initSession(this.isInitiator, this.prologue, this.staticKeys);
}
// stage 0
async propose(earlyData?: bytes) : Promise<NoiseSession> {
const ns = await this.xx.initSession(this.isInitiator, this.prologue, this.staticKeys, this.remotePublicKey);
async propose(earlyData?: bytes) : Promise<void> {
if (this.isInitiator) {
const signedPayload = signPayload(this.staticKeys.privateKey, getHandshakePayload(this.staticKeys.publicKey));
const signedPayload = signPayload(this.libp2pPrivateKey, getHandshakePayload(this.staticKeys.publicKey));
const signedEarlyDataPayload = signEarlyDataPayload(this.libp2pPrivateKey, earlyData || Buffer.alloc(0));
const handshakePayload = await createHandshakePayload(
this.staticKeys.publicKey,
this.libp2pPublicKey,
this.libp2pPrivateKey,
signedPayload,
earlyData,
this.staticKeys.privateKey
signedEarlyDataPayload
);
const messageBuffer = await this.xx.sendMessage(ns, handshakePayload);
const messageBuffer = await this.xx.sendMessage(this.session, handshakePayload);
this.connection.writeLP(encodeMessageBuffer(messageBuffer));
logger("Stage 0 - Initiator finished proposing");
logger("Stage 0 - Initiator finished proposing, sent signed NoiseHandshake payload.");
} else {
const receivedMessageBuffer = (await this.connection.readLP()).slice();
const plaintext = await this.xx.recvMessage(ns, decodeMessageBuffer(receivedMessageBuffer));
logger("Stage 0 - Responder received proposed message.");
}
const receivedMessageBuffer = decodeMessageBuffer((await this.connection.readLP()).slice());
this.remotePublicKey = receivedMessageBuffer.ne;
return ns;
const plaintext = await this.xx.recvMessage(this.session, receivedMessageBuffer);
logger("Stage 0 - Responder received proposed message and remote static public key.");
}
}
// stage 1
async exchange(session: NoiseSession) : Promise<void> {
async exchange() : Promise<void> {
if (this.isInitiator) {
const receivedMessageBuffer = (await this.connection.readLP()).slice();
const plaintext = await this.xx.recvMessage(session, decodeMessageBuffer(receivedMessageBuffer));
const receivedMessageBuffer = decodeMessageBuffer((await this.connection.readLP()).slice());
this.remotePublicKey = receivedMessageBuffer.ne;
const plaintext = await this.xx.recvMessage(this.session, receivedMessageBuffer);
logger('Stage 1 - Initiator received the message.');
} else {
// create payload as responder
const signedPayload = signPayload(this.staticKeys.privateKey, getHandshakePayload(this.staticKeys.publicKey));
const handshakePayload = await createHandshakePayload(this.remotePublicKey, signedPayload);
const signedPayload = signPayload(this.libp2pPrivateKey, getHandshakePayload(this.staticKeys.publicKey));
const handshakePayload = await createHandshakePayload(
this.libp2pPublicKey,
this.libp2pPrivateKey,
signedPayload,
);
const messageBuffer = await this.xx.sendMessage(session, handshakePayload);
const messageBuffer = await this.xx.sendMessage(this.session, handshakePayload);
this.connection.writeLP(encodeMessageBuffer(messageBuffer));
logger('Stage 1 - Responder sent the message.')
}
}
// stage 2
async finish(session: NoiseSession) : Promise<void> {
async finish() : Promise<void> {
if (this.isInitiator) {
const messageBuffer = await this.xx.sendMessage(session, Buffer.alloc(0));
const messageBuffer = await this.xx.sendMessage(this.session, Buffer.alloc(0));
this.connection.writeLP(encodeMessageBuffer(messageBuffer));
logger('Stage 2 - Initiator sent message.');
} else {
const receivedMessageBuffer = (await this.connection.readLP()).slice();
const plaintext = await this.xx.recvMessage(session, decodeMessageBuffer(receivedMessageBuffer));
const plaintext = await this.xx.recvMessage(this.session, decodeMessageBuffer(receivedMessageBuffer));
logger('Stage 2 - Responder received the message, finished handshake.')
}
}

View File

@ -12,6 +12,7 @@ import { decryptStream, encryptStream } from "./crypto";
import { bytes } from "./@types/basic";
import { NoiseConnection, PeerId, KeyPair, SecureOutbound } from "./@types/libp2p";
import { Duplex } from "./@types/it-pair";
import {NoiseSession} from "./xx";
export type WrappedConnection = ReturnType<typeof Wrap>;
@ -27,7 +28,7 @@ export class Noise implements NoiseConnection {
this.earlyData = earlyData || Buffer.alloc(0);
if (staticNoiseKey) {
const publicKey = x25519.publicKeyCreate(staticNoiseKey);
const publicKey = x25519.publicKeyCreate(staticNoiseKey); // TODO: verify this
this.staticKeys = {
privateKey: staticNoiseKey,
publicKey,
@ -46,8 +47,9 @@ export class Noise implements NoiseConnection {
*/
public async secureOutbound(localPeer: PeerId, connection: any, remotePeer: PeerId) : Promise<SecureOutbound> {
const wrappedConnection = Wrap(connection);
const remotePublicKey = remotePeer.pubKey.bytes;
const conn = await this.createSecureConnection(wrappedConnection, remotePublicKey, true);
const libp2pPublicKey = localPeer.pubKey.marshal();
const handshake = await this.performHandshake(wrappedConnection, true, libp2pPublicKey);
const conn = await this.createSecureConnection(wrappedConnection, handshake);
return {
conn,
@ -64,8 +66,9 @@ export class Noise implements NoiseConnection {
*/
public async secureInbound(localPeer: PeerId, connection: any, remotePeer: PeerId) : Promise<SecureOutbound> {
const wrappedConnection = Wrap(connection);
const remotePublicKey = remotePeer.pubKey.bytes;
const conn = await this.createSecureConnection(wrappedConnection, remotePublicKey, false);
const libp2pPublicKey = localPeer.pubKey.marshal();
const handshake = await this.performHandshake(wrappedConnection, false, libp2pPublicKey);
const conn = await this.createSecureConnection(wrappedConnection, handshake);
return {
conn,
@ -73,19 +76,25 @@ export class Noise implements NoiseConnection {
};
}
private async performHandshake(
connection: WrappedConnection,
isInitiator: boolean,
libp2pPublicKey: bytes,
): Promise<Handshake> {
const prologue = Buffer.from(this.protocol);
const handshake = new Handshake(isInitiator, this.privateKey, libp2pPublicKey, prologue, this.staticKeys, connection);
await handshake.propose(this.earlyData);
await handshake.exchange();
await handshake.finish();
return handshake;
}
private async createSecureConnection(
connection: WrappedConnection,
remotePublicKey: bytes,
isInitiator: boolean,
handshake: Handshake,
) : Promise<Duplex> {
// Perform handshake
const prologue = Buffer.from(this.protocol);
const handshake = new Handshake('XX', isInitiator, remotePublicKey, prologue, this.staticKeys, connection);
const session = await handshake.propose(this.earlyData);
await handshake.exchange(session);
await handshake.finish(session);
// Create encryption box/unbox wrapper
const [secure, user] = DuplexPair();
const network = connection.unwrap();
@ -93,12 +102,12 @@ export class Noise implements NoiseConnection {
pipe(
secure, // write to wrapper
ensureBuffer, // ensure any type of data is converted to buffer
encryptStream(handshake, session), // data is encrypted
encryptStream(handshake, handshake.session), // data is encrypted
lp.encode({ lengthEncoder: int16BEEncode }), // prefix with message length
network, // send to the remote peer
lp.decode({ lengthDecoder: int16BEDecode }), // read message length prefix
ensureBuffer, // ensure any type of data is converted to buffer
decryptStream(handshake, session), // decrypt the incoming data
decryptStream(handshake, handshake.session), // decrypt the incoming data
secure // pipe to the wrapper
);

View File

@ -26,42 +26,50 @@ export function generateKeypair() : KeyPair {
export async function createHandshakePayload(
libp2pPublicKey: bytes,
libp2pPrivateKey: bytes,
signedPayload: bytes,
earlyData?: bytes,
libp2pPrivateKey?: bytes,
signedEarlyData?: EarlyDataPayload,
) : Promise<bytes> {
const NoiseHandshakePayload = await loadPayloadProto();
const earlyDataPayload = signedEarlyData ?
{
libp2pData: signedEarlyData.libp2pData,
libp2pDataSignature: signedEarlyData.libp2pDataSignature,
} : {};
const payloadInit = NoiseHandshakePayload.create({
libp2pKey: libp2pPublicKey,
noiseStaticKeySignature: signedPayload,
...resolveEarlyDataPayload(libp2pPrivateKey, earlyData),
...earlyDataPayload,
});
return Buffer.from(NoiseHandshakePayload.encode(payloadInit).finish());
}
export function signPayload(privateKey: bytes, payload: bytes) {
return ed25519.sign(payload, privateKey);
export function signPayload(libp2pPrivateKey: bytes, payload: bytes) {
return ed25519.sign(payload, libp2pPrivateKey);
}
export const getHandshakePayload = (publicKey: bytes ) => Buffer.concat([Buffer.from("noise-libp2p-static-key:"), publicKey]);
export const getEarlyDataPayload = (earlyData: bytes) => Buffer.concat([Buffer.from("noise-libp2p-early-data:"), earlyData]);
function resolveEarlyDataPayload(privateKey?: bytes, earlyData?: bytes) : Object {
if (!earlyData || !privateKey) {
return {};
}
type EarlyDataPayload = {
libp2pData: bytes;
libp2pDataSignature: bytes;
}
export function signEarlyDataPayload(libp2pPrivateKey: bytes, earlyData: bytes) : EarlyDataPayload {
const payload = getEarlyDataPayload(earlyData);
const signedPayload = signPayload(privateKey, payload);
const signedPayload = signPayload(libp2pPrivateKey, payload);
return {
libp2pData: payload,
libp2pDataSignature: signedPayload,
}
}
export const getHandshakePayload = (publicKey: bytes ) => Buffer.concat([Buffer.from("noise-libp2p-static-key:"), publicKey]);
export const getEarlyDataPayload = (earlyData: bytes) => Buffer.concat([Buffer.from("noise-libp2p-early-data:"), earlyData]);
export function encodeMessageBuffer(message: MessageBuffer) : bytes {
return Buffer.concat([message.ne, message.ns, message.ciphertext]);
}
@ -69,8 +77,8 @@ export function encodeMessageBuffer(message: MessageBuffer) : bytes {
export function decodeMessageBuffer(message: bytes) : MessageBuffer {
return {
ne: message.slice(0, 32),
ns: message.slice(32, 80),
ciphertext: message.slice(80, message.length),
ns: message.slice(32, 64),
ciphertext: message.slice(64, message.length),
}
}
@ -82,5 +90,6 @@ int16BEEncode.bytes = 2;
export const int16BEDecode = data => {
if (data.length < 2) throw RangeError('Could not decode int16BE');
return data.readInt16BE(0);}
return data.readInt16BE(0);
};
int16BEDecode.bytes = 2;

View File

@ -50,18 +50,18 @@ export class XXHandshake {
return Buffer.alloc(32);
}
private async initializeInitiator(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): Promise<HandshakeState> {
private initializeInitiator(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = "Noise_XX_25519_ChaChaPoly_SHA256";
const ss = await this.initializeSymmetric(name);
const ss = this.initializeSymmetric(name);
this.mixHash(ss, prologue);
const re = Buffer.alloc(32);
return { ss, s, rs, psk, re };
}
private async initializeResponder(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): Promise<HandshakeState> {
private initializeResponder(prologue: bytes32, s: KeyPair, rs: bytes32, psk: bytes32): HandshakeState {
const name = "Noise_XX_25519_ChaChaPoly_SHA256";
const ss = await this.initializeSymmetric(name);
const ss = this.initializeSymmetric(name);
this.mixHash(ss, prologue);
const re = Buffer.alloc(32);
@ -145,9 +145,9 @@ export class XXHandshake {
// Symmetric state related
private async initializeSymmetric(protocolName: string): Promise<SymmetricState> {
private initializeSymmetric(protocolName: string): SymmetricState {
const protocolNameBytes: bytes = Buffer.from(protocolName, 'utf-8');
const h = await this.hashProtocolName(protocolNameBytes);
const h = this.hashProtocolName(protocolNameBytes);
const ck = h;
const key = this.createEmptyKey();
@ -162,7 +162,7 @@ export class XXHandshake {
ss.ck = ck;
}
private async hashProtocolName(protocolName: bytes): Promise<bytes32> {
private hashProtocolName(protocolName: bytes): bytes32 {
if (protocolName.length <= 32) {
const h = Buffer.alloc(32);
protocolName.copy(h);
@ -319,14 +319,15 @@ export class XXHandshake {
return this.decryptWithAd(cs, Buffer.alloc(0), message.ciphertext);
}
public async initSession(initiator: boolean, prologue: bytes32, s: KeyPair, rs: bytes32): Promise<NoiseSession> {
public initSession(initiator: boolean, prologue: bytes32, s: KeyPair): NoiseSession {
const psk = this.createEmptyKey();
const rs = Buffer.alloc(32); // no static key yet
let hs;
if (initiator) {
hs = await this.initializeInitiator(prologue, s, rs, psk);
hs = this.initializeInitiator(prologue, s, rs, psk);
} else {
hs = await this.initializeResponder(prologue, s, rs, psk);
hs = this.initializeResponder(prologue, s, rs, psk);
}
return {