GitLab will be temporarily unavailable on 27/09/22 from 13:00 - 14:00 whilst we carry out essential maintenance

Commit 8e66869c authored by Jesse Wright's avatar Jesse Wright
Browse files

cw

parent c81db322
.devcontainer/
test/
# COM2022
# Networking
\ No newline at end of file
node_modules/
\ No newline at end of file
{
"version": "0.2.0",
"configurations": [
{
"name": "ts-node",
"type": "node",
"request": "launch",
"args": [
"${relativeFile}"
],
"env": {
"TS_NODE_COMPILER_OPTIONS":"{\"noUnusedLocals\":false}"
},
"runtimeArgs": [
"--preserve-symlinks",
"-r",
"ts-node/register"
],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"console": "internalConsole",
// "outputCapture": "std"
}
]
}
\ No newline at end of file
{
"name": "cw",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"devDependencies": {
"@types/koa-compose": "^3.2.5",
"@types/node": "^17.0.25",
"ts-node": "^10.7.0",
"typescript": "^4.6.3"
},
"dependencies": {
"@noble/secp256k1": "^1.5.5",
"async-retry": "^1.3.3",
"eciesjs": "^0.3.14",
"koa-compose": "^4.1.0"
}
}
cw/recv.png

28.1 KB

import { Socket } from "net";
import { createConnection } from "net"
import { waitForDataUB, writeUB, destroyUB, setMessageID, waitForEncDataUB, writeEncUB, msgListenerUB, } from "./utils";
import { genIntro, parseIntro, receiveCapabilities, receiveEncCheck, receivePubKey, receiveSymKey, sendCapabilities, sendEncCheck, sendMsg, sendMsgReq, sendPubKey } from "./messages";
import { getPublicKey, utils } from "@noble/secp256k1"
import { createCipheriv, createDecipheriv } from "crypto";
import { createReadStream } from "fs";
export async function connect(): Promise<Socket> {
const sock = createConnection({ host: "localhost", port: 30522 })
return await new Promise(r => {
sock.on("connect", () => r(sock))
})
}
export type Context = {
conn: Socket
} & Record<any, any>
export const capabilities = [
"text:0.0.1",
"test:0.0.1",
"data:0.0.1"
]
async function main() {
const conn = await connect();
conn.setKeepAlive()
conn.setNoDelay(true)
conn.on("error", (err) => {
console.error(err)
})
conn.on("end", () => {
throw new Error(`Stream closed`)
})
// @ts-ignore
conn._writableState.highWaterMark = 1;
// @ts-ignore
conn._readableState.highWaterMark = 1;
const destroy = destroyUB.bind(undefined, conn)
const write = writeUB.bind(undefined, conn)
const waitForData = waitForDataUB.bind(undefined, conn)
await write(genIntro())
if (parseIntro(await waitForData())) destroy("Invalid intro")
console.log("intro done")
await sendCapabilities(write, capabilities)
const serverCaps = await receiveCapabilities(waitForData, destroy)
console.log("server capabilities: ", serverCaps)
// agreement.
await write(setMessageID(Buffer.alloc(2), 1))
const serverPubKey = await receivePubKey(waitForData, destroy)
// console.log("ServerPubKey: ", serverPubKey)
const privKey = Buffer.from(utils.randomPrivateKey())
const pubKey = Buffer.from(getPublicKey(privKey))
// console.log("pubKey: ", pubKey)
await sendPubKey(write, pubKey);
await receiveEncCheck(write, waitForData, destroy, privKey)
await sendEncCheck(write, waitForData, destroy, serverPubKey)
const { key, iv } = await receiveSymKey(waitForData, destroy, write, privKey, serverPubKey)
let cipher = createCipheriv("aes-256-ctr", key, iv)
let decipher = createDecipheriv("aes-256-ctr", key, iv)
const writeEnc = writeEncUB.bind(undefined, conn, cipher)
const waitForEncData = waitForEncDataUB.bind(undefined, conn, decipher)
console.log("done")
// await writeEnc(genIntro())
// console.log("p2pem:", (await waitForEncData()).toString("utf8"))
let state = { s: "waiting" }
// @ts-ignore
const msgListener = msgListenerUB.bind(undefined, writeEnc, waitForEncData, destroy, decipher, state, capabilities)
// now we event-emitter bind to the socket to allow for unprompted comms
conn.on("data", async (d) => {
//@ts-ignore
await msgListener(d)
})
state.s = "transmitting"
const data = "Hello, World!"
const reqStatus = await sendMsgReq(writeEnc, waitForEncData, "data:0.0.1", BigInt(data.length));
if (reqStatus) {
//const bData = Buffer.from(data, "utf8")
const bData = createReadStream("./llama.png")
await sendMsg(writeEnc, waitForEncData, bData)
} else {
console.log("validation failed");
}
console.log("done")
}
main()
import { destroy, getMessageID, setMessageID, SizeChunker, waitForData, write } from "./utils";
import * as Crypto from "crypto";
import { encrypt, decrypt } from 'eciesjs'
import { randomBytes } from "crypto";
import { utils, sign, verify } from "@noble/secp256k1";
import { Readable } from "stream";
export function genIntro(): Buffer {
let t = Buffer.alloc(7)
t.writeIntBE(0, 0, 2)
t.write("P2PEM", 2, "utf8")
return t
}
export function parseIntro(b: Buffer): boolean {
return (b.readIntBE(0, 2) != 0 || b.toString("utf8", 2) !== "P2PEM")
}
export async function sendCapabilities(write: (d: any) => Promise<unknown>, capabilities: string[]) {
//cap primer
const capLength = capabilities.length
let t = setMessageID(Buffer.alloc(4), 5)
t.writeUIntBE(capLength, 2, 2)
await write(t)
for (let i = 0; i < capLength; i++) {
let cap = capabilities[i]
t = setMessageID(Buffer.alloc(2 + 1 + cap.length), 6)
t.writeIntBE(cap.length, 2, 1)
t.write(cap, 3, "utf8")
await write(t);
}
}
export async function receiveCapabilities(waitForData: waitForData, destroy: any): Promise<string[]> {
const capPrimer = await waitForData();
if (getMessageID(capPrimer) != 5) destroy("Invalid cap primer message ID")
const capLength = capPrimer.readUIntBE(2, 2)
if (capLength > 1024 || capLength < 0) destroy("Invalid capability primer")
const clientCaps = []
for (let i = 0; i < capLength; i++) {
let d = await waitForData()
if (getMessageID(d) != 6) destroy("Invalid cap message ID")
const capLength = d.readUIntBE(2, 1)
const recvCap = d.toString("utf8", 3)
if (recvCap.length != capLength) {
destroy(`Received capability wrong length ${capLength} - ${recvCap}`)
}
clientCaps.push(recvCap)
}
return clientCaps
}
export async function sendPubKey(write: write, pubKey: Buffer) {
let t = setMessageID(Buffer.alloc(pubKey.byteLength + 2), 2)
pubKey.copy(t, 2)
await write(t)
}
export async function receivePubKey(waitForData: waitForData, destroy: destroy) {
let data = await waitForData();
if (getMessageID(data) !== 2) destroy("Expected pubKey message")
// const serverPubKey = data.toString("binary", 2)
return data.subarray(2)
}
export async function sendEncCheck(write: write, waitForData: waitForData, destroy: destroy, clientPubKey: Buffer) {
const byteCount = Crypto.randomInt(1_024, (65_536 - 97))
const bytes = Crypto.randomBytes(byteCount)
const message = setMessageID(Buffer.alloc(byteCount + 97 + 2), 3)
const enc = encrypt(clientPubKey, bytes)
enc.copy(message, 2)
await write(message)
const data = await waitForData();
if (getMessageID(data) !== 4) destroy("Expected message ID 4")
if (Buffer.compare(bytes, data.subarray(2)) !== 0) destroy("Encryption ID check buffer content mismatch")
await sendConfirmation(write)
}
export async function receiveEncCheck(write: write, waitForData: waitForData, destroy: destroy, privKey: Buffer) {
let d = await waitForData();
if (getMessageID(d) !== 3) destroy("Expected message ID 3")
const data = decrypt(privKey, d.subarray(2))
let msg = setMessageID(Buffer.alloc(data.length + 2), 4)
data.copy(msg, 2)
await write(msg)
d = await waitForData();
if (getMessageID(d) !== 1) destroy("Expected message ID 1")
}
export async function genAndSendSymKey(write: write, waitForData: waitForData, destroy: destroy, privKey: Buffer, clientPubKey: Buffer) {
const key = randomBytes(32)
const iv = randomBytes(16)
const fullKey = Buffer.alloc(key.length + iv.length)
key.copy(fullKey, 0)
iv.copy(fullKey, 32)
const sig = Buffer.from(await sign(await utils.sha256(fullKey), privKey)) // 70 bytes
const encKey = encrypt(clientPubKey, fullKey) //145 bytes
let t = setMessageID(Buffer.alloc(2 + 2 + encKey.length + 2 + sig.length), 7)
t.writeIntBE(encKey.length, 2, 2)
encKey.copy(t, 4)
t.writeIntBE(sig.length, encKey.length + 4, 2)
sig.copy(t, encKey.length + 6)
console.log({
keyLength: encKey.length, sigLength: sig.length,
kfb: encKey.at(0), klb: encKey.at(-1),
sfb: sig.at(0), slb: sig.at(-1)
})
await write(t)
if (getMessageID(await waitForData()) !== 1) destroy("expected confirmation of key validity")
return { key, iv }
}
export async function receiveSymKey(waitForData: waitForData, destroy: destroy, write: write, privKey: Buffer, serverPubKey: Buffer) {
let d = await waitForData()
if (getMessageID(d) !== 7) destroy("expected ID 7")
const keyLength = d.readIntBE(2, 2)
const encKey = d.subarray(4, keyLength + 4)
const sigLength = d.readIntBE(keyLength + 4, 2)
const sig = d.subarray(keyLength + 6, keyLength + 6 + sigLength)
console.log({
keyLength, sigLength,
kfb: encKey.at(0), klb: encKey.at(-1),
sfb: sig.at(0), slb: sig.at(-1)
})
const fullKey = decrypt(privKey, encKey)
if (!verify(sig, await utils.sha256(fullKey), serverPubKey)) destroy("Invalid signature for encrypted symKey")
await sendConfirmation(write)
const key = fullKey.subarray(0, 32)
const iv = fullKey.subarray(32)
return { key, iv }
}
export async function sendConfirmation(write: write) {
await write(setMessageID(Buffer.alloc(2), 1))
}
export async function sendMsgReq(write: write, waitForData: waitForData, capability: string, size: bigint): Promise<Boolean> {
let m = setMessageID(Buffer.alloc(2 + 1 + capability.length + 8), 8)
m.writeUIntBE(capability.length, 2, 1)
m.write(capability, 3, "utf8")
m.writeBigUInt64BE(size, capability.length + 3)
// m.writeUBigIntBE(size, capability.length + 2, 8)
await write(m)
const res = await waitForData()
if (getMessageID(res) === 9 && res.readIntBE(2, 1) === 1) return true;
return false
}
export async function sendMsg(write: write, waitForData: waitForData, data: Readable) {
const ckr = new SizeChunker({
chunkSize: 1_000_000,
flushTail: true
})
data.pipe(ckr)
for await (const chunk of ckr) {
const data: Buffer = chunk.data
let m = setMessageID(Buffer.alloc(2 + 8), 10)
m.writeBigUInt64BE(BigInt(data.length), 2)
const hash = Buffer.from(await utils.sha256(data))
// hash.copy(m, 10)
// data.copy(m, m.length)
m = Buffer.concat([m, hash, data])
let i = 0;
do {
await write(m)
const res = await waitForData()
if (getMessageID(res) !== 11) console.warn("Chunk ack bad ID");
if (res.at(2) === 1) i = 4;
i++
} while (i < 3);
}
console.log("done")
}
\ No newline at end of file
import { genAndSendSymKey, genIntro, parseIntro, receiveCapabilities, receiveEncCheck, receivePubKey, sendCapabilities, sendEncCheck, sendPubKey } from "./messages"
import { createServer, Socket } from "net"
import { destroyUB, getMessageID, waitForDataUB, waitForEncDataUB, writeEncUB, writeUB, msgListenerUB } from "./utils"
import { getPublicKey, utils } from "@noble/secp256k1"
import { createCipheriv, createDecipheriv } from "crypto"
export async function listen(port = 30522) {
const server = createServer()
server.listen(port, () => {
console.log(`Listening on ${port}`)
})
server.on("connection", handleConnection)
}
export const capabilities = [
"text:0.0.1",
"data:0.0.1"
]
export async function handleConnection(conn: Socket) {
const rAddr = `${conn.remoteAddress}:${conn.remotePort}`
console.log(`New connection from ${rAddr}`)
conn.setKeepAlive()
conn.setNoDelay(true)
conn.on("error", (err) => {
console.error(`${err} from ${rAddr}`);
return;
})
conn.on("end", () => {
console.error(`Stream from ${rAddr} closed`)
return;
})
// @ts-ignore
conn._writableState.highWaterMark = 1;
// @ts-ignore
conn._readableState.highWaterMark = 1;
const destroy = destroyUB.bind(undefined, conn)
const write = writeUB.bind(undefined, conn)
const waitForData = waitForDataUB.bind(undefined, conn)
if (parseIntro(await waitForData())) destroy("Invalid intro")
await write(genIntro())
console.log("intro done")
const clientCaps = await receiveCapabilities(waitForData, destroy)
// todo: capability negotiation logic
await sendCapabilities(write, capabilities)
console.log("client capabilities: ", clientCaps)
if (getMessageID(await waitForData()) != 1) destroy("No confirmation for capability negotations")
const privKey = Buffer.from(utils.randomPrivateKey())
const pubKey = Buffer.from(getPublicKey(privKey))
// console.log("pubKey: ", pubKey)
await sendPubKey(write, pubKey)
const clientPubKey = await receivePubKey(waitForData, destroy)
// console.log("ClientPubKey: ", clientPubKey)
await sendEncCheck(write, waitForData, destroy, clientPubKey)
await receiveEncCheck(write, waitForData, destroy, privKey)
const { key, iv } = await genAndSendSymKey(write, waitForData, destroy, privKey, clientPubKey)
const cipher = createCipheriv("aes-256-ctr", key, iv)
const decipher = createDecipheriv("aes-256-ctr", key, iv)
const writeEnc = writeEncUB.bind(undefined, conn, cipher)
const waitForEncData = waitForEncDataUB.bind(undefined, conn, decipher)
console.log("done")
// console.log("p2pem:", (await waitForEncData()).toString("utf8"))
// await writeEnc(genIntro())
let state = { s: "waiting" }
//@ts-ignore
const msgListener = msgListenerUB.bind(undefined, writeEnc, waitForEncData, destroy, decipher, state, capabilities)
// now we event-emitter bind to the socket to allow for unprompted comms
conn.on("data", async (d) => {
//@ts-ignore
await msgListener(d)
})
}
listen()
\ No newline at end of file
import { Socket } from "net";
import { Cipher, Decipher } from "crypto"
import { utils } from "@noble/secp256k1";
export const sleep = (ms: number): Promise<void> => new Promise(resolve => setTimeout(resolve, ms));
export type waitForData = () => Promise<Buffer>
export type write = (d: any) => Promise<unknown>
export type destroy = (message: any) => void
export const destroyUB = (conn: Socket, msg: string) => { conn.destroy(new Error(msg)) }
let sendDelay = 50;
export const writeUB = async (conn: Socket, d: any) => {
const diff = sendDelay - performance.now()
if (diff > 0) {
await sleep(diff)
}
sendDelay = performance.now() + 50
return new Promise((res, rej) => {
conn.write(d, (err) => {
conn.emit("drain");
if (err) rej(err)
setImmediate(() => res(true))
// res(true)
})
})
}
// let writes = 0;
// let reads = 0;
export const writeEncUB = async (conn: Socket, cipher: Cipher, d: any) => {
// console.log("enc writes:", ++writes)
await writeUB(conn, cipher.update(d))
}
export async function waitForDataUB(s: Socket): Promise<Buffer> {
return new Promise(res => s.once("data", res))
}
export async function waitForEncDataUB(s: Socket, decipher: Decipher) {
// console.log("enc reads:", ++reads)
const eData = await waitForDataUB(s)
const decData = decipher.update(eData)
return decData
}
export function getMessageID(b: Buffer) {
return b.readIntBE(0, 2)
}
export function setMessageID(b: Buffer, id: number) {
b.writeIntBE(id, 0, 2)
return b;
}
export async function msgListenerUB(write: write, waitForData: waitForData, destroy: destroy, decipher: Decipher, state: any, capabilities: string[], d: Buffer) {
// const { write, destroy, waitForData } = f
// let state = f.state;
// DO NOT DECRYPT UNLESS NEEDED
// WILL DESYNC COUNTERS
if (state.s === "waiting") {
const data = decipher.update(d)
if (getMessageID(data) !== 8) destroy("Expected ID 8")
const capLen = data.readIntBE(2, 1)
const cap = data.toString("utf8", 3, capLen + 3)
if (!capabilities.includes(cap)) destroy("Invalid capability")
const dataLen = data.readBigUInt64BE(capLen + 3)
console.log(`Request for ${cap} with size ${dataLen}`)
const res = setMessageID(Buffer.alloc(3), 9)
res.writeIntBE(1, 2, 1)
state.s = "receiving";
state.rec = 0;
state.size = dataLen;
state.ws = createWriteStream("./recv.data")
console.log(res)
await write(res)
return;
} else if (state.s === "receiving") {
const data = decipher.update(d)
if (getMessageID(data) !== 10) destroy("Expected ID 10 (chunk)")
const chnkSize = data.readBigUInt64BE(2)
const chnkHash = data.subarray(10, 42)
const chnkData = data.subarray(42)
const tstHash = await utils.sha256(chnkData)
const res = setMessageID(Buffer.alloc(3), 11)