2020-01-07 17:01:52 +01:00
|
|
|
import { x25519, HKDF, SHA256 } from 'bcrypto';
|
2019-11-20 21:38:14 +01:00
|
|
|
import protobuf from "protobufjs";
|
2019-11-22 12:52:59 +01:00
|
|
|
import { Buffer } from "buffer";
|
2019-12-02 12:53:00 +01:00
|
|
|
import PeerId from "peer-id";
|
2019-12-03 13:39:33 +01:00
|
|
|
import * as crypto from 'libp2p-crypto';
|
2019-11-20 13:23:36 +01:00
|
|
|
import { KeyPair } from "./@types/libp2p";
|
2019-12-24 21:15:38 +01:00
|
|
|
import {bytes, bytes32} from "./@types/basic";
|
|
|
|
import {Hkdf} from "./@types/handshake";
|
2020-02-06 09:51:39 +01:00
|
|
|
import payloadProto from "./proto/payload.json";
|
2019-11-20 21:38:14 +01:00
|
|
|
|
|
|
|
export async function loadPayloadProto () {
|
2020-02-06 09:51:39 +01:00
|
|
|
const payloadProtoBuf = await protobuf.Root.fromJSON(payloadProto);
|
|
|
|
return payloadProtoBuf.lookupType("NoiseHandshakePayload");
|
2019-11-20 21:38:14 +01:00
|
|
|
}
|
2019-11-11 21:58:04 +01:00
|
|
|
|
2019-11-28 17:53:27 +01:00
|
|
|
export function generateKeypair(): KeyPair {
|
2019-11-11 21:58:04 +01:00
|
|
|
const privateKey = x25519.privateKeyGenerate();
|
|
|
|
const publicKey = x25519.publicKeyCreate(privateKey);
|
|
|
|
|
|
|
|
return {
|
|
|
|
publicKey,
|
|
|
|
privateKey,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-07 16:59:41 +01:00
|
|
|
export async function getPayload(
|
|
|
|
localPeer: PeerId,
|
|
|
|
staticPublicKey: bytes,
|
|
|
|
earlyData?: bytes,
|
|
|
|
): Promise<bytes> {
|
|
|
|
const signedPayload = await signPayload(localPeer, getHandshakePayload(staticPublicKey));
|
2020-02-05 22:16:48 +01:00
|
|
|
const earlyDataPayload = earlyData || Buffer.alloc(0);
|
2020-01-07 16:59:41 +01:00
|
|
|
|
|
|
|
return await createHandshakePayload(
|
|
|
|
localPeer.marshalPubKey(),
|
|
|
|
signedPayload,
|
2020-02-05 22:16:48 +01:00
|
|
|
earlyDataPayload
|
2020-01-07 16:59:41 +01:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-11-20 21:38:14 +01:00
|
|
|
export async function createHandshakePayload(
|
2019-11-21 13:38:39 +01:00
|
|
|
libp2pPublicKey: bytes,
|
2019-11-20 21:38:14 +01:00
|
|
|
signedPayload: bytes,
|
2020-02-05 22:16:48 +01:00
|
|
|
earlyData?: bytes,
|
2019-11-28 17:53:27 +01:00
|
|
|
): Promise<bytes> {
|
2019-11-20 21:38:14 +01:00
|
|
|
const NoiseHandshakePayload = await loadPayloadProto();
|
2020-02-05 22:16:48 +01:00
|
|
|
const earlyDataPayload = earlyData ?
|
2019-11-28 17:32:46 +01:00
|
|
|
{
|
2020-02-05 22:16:48 +01:00
|
|
|
data: earlyData,
|
2019-11-28 17:32:46 +01:00
|
|
|
} : {};
|
|
|
|
|
2019-11-20 21:38:14 +01:00
|
|
|
const payloadInit = NoiseHandshakePayload.create({
|
2020-02-05 22:10:51 +01:00
|
|
|
identityKey: libp2pPublicKey,
|
|
|
|
identitySig: signedPayload,
|
2019-11-28 17:32:46 +01:00
|
|
|
...earlyDataPayload,
|
2019-11-20 21:38:14 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
return Buffer.from(NoiseHandshakePayload.encode(payloadInit).finish());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-01-07 16:59:41 +01:00
|
|
|
export async function signPayload(peerId: PeerId, payload: bytes): Promise<bytes> {
|
|
|
|
return peerId.privKey.sign(payload);
|
2019-11-11 21:58:04 +01:00
|
|
|
}
|
2019-11-20 21:38:14 +01:00
|
|
|
|
2020-02-07 20:21:27 +01:00
|
|
|
export async function getPeerIdFromPayload(payload: bytes): Promise<PeerId> {
|
|
|
|
const decodedPayload = await decodePayload(payload);
|
|
|
|
return await PeerId.createFromPubKey(Buffer.from(decodedPayload.identityKey));
|
|
|
|
}
|
|
|
|
|
|
|
|
async function decodePayload(payload: bytes){
|
|
|
|
const NoiseHandshakePayload = await loadPayloadProto();
|
|
|
|
return NoiseHandshakePayload.toObject(
|
|
|
|
NoiseHandshakePayload.decode(payload)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2019-11-28 17:32:46 +01:00
|
|
|
export const getHandshakePayload = (publicKey: bytes ) => Buffer.concat([Buffer.from("noise-libp2p-static-key:"), publicKey]);
|
|
|
|
|
2019-12-03 13:39:33 +01:00
|
|
|
async function isValidPeerId(peerId: bytes, publicKeyProtobuf: bytes) {
|
|
|
|
const generatedPeerId = await PeerId.createFromPubKey(publicKeyProtobuf);
|
|
|
|
return generatedPeerId.id.equals(peerId);
|
2019-12-02 12:53:00 +01:00
|
|
|
}
|
|
|
|
|
2019-12-03 13:39:33 +01:00
|
|
|
export async function verifySignedPayload(noiseStaticKey: bytes, plaintext: bytes, peerId: bytes) {
|
2020-01-15 17:27:32 +01:00
|
|
|
let receivedPayload;
|
|
|
|
try {
|
|
|
|
const NoiseHandshakePayload = await loadPayloadProto();
|
2020-02-06 09:51:39 +01:00
|
|
|
receivedPayload = NoiseHandshakePayload.toObject(
|
|
|
|
NoiseHandshakePayload.decode(plaintext)
|
|
|
|
);
|
|
|
|
//temporary fix until protobufsjs conversion options starts working
|
|
|
|
//by default it ends up as Uint8Array
|
2020-02-06 09:57:10 +01:00
|
|
|
receivedPayload.identityKey = Buffer.from(receivedPayload.identityKey);
|
|
|
|
receivedPayload.identitySig = Buffer.from(receivedPayload.identitySig);
|
2020-01-15 17:27:32 +01:00
|
|
|
} catch (e) {
|
2020-02-06 09:51:39 +01:00
|
|
|
throw new Error("Failed to decode received payload. Reason: " + e.message);
|
2020-01-15 17:27:32 +01:00
|
|
|
}
|
2019-12-02 12:53:00 +01:00
|
|
|
|
2020-02-05 22:10:51 +01:00
|
|
|
if (!(await isValidPeerId(peerId, receivedPayload.identityKey)) ) {
|
2019-12-03 13:39:33 +01:00
|
|
|
throw new Error("Peer ID doesn't match libp2p public key.");
|
|
|
|
}
|
|
|
|
|
|
|
|
const generatedPayload = getHandshakePayload(noiseStaticKey);
|
2020-01-07 17:08:08 +01:00
|
|
|
|
|
|
|
// Unmarshaling from PublicKey protobuf
|
2020-02-05 22:10:51 +01:00
|
|
|
const publicKey = crypto.keys.unmarshalPublicKey(receivedPayload.identityKey);
|
|
|
|
if (!publicKey.verify(generatedPayload, receivedPayload.identitySig)) {
|
2019-12-02 15:24:49 +01:00
|
|
|
throw new Error("Static key doesn't match to peer that signed payload!");
|
2019-12-02 12:53:00 +01:00
|
|
|
}
|
|
|
|
}
|
2019-12-24 21:15:38 +01:00
|
|
|
|
|
|
|
export function getHkdf(ck: bytes32, ikm: bytes): Hkdf {
|
|
|
|
const info = Buffer.alloc(0);
|
|
|
|
const prk = HKDF.extract(SHA256, ikm, ck);
|
|
|
|
const okm = HKDF.expand(SHA256, prk, info, 96);
|
|
|
|
|
|
|
|
const k1 = okm.slice(0, 32);
|
|
|
|
const k2 = okm.slice(32, 64);
|
|
|
|
const k3 = okm.slice(64, 96);
|
|
|
|
|
|
|
|
return [ k1, k2, k3 ];
|
|
|
|
}
|
2019-12-29 18:23:43 +01:00
|
|
|
|
|
|
|
export function isValidPublicKey(pk: bytes): boolean {
|
|
|
|
return x25519.publicKeyVerify(pk);
|
|
|
|
}
|