@microsoft/dev-tunnels-ssh
Version:
SSH library for Dev Tunnels
160 lines • 6.09 kB
JavaScript
"use strict";
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
Object.defineProperty(exports, "__esModule", { value: true });
exports.NodeEncryption = void 0;
const crypto = require("crypto");
const buffer_1 = require("buffer");
const encryptionAlgorithm_1 = require("../encryptionAlgorithm");
class NodeEncryption extends encryptionAlgorithm_1.EncryptionAlgorithm {
constructor(name, algorithmName, cipherMode, keySizeInBits) {
super(name);
this.algorithmName = algorithmName;
this.cipherMode = cipherMode;
this.keySizeInBits = keySizeInBits;
if (algorithmName !== 'AES') {
throw new Error(`Unsupported encryption algorithm: ${algorithmName}`);
}
this.blockSizeInBits = NodeEncryption.getBlockSize(algorithmName);
}
get keyLength() {
return this.keySizeInBits / 8;
}
get blockLength() {
return this.blockSizeInBits / 8;
}
async createCipher(isEncryption, key, iv) {
let cipher;
if (this.cipherMode === 'CTR' || this.cipherMode === 'CBC') {
cipher = new NodeAesCipher(isEncryption, this.keySizeInBits, this.blockSizeInBits, key, iv, this.cipherMode);
}
else if (this.cipherMode === 'GCM') {
cipher = new NodeAesGcmCipher(isEncryption, this.keySizeInBits, this.blockSizeInBits, key, iv);
}
else {
throw new Error(`Unsupported cipher mode: ${this.cipherMode}`);
}
return cipher;
}
static getBlockSize(algorithmName) {
if (algorithmName === 'AES') {
return 128;
}
else {
throw new Error(`Unsupported encryption algorithm: ${algorithmName}`);
}
}
}
exports.NodeEncryption = NodeEncryption;
class NodeAesCipher {
constructor(isEncryption, keySizeInBits, blockSizeInBits, key, iv, cipherMode) {
this.isEncryption = isEncryption;
this.keySizeInBits = keySizeInBits;
this.blockSizeInBits = blockSizeInBits;
const nodeAlgorithm = `AES-${this.keySizeInBits}-${cipherMode}`;
this.cipher = this.isEncryption
? crypto.createCipheriv(nodeAlgorithm, key, iv)
: crypto.createDecipheriv(nodeAlgorithm, key, iv);
this.cipher.setAutoPadding(false);
}
get blockLength() {
return this.blockSizeInBits / 8;
}
transform(data) {
const result = this.cipher.update(data);
if (result.length !== data.length) {
const message = 'Result from encrypt/decrypt has invalid length ' +
`${result.length}, expected ${data.length}.`;
throw new Error(message);
}
return Promise.resolve(result);
}
dispose() { }
}
class NodeAesGcmCipher {
constructor(isEncryption, keySizeInBits, blockSizeInBits, key, iv) {
this.isEncryption = isEncryption;
this.keySizeInBits = keySizeInBits;
this.blockSizeInBits = blockSizeInBits;
this.tag = null;
this.algorithmName = `aes-${this.keySizeInBits}-gcm`;
this.key = buffer_1.Buffer.alloc(key.length);
key.copy(this.key);
// Ininitialize the nonce to the first 12 bytes of the IV. It will be incremented by each op.
this.nonce = buffer_1.Buffer.alloc(12);
iv.copy(this.nonce, 0, 0, 12);
this.associatedData = buffer_1.Buffer.alloc(4);
}
get blockLength() {
return this.blockSizeInBits / 8;
}
get digestLength() {
return 16;
}
get authenticatedEncryption() {
return true;
}
transform(data) {
if (data.length % this.blockLength !== 0) {
const message = 'Encrypt/decrypt input has invalid length ' +
`${data.length}, not a multiple of block size ${this.blockLength}.`;
throw new Error(message);
}
const cipher = this.isEncryption
? crypto.createCipheriv(this.algorithmName, this.key, this.nonce)
: crypto.createDecipheriv(this.algorithmName, this.key, this.nonce);
// Associated data is the 32-bit packet length.
const packetLength = data.length;
this.associatedData[0] = packetLength >>> 24;
this.associatedData[1] = packetLength >>> 16;
this.associatedData[2] = packetLength >>> 8;
this.associatedData[3] = packetLength;
cipher.setAAD(this.associatedData);
if (!this.isEncryption) {
if (!this.tag) {
throw new Error('AES-GCM tag was not set before decrypting.');
}
cipher.setAuthTag(this.tag);
}
const result = cipher.update(data);
if (result.length !== data.length) {
const message = 'Result from encrypt/decrypt has invalid length ' +
`${result.length}, expected ${data.length}.`;
throw new Error(message);
}
cipher.final();
if (this.isEncryption) {
this.tag = cipher.getAuthTag();
}
else {
this.tag = null;
}
// Increment the counter (last 8 bytes of the nonce) as a big-endian integer.
// First increment the last byte, and if it reaches 0 then increment the
// next-to-last byte, and so on.
let k = 12;
while (--k >= 4) {
this.nonce[k]++;
if (this.nonce[k] !== 0) {
break;
}
}
return Promise.resolve(result);
}
async sign(data) {
if (!this.tag) {
throw new Error('AES-GCM tag was not obtained by encrypting.');
}
return this.tag;
}
async verify(data, signature) {
if (signature.length !== this.digestLength) {
throw new Error('Incorrect AES-GCM tag length.');
}
this.tag = signature;
return true;
}
dispose() { }
}
//# sourceMappingURL=nodeEncryption.js.map