UNPKG

pubnub

Version:

Publish & Subscribe Real-time Messaging with PubNub

274 lines (273 loc) 11.5 kB
"use strict"; /** * Legacy Node.js cryptography module. * * @internal */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); const crypto_1 = require("crypto"); const stream_1 = require("stream"); const buffer_1 = require("buffer"); /** * Legacy cryptography implementation for Node.js-based {@link PubNub} client. * * @internal */ class NodeCryptography { // -------------------------------------------------------- // --------------------- Encryption ----------------------- // -------------------------------------------------------- // region Encryption encrypt(key, input) { return __awaiter(this, void 0, void 0, function* () { const bKey = this.getKey(key); if (input instanceof buffer_1.Buffer) return this.encryptBuffer(bKey, input); if (input instanceof stream_1.Readable) return this.encryptStream(bKey, input); if (typeof input === 'string') return this.encryptString(bKey, input); throw new Error('Encryption error: unsupported input format'); }); } /** * Encrypt provided source {@link Buffer} using specific encryption {@link key}. * * @param key - Data encryption key. <br/>**Note:** Same key should be used to `decrypt` {@link Buffer}. * @param buffer - Source {@link Buffer} for encryption. * * @returns Encrypted data as {@link Buffer} object. */ encryptBuffer(key, buffer) { const bIv = this.getIv(); const aes = (0, crypto_1.createCipheriv)(this.algo, key, bIv); return buffer_1.Buffer.concat([bIv, aes.update(buffer), aes.final()]); } /** * Encrypt provided source {@link Readable} stream using specific encryption {@link key}. * * @param key - Data encryption key. <br/>**Note:** Same key should be used to `decrypt` {@link Readable} stream. * @param stream - Source {@link Readable} stream for encryption. * * @returns Encrypted data as {@link Transform} object. */ encryptStream(key, stream) { return __awaiter(this, void 0, void 0, function* () { const bIv = this.getIv(); const aes = (0, crypto_1.createCipheriv)(this.algo, key, bIv).setAutoPadding(true); let initiated = false; return stream.pipe(aes).pipe(new stream_1.Transform({ transform(chunk, _, cb) { if (!initiated) { initiated = true; this.push(buffer_1.Buffer.concat([bIv, chunk])); } else this.push(chunk); cb(); }, })); }); } /** * Encrypt provided source {@link string} using specific encryption {@link key}. * * @param key - Data encryption key. <br/>**Note:** Same key should be used to `decrypt` {@link string}. * @param text - Source {@link string} for encryption. * * @returns Encrypted data as byte {@link string}. */ encryptString(key, text) { const bIv = this.getIv(); const bPlaintext = buffer_1.Buffer.from(text); const aes = (0, crypto_1.createCipheriv)(this.algo, key, bIv); return buffer_1.Buffer.concat([bIv, aes.update(bPlaintext), aes.final()]).toString('utf8'); } encryptFile(key, file, File) { return __awaiter(this, void 0, void 0, function* () { const bKey = this.getKey(key); /** * Buffer type check also covers `string` which converted to the `Buffer` during file object creation. */ if (file.data instanceof buffer_1.Buffer) { if (file.data.byteLength <= 0) throw new Error('Encryption error: empty content.'); return File.create({ name: file.name, mimeType: file.mimeType, data: this.encryptBuffer(bKey, file.data), }); } if (file.data instanceof stream_1.Readable) { if (!file.contentLength || file.contentLength === 0) throw new Error('Encryption error: empty content.'); return File.create({ name: file.name, mimeType: file.mimeType, stream: yield this.encryptStream(bKey, file.data), }); } throw new Error('Cannot encrypt this file. In Node.js file encryption supports only string, Buffer or Stream.'); }); } // endregion // -------------------------------------------------------- // --------------------- Decryption ----------------------- // -------------------------------------------------------- // region Decryption decrypt(key, input) { return __awaiter(this, void 0, void 0, function* () { const bKey = this.getKey(key); if (input instanceof ArrayBuffer) { const decryptedBuffer = this.decryptBuffer(bKey, buffer_1.Buffer.from(input)); return decryptedBuffer.buffer.slice(decryptedBuffer.byteOffset, decryptedBuffer.byteOffset + decryptedBuffer.length); } if (input instanceof buffer_1.Buffer) return this.decryptBuffer(bKey, input); if (input instanceof stream_1.Readable) return this.decryptStream(bKey, input); if (typeof input === 'string') return this.decryptString(bKey, input); throw new Error('Decryption error: unsupported input format'); }); } /** * Decrypt provided encrypted {@link Buffer} using specific decryption {@link key}. * * @param key - Data decryption key. <br/>**Note:** Should be the same as used to `encrypt` {@link Buffer}. * @param buffer - Encrypted {@link Buffer} for decryption. * * @returns Decrypted data as {@link Buffer} object. */ decryptBuffer(key, buffer) { const bIv = buffer.slice(0, NodeCryptography.IV_LENGTH); const bCiphertext = buffer.slice(NodeCryptography.IV_LENGTH); if (bCiphertext.byteLength <= 0) throw new Error('Decryption error: empty content'); const aes = (0, crypto_1.createDecipheriv)(this.algo, key, bIv); return buffer_1.Buffer.concat([aes.update(bCiphertext), aes.final()]); } /** * Decrypt provided encrypted {@link Readable} stream using specific decryption {@link key}. * * @param key - Data decryption key. <br/>**Note:** Should be the same as used to `encrypt` {@link Readable} stream. * @param stream - Encrypted {@link Readable} stream for decryption. * * @returns Decrypted data as {@link Readable} object. */ decryptStream(key, stream) { let aes = null; const output = new stream_1.PassThrough(); let bIv = buffer_1.Buffer.alloc(0); const getIv = () => { let data = stream.read(); while (data !== null) { if (data) { const bChunk = buffer_1.Buffer.from(data); const sliceLen = NodeCryptography.IV_LENGTH - bIv.byteLength; if (bChunk.byteLength < sliceLen) bIv = buffer_1.Buffer.concat([bIv, bChunk]); else { bIv = buffer_1.Buffer.concat([bIv, bChunk.slice(0, sliceLen)]); aes = (0, crypto_1.createDecipheriv)(this.algo, key, bIv); aes.pipe(output); aes.write(bChunk.slice(sliceLen)); } } data = stream.read(); } }; stream.on('readable', getIv); stream.on('end', () => { if (aes) aes.end(); output.end(); }); return output; } /** * Decrypt provided encrypted {@link string} using specific decryption {@link key}. * * @param key - Data decryption key. <br/>**Note:** Should be the same as used to `encrypt` {@link string}. * @param text - Encrypted {@link string} for decryption. * * @returns Decrypted data as byte {@link string}. */ decryptString(key, text) { const ciphertext = buffer_1.Buffer.from(text); const bIv = ciphertext.slice(0, NodeCryptography.IV_LENGTH); const bCiphertext = ciphertext.slice(NodeCryptography.IV_LENGTH); const aes = (0, crypto_1.createDecipheriv)(this.algo, key, bIv); return buffer_1.Buffer.concat([aes.update(bCiphertext), aes.final()]).toString('utf8'); } decryptFile(key, file, File) { return __awaiter(this, void 0, void 0, function* () { const bKey = this.getKey(key); /** * Buffer type check also covers `string` which converted to the `Buffer` during file object creation. */ if (file.data instanceof buffer_1.Buffer) { return File.create({ name: file.name, mimeType: file.mimeType, data: this.decryptBuffer(bKey, file.data), }); } if (file.data instanceof stream_1.Readable) { return File.create({ name: file.name, mimeType: file.mimeType, stream: this.decryptStream(bKey, file.data), }); } throw new Error('Cannot decrypt this file. In Node.js file decryption supports only string, Buffer or Stream.'); }); } // endregion // -------------------------------------------------------- // ----------------------- Helpers ------------------------ // -------------------------------------------------------- // region Helpers /** * Cryptography algorithm. * * @returns Cryptography module algorithm. */ get algo() { return 'aes-256-cbc'; } /** * Convert cipher key to the {@link Buffer}. * * @param key - String cipher key. * * @returns SHA256 HEX encoded cipher key {@link Buffer}. */ getKey(key) { const sha = (0, crypto_1.createHash)('sha256'); sha.update(buffer_1.Buffer.from(key, 'utf8')); return buffer_1.Buffer.from(sha.digest('hex').slice(0, 32), 'utf8'); } /** * Generate random initialization vector. * * @returns Random initialization vector. */ getIv() { return (0, crypto_1.randomBytes)(NodeCryptography.IV_LENGTH); } } /** * Random initialization vector size. */ NodeCryptography.IV_LENGTH = 16; exports.default = NodeCryptography;