2020-06-19 12:49:40 +02:00
|
|
|
import HKDF from 'bcrypto/lib/hkdf'
|
|
|
|
import x25519 from 'bcrypto/lib/js/x25519'
|
|
|
|
import SHA256 from 'bcrypto/lib/js/sha256'
|
|
|
|
import { Buffer } from 'buffer'
|
|
|
|
import PeerId from 'peer-id'
|
|
|
|
import { keys } from 'libp2p-crypto'
|
|
|
|
import { KeyPair } from './@types/libp2p'
|
|
|
|
import { bytes, bytes32 } from './@types/basic'
|
|
|
|
import { Hkdf, INoisePayload } from './@types/handshake'
|
|
|
|
import { pb } from './proto/payload'
|
2020-08-11 11:41:10 +01:00
|
|
|
import uint8ArrayEquals from 'uint8arrays/equals'
|
2020-06-19 12:49:40 +02:00
|
|
|
|
|
|
|
const NoiseHandshakePayloadProto = pb.NoiseHandshakePayload
|
|
|
|
|
|
|
|
export function generateKeypair (): KeyPair {
|
|
|
|
const privateKey = x25519.privateKeyGenerate()
|
|
|
|
const publicKey = x25519.publicKeyCreate(privateKey)
|
2019-11-11 21:58:04 +01:00
|
|
|
|
|
|
|
return {
|
|
|
|
publicKey,
|
2020-06-19 12:49:40 +02:00
|
|
|
privateKey
|
2019-11-11 21:58:04 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-19 12:49:40 +02:00
|
|
|
export async function getPayload (
|
2020-01-07 16:59:41 +01:00
|
|
|
localPeer: PeerId,
|
|
|
|
staticPublicKey: bytes,
|
2020-06-19 12:49:40 +02:00
|
|
|
earlyData?: bytes
|
2020-01-07 16:59:41 +01:00
|
|
|
): Promise<bytes> {
|
2020-06-19 12:49:40 +02:00
|
|
|
const signedPayload = await signPayload(localPeer, getHandshakePayload(staticPublicKey))
|
|
|
|
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-06-19 12:49:40 +02:00
|
|
|
)
|
2020-01-07 16:59:41 +01:00
|
|
|
}
|
|
|
|
|
2020-06-19 13:06:31 +02:00
|
|
|
export function createHandshakePayload (
|
2020-08-11 11:41:10 +01:00
|
|
|
libp2pPublicKey: Uint8Array,
|
|
|
|
signedPayload: Uint8Array,
|
|
|
|
earlyData?: Uint8Array
|
2020-06-19 13:06:31 +02:00
|
|
|
): bytes {
|
2020-02-17 09:18:44 +01:00
|
|
|
const payloadInit = NoiseHandshakePayloadProto.create({
|
2020-08-11 11:41:10 +01:00
|
|
|
identityKey: Buffer.from(libp2pPublicKey),
|
2020-02-05 22:10:51 +01:00
|
|
|
identitySig: signedPayload,
|
2020-06-19 12:49:40 +02:00
|
|
|
data: earlyData || null
|
|
|
|
})
|
2019-11-20 21:38:14 +01:00
|
|
|
|
2020-06-19 12:49:40 +02:00
|
|
|
return Buffer.from(NoiseHandshakePayloadProto.encode(payloadInit).finish())
|
2019-11-20 21:38:14 +01:00
|
|
|
}
|
|
|
|
|
2020-06-19 12:49:40 +02:00
|
|
|
export async function signPayload (peerId: PeerId, payload: bytes): Promise<bytes> {
|
2020-08-11 11:41:10 +01:00
|
|
|
return Buffer.from(await peerId.privKey.sign(payload))
|
2019-11-11 21:58:04 +01:00
|
|
|
}
|
2019-11-20 21:38:14 +01:00
|
|
|
|
2020-06-19 12:49:40 +02:00
|
|
|
export async function getPeerIdFromPayload (payload: pb.INoiseHandshakePayload): Promise<PeerId> {
|
|
|
|
return await PeerId.createFromPubKey(Buffer.from(payload.identityKey as Uint8Array))
|
2020-02-07 20:21:27 +01:00
|
|
|
}
|
|
|
|
|
2020-06-19 13:06:31 +02:00
|
|
|
export function decodePayload (payload: bytes|Uint8Array): pb.INoiseHandshakePayload {
|
2020-02-17 09:18:44 +01:00
|
|
|
return NoiseHandshakePayloadProto.toObject(
|
2020-03-11 09:43:40 +01:00
|
|
|
NoiseHandshakePayloadProto.decode(Buffer.from(payload))
|
2020-06-19 12:49:40 +02:00
|
|
|
) as INoisePayload
|
2020-02-07 20:21:27 +01:00
|
|
|
}
|
|
|
|
|
2020-06-19 12:49:40 +02:00
|
|
|
export function getHandshakePayload (publicKey: bytes): bytes {
|
|
|
|
return Buffer.concat([Buffer.from('noise-libp2p-static-key:'), publicKey])
|
2020-02-14 10:10:42 +01:00
|
|
|
}
|
2019-11-28 17:32:46 +01:00
|
|
|
|
2020-08-11 11:41:10 +01:00
|
|
|
async function isValidPeerId (peerId: Uint8Array, publicKeyProtobuf: bytes) {
|
2020-06-19 12:49:40 +02:00
|
|
|
const generatedPeerId = await PeerId.createFromPubKey(publicKeyProtobuf)
|
2020-08-11 11:41:10 +01:00
|
|
|
return uint8ArrayEquals(generatedPeerId.id, peerId)
|
2019-12-02 12:53:00 +01:00
|
|
|
}
|
|
|
|
|
2020-02-08 12:23:35 +01:00
|
|
|
/**
|
2020-02-10 13:51:32 +01:00
|
|
|
* Verifies signed payload, throws on any irregularities.
|
2021-01-25 11:30:58 +01:00
|
|
|
*
|
2020-02-08 12:23:35 +01:00
|
|
|
* @param {bytes} noiseStaticKey - owner's noise static key
|
2020-02-10 13:51:32 +01:00
|
|
|
* @param {bytes} payload - decoded payload
|
|
|
|
* @param {PeerId} remotePeer - owner's libp2p peer ID
|
2020-02-08 12:23:35 +01:00
|
|
|
* @returns {Promise<PeerId>} - peer ID of payload owner
|
|
|
|
*/
|
2020-06-19 12:49:40 +02:00
|
|
|
export async function verifySignedPayload (
|
2020-02-08 12:23:35 +01:00
|
|
|
noiseStaticKey: bytes,
|
2020-02-17 09:18:44 +01:00
|
|
|
payload: pb.INoiseHandshakePayload,
|
2020-02-10 13:51:32 +01:00
|
|
|
remotePeer: PeerId
|
2020-02-08 12:23:35 +01:00
|
|
|
): Promise<PeerId> {
|
2020-06-19 12:49:40 +02:00
|
|
|
const identityKey = Buffer.from(payload.identityKey as Uint8Array)
|
2020-03-11 09:43:40 +01:00
|
|
|
if (!(await isValidPeerId(remotePeer.id, identityKey))) {
|
2020-06-19 12:49:40 +02:00
|
|
|
throw new Error("Peer ID doesn't match libp2p public key.")
|
2019-12-03 13:39:33 +01:00
|
|
|
}
|
2020-06-19 12:49:40 +02:00
|
|
|
const generatedPayload = getHandshakePayload(noiseStaticKey)
|
2020-01-07 17:08:08 +01:00
|
|
|
// Unmarshaling from PublicKey protobuf
|
2020-06-19 12:49:40 +02:00
|
|
|
const publicKey = keys.unmarshalPublicKey(identityKey)
|
2020-04-22 19:31:24 +02:00
|
|
|
if (!payload.identitySig || !publicKey.verify(generatedPayload, Buffer.from(payload.identitySig))) {
|
2020-06-19 12:49:40 +02:00
|
|
|
throw new Error("Static key doesn't match to peer that signed payload!")
|
2019-12-02 12:53:00 +01:00
|
|
|
}
|
2020-06-19 12:49:40 +02:00
|
|
|
return PeerId.createFromPubKey(identityKey)
|
2019-12-02 12:53:00 +01:00
|
|
|
}
|
2019-12-24 21:15:38 +01:00
|
|
|
|
2020-06-19 12:49:40 +02: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)
|
2019-12-24 21:15:38 +01:00
|
|
|
|
2020-06-19 12:49:40 +02:00
|
|
|
const k1 = okm.slice(0, 32)
|
|
|
|
const k2 = okm.slice(32, 64)
|
|
|
|
const k3 = okm.slice(64, 96)
|
2019-12-24 21:15:38 +01:00
|
|
|
|
2020-06-19 12:49:40 +02:00
|
|
|
return [k1, k2, k3]
|
2019-12-24 21:15:38 +01:00
|
|
|
}
|
2019-12-29 18:23:43 +01:00
|
|
|
|
2020-06-19 12:49:40 +02:00
|
|
|
export function isValidPublicKey (pk: bytes): boolean {
|
|
|
|
return x25519.publicKeyVerify(pk.slice(0, 32))
|
2019-12-29 18:23:43 +01:00
|
|
|
}
|