Skip to content

[Bug]: S/MIME cannot decrypt RSAES-PKCS1-v1_5 messages with an unlocked matching key #14

Description

@JuergenTenckhoff

Description

The S/MIME plugin v1.0.2 cannot decrypt incoming S/MIME messages although the matching PKCS#12 certificate and private key are imported and successfully unlocked.

The same messages are decrypted correctly by Thunderbird using exactly the same PKCS#12 identity.

Steps to Reproduce

Import a PKCS#12 identity containing an RSA certificate and private key into both Thunderbird and the Bulwark S/MIME plugin.
Unlock the key successfully in Bulwark.
In Thunderbird, send an S/MIME-encrypted message to the same email address using this certificate.
Open the received message in Thunderbird.
Open the same message in Bulwark.
Enter the correct storage passphrase when Bulwark requests it.

Expected Behavior

Bulwark decrypts and displays the message.

Actual Behavior

Bulwark prompts for the storage passphrase but subsequently displays:

Encrypted — couldn’t be decrypted with your keys

The browser console reports:

[plugin:smime] S/MIME decrypt failed Error: Failed to decrypt message with any available key
at smimeDecrypt (:49903:9)
at async onRenderEmailBody (:50913:18)
Thunderbird decrypts the same message immediately.

Only one personal S/MIME certificate is installed in Thunderbird, and the same PKCS#12 identity was imported into Bulwark. Therefore, this is not caused by an old or mismatching private key.

CMS details from another message showing the same failure

The failing smime.p7m is structurally valid and can be parsed successfully with OpenSSL:

contentType: pkcs7-envelopedData (1.2.840.113549.1.7.3)

keyEncryptionAlgorithm:
algorithm: rsaEncryption (1.2.840.113549.1.1.1)
parameter: NULL

contentEncryptionAlgorithm:
algorithm: aes-256-cbc (2.16.840.1.101.3.4.1.42)

The encrypted session key is 256 bytes long, corresponding to a 2048-bit RSA key.

The recipient issuer and serial number match the certificate imported into Bulwark. The plugin therefore finds a matching key record, but both decryption attempts fail.

Suspected cause

unlockPrivateKey() imports a legacy RSAES-PKCS1-v1_5 key using the bundled WebCrypto liner:

legacyDecryptionKey = await getLinerCrypto().subtle.importKey(
"pkcs8",
pkcs8Bytes,
{ name: "RSAES-PKCS1-v1_5" },
false,
["decrypt"]
);

The resulting custom RsaCryptoKey object is then passed to saveSessionKeys() and stored in IndexedDB:

await saveSessionKeys({
id: rec.id,
signingKey,
decryptionKey,
legacyDecryptionKey
});

IndexedDB uses the structured-clone algorithm. Custom class prototypes are not preserved by structured cloning. After reading the object back from IndexedDB, the legacy key may therefore no longer satisfy:

key instanceof RsaCryptoKey

The bundled RSA provider explicitly requires this check in RsaCrypto.checkCryptoKey(). This would explain why:

the matching certificate is found;
the storage passphrase is accepted;
RSA-OAEP is unsuitable for the incoming rsaEncryption recipient;
the legacy RSAES-PKCS1-v1_5 fallback also fails;
Thunderbird decrypts the same message correctly.

Bulwark Version

1.9.2

Stalwart Mail Server Version

0.16.21

Browser

Chrome / Chromium

Operating System

Linux

Screenshots / Screen Recording

Suggested investigation
Inspect the constructor/prototype of legacyDecryptionKey before and after the IndexedDB round trip.
Avoid storing a custom liner RsaCryptoKey instance directly in IndexedDB.
Instead, store a securely wrapped serialized key and re-import it into the liner engine in the consuming iframe/realm.
Preserve and log the original exceptions from both decryptWithKey() attempts. The current empty catch blocks hide the underlying failure and replace it with the generic message:
Failed to decrypt message with any available key

Relevant Logs or Error Output

Additional Context

S/MIME plugin: 1.0.2

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions