Skip to content

Commit

Permalink
Convert platform crypto.js files to TypeScript
Browse files Browse the repository at this point in the history
As part of this conversion, I intend to convert Crypto and CipherParams
to ECMAScript classes, but I’m going to do that in a separate commit so
that the conversion to TypeScript is reviewable without being lost in a
sea of indentation changes. The underscored interfaces that this commit
introduces are temporary and will be removed upon conversion to classes.

Resolves #1252.
  • Loading branch information
lawrence-forooghian committed May 25, 2023
1 parent 026c329 commit e4c4b9c
Show file tree
Hide file tree
Showing 3 changed files with 196 additions and 53 deletions.
4 changes: 4 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ module.exports = {
// see https://github.com/nodesecurity/eslint-plugin-security/issues/21
"security/detect-object-injection": "off",
"@typescript-eslint/no-var-requires": "error",
// Use typescript-eslint’s version of the no-redeclare rule, which isn’t triggered by overload signatures.
// TODO remove this once we start using the full @typescript-eslint/recommended ruleset in #958
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': 'error',
},
overrides: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,23 @@
import Logger from '../../../../common/lib/util/logger';
import crypto from 'crypto';
import ErrorInfo from '../../../../common/lib/types/errorinfo';
import * as API from '../../../../../ably';
import ICryptoStatic, { IGetCipherParams } from '../../../../common/types/ICryptoStatic';
import ICipher from '../../../../common/types/ICipher';
import { CryptoDataTypes } from '../../../../common/types/cryptoDataTypes';
import { Cipher as NodeCipher, CipherKey as NodeCipherKey } from 'crypto';
import BufferUtils, { Bufferlike, Output as BufferUtilsOutput } from './bufferutils';

