UNPKG

@mobile-wallet-protocol/client

Version:
185 lines (184 loc) 6.39 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.generateKeyPair = generateKeyPair; exports.deriveSharedSecret = deriveSharedSecret; exports.encrypt = encrypt; exports.decrypt = decrypt; exports.exportKeyToHexString = exportKeyToHexString; exports.importKeyFromHexString = importKeyFromHexString; exports.encryptContent = encryptContent; exports.decryptContent = decryptContent; const aes_1 = require("@noble/ciphers/aes"); const webcrypto_1 = require("@noble/ciphers/webcrypto"); const p256_1 = require("@noble/curves/p256"); const buffer_1 = require("buffer"); const fflate_1 = require("fflate"); const util_1 = require("../type/util"); async function generateKeyPair() { const privateKey = p256_1.secp256r1.utils.randomPrivateKey(); const publicKey = p256_1.secp256r1.getPublicKey(privateKey, false); return { privateKey: { type: 'private', algorithm: { name: 'ECDH', namedCurve: 'P-256', }, extractable: true, usages: ['deriveKey'], _key: privateKey, }, publicKey: { type: 'public', algorithm: { name: 'ECDH', namedCurve: 'P-256', }, extractable: true, usages: [], _key: publicKey, }, }; } async function deriveSharedSecret(ownPrivateKey, peerPublicKey) { const privateKeyRaw = ownPrivateKey._key; const publicKeyRaw = peerPublicKey._key; const sharedSecret = p256_1.secp256r1.getSharedSecret(privateKeyRaw, publicKeyRaw); const aesKey = sharedSecret.slice(1, 33); return { type: 'secret', algorithm: { name: 'AES-GCM', length: 256, }, extractable: false, usages: ['encrypt', 'decrypt'], _key: aesKey, }; } async function encrypt(sharedSecret, plainText) { const iv = (0, webcrypto_1.randomBytes)(12); const stream = (0, aes_1.gcm)(sharedSecret._key, iv); const plainTextBytes = new Uint8Array(buffer_1.Buffer.from(plainText, 'utf8')); const compressedBytes = (0, fflate_1.zlibSync)(plainTextBytes); const cipherText = stream.encrypt(compressedBytes); return { iv, cipherText, }; } async function decrypt(sharedSecret, { iv, cipherText }) { const stream = (0, aes_1.gcm)(new Uint8Array(sharedSecret._key), new Uint8Array(iv)); const compressedBytes = stream.decrypt(new Uint8Array(cipherText)); const plainTextBytes = (0, fflate_1.unzlibSync)(compressedBytes); return buffer_1.Buffer.from(plainTextBytes).toString('utf8'); } async function exportKeyToHexString(type, key) { let exported; if (type === 'public') { exported = encodeECPublicKey(key._key); } else { exported = key._key; } return (0, util_1.uint8ArrayToHex)(exported); } async function importKeyFromHexString(type, hexString) { const data = (0, util_1.hexStringToUint8Array)(hexString); const [, sequenceContent] = decodeDER(data, 0); let importedKey; if (type === 'public') { // The public key is the last 65 bytes of the sequence importedKey = sequenceContent.slice(-65); } else { if (!p256_1.secp256r1.utils.isValidPrivateKey(data)) { throw new Error('Invalid private key'); } importedKey = data; } return { type, algorithm: { name: 'ECDH', namedCurve: 'P-256', }, extractable: true, usages: type === 'private' ? ['deriveKey'] : [], _key: importedKey, }; } async function encryptContent(content, sharedSecret) { const serialized = JSON.stringify(content, (_, value) => { if (!(value instanceof Error)) return value; const error = value; return Object.assign(Object.assign({}, (error.code ? { code: error.code } : {})), { message: error.message }); }); return encrypt(sharedSecret, serialized); } async function decryptContent(encryptedData, sharedSecret) { return JSON.parse(await decrypt(sharedSecret, encryptedData)); } function encodeLength(length) { if (length < 128) { return new Uint8Array([length]); } const lengthBytes = []; while (length > 0) { lengthBytes.unshift(length & 0xff); length >>= 8; } return new Uint8Array([0x80 | lengthBytes.length, ...lengthBytes]); } function encodeDER(tag, value) { const length = encodeLength(value.length); return new Uint8Array([tag, ...length, ...value]); } function encodeSEQUENCE(...children) { const totalLength = children.reduce((sum, child) => sum + child.length, 0); const length = encodeLength(totalLength); return new Uint8Array([0x30, ...length, ...children.flatMap((child) => Array.from(child))]); } function encodeOID(oid) { const parts = oid.split('.').map(Number); const firstByte = 40 * parts[0] + parts[1]; const restBytes = parts.slice(2).flatMap((part) => { const bytes = []; do { bytes.unshift(part & 0x7f); part >>= 7; } while (part > 0); bytes[0] |= 0x80; bytes[bytes.length - 1] &= 0x7f; return bytes; }); return encodeDER(0x06, new Uint8Array([firstByte, ...restBytes])); } function encodeBITSTRING(data) { return encodeDER(0x03, new Uint8Array([0x00, ...data])); } function encodeECPublicKey(publicKey) { const algorithmIdentifier = encodeSEQUENCE(encodeOID('1.2.840.10045.2.1'), // ecPublicKey OID encodeOID('1.2.840.10045.3.1.7') // P-256 curve OID ); return encodeSEQUENCE(algorithmIdentifier, encodeBITSTRING(publicKey)); } function decodeLength(data, startIndex) { if (data[startIndex] < 128) { return [data[startIndex], 1]; } const octets = data[startIndex] & 0x7f; let length = 0; for (let i = 0; i < octets; i++) { length = (length << 8) | data[startIndex + 1 + i]; } return [length, 1 + octets]; } function decodeDER(data, startIndex) { const tag = data[startIndex]; const [length, lengthSize] = decodeLength(data, startIndex + 1); const contentStart = startIndex + 1 + lengthSize; const content = data.slice(contentStart, contentStart + length); return [tag, content, 1 + lengthSize + length]; }