@twilio/plugin-microvisor
Version:
Interact with your Twilio Microvisor devices
111 lines (110 loc) • 4.99 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EncryptionToTunnel = void 0;
const node_events_1 = require("node:events");
const hpke_1 = require("./hpke");
const proto_1 = require("../../../../proto_gen/proto");
var MessageType = proto_1.encryption.EncryptionLayer_MessageTypes;
var DebuggerKey = proto_1.encryption.EncryptionDebuggerKey;
var DeviceKey = proto_1.encryption.EncryptionDeviceKey;
var Envelope = proto_1.encryption.EncryptionEnvelope;
var Abort = proto_1.encryption.EncryptionAbort;
class EncryptionToTunnel extends node_events_1.EventEmitter {
constructor(tunnel, debugKey) {
super();
this.messageEmitter = new node_events_1.EventEmitter();
this.buffer = Buffer.concat([]);
this.session = null;
this.writesPending = [];
this.privateKey = debugKey;
this.tunnel = tunnel;
this.tunnel.on('data', (data) => {
this.buffer = Buffer.concat([this.buffer, Buffer.from(data)]);
for (;;) {
const dataArray = new Uint8Array(this.buffer);
if (dataArray.length < 3) {
return;
}
const messageLength = (dataArray[1] << 8) + dataArray[2];
if (dataArray.length < 3 + messageLength) {
return;
}
const method = MessageType[dataArray[0]];
const body = dataArray.slice(3, 3 + messageLength);
this.emit('debug', 'Received ' + method + ' message: ' + body.toString());
this.messageEmitter.emit(method, body);
this.buffer = this.buffer.subarray(3 + messageLength);
}
});
this.tunnel.on('open', () => {
this.emit('debug', 'Tunnel opened, waiting for handshake');
this.messageEmitter.once('TYPE_DEVICE_KEY', this.onReceiveDeviceKey.bind(this));
this.messageEmitter.once('TYPE_ABORT', this.onReceiveAbort.bind(this));
});
this.tunnel.on('close', () => {
this.session = null;
this.emit('close');
});
this.tunnel.on('error', (_error) => {
this.session = null;
this.emit('close');
});
}
onReceiveDeviceKey(data) {
const dk = DeviceKey.decode(data);
this.emit('debug', 'Received device key ' + JSON.stringify(dk));
this.session = new hpke_1.Session(Buffer.from(dk.pkr), this.privateKey);
this.sendMessagePlain(MessageType.TYPE_DEBUGGER_KEY, DebuggerKey.encode({ enc: this.session.encap() }).finish());
this.messageEmitter.on('TYPE_ENVELOPE', this.onReceiveEnvelope.bind(this));
this.flushPendingWrites();
this.emit('open');
}
onReceiveAbort(data) {
this.session = null;
const abort = Abort.decode(data);
this.emit('debug', 'Received device abort ' + JSON.stringify(abort));
this.emit('error', 'Device rejected debugging session: ' +
(EncryptionToTunnel.rejectionReasons.get(abort.error) || abort.error.toString()));
this.emit('close');
}
onReceiveEnvelope(data) {
if (this.session === null) {
return;
}
const env = Envelope.decode(data);
this.emit('debug', 'Received device envelope ' + JSON.stringify(env));
const pt = this.session.decryptMessage(Buffer.from(env.ct));
this.emit('data', pt);
}
flushPendingWrites() {
for (const message of this.writesPending) {
this.write(message);
}
this.writesPending.length = 0;
}
write(message) {
if (this.session === null) {
// buffer up messages which we can't send just yet.
this.writesPending.push(message);
return;
}
const ct = this.session.encryptMessage(message);
this.sendMessagePlain(MessageType.TYPE_ENVELOPE, Envelope.encode({ ct: ct }).finish());
}
sendMessagePlain(purpose, requestData) {
const header = new Uint8Array([purpose, requestData.length >> 8, requestData.length & 0xFF]);
this.tunnel.write(Buffer.concat([header, requestData]));
}
}
exports.EncryptionToTunnel = EncryptionToTunnel;
EncryptionToTunnel.rejectionReasons = new Map([
[Abort.CryptoError.NO_USER_CODE, 'no application is running'],
[Abort.CryptoError.NO_DEBUG_KEY, 'application manifest does not allow debugging'],
[Abort.CryptoError.INVALID_FORMAT, 'internal error: invalid format'],
[Abort.CryptoError.OUT_OF_ORDER_MESSAGE, 'internal error: out of order message'],
[Abort.CryptoError.UNKNOWN_KEY_TYPE, 'internal error: unknown key type'],
[Abort.CryptoError.AUTHENTICATION_ERROR, 'authentication error'],
[Abort.CryptoError.INTERNAL_ERROR, 'internal error'],
[Abort.CryptoError.MESSAGE_TOO_LARGE, 'message too large'],
[Abort.CryptoError.INVALID_DEBUG_PUBLIC_KEY, 'internal error: invalid public key']
]);