Skip to content

Commit ee4ad89

Browse files
committed
Enforce AES with PKESK v3 using x25519 (new format)
Fail on PKESK parsing as well as session key generation and encryption
1 parent 1c07d26 commit ee4ad89

5 files changed

Lines changed: 65 additions & 3 deletions

File tree

src/crypto/crypto.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,12 +67,15 @@ export async function publicKeyEncrypt(keyAlgo, symmetricAlgo, publicParams, dat
6767
return { V, C: new ECDHSymkey(C) };
6868
}
6969
case enums.publicKey.x25519: {
70+
if (!util.isAES(symmetricAlgo)) {
71+
// see https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/276
72+
throw new Error('X25519 keys can only encrypt AES session keys');
73+
}
7074
const { A } = publicParams;
7175
const { ephemeralPublicKey, wrappedKey } = await publicKey.elliptic.ecdhX.encrypt(
7276
keyAlgo, data, A);
7377
const C = ECDHXSymmetricKey.fromObject({ algorithm: symmetricAlgo, wrappedKey });
7478
return { ephemeralPublicKey, C };
75-
7679
}
7780
default:
7881
return [];
@@ -119,6 +122,9 @@ export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams,
119122
const { A } = publicKeyParams;
120123
const { k } = privateKeyParams;
121124
const { ephemeralPublicKey, C } = sessionKeyParams;
125+
if (!util.isAES(C.algorithm)) {
126+
throw new Error('AES session key expected');
127+
}
122128
return publicKey.elliptic.ecdhX.decrypt(
123129
algo, ephemeralPublicKey, C.wrappedKey, A, k);
124130
}