var CryptoFactory = function (bufferUtils) {
// The type to which ably-forks/msgpack-js deserializes elements of the `bin` or `ext` type
type MessagePackBinaryType = Buffer;

type IV = CryptoDataTypes.IV<BufferUtilsOutput>;
type InputPlaintext = CryptoDataTypes.InputPlaintext<Bufferlike, BufferUtilsOutput>;
type OutputCiphertext = Buffer;
type InputCiphertext = CryptoDataTypes.InputCiphertext<MessagePackBinaryType, BufferUtilsOutput>;
type OutputPlaintext = Buffer;

var CryptoFactory = function (bufferUtils: typeof BufferUtils) {
var DEFAULT_ALGORITHM = 'aes';
var DEFAULT_KEYLENGTH = 256; // bits
var DEFAULT_MODE = 'cbc';
Expand All @@ -14,7 +29,9 @@ var CryptoFactory = function (bufferUtils) {
* @param bytes
* @param callback (optional)
*/
function generateRandom(bytes, callback) {
function generateRandom(bytes: number): Buffer;
function generateRandom(bytes: number, callback: (err: Error | null, buf: Buffer) => void): void;
function generateRandom(bytes: number, callback?: (err: Error | null, buf: Buffer) => void) {
return callback === undefined ? crypto.randomBytes(bytes) : crypto.randomBytes(bytes, callback);
}

Expand All @@ -24,15 +41,15 @@ var CryptoFactory = function (bufferUtils) {
* @param plaintextLength
* @return
*/
function getPaddedLength(plaintextLength) {
function getPaddedLength(plaintextLength: number) {
return (plaintextLength + DEFAULT_BLOCKLENGTH) & -DEFAULT_BLOCKLENGTH;
}

/**
* Internal: checks that the cipherParams are a valid combination. Currently
* just checks that the calculated keyLength is a valid one for aes-cbc
*/
function validateCipherParams(params) {
function validateCipherParams(params: API.Types.CipherParams) {
if (params.algorithm === 'aes' && params.mode === 'cbc') {
if (params.keyLength === 128 || params.keyLength === 256) {
return;
Expand All @@ -45,15 +62,15 @@ var CryptoFactory = function (bufferUtils) {
}
}

function normaliseBase64(string) {
function normaliseBase64(string: string) {
/* url-safe base64 strings use _ and - instread of / and + */
return string.replace('_', '/').replace('-', '+');
}

/**
* Internal: obtain the pkcs5 padding string for a given padded length;
*/
function filledBuffer(length, value) {
function filledBuffer(length: number, value: number) {
var result = Buffer.alloc(length);
result.fill(value);
return result;
Expand All @@ -66,10 +83,22 @@ var CryptoFactory = function (bufferUtils) {
* @param bufferOrString
* @returns {Buffer}
*/
function toBuffer(bufferOrString) {
function toBuffer(bufferOrString: Buffer | string) {
return typeof bufferOrString == 'string' ? Buffer.from(bufferOrString, 'binary') : bufferOrString;
}

interface _CipherParams extends API.Types.CipherParams {
algorithm: string;
keyLength: number;
mode: string;
key: NodeCipherKey;
iv: unknown;
}

interface _CipherParamsConstructor {
new (algorithm: string, keyLength: number, mode: string, key: NodeCipherKey): _CipherParams;
}

/**
* A class encapsulating the client-specifiable parameters for
* the cipher.
Expand All @@ -80,21 +109,35 @@ var CryptoFactory = function (bufferUtils) {
* Clients may instance a CipherParams directly and populate it, or may
* query the implementation to obtain a default system CipherParams.
*/
function CipherParams(algorithm, keyLength, mode, key) {
const CipherParams = function (
this: _CipherParams,
algorithm: string,
keyLength: number,
mode: string,
key: NodeCipherKey
) {
this.algorithm = algorithm;
this.keyLength = keyLength;
this.mode = mode;
this.key = key;
this.iv = null;
}
} as unknown as _CipherParamsConstructor;

function isInstCipherParams(params) {
function isInstCipherParams(
params: API.Types.CipherParams | API.Types.CipherParamOptions
): params is API.Types.CipherParams {
/* In node, can't use instanceof CipherParams due to the vm context problem (see
* https://github.com/nwjs/nw.js/wiki/Differences-of-JavaScript-contexts).
* So just test for presence of all necessary attributes */
return params.algorithm && params.key && params.keyLength && params.mode ? true : false;
}

interface _CryptoStatic
extends ICryptoStatic<IV, InputPlaintext, OutputCiphertext, InputCiphertext, OutputPlaintext> {
CipherParams: typeof CipherParams;
getDefaultParams(params: API.Types.CipherParamOptions): _CipherParams;
}

/**
* Utility classes and interfaces for message payload encryption.
*
Expand All @@ -112,7 +155,7 @@ var CryptoFactory = function (bufferUtils) {
* concatenated with the resulting raw ciphertext to construct the "ciphertext"
* data passed to the recipient.
*/
function Crypto() {}
const Crypto = function () {} as unknown as _CryptoStatic;

Crypto.CipherParams = CipherParams;

Expand All @@ -125,8 +168,8 @@ var CryptoFactory = function (bufferUtils) {
* base64-encoded string. May optionally also contain: algorithm (defaults to
* AES), mode (defaults to 'cbc')
*/
Crypto.getDefaultParams = function (params) {
var key;
Crypto.getDefaultParams = function (params: API.Types.CipherParamOptions) {
var key: NodeCipherKey;

if (!params.key) {
throw new Error('Crypto.getDefaultParams: a key is required');
Expand Down Expand Up @@ -164,7 +207,7 @@ var CryptoFactory = function (bufferUtils) {
* @param keyLength (optional) the required keyLength in bits
* @param callback (optional) (err, key)
*/
Crypto.generateRandomKey = function (keyLength, callback) {
Crypto.generateRandomKey = function (keyLength?: number, callback?: API.Types.StandardCallback<API.Types.CipherKey>) {
if (arguments.length == 1 && typeof keyLength == 'function') {
callback = keyLength;
keyLength = undefined;
Expand All @@ -182,23 +225,42 @@ var CryptoFactory = function (bufferUtils) {
* @param params either a CipherParams instance or some subset of its
* fields that includes a key
*/
Crypto.getCipher = function (params) {
var cipherParams = isInstCipherParams(params) ? params : Crypto.getDefaultParams(params);
Crypto.getCipher = function (params: IGetCipherParams<IV>) {
var cipherParams = isInstCipherParams(params) ? (params as _CipherParams) : Crypto.getDefaultParams(params);

var iv = params.iv || generateRandom(DEFAULT_BLOCKLENGTH);
return { cipherParams: cipherParams, cipher: new CBCCipher(cipherParams, iv) };
return {
cipherParams: cipherParams,
cipher: new CBCCipher(cipherParams, iv),
};
};

function CBCCipher(params, iv) {
var algorithm = (this.algorithm = params.algorithm + '-' + String(params.keyLength) + '-' + params.mode);
var key = (this.key = params.key);
// eslint-disable-next-line no-redeclare
var iv = (this.iv = iv);
this.encryptCipher = crypto.createCipheriv(algorithm, key, iv);
this.blockLength = iv.length;
interface _CBCCipher extends ICipher<InputPlaintext, OutputCiphertext, InputCiphertext, OutputPlaintext> {
algorithm: string;
key: NodeCipherKey;
iv: Buffer | null;
encryptCipher: NodeCipher;
blockLength: number;
getIv: () => Buffer;
}

CBCCipher.prototype.encrypt = function (plaintext, callback) {
interface _CBCCipherConstructor {
new (params: _CipherParams, iv: Buffer): _CBCCipher;
}

const CBCCipher = function CBCCipher(this: _CBCCipher, params: _CipherParams, iv: Buffer) {
this.algorithm = params.algorithm + '-' + String(params.keyLength) + '-' + params.mode;
this.key = params.key;
this.iv = iv;
this.encryptCipher = crypto.createCipheriv(this.algorithm, this.key, this.iv);
this.blockLength = this.iv.length;
} as unknown as _CBCCipherConstructor;

CBCCipher.prototype.encrypt = function (
this: _CBCCipher,
plaintext: InputPlaintext,
callback: (error: Error | null, data: OutputCiphertext | null) => void
) {
Logger.logAction(Logger.LOG_MICRO, 'CBCCipher.encrypt()', '');
var plaintextBuffer = bufferUtils.toBuffer(plaintext);
var plaintextLength = plaintextBuffer.length,
Expand All @@ -211,7 +273,7 @@ var CryptoFactory = function (bufferUtils) {
return callback(null, ciphertext);
};

CBCCipher.prototype.decrypt = function (ciphertext) {
CBCCipher.prototype.decrypt = function (this: _CBCCipher, ciphertext: InputCiphertext) {
var blockLength = this.blockLength,
decryptCipher = crypto.createDecipheriv(this.algorithm, this.key, ciphertext.slice(0, blockLength)),
plaintext = toBuffer(decryptCipher.update(ciphertext.slice(blockLength))),
Expand All @@ -220,7 +282,7 @@ var CryptoFactory = function (bufferUtils) {
return plaintext;
};

CBCCipher.prototype.getIv = function () {
CBCCipher.prototype.getIv = function (this: _CBCCipher) {
if (this.iv) {
var iv = this.iv;
this.iv = null;
Expand Down
Loading

0 comments on commit e4c4b9c

Please sign in to comment.