UNPKG

react-native-quick-crypto

Version:

A fast implementation of Node's `crypto` module written in C/C++ JSI

164 lines (163 loc) 5.74 kB
"use strict"; import { NitroModules } from 'react-native-nitro-modules'; import { Buffer } from '@craftzdog/react-native-buffer'; import { binaryLikeToArrayBuffer as toAB, isStringOrBuffer, KFormatType, KeyEncoding } from '../utils'; import { isCryptoKey } from './utils'; import { KeyObject } from './classes'; import { constants } from '../constants'; function preparePublicCipherKey(key, isEncrypt) { let keyObj; let padding; let oaepHash; let oaepLabel; if (key instanceof KeyObject) { if (isEncrypt && key.type !== 'public') { throw new Error('publicEncrypt requires a public key'); } // publicDecrypt accepts both public and private keys (Node.js behavior) // A private key contains the public components needed for verify_recover keyObj = key; } else if (isCryptoKey(key)) { const cryptoKey = key; keyObj = cryptoKey.keyObject; } else if (isStringOrBuffer(key)) { const data = toAB(key); const isPem = typeof key === 'string' && key.includes('-----BEGIN'); const isPrivatePem = typeof key === 'string' && key.includes('-----BEGIN PRIVATE'); // publicDecrypt accepts both public and private keys (Node.js behavior) if (!isEncrypt && isPrivatePem) { keyObj = KeyObject.createKeyObject('private', data, KFormatType.PEM, KeyEncoding.PKCS8); } else { keyObj = KeyObject.createKeyObject('public', data, isPem ? KFormatType.PEM : KFormatType.DER, KeyEncoding.SPKI); } } else if (typeof key === 'object' && 'key' in key) { const options = key; const result = preparePublicCipherKey(options.key, isEncrypt); keyObj = result.keyHandle; padding = options.padding; oaepHash = options.oaepHash; if (options.oaepLabel) { oaepLabel = toAB(options.oaepLabel); } } else { throw new Error('Invalid key input'); } return { keyHandle: keyObj, padding, oaepHash, oaepLabel }; } export function publicEncrypt(key, buffer) { const { keyHandle, padding, oaepHash, oaepLabel } = preparePublicCipherKey(key, true); const rsaCipher = NitroModules.createHybridObject('RsaCipher'); const data = toAB(buffer); const paddingMode = padding ?? constants.RSA_PKCS1_OAEP_PADDING; const hashAlgorithm = oaepHash || 'sha1'; try { const encrypted = rsaCipher.encrypt(keyHandle.handle, data, paddingMode, hashAlgorithm, oaepLabel); return Buffer.from(encrypted); } catch (error) { throw new Error(`publicEncrypt failed: ${error.message}`); } } export function publicDecrypt(key, buffer) { const { keyHandle, padding } = preparePublicCipherKey(key, false); const rsaCipher = NitroModules.createHybridObject('RsaCipher'); const data = toAB(buffer); const paddingMode = padding ?? constants.RSA_PKCS1_PADDING; try { const decrypted = rsaCipher.publicDecrypt(keyHandle.handle, data, paddingMode); return Buffer.from(decrypted); } catch { // Bleichenbacher mitigation: surface a single, content-independent error // for every decrypt failure so an attacker cannot use error-message // differences as a padding oracle. The native side already collapses its // OpenSSL error codes to the same opaque message; we drop it here too // rather than re-leaking it via string interpolation. throw new Error('publicDecrypt failed'); } } function preparePrivateCipherKey(key, isEncrypt) { let keyObj; let padding; let oaepHash; let oaepLabel; if (key instanceof KeyObject) { if (isEncrypt && key.type !== 'private') { throw new Error('privateEncrypt requires a private key'); } if (!isEncrypt && key.type !== 'private') { throw new Error('privateDecrypt requires a private key'); } keyObj = key; } else if (isCryptoKey(key)) { const cryptoKey = key; keyObj = cryptoKey.keyObject; } else if (isStringOrBuffer(key)) { const data = toAB(key); const isPem = typeof key === 'string' && key.includes('-----BEGIN'); keyObj = KeyObject.createKeyObject('private', data, isPem ? KFormatType.PEM : KFormatType.DER, KeyEncoding.PKCS8); } else if (typeof key === 'object' && 'key' in key) { const options = key; const result = preparePrivateCipherKey(options.key, isEncrypt); keyObj = result.keyHandle; padding = options.padding; oaepHash = options.oaepHash; if (options.oaepLabel) { oaepLabel = toAB(options.oaepLabel); } } else { throw new Error('Invalid key input'); } return { keyHandle: keyObj, padding, oaepHash, oaepLabel }; } export function privateEncrypt(key, buffer) { const { keyHandle, padding } = preparePrivateCipherKey(key, true); const rsaCipher = NitroModules.createHybridObject('RsaCipher'); const data = toAB(buffer); const paddingMode = padding ?? constants.RSA_PKCS1_PADDING; try { const encrypted = rsaCipher.privateEncrypt(keyHandle.handle, data, paddingMode); return Buffer.from(encrypted); } catch (error) { throw new Error(`privateEncrypt failed: ${error.message}`); } } export function privateDecrypt(key, buffer) { const { keyHandle, padding, oaepHash, oaepLabel } = preparePrivateCipherKey(key, false); const rsaCipher = NitroModules.createHybridObject('RsaCipher'); const data = toAB(buffer); const paddingMode = padding ?? constants.RSA_PKCS1_OAEP_PADDING; const hashAlgorithm = oaepHash || 'sha1'; try { const decrypted = rsaCipher.privateDecrypt(keyHandle.handle, data, paddingMode, hashAlgorithm, oaepLabel); return Buffer.from(decrypted); } catch { // Bleichenbacher mitigation — see publicDecrypt above. throw new Error('privateDecrypt failed'); } } //# sourceMappingURL=publicCipher.js.map