src/crypto/mode/cfb.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export async function encrypt(algo, key, plaintext, iv, config) {
5757
if (util.getNodeCrypto() && nodeAlgos[algoName]) { // Node crypto library.
5858
return nodeEncrypt(algo, key, plaintext, iv);
5959
}
60-
if (algoName.substr(0, 3) === 'aes') {
60+
if (util.isAES(algo)) {
6161
return aesEncrypt(algo, key, plaintext, iv, config);
6262
}
6363

@@ -100,7 +100,7 @@ export async function decrypt(algo, key, ciphertext, iv) {
100100
if (util.getNodeCrypto() && nodeAlgos[algoName]) { // Node crypto library.
101101
return nodeDecrypt(algo, key, ciphertext, iv);
102102
}
103-
if (algoName.substr(0, 3) === 'aes') {
103+
if (util.isAES(algo)) {
104104
return aesDecrypt(algo, key, ciphertext, iv);
105105
}
106106

src/message.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,15 @@ export class Message {
345345
enums.read(enums.aead, await getPreferredAlgo('aead', encryptionKeys, date, userIDs, config)) :
346346
undefined;
347347

348+
await Promise.all(encryptionKeys.map(key => key.getEncryptionKey()
349+
.catch(() => null) // ignore key strength requirements
350+
.then(maybeKey => {
351+
if (maybeKey && (maybeKey.keyPacket.algorithm === enums.publicKey.x25519) && !util.isAES(algo)) {
352+
throw new Error('Could not generate a session key compatible with the given `encryptionKeys`: X22519 keys can only be used to encrypt AES session keys; change `config.preferredSymmetricAlgorithm` accordingly.');
353+
}
354+
})
355+
));
356+
348357
const sessionKeyData = crypto.generateSessionKey(algo);
349358
return { data: sessionKeyData, algorithm: algorithmName, aeadAlgorithm: aeadAlgorithmName };
350359
}

src/util.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
import * as stream from '@openpgp/web-stream-tools';
2727
import { getBigInteger } from './biginteger';
28+
import enums from './enums';
2829

2930
const debugMode = (() => {
3031
try {
@@ -605,6 +606,12 @@ const util = {
605606
*/
606607
selectUint8: function(cond, a, b) {
607608
return (a & (256 - cond)) | (b & (255 + cond));
609+
},
610+
/**
611+
* @param {module:enums.symmetric} cipherAlgo
612+
*/
613+
isAES: function(cipherAlgo) {
614+
return cipherAlgo === enums.symmetric.aes128 || cipherAlgo === enums.symmetric.aes192 || cipherAlgo === enums.symmetric.aes256;
608615
}
609616
};
610617

test/general/openpgp.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2122,6 +2122,7 @@ VFBLG8uc9IiaKann/DYBAJcZNZHRSfpDoV2pUA5EAEi2MdjxkRysFQnYPRAu
21222122
decryptionKeys: originalDecryptedKey
21232123
});
21242124
expect(decrypted.data).to.equal('test');
2125+
});
21252126
});
21262127

21272128
describe('encryptSessionKey - unit tests', function() {
@@ -4081,6 +4082,45 @@ bsZgJWVlAa5eil6J9ePX2xbo1vVAkLQdzE9+1jL+l7PRIZuVBQ==
40814082
expect(data).to.equal('test');
40824083
});
40834084

4085+
it('should enforce using AES session keys with x25519 keys (new format)', async function () {
4086+
// x25519 key (v4) with cast5 as preferred cipher
4087+
const privateKeyCast5 = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
4088+
4089+
xUkEZK8BixuMghYwdEgHl+3ASI4VZkn048KG4DVuugT1bMe4QTtFtQCoKBOG
4090+
JxrZh8E+7I5nK7McXP2U9gyC0+RFcD46AxSmRA46zQDCiAQQGwgAPgWCZK8B
4091+
iwQLAwcICZCaWrTxMIPhVwMVCAoEFgACAQIZAQKbAwIeARYhBDFBS8Xnfotk
4092+
Oun5WZpatPEwg+FXAABwwuNWCdr1WahiGrLupYaOYQO4S9y+FYTxqEV/gsOP
4093+
TKwmNIcIJPROV2LgyxvzQo79//0CocEYojEeUhGn7BH5lwvHSQRkrwGLGbVM
4094+
1JxFUJeQ253sHMko73uPkyyb9DvaeyWHPwgF2k9GACA9caoO8GsZI7KMnVGP
4095+
c4EpytBwVIsr4ck3QaEV/UxvDpnCdAQYGwgAKgWCZK8BiwmQmlq08TCD4VcC
4096+
mwwWIQQxQUvF536LZDrp+VmaWrTxMIPhVwAAXycLtMyiv0lon4qU5/rKWjrq
4097+
MIxMchUbHvktvUqomU0pDDLMPqLFtzBbtHqODPVbLTOygJRVLeHyWTOEfmOD
4098+
kl0L
4099+
=SYJZ
4100+
-----END PGP PRIVATE KEY BLOCK-----` });
4101+
4102+
await expect(openpgp.generateSessionKey({
4103+
encryptionKeys: privateKeyCast5,
4104+
config: { preferredSymmetricAlgorithm: openpgp.enums.symmetric.cast5 }
4105+
})).to.be.rejectedWith(/Could not generate a session key compatible with the given `encryptionKeys`/);
4106+
4107+
await expect(openpgp.encrypt({
4108+
message: await openpgp.createMessage({ text: plaintext }),
4109+
encryptionKeys: privateKeyCast5,
4110+
sessionKey: { data: new Uint8Array(16).fill(1), algorithm: 'cast5' }
4111+
})).to.be.rejectedWith(/X25519 keys can only encrypt AES session keys/);
4112+
4113+
await expect(openpgp.decryptSessionKeys({
4114+
message: await openpgp.readMessage({ armoredMessage: `-----BEGIN PGP MESSAGE-----
4115+
4116+
wUQD66NYAXF0vfYZNWpc7s9eihtgj7EhHBeLOq2Ktw79artbhN5JMs+9aCIZ
4117+
A7sB7uYCTVCLIMfPFwVZH+c29gpCzPxSXQ==
4118+
=Dr02
4119+
-----END PGP MESSAGE-----` }),
4120+
decryptionKeys: privateKeyCast5
4121+
})).to.be.rejectedWith(/AES session key expected/);
4122+
});
4123+
40844124
describe('Sign and verify with each curve', function() {
40854125
const curves = ['secp256k1' , 'p256', 'p384', 'p521', 'curve25519', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'];
40864126
curves.forEach(curve => {

0 commit comments

Comments
 (0)