Skip to content

Commit

Permalink
Add HandshakeFactory
Browse files Browse the repository at this point in the history
  • Loading branch information
bashlund committed Aug 25, 2022
1 parent 53ac50f commit b0a2493
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 1 deletion.
134 changes: 134 additions & 0 deletions src/HandshakeFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import {
SocketFactory,
Client,
ConnectCallback,
ClientRefuseCallback,
} from "pocket-sockets";

import {
HandshakeFactoryConfig,
HandshakeResult,
} from "./types";

import {
Messaging,
} from "./Messaging";

import {
HandshakeAsServer,
HandshakeAsClient,
} from "./Handshake";

/**
* Event emitted when client is successfully handshaked and setup for encryption.
* Note that the returned Messaging object first needs to be .open()'d to be ready for communication.
*/
export type HandshakeCallback = (e: {messaging: Messaging, isServer: boolean, handshakeResult: HandshakeResult}) => void;

/** Event emitted when client could not handshake. */
export type HandshakeErrorCallback = (e: {client: Client, error: Error}) => void;


/**
* This class extends the SocketFactory with handshake capabilties.
*
* The general SocketFactory EVENT_ERROR is also emitted for EVENT_HANDSHAKE_ERROR and the data property is set to the Client object.
* The SocketFactory EVENT_CLIENT_REFUSE is extended with a type of HandshakeFactory.EVENT_CLIENT_REFUSE_PUBLICKEY_OVERFLOW.
*/
export class HandshakeFactory extends SocketFactory {
public static readonly EVENT_HANDSHAKE = "handshake";
public static readonly EVENT_HANDSHAKE_ERROR = "handshakeError";
public static readonly EVENT_CLIENT_REFUSE_PUBLICKEY_OVERFLOW = "publicKey-overflow";

protected handshakeFactoryConfig: HandshakeFactoryConfig;

constructor(handshakeFactoryConfig: HandshakeFactoryConfig) {
super(handshakeFactoryConfig.socketFactoryConfig);
this.handshakeFactoryConfig = handshakeFactoryConfig;
}

protected handleOnConnect: ConnectCallback = async (e) => {
try {
const messaging = new Messaging(e.client);
let handshakeResult: HandshakeResult;
if (e.isServer) {
handshakeResult = await HandshakeAsServer(e.client, this.handshakeFactoryConfig.keyPair.secretKey, this.handshakeFactoryConfig.keyPair.publicKey, this.handshakeFactoryConfig.discriminator, this.handshakeFactoryConfig.allowedClients, this.handshakeFactoryConfig.peerData);
await messaging.setEncrypted(handshakeResult.serverToClientKey, handshakeResult.serverNonce, handshakeResult.clientToServerKey, handshakeResult.clientNonce, handshakeResult.peerLongtermPk);
}
else {
if (!this.handshakeFactoryConfig.serverPublicKey) {
e.client.close();
return;
}
handshakeResult = await HandshakeAsClient(e.client, this.handshakeFactoryConfig.keyPair.secretKey, this.handshakeFactoryConfig.keyPair.publicKey, this.handshakeFactoryConfig.serverPublicKey, this.handshakeFactoryConfig.discriminator, this.handshakeFactoryConfig.peerData);
await messaging.setEncrypted(handshakeResult.clientToServerKey, handshakeResult.clientNonce, handshakeResult.serverToClientKey, handshakeResult.serverNonce, handshakeResult.peerLongtermPk);
}
if (!handshakeResult) {
return;
}

const publicKeyStr = handshakeResult.peerLongtermPk.toString("hex");
if (this.checkClientsOverflow(publicKeyStr)) {
messaging.close();
this.triggerEvent(HandshakeFactory.EVENT_CLIENT_REFUSE, {type: HandshakeFactory.EVENT_CLIENT_REFUSE_PUBLICKEY_OVERFLOW, key: handshakeResult.peerLongtermPk});
return;
}
this.increaseClientsCounter(publicKeyStr);
e.client.onClose( () => {
this.decreaseClientsCounter(publicKeyStr);
});

this.triggerEvent(HandshakeFactory.EVENT_HANDSHAKE, {messaging, isServer: e.isServer, handshakeResult});
}
catch(error) {
if (typeof error === "string") {
error = new Error(error);
}
this.triggerEvent(HandshakeFactory.EVENT_HANDSHAKE_ERROR, {client: e.client, error});
this.triggerEvent(HandshakeFactory.EVENT_ERROR, {type: HandshakeFactory.EVENT_HANDSHAKE_ERROR, error, data: e.client});
e.client.close();
}
}

public init() {
this.onConnect(this.handleOnConnect);
super.init();
}

public getHandshakeFactoryConfig(): HandshakeFactoryConfig {
return this.handshakeFactoryConfig;
}

/**
* Increase the counter connections per client public key.
*/
protected increaseClientsCounter(publicKey: string) {
this.increaseCounter(publicKey);
}

protected decreaseClientsCounter(publicKey: string) {
this.decreaseCounter(publicKey);
}

/**
* @params publicKey
* @returns true if any limit is reached.
*/
protected checkClientsOverflow(publicKey: string): boolean {
if (this.handshakeFactoryConfig.maxConnectionsPerClient !== undefined) {
const clientCount = this.readCounter(publicKey);
if (clientCount >= this.handshakeFactoryConfig.maxConnectionsPerClient) {
return true;
}
}
return false;
}

onHandshakeError(callback: HandshakeErrorCallback) {
this.hookEvent(HandshakeFactory.EVENT_HANDSHAKE_ERROR, callback);
}

onHandshake(callback: HandshakeCallback) {
this.hookEvent(HandshakeFactory.EVENT_HANDSHAKE, callback);
}
}
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./Messaging";
export * from "./Handshake";
export * from "./HandshakeFactory";
export * from "./Crypto";
export * from "./types";
35 changes: 34 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import EventEmitter from "eventemitter3";

import {
SocketFactoryConfig,
} from "pocket-sockets";

/**
* A single message cannot exceed 65535 bytes in total.
*/
Expand Down Expand Up @@ -144,7 +148,7 @@ export enum EventType {
}

export type HandshakeResult = {
longtermPk: Buffer, // The public which was used to handshake.
longtermPk: Buffer, // The public key which was used to handshake
peerLongtermPk: Buffer, // The handshaked longterm public key of the peer
clientToServerKey: Buffer, // box key
clientNonce: Buffer, // box nonce
Expand All @@ -153,3 +157,32 @@ export type HandshakeResult = {
peerData: Buffer, // arbitrary data provided by peer
sessionId: Buffer, // A mutual 32 byte session ID which can be used. Same for client and server. Derived by the hashing of a shared secret.
};

export type KeyPair = {
publicKey: Buffer,
secretKey: Buffer,
};

export type ClientValidatorFunctionInterface = (clientLongTermPk: Buffer) => boolean;

export type HandshakeFactoryConfig = {
socketFactoryConfig: SocketFactoryConfig,

/** The keypair to use in the cryptographic handshake. */
keyPair: KeyPair,

/** The discriminator which must match the peer's discriminator when handshaking. */
discriminator: Buffer,

/** Arbitrary data sent to the other peer. */
peerData?: Buffer,

/** If connecting as client the public key of the server must be set. */
serverPublicKey?: Buffer,

/** If opening a server we can discriminate on peers public keys in the handshake. */
allowedClients?: Buffer[] | ClientValidatorFunctionInterface;

/** If set, the maximum no of connections each cryptographically handshaked publicKey is allowed. */
maxConnectionsPerClient?: number,
};

0 comments on commit b0a2493

Please sign in to comment.