UNPKG

blockstack

Version:

The Blockstack Javascript library for authentication, identity, and storage.

347 lines 12.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const tslib_1 = require("tslib"); const elliptic_1 = require("elliptic"); const cryptoRandom_1 = require("./cryptoRandom"); const errors_1 = require("../errors"); const keys_1 = require("../keys"); const sha2Hash_1 = require("./sha2Hash"); const hmacSha256_1 = require("./hmacSha256"); const aesCipher_1 = require("./aesCipher"); const utils_1 = require("../utils"); const ecurve = new elliptic_1.ec('secp256k1'); /** * @ignore */ function aes256CbcEncrypt(iv, key, plaintext) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const cipher = yield aesCipher_1.createCipher(); const result = yield cipher.encrypt('aes-256-cbc', key, iv, plaintext); return result; }); } exports.aes256CbcEncrypt = aes256CbcEncrypt; /** * @ignore */ function aes256CbcDecrypt(iv, key, ciphertext) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const cipher = yield aesCipher_1.createCipher(); const result = yield cipher.decrypt('aes-256-cbc', key, iv, ciphertext); return result; }); } /** * @ignore */ function hmacSha256(key, content) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const hmacSha256 = yield hmacSha256_1.createHmacSha256(); return hmacSha256.digest(key, content); }); } /** * @ignore */ function equalConstTime(b1, b2) { if (b1.length !== b2.length) { return false; } let res = 0; for (let i = 0; i < b1.length; i++) { res |= b1[i] ^ b2[i]; // jshint ignore:line } return res === 0; } /** * @ignore */ function sharedSecretToKeys(sharedSecret) { // generate mac and encryption key from shared secret const hashedSecret = sha2Hash_1.hashSha512Sync(sharedSecret); return { encryptionKey: hashedSecret.slice(0, 32), hmacKey: hashedSecret.slice(32) }; } /** * Hex encodes a 32-byte BN.js instance. * The result string is zero padded and always 64 characters in length. * @ignore */ function getHexFromBN(bnInput) { const hexOut = bnInput.toString('hex', 64); if (hexOut.length === 64) { return hexOut; } else if (hexOut.length < 64) { // pad with leading zeros // the padStart function would require node 9 const padding = '0'.repeat(64 - hexOut.length); return `${padding}${hexOut}`; } else { throw new Error('Generated a > 32-byte BN for encryption. Failing.'); } } exports.getHexFromBN = getHexFromBN; /** * Returns a big-endian encoded 32-byte BN.js instance. * The result Buffer is zero padded and always 32 bytes in length. * @ignore */ function getBufferFromBN(bnInput) { const result = bnInput.toArrayLike(Buffer, 'be', 32); if (result.byteLength !== 32) { throw new Error('Generated a 32-byte BN for encryption. Failing.'); } return result; } exports.getBufferFromBN = getBufferFromBN; /** * Get details about the JSON envelope size overhead for ciphertext payloads. * @ignore */ function getCipherObjectWrapper(opts) { // Placeholder structure of the ciphertext payload, used to determine the // stringified JSON overhead length. const shell = { iv: '', ephemeralPK: '', mac: '', cipherText: '', wasString: !!opts.wasString, }; if (opts.cipherTextEncoding === 'base64') { shell.cipherTextEncoding = 'base64'; } // Hex encoded 16 byte buffer. const ivLength = 32; // Hex encoded, compressed EC pubkey of 33 bytes. const ephemeralPKLength = 66; // Hex encoded 32 byte hmac-sha256. const macLength = 64; return { payloadValuesLength: ivLength + ephemeralPKLength + macLength, payloadShell: JSON.stringify(shell) }; } exports.getCipherObjectWrapper = getCipherObjectWrapper; /** * Get details about the JSON envelope size overhead for signed ciphertext payloads. * @param payloadShell - The JSON stringified empty `CipherObject` * @ignore */ function getSignedCipherObjectWrapper(payloadShell) { // Placeholder structure of the signed ciphertext payload, used to determine the // stringified JSON overhead length. const shell = { signature: '', publicKey: '', cipherText: payloadShell }; // Hex encoded DER signature, up to 72 byte length. const signatureLength = 144; // Hex encoded 33 byte public key. const publicKeyLength = 66; return { signedPayloadValuesLength: signatureLength + publicKeyLength, signedPayloadShell: JSON.stringify(shell) }; } exports.getSignedCipherObjectWrapper = getSignedCipherObjectWrapper; /** * Fast function that determines the final ASCII string byte length of the * JSON stringified ECIES encrypted payload. * @ignore */ function eciesGetJsonStringLength(opts) { const { payloadShell, payloadValuesLength } = getCipherObjectWrapper(opts); // Calculate the AES output length given the input length. const cipherTextLength = utils_1.getAesCbcOutputLength(opts.contentLength); // Get the encoded string length of the cipherText. let encodedCipherTextLength; if (!opts.cipherTextEncoding || opts.cipherTextEncoding === 'hex') { encodedCipherTextLength = (cipherTextLength * 2); } else if (opts.cipherTextEncoding === 'base64') { encodedCipherTextLength = utils_1.getBase64OutputLength(cipherTextLength); } else { throw new Error(`Unexpected cipherTextEncoding "${opts.cipherTextEncoding}"`); } if (!opts.sign) { // Add the length of the JSON envelope, ciphertext length, and length of const values. return payloadShell.length + payloadValuesLength + encodedCipherTextLength; } else { // Get the signed version of the JSON envelope const { signedPayloadShell, signedPayloadValuesLength } = getSignedCipherObjectWrapper(payloadShell); // Add length of the JSON envelope, ciphertext length, and length of the const values. return signedPayloadShell.length + signedPayloadValuesLength + payloadValuesLength + encodedCipherTextLength; } } exports.eciesGetJsonStringLength = eciesGetJsonStringLength; /** * Encrypt content to elliptic curve publicKey using ECIES * @param publicKey - secp256k1 public key hex string * @param content - content to encrypt * @return Object containing: * iv (initialization vector, hex encoding), * cipherText (cipher text either hex or base64 encoded), * mac (message authentication code, hex encoded), * ephemeral public key (hex encoded), * wasString (boolean indicating with or not to return a buffer or string on decrypt) * @private * @ignore */ function encryptECIES(publicKey, content, wasString, cipherTextEncoding) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const ecPK = ecurve.keyFromPublic(publicKey, 'hex').getPublic(); const ephemeralSK = ecurve.genKeyPair(); const ephemeralPK = Buffer.from(ephemeralSK.getPublic().encodeCompressed()); const sharedSecret = ephemeralSK.derive(ecPK); const sharedSecretBuffer = getBufferFromBN(sharedSecret); const sharedKeys = sharedSecretToKeys(sharedSecretBuffer); const initializationVector = cryptoRandom_1.randomBytes(16); const cipherText = yield aes256CbcEncrypt(initializationVector, sharedKeys.encryptionKey, content); const macData = Buffer.concat([initializationVector, ephemeralPK, cipherText]); const mac = yield hmacSha256(sharedKeys.hmacKey, macData); let cipherTextString; if (!cipherTextEncoding || cipherTextEncoding === 'hex') { cipherTextString = cipherText.toString('hex'); } else if (cipherTextEncoding === 'base64') { cipherTextString = cipherText.toString('base64'); } else { throw new Error(`Unexpected cipherTextEncoding "${cipherTextEncoding}"`); } const result = { iv: initializationVector.toString('hex'), ephemeralPK: ephemeralPK.toString('hex'), cipherText: cipherTextString, mac: mac.toString('hex'), wasString: !!wasString }; if (cipherTextEncoding && cipherTextEncoding !== 'hex') { result.cipherTextEncoding = cipherTextEncoding; } return result; }); } exports.encryptECIES = encryptECIES; /** * Decrypt content encrypted using ECIES * @param {String} privateKey - secp256k1 private key hex string * @param {Object} cipherObject - object to decrypt, should contain: * iv (initialization vector), cipherText (cipher text), * mac (message authentication code), ephemeralPublicKey * wasString (boolean indicating with or not to return a buffer or string on decrypt) * @return {Buffer} plaintext * @throws {FailedDecryptionError} if unable to decrypt * @private * @ignore */ function decryptECIES(privateKey, cipherObject) { return tslib_1.__awaiter(this, void 0, void 0, function* () { const ecSK = ecurve.keyFromPrivate(privateKey, 'hex'); let ephemeralPK = null; try { ephemeralPK = ecurve.keyFromPublic(cipherObject.ephemeralPK, 'hex').getPublic(); } catch (error) { throw new errors_1.FailedDecryptionError('Unable to get public key from cipher object. ' + 'You might be trying to decrypt an unencrypted object.'); } const sharedSecret = ecSK.derive(ephemeralPK); const sharedSecretBuffer = getBufferFromBN(sharedSecret); const sharedKeys = sharedSecretToKeys(sharedSecretBuffer); const ivBuffer = Buffer.from(cipherObject.iv, 'hex'); let cipherTextBuffer; if (!cipherObject.cipherTextEncoding || cipherObject.cipherTextEncoding === 'hex') { cipherTextBuffer = Buffer.from(cipherObject.cipherText, 'hex'); } else if (cipherObject.cipherTextEncoding === 'base64') { cipherTextBuffer = Buffer.from(cipherObject.cipherText, 'base64'); } else { throw new Error(`Unexpected cipherTextEncoding "${cipherObject.cipherText}"`); } const macData = Buffer.concat([ivBuffer, Buffer.from(ephemeralPK.encodeCompressed()), cipherTextBuffer]); const actualMac = yield hmacSha256(sharedKeys.hmacKey, macData); const expectedMac = Buffer.from(cipherObject.mac, 'hex'); if (!equalConstTime(expectedMac, actualMac)) { throw new errors_1.FailedDecryptionError('Decryption failed: failure in MAC check'); } const plainText = yield aes256CbcDecrypt(ivBuffer, sharedKeys.encryptionKey, cipherTextBuffer); if (cipherObject.wasString) { return plainText.toString(); } else { return plainText; } }); } exports.decryptECIES = decryptECIES; /** * Sign content using ECDSA * * @param {String} privateKey - secp256k1 private key hex string * @param {Object} content - content to sign * @return {Object} contains: * signature - Hex encoded DER signature * public key - Hex encoded private string taken from privateKey * @private * @ignore */ function signECDSA(privateKey, content) { const contentBuffer = content instanceof Buffer ? content : Buffer.from(content); const ecPrivate = ecurve.keyFromPrivate(privateKey, 'hex'); const publicKey = keys_1.getPublicKeyFromPrivate(privateKey); const contentHash = sha2Hash_1.hashSha256Sync(contentBuffer); const signature = ecPrivate.sign(contentHash); const signatureString = signature.toDER('hex'); return { signature: signatureString, publicKey }; } exports.signECDSA = signECDSA; /** * @ignore */ function getBuffer(content) { if (content instanceof Buffer) return content; else if (content instanceof ArrayBuffer) return Buffer.from(content); else return Buffer.from(content); } /** * Verify content using ECDSA * @param {String | Buffer} content - Content to verify was signed * @param {String} publicKey - secp256k1 private key hex string * @param {String} signature - Hex encoded DER signature * @return {Boolean} returns true when signature matches publickey + content, false if not * @private * @ignore */ function verifyECDSA(content, publicKey, signature) { const contentBuffer = getBuffer(content); const ecPublic = ecurve.keyFromPublic(publicKey, 'hex'); const contentHash = sha2Hash_1.hashSha256Sync(contentBuffer); return ecPublic.verify(contentHash, signature); } exports.verifyECDSA = verifyECDSA; //# sourceMappingURL=ec.js.map