UNPKG

@peculiar/webcrypto

Version:
1,281 lines (1,254 loc) 89 kB
/*! Copyright (c) Peculiar Ventures, LLC */ import * as core from 'webcrypto-core'; import { BufferSourceConverter as BufferSourceConverter$1 } from 'webcrypto-core'; export { CryptoKey } from 'webcrypto-core'; import { Buffer } from 'buffer'; import * as crypto from 'crypto'; import crypto__default from 'crypto'; import * as process from 'process'; import { __decorate } from 'tslib'; import { JsonProp, JsonPropTypes, JsonSerializer, JsonParser } from '@peculiar/json-schema'; import { Convert, BufferSourceConverter } from 'pvtsutils'; import { AsnParser, AsnSerializer } from '@peculiar/asn1-schema'; const JsonBase64UrlConverter = { fromJSON: (value) => Buffer.from(Convert.FromBase64Url(value)), toJSON: (value) => Convert.ToBase64Url(value), }; class CryptoKey extends core.CryptoKey { constructor() { super(...arguments); this.data = Buffer.alloc(0); this.algorithm = { name: "" }; this.extractable = false; this.type = "secret"; this.usages = []; this.kty = "oct"; this.alg = ""; } } __decorate([ JsonProp({ name: "ext", type: JsonPropTypes.Boolean, optional: true }) ], CryptoKey.prototype, "extractable", void 0); __decorate([ JsonProp({ name: "key_ops", type: JsonPropTypes.String, repeated: true, optional: true }) ], CryptoKey.prototype, "usages", void 0); __decorate([ JsonProp({ type: JsonPropTypes.String }) ], CryptoKey.prototype, "kty", void 0); __decorate([ JsonProp({ type: JsonPropTypes.String, optional: true }) ], CryptoKey.prototype, "alg", void 0); class SymmetricKey extends CryptoKey { constructor() { super(...arguments); this.kty = "oct"; this.type = "secret"; } } class AsymmetricKey extends CryptoKey { } class AesCryptoKey extends SymmetricKey { get alg() { switch (this.algorithm.name.toUpperCase()) { case "AES-CBC": return `A${this.algorithm.length}CBC`; case "AES-CTR": return `A${this.algorithm.length}CTR`; case "AES-GCM": return `A${this.algorithm.length}GCM`; case "AES-KW": return `A${this.algorithm.length}KW`; case "AES-CMAC": return `A${this.algorithm.length}CMAC`; case "AES-ECB": return `A${this.algorithm.length}ECB`; default: throw new core.AlgorithmError("Unsupported algorithm name"); } } set alg(value) { } } __decorate([ JsonProp({ name: "k", converter: JsonBase64UrlConverter }) ], AesCryptoKey.prototype, "data", void 0); class AesCrypto { static async generateKey(algorithm, extractable, keyUsages) { const key = new AesCryptoKey(); key.algorithm = algorithm; key.extractable = extractable; key.usages = keyUsages; key.data = crypto__default.randomBytes(algorithm.length >> 3); return key; } static async exportKey(format, key) { if (!(key instanceof AesCryptoKey)) { throw new Error("key: Is not AesCryptoKey"); } switch (format.toLowerCase()) { case "jwk": return JsonSerializer.toJSON(key); case "raw": return new Uint8Array(key.data).buffer; default: throw new core.OperationError("format: Must be 'jwk' or 'raw'"); } } static async importKey(format, keyData, algorithm, extractable, keyUsages) { let key; switch (format.toLowerCase()) { case "jwk": key = JsonParser.fromJSON(keyData, { targetSchema: AesCryptoKey }); break; case "raw": key = new AesCryptoKey(); key.data = Buffer.from(keyData); break; default: throw new core.OperationError("format: Must be 'jwk' or 'raw'"); } key.algorithm = algorithm; key.algorithm.length = key.data.length << 3; key.extractable = extractable; key.usages = keyUsages; switch (key.algorithm.length) { case 128: case 192: case 256: break; default: throw new core.OperationError("keyData: Is wrong key length"); } return key; } static async encrypt(algorithm, key, data) { switch (algorithm.name.toUpperCase()) { case "AES-CBC": return this.encryptAesCBC(algorithm, key, Buffer.from(data)); case "AES-CTR": return this.encryptAesCTR(algorithm, key, Buffer.from(data)); case "AES-GCM": return this.encryptAesGCM(algorithm, key, Buffer.from(data)); case "AES-KW": return this.encryptAesKW(algorithm, key, Buffer.from(data)); case "AES-ECB": return this.encryptAesECB(algorithm, key, Buffer.from(data)); default: throw new core.OperationError("algorithm: Is not recognized"); } } static async decrypt(algorithm, key, data) { if (!(key instanceof AesCryptoKey)) { throw new Error("key: Is not AesCryptoKey"); } switch (algorithm.name.toUpperCase()) { case "AES-CBC": return this.decryptAesCBC(algorithm, key, Buffer.from(data)); case "AES-CTR": return this.decryptAesCTR(algorithm, key, Buffer.from(data)); case "AES-GCM": return this.decryptAesGCM(algorithm, key, Buffer.from(data)); case "AES-KW": return this.decryptAesKW(algorithm, key, Buffer.from(data)); case "AES-ECB": return this.decryptAesECB(algorithm, key, Buffer.from(data)); default: throw new core.OperationError("algorithm: Is not recognized"); } } static async encryptAesCBC(algorithm, key, data) { const cipher = crypto__default.createCipheriv(`aes-${key.algorithm.length}-cbc`, key.data, new Uint8Array(algorithm.iv)); let enc = cipher.update(data); enc = Buffer.concat([enc, cipher.final()]); const res = new Uint8Array(enc).buffer; return res; } static async decryptAesCBC(algorithm, key, data) { const decipher = crypto__default.createDecipheriv(`aes-${key.algorithm.length}-cbc`, key.data, new Uint8Array(algorithm.iv)); let dec = decipher.update(data); dec = Buffer.concat([dec, decipher.final()]); return new Uint8Array(dec).buffer; } static async encryptAesCTR(algorithm, key, data) { const cipher = crypto__default.createCipheriv(`aes-${key.algorithm.length}-ctr`, key.data, Buffer.from(algorithm.counter)); let enc = cipher.update(data); enc = Buffer.concat([enc, cipher.final()]); const res = new Uint8Array(enc).buffer; return res; } static async decryptAesCTR(algorithm, key, data) { const decipher = crypto__default.createDecipheriv(`aes-${key.algorithm.length}-ctr`, key.data, new Uint8Array(algorithm.counter)); let dec = decipher.update(data); dec = Buffer.concat([dec, decipher.final()]); return new Uint8Array(dec).buffer; } static async encryptAesGCM(algorithm, key, data) { const cipher = crypto__default.createCipheriv(`aes-${key.algorithm.length}-gcm`, key.data, Buffer.from(algorithm.iv), { authTagLength: (algorithm.tagLength || 128) >> 3, }); if (algorithm.additionalData) { cipher.setAAD(Buffer.from(algorithm.additionalData)); } let enc = cipher.update(data); enc = Buffer.concat([enc, cipher.final(), cipher.getAuthTag()]); const res = new Uint8Array(enc).buffer; return res; } static async decryptAesGCM(algorithm, key, data) { const tagLength = (algorithm.tagLength || 128) >> 3; const decipher = crypto__default.createDecipheriv(`aes-${key.algorithm.length}-gcm`, key.data, new Uint8Array(algorithm.iv), { authTagLength: tagLength, }); const enc = data.slice(0, data.length - tagLength); const tag = data.slice(data.length - tagLength); if (algorithm.additionalData) { decipher.setAAD(Buffer.from(algorithm.additionalData)); } decipher.setAuthTag(tag); let dec = decipher.update(enc); dec = Buffer.concat([dec, decipher.final()]); return new Uint8Array(dec).buffer; } static async encryptAesKW(algorithm, key, data) { const cipher = crypto__default.createCipheriv(`id-aes${key.algorithm.length}-wrap`, key.data, this.AES_KW_IV); let enc = cipher.update(data); enc = Buffer.concat([enc, cipher.final()]); return new Uint8Array(enc).buffer; } static async decryptAesKW(algorithm, key, data) { const decipher = crypto__default.createDecipheriv(`id-aes${key.algorithm.length}-wrap`, key.data, this.AES_KW_IV); let dec = decipher.update(data); dec = Buffer.concat([dec, decipher.final()]); return new Uint8Array(dec).buffer; } static async encryptAesECB(algorithm, key, data) { const cipher = crypto__default.createCipheriv(`aes-${key.algorithm.length}-ecb`, key.data, new Uint8Array(0)); let enc = cipher.update(data); enc = Buffer.concat([enc, cipher.final()]); const res = new Uint8Array(enc).buffer; return res; } static async decryptAesECB(algorithm, key, data) { const decipher = crypto__default.createDecipheriv(`aes-${key.algorithm.length}-ecb`, key.data, new Uint8Array(0)); let dec = decipher.update(data); dec = Buffer.concat([dec, decipher.final()]); return new Uint8Array(dec).buffer; } } AesCrypto.AES_KW_IV = Buffer.from("A6A6A6A6A6A6A6A6", "hex"); const keyStorage = new WeakMap(); function getCryptoKey(key) { const res = keyStorage.get(key); if (!res) { throw new core.OperationError("Cannot get CryptoKey from secure storage"); } return res; } function setCryptoKey(value) { const key = core.CryptoKey.create(value.algorithm, value.type, value.extractable, value.usages); Object.freeze(key); keyStorage.set(key, value); return key; } class AesCbcProvider extends core.AesCbcProvider { async onGenerateKey(algorithm, extractable, keyUsages) { const key = await AesCrypto.generateKey({ name: this.name, length: algorithm.length, }, extractable, keyUsages); return setCryptoKey(key); } async onEncrypt(algorithm, key, data) { return AesCrypto.encrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onDecrypt(algorithm, key, data) { return AesCrypto.decrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onExportKey(format, key) { return AesCrypto.exportKey(format, getCryptoKey(key)); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { const key = await AesCrypto.importKey(format, keyData, { name: algorithm.name }, extractable, keyUsages); return setCryptoKey(key); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); if (!(getCryptoKey(key) instanceof AesCryptoKey)) { throw new TypeError("key: Is not a AesCryptoKey"); } } } const zero = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); const rb = Buffer.from([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 135]); const blockSize = 16; function bitShiftLeft(buffer) { const shifted = Buffer.alloc(buffer.length); const last = buffer.length - 1; for (let index = 0; index < last; index++) { shifted[index] = buffer[index] << 1; if (buffer[index + 1] & 0x80) { shifted[index] += 0x01; } } shifted[last] = buffer[last] << 1; return shifted; } function xor(a, b) { const length = Math.min(a.length, b.length); const output = Buffer.alloc(length); for (let index = 0; index < length; index++) { output[index] = a[index] ^ b[index]; } return output; } function aes(key, message) { const cipher = crypto.createCipheriv(`aes${key.length << 3}`, key, zero); const result = cipher.update(message); cipher.final(); return result; } function getMessageBlock(message, blockIndex) { const block = Buffer.alloc(blockSize); const start = blockIndex * blockSize; const end = start + blockSize; message.copy(block, 0, start, end); return block; } function getPaddedMessageBlock(message, blockIndex) { const block = Buffer.alloc(blockSize); const start = blockIndex * blockSize; const end = message.length; block.fill(0); message.copy(block, 0, start, end); block[end - start] = 0x80; return block; } function generateSubkeys(key) { const l = aes(key, zero); let subkey1 = bitShiftLeft(l); if (l[0] & 0x80) { subkey1 = xor(subkey1, rb); } let subkey2 = bitShiftLeft(subkey1); if (subkey1[0] & 0x80) { subkey2 = xor(subkey2, rb); } return { subkey1, subkey2 }; } function aesCmac(key, message) { const subkeys = generateSubkeys(key); let blockCount = Math.ceil(message.length / blockSize); let lastBlockCompleteFlag; let lastBlock; if (blockCount === 0) { blockCount = 1; lastBlockCompleteFlag = false; } else { lastBlockCompleteFlag = (message.length % blockSize === 0); } const lastBlockIndex = blockCount - 1; if (lastBlockCompleteFlag) { lastBlock = xor(getMessageBlock(message, lastBlockIndex), subkeys.subkey1); } else { lastBlock = xor(getPaddedMessageBlock(message, lastBlockIndex), subkeys.subkey2); } let x = zero; let y; for (let index = 0; index < lastBlockIndex; index++) { y = xor(x, getMessageBlock(message, index)); x = aes(key, y); } y = xor(lastBlock, x); return aes(key, y); } class AesCmacProvider extends core.AesCmacProvider { async onGenerateKey(algorithm, extractable, keyUsages) { const key = await AesCrypto.generateKey({ name: this.name, length: algorithm.length, }, extractable, keyUsages); return setCryptoKey(key); } async onSign(algorithm, key, data) { const result = aesCmac(getCryptoKey(key).data, Buffer.from(data)); return new Uint8Array(result).buffer; } async onVerify(algorithm, key, signature, data) { const signature2 = await this.sign(algorithm, key, data); return Buffer.from(signature).compare(Buffer.from(signature2)) === 0; } async onExportKey(format, key) { return AesCrypto.exportKey(format, getCryptoKey(key)); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { const res = await AesCrypto.importKey(format, keyData, { name: algorithm.name }, extractable, keyUsages); return setCryptoKey(res); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); if (!(getCryptoKey(key) instanceof AesCryptoKey)) { throw new TypeError("key: Is not a AesCryptoKey"); } } } class AesCtrProvider extends core.AesCtrProvider { async onGenerateKey(algorithm, extractable, keyUsages) { const key = await AesCrypto.generateKey({ name: this.name, length: algorithm.length, }, extractable, keyUsages); return setCryptoKey(key); } async onEncrypt(algorithm, key, data) { return AesCrypto.encrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onDecrypt(algorithm, key, data) { return AesCrypto.decrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onExportKey(format, key) { return AesCrypto.exportKey(format, getCryptoKey(key)); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { const res = await AesCrypto.importKey(format, keyData, { name: algorithm.name }, extractable, keyUsages); return setCryptoKey(res); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); if (!(getCryptoKey(key) instanceof AesCryptoKey)) { throw new TypeError("key: Is not a AesCryptoKey"); } } } class AesGcmProvider extends core.AesGcmProvider { async onGenerateKey(algorithm, extractable, keyUsages) { const key = await AesCrypto.generateKey({ name: this.name, length: algorithm.length, }, extractable, keyUsages); return setCryptoKey(key); } async onEncrypt(algorithm, key, data) { return AesCrypto.encrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onDecrypt(algorithm, key, data) { return AesCrypto.decrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onExportKey(format, key) { return AesCrypto.exportKey(format, getCryptoKey(key)); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { const res = await AesCrypto.importKey(format, keyData, { name: algorithm.name }, extractable, keyUsages); return setCryptoKey(res); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); if (!(getCryptoKey(key) instanceof AesCryptoKey)) { throw new TypeError("key: Is not a AesCryptoKey"); } } } class AesKwProvider extends core.AesKwProvider { async onGenerateKey(algorithm, extractable, keyUsages) { const res = await AesCrypto.generateKey({ name: this.name, length: algorithm.length, }, extractable, keyUsages); return setCryptoKey(res); } async onExportKey(format, key) { return AesCrypto.exportKey(format, getCryptoKey(key)); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { const res = await AesCrypto.importKey(format, keyData, { name: algorithm.name }, extractable, keyUsages); return setCryptoKey(res); } async onEncrypt(algorithm, key, data) { return AesCrypto.encrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onDecrypt(algorithm, key, data) { return AesCrypto.decrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); if (!(getCryptoKey(key) instanceof AesCryptoKey)) { throw new TypeError("key: Is not a AesCryptoKey"); } } } class AesEcbProvider extends core.AesEcbProvider { async onGenerateKey(algorithm, extractable, keyUsages) { const key = await AesCrypto.generateKey({ name: this.name, length: algorithm.length, }, extractable, keyUsages); return setCryptoKey(key); } async onEncrypt(algorithm, key, data) { return AesCrypto.encrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onDecrypt(algorithm, key, data) { return AesCrypto.decrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onExportKey(format, key) { return AesCrypto.exportKey(format, getCryptoKey(key)); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { const res = await AesCrypto.importKey(format, keyData, { name: algorithm.name }, extractable, keyUsages); return setCryptoKey(res); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); if (!(getCryptoKey(key) instanceof AesCryptoKey)) { throw new TypeError("key: Is not a AesCryptoKey"); } } } class DesCryptoKey extends SymmetricKey { get alg() { switch (this.algorithm.name.toUpperCase()) { case "DES-CBC": return `DES-CBC`; case "DES-EDE3-CBC": return `3DES-CBC`; default: throw new core.AlgorithmError("Unsupported algorithm name"); } } set alg(value) { } } __decorate([ JsonProp({ name: "k", converter: JsonBase64UrlConverter }) ], DesCryptoKey.prototype, "data", void 0); class DesCrypto { static async generateKey(algorithm, extractable, keyUsages) { const key = new DesCryptoKey(); key.algorithm = algorithm; key.extractable = extractable; key.usages = keyUsages; key.data = crypto__default.randomBytes(algorithm.length >> 3); return key; } static async exportKey(format, key) { switch (format.toLowerCase()) { case "jwk": return JsonSerializer.toJSON(key); case "raw": return new Uint8Array(key.data).buffer; default: throw new core.OperationError("format: Must be 'jwk' or 'raw'"); } } static async importKey(format, keyData, algorithm, extractable, keyUsages) { let key; switch (format.toLowerCase()) { case "jwk": key = JsonParser.fromJSON(keyData, { targetSchema: DesCryptoKey }); break; case "raw": key = new DesCryptoKey(); key.data = Buffer.from(keyData); break; default: throw new core.OperationError("format: Must be 'jwk' or 'raw'"); } key.algorithm = algorithm; key.extractable = extractable; key.usages = keyUsages; return key; } static async encrypt(algorithm, key, data) { switch (algorithm.name.toUpperCase()) { case "DES-CBC": return this.encryptDesCBC(algorithm, key, Buffer.from(data)); case "DES-EDE3-CBC": return this.encryptDesEDE3CBC(algorithm, key, Buffer.from(data)); default: throw new core.OperationError("algorithm: Is not recognized"); } } static async decrypt(algorithm, key, data) { if (!(key instanceof DesCryptoKey)) { throw new Error("key: Is not DesCryptoKey"); } switch (algorithm.name.toUpperCase()) { case "DES-CBC": return this.decryptDesCBC(algorithm, key, Buffer.from(data)); case "DES-EDE3-CBC": return this.decryptDesEDE3CBC(algorithm, key, Buffer.from(data)); default: throw new core.OperationError("algorithm: Is not recognized"); } } static async encryptDesCBC(algorithm, key, data) { const cipher = crypto__default.createCipheriv(`des-cbc`, key.data, new Uint8Array(algorithm.iv)); let enc = cipher.update(data); enc = Buffer.concat([enc, cipher.final()]); const res = new Uint8Array(enc).buffer; return res; } static async decryptDesCBC(algorithm, key, data) { const decipher = crypto__default.createDecipheriv(`des-cbc`, key.data, new Uint8Array(algorithm.iv)); let dec = decipher.update(data); dec = Buffer.concat([dec, decipher.final()]); return new Uint8Array(dec).buffer; } static async encryptDesEDE3CBC(algorithm, key, data) { const cipher = crypto__default.createCipheriv(`des-ede3-cbc`, key.data, Buffer.from(algorithm.iv)); let enc = cipher.update(data); enc = Buffer.concat([enc, cipher.final()]); const res = new Uint8Array(enc).buffer; return res; } static async decryptDesEDE3CBC(algorithm, key, data) { const decipher = crypto__default.createDecipheriv(`des-ede3-cbc`, key.data, new Uint8Array(algorithm.iv)); let dec = decipher.update(data); dec = Buffer.concat([dec, decipher.final()]); return new Uint8Array(dec).buffer; } } class DesCbcProvider extends core.DesProvider { constructor() { super(...arguments); this.keySizeBits = 64; this.ivSize = 8; this.name = "DES-CBC"; } async onGenerateKey(algorithm, extractable, keyUsages) { const key = await DesCrypto.generateKey({ name: this.name, length: this.keySizeBits, }, extractable, keyUsages); return setCryptoKey(key); } async onEncrypt(algorithm, key, data) { return DesCrypto.encrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onDecrypt(algorithm, key, data) { return DesCrypto.decrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onExportKey(format, key) { return DesCrypto.exportKey(format, getCryptoKey(key)); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { const key = await DesCrypto.importKey(format, keyData, { name: this.name, length: this.keySizeBits }, extractable, keyUsages); if (key.data.length !== (this.keySizeBits >> 3)) { throw new core.OperationError("keyData: Wrong key size"); } return setCryptoKey(key); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); if (!(getCryptoKey(key) instanceof DesCryptoKey)) { throw new TypeError("key: Is not a DesCryptoKey"); } } } class DesEde3CbcProvider extends core.DesProvider { constructor() { super(...arguments); this.keySizeBits = 192; this.ivSize = 8; this.name = "DES-EDE3-CBC"; } async onGenerateKey(algorithm, extractable, keyUsages) { const key = await DesCrypto.generateKey({ name: this.name, length: this.keySizeBits, }, extractable, keyUsages); return setCryptoKey(key); } async onEncrypt(algorithm, key, data) { return DesCrypto.encrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onDecrypt(algorithm, key, data) { return DesCrypto.decrypt(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onExportKey(format, key) { return DesCrypto.exportKey(format, getCryptoKey(key)); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { const key = await DesCrypto.importKey(format, keyData, { name: this.name, length: this.keySizeBits }, extractable, keyUsages); if (key.data.length !== (this.keySizeBits >> 3)) { throw new core.OperationError("keyData: Wrong key size"); } return setCryptoKey(key); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); if (!(getCryptoKey(key) instanceof DesCryptoKey)) { throw new TypeError("key: Is not a DesCryptoKey"); } } } function getJwkAlgorithm(algorithm) { switch (algorithm.name.toUpperCase()) { case "RSA-OAEP": { const mdSize = /(\d+)$/.exec(algorithm.hash.name)[1]; return `RSA-OAEP${mdSize !== "1" ? `-${mdSize}` : ""}`; } case "RSASSA-PKCS1-V1_5": return `RS${/(\d+)$/.exec(algorithm.hash.name)[1]}`; case "RSA-PSS": return `PS${/(\d+)$/.exec(algorithm.hash.name)[1]}`; case "RSA-PKCS1": return `RS1`; default: throw new core.OperationError("algorithm: Is not recognized"); } } class RsaPrivateKey extends AsymmetricKey { constructor() { super(...arguments); this.type = "private"; } getKey() { const keyInfo = AsnParser.parse(this.data, core.asn1.PrivateKeyInfo); return AsnParser.parse(keyInfo.privateKey, core.asn1.RsaPrivateKey); } toJSON() { const key = this.getKey(); const json = { kty: "RSA", alg: getJwkAlgorithm(this.algorithm), key_ops: this.usages, ext: this.extractable, }; return Object.assign(json, JsonSerializer.toJSON(key)); } fromJSON(json) { const key = JsonParser.fromJSON(json, { targetSchema: core.asn1.RsaPrivateKey }); const keyInfo = new core.asn1.PrivateKeyInfo(); keyInfo.privateKeyAlgorithm.algorithm = "1.2.840.113549.1.1.1"; keyInfo.privateKeyAlgorithm.parameters = null; keyInfo.privateKey = AsnSerializer.serialize(key); this.data = Buffer.from(AsnSerializer.serialize(keyInfo)); } } class RsaPublicKey extends AsymmetricKey { constructor() { super(...arguments); this.type = "public"; } getKey() { const keyInfo = AsnParser.parse(this.data, core.asn1.PublicKeyInfo); return AsnParser.parse(keyInfo.publicKey, core.asn1.RsaPublicKey); } toJSON() { const key = this.getKey(); const json = { kty: "RSA", alg: getJwkAlgorithm(this.algorithm), key_ops: this.usages, ext: this.extractable, }; return Object.assign(json, JsonSerializer.toJSON(key)); } fromJSON(json) { const key = JsonParser.fromJSON(json, { targetSchema: core.asn1.RsaPublicKey }); const keyInfo = new core.asn1.PublicKeyInfo(); keyInfo.publicKeyAlgorithm.algorithm = "1.2.840.113549.1.1.1"; keyInfo.publicKeyAlgorithm.parameters = null; keyInfo.publicKey = AsnSerializer.serialize(key); this.data = Buffer.from(AsnSerializer.serialize(keyInfo)); } } class RsaCrypto { static async generateKey(algorithm, extractable, keyUsages) { const privateKey = new RsaPrivateKey(); privateKey.algorithm = algorithm; privateKey.extractable = extractable; privateKey.usages = keyUsages.filter((usage) => this.privateKeyUsages.indexOf(usage) !== -1); const publicKey = new RsaPublicKey(); publicKey.algorithm = algorithm; publicKey.extractable = true; publicKey.usages = keyUsages.filter((usage) => this.publicKeyUsages.indexOf(usage) !== -1); const publicExponent = Buffer.concat([ Buffer.alloc(4 - algorithm.publicExponent.byteLength, 0), Buffer.from(algorithm.publicExponent), ]).readInt32BE(0); const keys = crypto__default.generateKeyPairSync("rsa", { modulusLength: algorithm.modulusLength, publicExponent, publicKeyEncoding: { format: "der", type: "spki", }, privateKeyEncoding: { format: "der", type: "pkcs8", }, }); privateKey.data = keys.privateKey; publicKey.data = keys.publicKey; const res = { privateKey, publicKey, }; return res; } static async exportKey(format, key) { switch (format.toLowerCase()) { case "jwk": return JsonSerializer.toJSON(key); case "pkcs8": case "spki": return new Uint8Array(key.data).buffer; default: throw new core.OperationError("format: Must be 'jwk', 'pkcs8' or 'spki'"); } } static async importKey(format, keyData, algorithm, extractable, keyUsages) { switch (format.toLowerCase()) { case "jwk": { const jwk = keyData; if (jwk.d) { const asnKey = JsonParser.fromJSON(keyData, { targetSchema: core.asn1.RsaPrivateKey }); return this.importPrivateKey(asnKey, algorithm, extractable, keyUsages); } else { const asnKey = JsonParser.fromJSON(keyData, { targetSchema: core.asn1.RsaPublicKey }); return this.importPublicKey(asnKey, algorithm, extractable, keyUsages); } } case "spki": { const keyInfo = AsnParser.parse(new Uint8Array(keyData), core.asn1.PublicKeyInfo); const asnKey = AsnParser.parse(keyInfo.publicKey, core.asn1.RsaPublicKey); return this.importPublicKey(asnKey, algorithm, extractable, keyUsages); } case "pkcs8": { const keyInfo = AsnParser.parse(new Uint8Array(keyData), core.asn1.PrivateKeyInfo); const asnKey = AsnParser.parse(keyInfo.privateKey, core.asn1.RsaPrivateKey); return this.importPrivateKey(asnKey, algorithm, extractable, keyUsages); } default: throw new core.OperationError("format: Must be 'jwk', 'pkcs8' or 'spki'"); } } static async sign(algorithm, key, data) { switch (algorithm.name.toUpperCase()) { case "RSA-PSS": case "RSASSA-PKCS1-V1_5": return this.signRsa(algorithm, key, data); default: throw new core.OperationError("algorithm: Is not recognized"); } } static async verify(algorithm, key, signature, data) { switch (algorithm.name.toUpperCase()) { case "RSA-PSS": case "RSASSA-PKCS1-V1_5": return this.verifySSA(algorithm, key, data, signature); default: throw new core.OperationError("algorithm: Is not recognized"); } } static async encrypt(algorithm, key, data) { switch (algorithm.name.toUpperCase()) { case "RSA-OAEP": return this.encryptOAEP(algorithm, key, data); default: throw new core.OperationError("algorithm: Is not recognized"); } } static async decrypt(algorithm, key, data) { switch (algorithm.name.toUpperCase()) { case "RSA-OAEP": return this.decryptOAEP(algorithm, key, data); default: throw new core.OperationError("algorithm: Is not recognized"); } } static importPrivateKey(asnKey, algorithm, extractable, keyUsages) { const keyInfo = new core.asn1.PrivateKeyInfo(); keyInfo.privateKeyAlgorithm.algorithm = "1.2.840.113549.1.1.1"; keyInfo.privateKeyAlgorithm.parameters = null; keyInfo.privateKey = AsnSerializer.serialize(asnKey); const key = new RsaPrivateKey(); key.data = Buffer.from(AsnSerializer.serialize(keyInfo)); key.algorithm = Object.assign({}, algorithm); key.algorithm.publicExponent = new Uint8Array(asnKey.publicExponent); key.algorithm.modulusLength = asnKey.modulus.byteLength << 3; key.extractable = extractable; key.usages = keyUsages; return key; } static importPublicKey(asnKey, algorithm, extractable, keyUsages) { const keyInfo = new core.asn1.PublicKeyInfo(); keyInfo.publicKeyAlgorithm.algorithm = "1.2.840.113549.1.1.1"; keyInfo.publicKeyAlgorithm.parameters = null; keyInfo.publicKey = AsnSerializer.serialize(asnKey); const key = new RsaPublicKey(); key.data = Buffer.from(AsnSerializer.serialize(keyInfo)); key.algorithm = Object.assign({}, algorithm); key.algorithm.publicExponent = new Uint8Array(asnKey.publicExponent); key.algorithm.modulusLength = asnKey.modulus.byteLength << 3; key.extractable = extractable; key.usages = keyUsages; return key; } static getCryptoAlgorithm(alg) { switch (alg.hash.name.toUpperCase()) { case "SHA-1": return "RSA-SHA1"; case "SHA-256": return "RSA-SHA256"; case "SHA-384": return "RSA-SHA384"; case "SHA-512": return "RSA-SHA512"; case "SHA3-256": return "RSA-SHA3-256"; case "SHA3-384": return "RSA-SHA3-384"; case "SHA3-512": return "RSA-SHA3-512"; default: throw new core.OperationError("algorithm.hash: Is not recognized"); } } static signRsa(algorithm, key, data) { const cryptoAlg = this.getCryptoAlgorithm(key.algorithm); const signer = crypto__default.createSign(cryptoAlg); signer.update(Buffer.from(data)); if (!key.pem) { key.pem = `-----BEGIN PRIVATE KEY-----\n${key.data.toString("base64")}\n-----END PRIVATE KEY-----`; } const options = { key: key.pem, }; if (algorithm.name.toUpperCase() === "RSA-PSS") { options.padding = crypto__default.constants.RSA_PKCS1_PSS_PADDING; options.saltLength = algorithm.saltLength; } const signature = signer.sign(options); return new Uint8Array(signature).buffer; } static verifySSA(algorithm, key, data, signature) { const cryptoAlg = this.getCryptoAlgorithm(key.algorithm); const signer = crypto__default.createVerify(cryptoAlg); signer.update(Buffer.from(data)); if (!key.pem) { key.pem = `-----BEGIN PUBLIC KEY-----\n${key.data.toString("base64")}\n-----END PUBLIC KEY-----`; } const options = { key: key.pem, }; if (algorithm.name.toUpperCase() === "RSA-PSS") { options.padding = crypto__default.constants.RSA_PKCS1_PSS_PADDING; options.saltLength = algorithm.saltLength; } const ok = signer.verify(options, signature); return ok; } static encryptOAEP(algorithm, key, data) { const options = { key: `-----BEGIN PUBLIC KEY-----\n${key.data.toString("base64")}\n-----END PUBLIC KEY-----`, padding: crypto__default.constants.RSA_PKCS1_OAEP_PADDING, }; if (algorithm.label) ; return new Uint8Array(crypto__default.publicEncrypt(options, data)).buffer; } static decryptOAEP(algorithm, key, data) { const options = { key: `-----BEGIN PRIVATE KEY-----\n${key.data.toString("base64")}\n-----END PRIVATE KEY-----`, padding: crypto__default.constants.RSA_PKCS1_OAEP_PADDING, }; if (algorithm.label) ; return new Uint8Array(crypto__default.privateDecrypt(options, data)).buffer; } } RsaCrypto.publicKeyUsages = ["verify", "encrypt", "wrapKey"]; RsaCrypto.privateKeyUsages = ["sign", "decrypt", "unwrapKey"]; class RsaSsaProvider extends core.RsaSsaProvider { constructor() { super(...arguments); this.hashAlgorithms = [ "SHA-1", "SHA-256", "SHA-384", "SHA-512", "shake128", "shake256", "SHA3-256", "SHA3-384", "SHA3-512" ]; } async onGenerateKey(algorithm, extractable, keyUsages) { const keys = await RsaCrypto.generateKey({ ...algorithm, name: this.name, }, extractable, keyUsages); return { privateKey: setCryptoKey(keys.privateKey), publicKey: setCryptoKey(keys.publicKey), }; } async onSign(algorithm, key, data) { return RsaCrypto.sign(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onVerify(algorithm, key, signature, data) { return RsaCrypto.verify(algorithm, getCryptoKey(key), new Uint8Array(signature), new Uint8Array(data)); } async onExportKey(format, key) { return RsaCrypto.exportKey(format, getCryptoKey(key)); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { const key = await RsaCrypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages); return setCryptoKey(key); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); const internalKey = getCryptoKey(key); if (!(internalKey instanceof RsaPrivateKey || internalKey instanceof RsaPublicKey)) { throw new TypeError("key: Is not RSA CryptoKey"); } } } class RsaPssProvider extends core.RsaPssProvider { constructor() { super(...arguments); this.hashAlgorithms = [ "SHA-1", "SHA-256", "SHA-384", "SHA-512", "shake128", "shake256", "SHA3-256", "SHA3-384", "SHA3-512" ]; } async onGenerateKey(algorithm, extractable, keyUsages) { const keys = await RsaCrypto.generateKey({ ...algorithm, name: this.name, }, extractable, keyUsages); return { privateKey: setCryptoKey(keys.privateKey), publicKey: setCryptoKey(keys.publicKey), }; } async onSign(algorithm, key, data) { return RsaCrypto.sign(algorithm, getCryptoKey(key), new Uint8Array(data)); } async onVerify(algorithm, key, signature, data) { return RsaCrypto.verify(algorithm, getCryptoKey(key), new Uint8Array(signature), new Uint8Array(data)); } async onExportKey(format, key) { return RsaCrypto.exportKey(format, getCryptoKey(key)); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { const key = await RsaCrypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages); return setCryptoKey(key); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); const internalKey = getCryptoKey(key); if (!(internalKey instanceof RsaPrivateKey || internalKey instanceof RsaPublicKey)) { throw new TypeError("key: Is not RSA CryptoKey"); } } } class ShaCrypto { static size(algorithm) { switch (algorithm.name.toUpperCase()) { case "SHA-1": return 160; case "SHA-256": case "SHA3-256": return 256; case "SHA-384": case "SHA3-384": return 384; case "SHA-512": case "SHA3-512": return 512; default: throw new Error("Unrecognized name"); } } static getAlgorithmName(algorithm) { switch (algorithm.name.toUpperCase()) { case "SHA-1": return "sha1"; case "SHA-256": return "sha256"; case "SHA-384": return "sha384"; case "SHA-512": return "sha512"; case "SHA3-256": return "sha3-256"; case "SHA3-384": return "sha3-384"; case "SHA3-512": return "sha3-512"; default: throw new Error("Unrecognized name"); } } static digest(algorithm, data) { const hashAlg = this.getAlgorithmName(algorithm); const hash = crypto__default.createHash(hashAlg) .update(Buffer.from(data)).digest(); return new Uint8Array(hash).buffer; } } class RsaOaepProvider extends core.RsaOaepProvider { async onGenerateKey(algorithm, extractable, keyUsages) { const keys = await RsaCrypto.generateKey({ ...algorithm, name: this.name, }, extractable, keyUsages); return { privateKey: setCryptoKey(keys.privateKey), publicKey: setCryptoKey(keys.publicKey), }; } async onEncrypt(algorithm, key, data) { const internalKey = getCryptoKey(key); const dataView = new Uint8Array(data); const keySize = Math.ceil(internalKey.algorithm.modulusLength >> 3); const hashSize = ShaCrypto.size(internalKey.algorithm.hash) >> 3; const dataLength = dataView.byteLength; const psLength = keySize - dataLength - 2 * hashSize - 2; if (dataLength > keySize - 2 * hashSize - 2) { throw new Error("Data too large"); } const message = new Uint8Array(keySize); const seed = message.subarray(1, hashSize + 1); const dataBlock = message.subarray(hashSize + 1); dataBlock.set(dataView, hashSize + psLength + 1); const labelHash = crypto__default.createHash(internalKey.algorithm.hash.name.replace("-", "")) .update(core.BufferSourceConverter.toUint8Array(algorithm.label || new Uint8Array(0))) .digest(); dataBlock.set(labelHash, 0); dataBlock[hashSize + psLength] = 1; crypto__default.randomFillSync(seed); const dataBlockMask = this.mgf1(internalKey.algorithm.hash, seed, dataBlock.length); for (let i = 0; i < dataBlock.length; i++) { dataBlock[i] ^= dataBlockMask[i]; } const seedMask = this.mgf1(internalKey.algorithm.hash, dataBlock, seed.length); for (let i = 0; i < seed.length; i++) { seed[i] ^= seedMask[i]; } if (!internalKey.pem) { internalKey.pem = `-----BEGIN PUBLIC KEY-----\n${internalKey.data.toString("base64")}\n-----END PUBLIC KEY-----`; } const pkcs0 = crypto__default.publicEncrypt({ key: internalKey.pem, padding: crypto__default.constants.RSA_NO_PADDING, }, Buffer.from(message)); return new Uint8Array(pkcs0).buffer; } async onDecrypt(algorithm, key, data) { const internalKey = getCryptoKey(key); const keySize = Math.ceil(internalKey.algorithm.modulusLength >> 3); const hashSize = ShaCrypto.size(internalKey.algorithm.hash) >> 3; const dataLength = data.byteLength; if (dataLength !== keySize) { throw new Error("Bad data"); } if (!internalKey.pem) { internalKey.pem = `-----BEGIN PRIVATE KEY-----\n${internalKey.data.toString("base64")}\n-----END PRIVATE KEY-----`; } let pkcs0 = crypto__default.privateDecrypt({ key: internalKey.pem, padding: crypto__default.constants.RSA_NO_PADDING, }, Buffer.from(data)); const z = pkcs0[0]; const seed = pkcs0.subarray(1, hashSize + 1); const dataBlock = pkcs0.subarray(hashSize + 1); if (z !== 0) { throw new Error("Decryption failed"); } const seedMask = this.mgf1(internalKey.algorithm.hash, dataBlock, seed.length); for (let i = 0; i < seed.length; i++) { seed[i] ^= seedMask[i]; } const dataBlockMask = this.mgf1(internalKey.algorithm.hash, seed, dataBlock.length); for (let i = 0; i < dataBlock.length; i++) { dataBlock[i] ^= dataBlockMask[i]; } const labelHash = crypto__default.createHash(internalKey.algorithm.hash.name.replace("-", "")) .update(core.BufferSourceConverter.toUint8Array(algorithm.label || new Uint8Array(0))) .digest(); for (let i = 0; i < hashSize; i++) { if (labelHash[i] !== dataBlock[i]) { throw new Error("Decryption failed"); } } let psEnd = hashSize; for (; psEnd < dataBlock.length; psEnd++) { const psz = dataBlock[psEnd]; if (psz === 1) { break; } if (psz !== 0) { throw new Error("Decryption failed"); } } if (psEnd === dataBlock.length) { throw new Error("Decryption failed"); } pkcs0 = dataBlock.subarray(psEnd + 1); return new Uint8Array(pkcs0).buffer; } async onExportKey(format, key) { return RsaCrypto.exportKey(format, getCryptoKey(key)); } async onImportKey(format, keyData, algorithm, extractable, keyUsages) { const key = await RsaCrypto.importKey(format, keyData, { ...algorithm, name: this.name }, extractable, keyUsages); return setCryptoKey(key); } checkCryptoKey(key, keyUsage) { super.checkCryptoKey(key, keyUsage); const internalKey = getCryptoKey(key); if (!(internalKey instanceof RsaPrivateKey || internalKey instanceof RsaPublicKey)) { throw new TypeError("key: Is not RSA CryptoKey"); } } mgf1(algorithm, seed, length = 0) { const hashSize = ShaCrypto.size(algorithm) >> 3; const mask = new Uint8Array(length); const counter = new Uint8Array(4); const chunks = Math.ceil(length / hashSize); for (let i = 0; i < chunks; i++) { counter[0] = i >>> 24; counter[1] = (i >>> 16) & 255; counter[2] = (i >>> 8) & 255; counter[3] = i & 255; const submask = mask.subarray(i * hashSize); let chunk = crypto__default.createHash(algorithm.name.replace("-", "")) .update(seed) .update(counter) .digest(); if (chunk.length > submask.length) { chunk = chunk.subarray(0, submask.length); } submask.set(chunk); } return mask; } } class RsaEsProvider extends core.ProviderCrypto { constructor() { super(...arguments); this.name = "RSAES-PKCS1-v1_5"; this.usages = { publicKey: ["encrypt", "wrapKey"], privateKey: ["decrypt", "unwrapKey"], }; } async onGenerateKey(algorithm, extractable, keyUsages) { const keys = await RsaCrypto.generateKey({ ...algorithm, name: this.name, }, extractable, keyUsages); return { privateKey: setCryptoKey(keys.privateKey), publicKey: setCryptoKey(keys.publicKey), }; } checkGenerateKeyParams(algorithm) { this.checkRequiredProperty(algorithm, "publicExponent"); if (!(algorithm.publicExponent && algorithm.publicExponent instanceof Uint8Array)) { throw new TypeError("publicExponent: Missing or not a Uint8Array"); } const publicExponent = Convert.ToBa