@mistweaverco/libas2
Version:
Implementation of the AS2 protocol as presented in RFC 4130 and related RFCs
290 lines (289 loc) • 12.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AS2EnvelopedData = void 0;
const asn1js = require("asn1js");
const pkijs = require("pkijs/build/index");
const webcrypto_1 = require("@peculiar/webcrypto");
const PemFile_1 = require("./PemFile");
const LibOid_1 = require("./LibOid");
const crypto_1 = require("crypto");
const webcrypto = new webcrypto_1.Crypto();
const CRYPTO_MODE = {
PKIJS_SUPPORTED: 'PKIJS_SUPPORTED',
SUBTLE_CRYPTO: 'SUBTLE_CRYPTO',
PKCS1_V1_5: 'PKCS1_V1_5'
};
const { RSA_PKCS1_PADDING } = crypto_1.constants;
class AS2EnvelopedData {
constructor(data, enveloped = false) {
pkijs.setEngine('newEngine', webcrypto, new pkijs.CryptoEngine({
name: '@peculiar/webcrypto',
crypto: webcrypto,
subtle: webcrypto.subtle
}));
if (enveloped) {
const bufferBer = new Uint8Array(data).buffer;
const envelopedDataContentAsn1 = asn1js.fromBER(bufferBer);
const envelopedDataContent = new pkijs.ContentInfo({
schema: envelopedDataContentAsn1.result
});
this.enveloped = new pkijs.EnvelopedData({
schema: envelopedDataContent.content
});
}
else {
this.data = new Uint8Array(data).buffer;
this.enveloped = new pkijs.EnvelopedData();
}
}
_toCertificate(cert) {
const certPemFile = new PemFile_1.PemFile(cert);
const certAsn1 = asn1js.fromBER(certPemFile.data);
return new pkijs.Certificate({ schema: certAsn1.result });
}
_getEncryptionAlgorithm(encryption) {
const CBC = 'AES-CBC';
const GCM = 'AES-GCM';
switch (encryption) {
case 'aes128-CBC':
return {
name: CBC,
length: 128
};
case 'aes192-CBC':
return {
name: CBC,
length: 192
};
case 'aes256-CBC':
return {
name: CBC,
length: 256
};
case 'aes128-GCM':
return {
name: GCM,
length: 128
};
case 'aes192-GCM':
return {
name: GCM,
length: 192
};
case 'aes256-GCM':
return {
name: GCM,
length: 256
};
case 'des-EDE3-CBC':
return {
name: 'des-EDE3-CBC',
length: 192
};
default:
throw new Error('Unsupported algorithm: ' + encryption);
}
}
_getCryptoInfo(index = 0) {
const crypto = pkijs.getCrypto();
const algorithmId = this.enveloped.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId;
const encryptionId = this.enveloped.encryptedContentInfo.contentEncryptionAlgorithm.algorithmId;
const encryptionIdParams = crypto.getAlgorithmByOID(encryptionId);
let mode = CRYPTO_MODE.PKIJS_SUPPORTED;
let algorithm = encryptionIdParams.name;
if (algorithmId === '1.2.840.113549.1.1.1') {
mode = CRYPTO_MODE.PKCS1_V1_5;
if ('name' in encryptionIdParams === false) {
algorithm = new LibOid_1.ObjectID({ id: encryptionId }).name;
}
}
else {
if ('name' in encryptionIdParams === false) {
mode = CRYPTO_MODE.SUBTLE_CRYPTO;
algorithm = new LibOid_1.ObjectID({ id: encryptionId }).name;
}
}
return { mode, algorithm };
}
async _getDecryptionKey(index, options) {
const crypto = pkijs.getCrypto();
const schema = this.enveloped.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmParams;
let encryptedKey;
if (options.rsaOaep) {
const rsaOAEPParams = new pkijs.RSAESOAEPParams({ schema });
const hashAlgorithm = crypto.getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId);
const privateKey = await crypto.importKey('pkcs8', options.recipientPrivateKey, {
name: 'RSA-OAEP',
hash: {
name: hashAlgorithm.name
}
}, true, ['decrypt']);
encryptedKey = await crypto.decrypt(privateKey.algorithm, privateKey, this.enveloped.recipientInfos[index].value.encryptedKey.valueBlock.valueHex);
}
else {
const decryptedPayload = (0, crypto_1.privateDecrypt)({
key: (0, crypto_1.createPrivateKey)({
key: Buffer.from(options.recipientPrivateKey),
format: 'der',
type: 'pkcs8'
}),
padding: RSA_PKCS1_PADDING
}, Buffer.from(this.enveloped.recipientInfos[index].value.encryptedKey.valueBlock.valueHex));
encryptedKey = new Uint8Array(decryptedPayload).buffer;
}
return await crypto.importKey('raw', encryptedKey, options.algorithm, true, ['decrypt']);
}
async _extendedDecrypt(decryptionKey, algorithm) {
const crypto = pkijs.getCrypto();
const ivBuffer = this.enveloped.encryptedContentInfo.contentEncryptionAlgorithm.algorithmParams.valueBlock.valueHex;
const ivView = new Uint8Array(ivBuffer);
let dataBuffer = new ArrayBuffer(0);
if (this.enveloped.encryptedContentInfo.encryptedContent.idBlock.isConstructed === false) {
dataBuffer = this.enveloped.encryptedContentInfo.encryptedContent.valueBlock.valueHex;
}
else {
let _iteratorNormalCompletion = true;
let _didIteratorError = false;
let _iteratorError = undefined;
let _iterator;
let _step;
try {
for (_iterator = this.enveloped.encryptedContentInfo.encryptedContent.valueBlock.value[Symbol.iterator](); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
const content = _step.value;
let outputLength = 0;
let prevLength = 0;
for (const buffer of [dataBuffer, content.valueBlock.valueHex]) {
outputLength += buffer.byteLength;
}
const retBuf = new ArrayBuffer(outputLength);
const retView = new Uint8Array(retBuf);
for (const buffer of [dataBuffer, content.valueBlock.valueHex]) {
retView.set(new Uint8Array(buffer), prevLength);
prevLength += buffer.byteLength;
}
dataBuffer = retBuf;
}
}
catch (error) {
_didIteratorError = true;
_iteratorError = error;
}
try {
if (!_iteratorNormalCompletion && _iterator.return != null) {
_iterator.return();
}
}
catch (error) {
_didIteratorError = true;
_iteratorError = error;
}
if (_didIteratorError) {
throw _iteratorError;
}
}
return await crypto.decrypt({
name: algorithm,
iv: ivView
}, decryptionKey, dataBuffer);
}
async _extendedEncrypt(content, algorithm) {
const crypto = pkijs.getCrypto();
const ivLength = 8;
const ivBuffer = new ArrayBuffer(ivLength);
const ivView = new Uint8Array(ivBuffer);
const contentView = new Uint8Array(content);
const sessionKey = await crypto.generateKey(algorithm, true, ['encrypt']);
const exportedSessionKey = await crypto.exportKey('raw', sessionKey);
const contentEncryptionOID = new LibOid_1.ObjectID(algorithm).id;
pkijs.getRandomValues(ivView);
const encryptedContent = await crypto.encrypt({ name: algorithm.name, iv: ivView }, sessionKey, contentView);
this.enveloped.version = 2;
this.enveloped.encryptedContentInfo = new pkijs.EncryptedContentInfo({
contentType: new LibOid_1.ObjectID({ name: 'data' }).id,
contentEncryptionAlgorithm: new pkijs.AlgorithmIdentifier({
algorithmId: contentEncryptionOID,
algorithmParams: new asn1js.OctetString({
valueHex: ivBuffer
})
}),
encryptedContent: new asn1js.OctetString({
valueHex: encryptedContent
})
});
const oaepOID = crypto.getOIDByAlgorithm({ name: 'RSA-OAEP' });
for (let index = 0; index < this.enveloped.recipientInfos.length; index += 1) {
if (this.enveloped.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmId !== oaepOID) {
throw new Error('Not supported encryption scheme, only RSA-OAEP is supported for key transport encryption scheme');
}
const schema = this.enveloped.recipientInfos[index].value.keyEncryptionAlgorithm.algorithmParams;
const rsaOAEPParams = new pkijs.RSAESOAEPParams({ schema });
const hashAlgorithm = crypto.getAlgorithmByOID(rsaOAEPParams.hashAlgorithm.algorithmId);
if ('name' in hashAlgorithm === false) {
throw new Error(`Incorrect or unsupported OID for hash algorithm: ${rsaOAEPParams.hashAlgorithm.algorithmId}`);
}
const publicKey = await this.enveloped.recipientInfos[index].value.recipientCertificate.getPublicKey({
algorithm: {
algorithm: {
name: 'RSA-OAEP',
hash: {
name: hashAlgorithm.name
}
},
usages: ['encrypt', 'wrapKey']
}
});
const encryptedKey = await crypto.encrypt(publicKey.algorithm, publicKey, exportedSessionKey);
this.enveloped.recipientInfos[index].value.encryptedKey = new asn1js.OctetString({
valueHex: encryptedKey
});
}
}
async encrypt(cert, encryption, oaepHashAlgorithm = 'sha-1') {
const certificate = this._toCertificate(cert);
const encryptionAlgorithm = this._getEncryptionAlgorithm(encryption);
this.enveloped.addRecipientByCertificate(certificate, { oaepHashAlgorithm });
if (encryption === 'des-EDE3-CBC') {
await this._extendedEncrypt(this.data, encryptionAlgorithm);
}
else {
await this.enveloped.encrypt(encryptionAlgorithm, this.data);
}
const envelopedDataContent = new pkijs.ContentInfo({
contentType: new LibOid_1.ObjectID({ name: 'envelopedData' }).id,
content: this.enveloped.toSchema()
});
const envelopedDataSchema = envelopedDataContent.toSchema();
const envelopedDataBuffer = envelopedDataSchema.toBER();
return Buffer.from(envelopedDataBuffer);
}
async decrypt(cert, key) {
const certificate = this._toCertificate(cert);
const privateKey = new PemFile_1.PemFile(key).data;
const cryptoInfo = this._getCryptoInfo();
if (cryptoInfo.mode === CRYPTO_MODE.PKIJS_SUPPORTED) {
this.data = await this.enveloped.decrypt(0, {
recipientCertificate: certificate,
recipientPrivateKey: privateKey
});
}
else {
let decryptionKey;
if (cryptoInfo.mode === CRYPTO_MODE.SUBTLE_CRYPTO) {
decryptionKey = await this._getDecryptionKey(0, {
recipientPrivateKey: privateKey,
algorithm: cryptoInfo.algorithm,
rsaOaep: true
});
}
else {
decryptionKey = await this._getDecryptionKey(0, {
recipientPrivateKey: privateKey,
algorithm: cryptoInfo.algorithm
});
}
this.data = await this._extendedDecrypt(decryptionKey, cryptoInfo.algorithm);
}
return Buffer.from(this.data || '');
}
}
exports.AS2EnvelopedData = AS2EnvelopedData;