fix: extend CryptoEngine to support legacy algorithms and integrate with LinerEngine for decryption
This commit is contained in:
@@ -145,11 +145,50 @@ function passwordToBMP(password: ArrayBuffer): Uint8Array {
|
|||||||
return bmp;
|
return bmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── CMS content encryption OIDs (for EnvelopedData decryption) ─────
|
||||||
|
const OID_DES_EDE3_CBC = '1.2.840.113549.3.7'; // des-EDE3-CBC (3DES)
|
||||||
|
const OID_DES_CBC = '1.3.14.3.2.7'; // desCBC
|
||||||
|
const OID_RC2_CBC = '1.2.840.113549.3.2'; // rc2CBC
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extended CryptoEngine that handles legacy PKCS#12 PBE algorithms.
|
* Extended CryptoEngine that handles legacy algorithms (3DES, etc.)
|
||||||
* Falls through to the base CryptoEngine for everything else.
|
* not recognized by pkijs's default CryptoEngine.
|
||||||
|
*
|
||||||
|
* - Adds OID→algorithm mappings for DES-EDE3-CBC so that
|
||||||
|
* EnvelopedData.decrypt() can process 3DES-encrypted S/MIME messages.
|
||||||
|
* - Handles legacy PKCS#12 PBE algorithms via custom KDF.
|
||||||
*/
|
*/
|
||||||
class Pkcs12CryptoEngine extends pkijs.CryptoEngine {
|
class Pkcs12CryptoEngine extends pkijs.CryptoEngine {
|
||||||
|
/**
|
||||||
|
* Extend OID→algorithm mapping with legacy algorithms that webcrypto-liner
|
||||||
|
* supports but pkijs does not know about.
|
||||||
|
*/
|
||||||
|
getAlgorithmByOID(oid: string, safety?: boolean, target?: string): object {
|
||||||
|
switch (oid) {
|
||||||
|
case OID_DES_EDE3_CBC:
|
||||||
|
return { name: 'DES-EDE3-CBC', length: 192 };
|
||||||
|
case OID_DES_CBC:
|
||||||
|
return { name: 'DES-CBC', length: 64 };
|
||||||
|
case OID_RC2_CBC:
|
||||||
|
return { name: 'RC2-CBC', length: 128 };
|
||||||
|
default:
|
||||||
|
return super.getAlgorithmByOID(oid, safety, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getOIDByAlgorithm(algorithm: { name: string; length?: number }, safety?: boolean, target?: string): string {
|
||||||
|
switch (algorithm.name.toUpperCase()) {
|
||||||
|
case 'DES-EDE3-CBC':
|
||||||
|
return OID_DES_EDE3_CBC;
|
||||||
|
case 'DES-CBC':
|
||||||
|
return OID_DES_CBC;
|
||||||
|
case 'RC2-CBC':
|
||||||
|
return OID_RC2_CBC;
|
||||||
|
default:
|
||||||
|
return super.getOIDByAlgorithm(algorithm, safety, target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async decryptEncryptedContentInfo(
|
async decryptEncryptedContentInfo(
|
||||||
parameters: Parameters<pkijs.CryptoEngine['decryptEncryptedContentInfo']>[0],
|
parameters: Parameters<pkijs.CryptoEngine['decryptEncryptedContentInfo']>[0],
|
||||||
): Promise<ArrayBuffer> {
|
): Promise<ArrayBuffer> {
|
||||||
@@ -182,9 +221,11 @@ class Pkcs12CryptoEngine extends pkijs.CryptoEngine {
|
|||||||
const ivBytes = await pkcs12KDF(bmpPassword, salt, iterations, 2, ivLen);
|
const ivBytes = await pkcs12KDF(bmpPassword, salt, iterations, 2, ivLen);
|
||||||
|
|
||||||
// Import key via webcrypto-liner (supports DES-EDE3-CBC)
|
// Import key via webcrypto-liner (supports DES-EDE3-CBC)
|
||||||
|
const keyData = new Uint8Array(keyBytes.buffer as ArrayBuffer, keyBytes.byteOffset, keyBytes.byteLength);
|
||||||
const cryptoKey = await this.importKey(
|
const cryptoKey = await this.importKey(
|
||||||
'raw',
|
'raw',
|
||||||
new Uint8Array(keyBytes.buffer as ArrayBuffer, keyBytes.byteOffset, keyBytes.byteLength) as unknown as BufferSource,
|
keyData,
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
{ name: algName, length: keyLen * 8 } as Algorithm,
|
{ name: algName, length: keyLen * 8 } as Algorithm,
|
||||||
false,
|
false,
|
||||||
['decrypt'],
|
['decrypt'],
|
||||||
@@ -193,6 +234,7 @@ class Pkcs12CryptoEngine extends pkijs.CryptoEngine {
|
|||||||
// Decrypt
|
// Decrypt
|
||||||
const ciphertext = parameters.encryptedContentInfo.getEncryptedContent();
|
const ciphertext = parameters.encryptedContentInfo.getEncryptedContent();
|
||||||
return this.decrypt(
|
return this.decrypt(
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
{ name: algName, iv: ivBytes } as Algorithm,
|
{ name: algName, iv: ivBytes } as Algorithm,
|
||||||
cryptoKey,
|
cryptoKey,
|
||||||
ciphertext,
|
ciphertext,
|
||||||
|
|||||||
+16
-13
@@ -8,7 +8,7 @@
|
|||||||
import * as pkijs from 'pkijs';
|
import * as pkijs from 'pkijs';
|
||||||
import * as asn1js from 'asn1js';
|
import * as asn1js from 'asn1js';
|
||||||
import type { SmimeKeyRecord } from './types';
|
import type { SmimeKeyRecord } from './types';
|
||||||
import { getLinerCryptoEngine } from './crypto-engine';
|
import { getLinerCryptoEngine, withLinerEngine } from './crypto-engine';
|
||||||
|
|
||||||
export interface DecryptionInput {
|
export interface DecryptionInput {
|
||||||
/** Raw CMS EnvelopedData bytes (DER) */
|
/** Raw CMS EnvelopedData bytes (DER) */
|
||||||
@@ -360,17 +360,20 @@ async function decryptWithKey(
|
|||||||
const certAsn1 = asn1js.fromBER(keyRecord.certificate);
|
const certAsn1 = asn1js.fromBER(keyRecord.certificate);
|
||||||
const cert = new pkijs.Certificate({ schema: certAsn1.result });
|
const cert = new pkijs.Certificate({ schema: certAsn1.result });
|
||||||
|
|
||||||
// Use webcrypto-liner engine for legacy algorithm support (e.g. 3DES)
|
// Use withLinerEngine to set the global pkijs engine to webcrypto-liner.
|
||||||
const cryptoEngine = getLinerCryptoEngine();
|
// This is required because pkijs internally may use getEngine() for
|
||||||
|
// OID lookups and crypto operations. Without this, 3DES-encrypted
|
||||||
|
// messages fail because the default engine doesn't know about DES-EDE3-CBC.
|
||||||
|
return withLinerEngine(async () => {
|
||||||
|
const cryptoEngine = getLinerCryptoEngine();
|
||||||
|
|
||||||
const result = await envelopedData.decrypt(
|
return envelopedData.decrypt(
|
||||||
recipientIndex,
|
recipientIndex,
|
||||||
{
|
{
|
||||||
recipientCertificate: cert,
|
recipientCertificate: cert,
|
||||||
recipientPrivateKey: privateKey,
|
recipientPrivateKey: privateKey,
|
||||||
},
|
},
|
||||||
cryptoEngine,
|
cryptoEngine,
|
||||||
);
|
);
|
||||||
|
});
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user