crypto-refresh: add support for X448

This commit is contained in:
larabr 2023-03-30 15:35:50 +02:00
parent 1ebf7034f5
commit 56cd448a32
9 changed files with 253 additions and 42 deletions

View File

@ -64,10 +64,11 @@ export async function publicKeyEncrypt(keyAlgo, symmetricAlgo, publicParams, dat
oid, kdfParams, data, Q, fingerprint);
return { V, C: new ECDHSymkey(C) };
}
case enums.publicKey.x25519: {
case enums.publicKey.x25519:
case enums.publicKey.x448: {
if (!util.isAES(symmetricAlgo)) {
// see https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/276
throw new Error('X25519 keys can only encrypt AES session keys');
throw new Error('X25519 and X448 keys can only encrypt AES session keys');
}
const { A } = publicParams;
const { ephemeralPublicKey, wrappedKey } = await publicKey.elliptic.ecdhX.encrypt(
@ -116,7 +117,8 @@ export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams,
return publicKey.elliptic.ecdh.decrypt(
oid, kdfParams, V, C.data, Q, d, fingerprint);
}
case enums.publicKey.x25519: {
case enums.publicKey.x25519:
case enums.publicKey.x448: {
const { A } = publicKeyParams;
const { k } = privateKeyParams;
const { ephemeralPublicKey, C } = sessionKeyParams;
@ -182,7 +184,8 @@ export function parsePublicKeyParams(algo, bytes) {
}
case enums.publicKey.ed25519:
case enums.publicKey.ed448:
case enums.publicKey.x25519: {
case enums.publicKey.x25519:
case enums.publicKey.x448: {
const A = bytes.subarray(read, read + getCurvePayloadSize(algo)); read += A.length;
return { read, publicParams: { A } };
}
@ -234,8 +237,10 @@ export function parsePrivateKeyParams(algo, bytes, publicParams) {
const seed = bytes.subarray(read, read + payloadSize); read += seed.length;
return { read, privateParams: { seed } };
}
case enums.publicKey.x25519: {
const k = bytes.subarray(read, read + 32); read += k.length;
case enums.publicKey.x25519:
case enums.publicKey.x448: {
const payloadSize = getCurvePayloadSize(algo);
const k = bytes.subarray(read, read + payloadSize); read += k.length;
return { read, privateParams: { k } };
}
default:
@ -275,13 +280,15 @@ export function parseEncSessionKeyParams(algo, bytes) {
const C = new ECDHSymkey(); C.read(bytes.subarray(read));
return { V, C };
}
// Algorithm-Specific Fields for X25519 encrypted session keys:
// - 32 octets representing an ephemeral X25519 public key.
// Algorithm-Specific Fields for X25519 or X448 encrypted session keys:
// - 32 octets representing an ephemeral X25519 public key (or 57 octets for X448).
// - A one-octet size of the following fields.
// - The one-octet algorithm identifier, if it was passed (in the case of a v3 PKESK packet).
// - The encrypted session key.
case enums.publicKey.x25519: {
const ephemeralPublicKey = bytes.subarray(read, read + 32); read += ephemeralPublicKey.length;
case enums.publicKey.x25519:
case enums.publicKey.x448: {
const pointSize = getCurvePayloadSize(algo);
const ephemeralPublicKey = bytes.subarray(read, read + pointSize); read += ephemeralPublicKey.length;
const C = new ECDHXSymmetricKey(); C.read(bytes.subarray(read));
return { ephemeralPublicKey, C };
}
@ -298,7 +305,12 @@ export function parseEncSessionKeyParams(algo, bytes) {
*/
export function serializeParams(algo, params) {
// Some algorithms do not rely on MPIs to store the binary params
const algosWithNativeRepresentation = new Set([enums.publicKey.ed25519, enums.publicKey.x25519, enums.publicKey.ed448]);
const algosWithNativeRepresentation = new Set([
enums.publicKey.ed25519,
enums.publicKey.x25519,
enums.publicKey.ed448,
enums.publicKey.x448
]);
const orderedParams = Object.keys(params).map(name => {
const param = params[name];
if (!util.isUint8Array(param)) return param.write();
@ -351,6 +363,7 @@ export function generateParams(algo, bits, oid) {
publicParams: { A }
}));
case enums.publicKey.x25519:
case enums.publicKey.x448:
return publicKey.elliptic.ecdhX.generate(algo).then(({ A, k }) => ({
privateParams: { k },
publicParams: { A }
@ -411,7 +424,8 @@ export async function validateParams(algo, publicParams, privateParams) {
const { seed } = privateParams;
return publicKey.elliptic.eddsa.validateParams(algo, A, seed);
}
case enums.publicKey.x25519: {
case enums.publicKey.x25519:
case enums.publicKey.x448: {
const { A } = publicParams;
const { k } = privateParams;
return publicKey.elliptic.ecdhX.validateParams(algo, A, k);

View File

@ -3,7 +3,8 @@
* @module crypto/public_key/elliptic/ecdh
*/
import nacl from '@openpgp/tweetnacl/nacl-fast-light';
import x25519 from '@openpgp/tweetnacl/nacl-fast-light';
import { x448 } from '@openpgp/noble-curves/ed448';
import * as aesKW from '../../aes_kw';
import { getRandomBytes } from '../../random';
@ -13,7 +14,8 @@ import getCipher from '../../cipher/getCipher';
import computeHKDF from '../../hkdf';
const HKDF_INFO = {
x25519: util.encodeUTF8('OpenPGP X25519')
x25519: util.encodeUTF8('OpenPGP X25519'),
x448: util.encodeUTF8('OpenPGP X448')
};
/**
@ -26,7 +28,12 @@ export async function generate(algo) {
case enums.publicKey.x25519: {
// k stays in little-endian, unlike legacy ECDH over curve25519
const k = getRandomBytes(32);
const { publicKey: A } = nacl.box.keyPair.fromSecretKey(k);
const { publicKey: A } = x25519.box.keyPair.fromSecretKey(k);
return { A, k };
}
case enums.publicKey.x448: {
const k = x448.utils.randomPrivateKey();
const A = x448.getPublicKey(k);
return { A, k };
}
default:
@ -49,7 +56,15 @@ export async function validateParams(algo, A, k) {
* Derive public point A' from private key
* and expect A == A'
*/
const { publicKey } = nacl.box.keyPair.fromSecretKey(k);
const { publicKey } = x25519.box.keyPair.fromSecretKey(k);
return util.equalsUint8Array(A, publicKey);
}
case enums.publicKey.x448: {
/**
* Derive public point A' from private key
* and expect A == A'
*/
const publicKey = x448.getPublicKey(k);
return util.equalsUint8Array(A, publicKey);
}
@ -74,8 +89,8 @@ export async function encrypt(algo, data, recipientA) {
switch (algo) {
case enums.publicKey.x25519: {
const ephemeralSecretKey = getRandomBytes(32);
const sharedSecret = nacl.scalarMult(ephemeralSecretKey, recipientA);
const { publicKey: ephemeralPublicKey } = nacl.box.keyPair.fromSecretKey(ephemeralSecretKey);
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey);
const hkdfInput = util.concatUint8Array([
ephemeralPublicKey,
recipientA,
@ -86,6 +101,20 @@ export async function encrypt(algo, data, recipientA) {
const wrappedKey = aesKW.wrap(encryptionKey, data);
return { ephemeralPublicKey, wrappedKey };
}
case enums.publicKey.x448: {
const ephemeralSecretKey = x448.utils.randomPrivateKey();
const sharedSecret = x448.getSharedSecret(ephemeralSecretKey, recipientA);
const ephemeralPublicKey = x448.getPublicKey(ephemeralSecretKey);
const hkdfInput = util.concatUint8Array([
ephemeralPublicKey,
recipientA,
sharedSecret
]);
const { keySize } = getCipher(enums.symmetric.aes256);
const encryptionKey = await computeHKDF(enums.hash.sha512, hkdfInput, new Uint8Array(), HKDF_INFO.x448, keySize);
const wrappedKey = aesKW.wrap(encryptionKey, data);
return { ephemeralPublicKey, wrappedKey };
}
default:
throw new Error('Unsupported ECDH algorithm');
@ -106,7 +135,7 @@ export async function encrypt(algo, data, recipientA) {
export async function decrypt(algo, ephemeralPublicKey, wrappedKey, A, k) {
switch (algo) {
case enums.publicKey.x25519: {
const sharedSecret = nacl.scalarMult(k, ephemeralPublicKey);
const sharedSecret = x25519.scalarMult(k, ephemeralPublicKey);
const hkdfInput = util.concatUint8Array([
ephemeralPublicKey,
A,
@ -116,6 +145,17 @@ export async function decrypt(algo, ephemeralPublicKey, wrappedKey, A, k) {
const encryptionKey = await computeHKDF(enums.hash.sha256, hkdfInput, new Uint8Array(), HKDF_INFO.x25519, keySize);
return aesKW.unwrap(encryptionKey, wrappedKey);
}
case enums.publicKey.x448: {
const sharedSecret = x448.getSharedSecret(k, ephemeralPublicKey);
const hkdfInput = util.concatUint8Array([
ephemeralPublicKey,
A,
sharedSecret
]);
const { keySize } = getCipher(enums.symmetric.aes256);
const encryptionKey = await computeHKDF(enums.hash.sha512, hkdfInput, new Uint8Array(), HKDF_INFO.x448, keySize);
return aesKW.unwrap(encryptionKey, wrappedKey);
}
default:
throw new Error('Unsupported ECDH algorithm');
}

View File

@ -356,6 +356,7 @@ export function isValidSigningKeyPacket(keyPacket, signature) {
keyAlgo !== enums.publicKey.elgamal &&
keyAlgo !== enums.publicKey.ecdh &&
keyAlgo !== enums.publicKey.x25519 &&
keyAlgo !== enums.publicKey.x448 &&
(!signature.keyFlags ||
(signature.keyFlags[0] & enums.keyFlags.signData) !== 0);
}

View File

@ -357,8 +357,9 @@ export class Message {
await Promise.all(encryptionKeys.map(key => key.getEncryptionKey()
.catch(() => null) // ignore key strength requirements
.then(maybeKey => {
if (maybeKey && (maybeKey.keyPacket.algorithm === enums.publicKey.x25519) && !aeadAlgoName && !util.isAES(symmetricAlgo)) { // if AEAD is defined, then PKESK v6 are used, and the algo info is encrypted
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.');
if (maybeKey && (maybeKey.keyPacket.algorithm === enums.publicKey.x25519 || maybeKey.keyPacket.algorithm === enums.publicKey.x448) &&
!aeadAlgoName && !util.isAES(symmetricAlgo)) { // if AEAD is defined, then PKESK v6 are used, and the algo info is encrypted
throw new Error('Could not generate a session key compatible with the given `encryptionKeys`: X22519 and X448 keys can only be used to encrypt AES session keys; change `config.preferredSymmetricAlgorithm` accordingly.');
}
})
));

View File

@ -128,7 +128,8 @@ class PublicKeyEncryptedSessionKeyPacket {
}
this.publicKeyAlgorithm = bytes[offset++];
this.encrypted = crypto.parseEncSessionKeyParams(this.publicKeyAlgorithm, bytes.subarray(offset));
if (this.version === 3 && this.publicKeyAlgorithm === enums.publicKey.x25519) {
if (this.version === 3 && (
this.publicKeyAlgorithm === enums.publicKey.x25519 || this.publicKeyAlgorithm === enums.publicKey.x448)) {
this.sessionKeyAlgorithm = enums.write(enums.symmetric, this.encrypted.C.algorithm);
}
}
@ -200,7 +201,9 @@ class PublicKeyEncryptedSessionKeyPacket {
const { sessionKey, sessionKeyAlgorithm } = decodeSessionKey(this.version, this.publicKeyAlgorithm, decryptedData, randomSessionKey);
// v3 Montgomery curves have cleartext cipher algo
if (this.version === 3 && this.publicKeyAlgorithm !== enums.publicKey.x25519) {
if (this.version === 3 && (
this.publicKeyAlgorithm !== enums.publicKey.x25519 && this.publicKeyAlgorithm !== enums.publicKey.x448)
) {
this.sessionKeyAlgorithm = sessionKeyAlgorithm;
}
this.sessionKey = sessionKey;
@ -224,6 +227,7 @@ function encodeSessionKey(version, keyAlgo, cipherAlgo, sessionKeyData) {
]);
}
case enums.publicKey.x25519:
case enums.publicKey.x448:
return sessionKeyData;
default:
throw new Error('Unsupported public key algorithm');
@ -270,6 +274,7 @@ function decodeSessionKey(version, keyAlgo, decryptedData, randomSessionKey) {
}
}
case enums.publicKey.x25519:
case enums.publicKey.x448:
return {
sessionKey: decryptedData
};

View File

@ -189,6 +189,16 @@ export default () => describe('ECDH key exchange @lightweight', function () {
expect(await ecdhX.decrypt(openpgp.enums.publicKey.x25519, ephemeralPublicKey, wrappedKey, K_B, b)).to.deep.equal(data);
});
it('Successful exchange x448', async function () {
const { ecdhX } = elliptic_curves;
const data = random.getRandomBytes();
// Bob's keys from https://www.rfc-editor.org/rfc/rfc7748#section-6.2
const b = util.hexToUint8Array('1c306a7ac2a0e2e0990b294470cba339e6453772b075811d8fad0d1d6927c120bb5ee8972b0d3e21374c9c921b09d1b0366f10b65173992d');
const K_B = util.hexToUint8Array('3eb7a829b0cd20f5bcfc0b599b6feccf6da4627107bdb0d4f345b43027d8b972fc3e34fb4232a13ca706dcb57aec3dae07bdc1c67bf33609');
const { ephemeralPublicKey, wrappedKey } = await ecdhX.encrypt(openpgp.enums.publicKey.x448, data, K_B);
expect(await ecdhX.decrypt(openpgp.enums.publicKey.x448, ephemeralPublicKey, wrappedKey, K_B, b)).to.deep.equal(data);
});
['p256', 'p384', 'p521'].forEach(curveName => {
it(`NIST ${curveName} - Successful exchange`, async function () {
const curve = new elliptic_curves.CurveWithOID(curveName);

View File

@ -247,6 +247,61 @@ export default () => {
});
});
describe('Ed448/X448 parameter validation', function() {
let eddsaKey;
let ecdhXKey;
before(async () => {
eddsaKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
xXsEZCWHXBwwtqciq6ZFU13s+dyhkWR5tOEmF1oX8OiP1B5ypfqyGVM8DkQh
5eTIMwB1oqJCROANoyA0q2dSigAAbDA5xr74DeClPPXC4ZXJ9uzuJWKvQvE8
x3EflhgoQCGBM7JfvH5zwdrJvPt8RKDvm0QkZzhPvnFoHnzNBHRlc3TCugQQ
HAgAPgWCZCWHXAQLCQcICZDsN6h/ys3ppwMVCAoEFgACAQIZAQKbAwIeARYh
BOJyE9P2eIcU2N2Ne+w3qH/KzemnAAAh1hTFCcEU77bU3YelrJTCNIOQnvt7
Hs6yZz2053CQTOC+wHkUQLaYYBEXSNyLZxoyv+NuGTiwbuYtAOlbE2erM7Cx
8B2Qz7M29UkFLMBUfb+yi+gTYYUWCXVQ7Um7MGjjgUG8+9p452i6f28mhRD8
tTgNAMd5BGQlh1wavTIFgILtbzrqQCiwDGx0YcFNzu9+FZ8vK5Mmm7UEZj0a
y7FWQtZw8tTaU6mY+RrSa52RjzkGLtQAQO++tgYqc+BnCFdCZ3ZYPRvD3mof
ffoo3l4xmto+iyvJZbQ4wQPXttg7VjCpEfOsL9TW9Xs09aIbysKmBBgcCAAq
BYJkJYdcCZDsN6h/ys3ppwKbDBYhBOJyE9P2eIcU2N2Ne+w3qH/KzemnAAC0
6/eZhh/Oj2gRdab2JeFGWACGIRDKxPXsWRCXR4YrSxcvCKK6rOvsyxQsgIsJ
JyPYkRPfmbKcseUDAEkSBLAfeizDGh7ea0GOdIMhwE/CW4f/H8ULbwi36y13
x3oMNVaYsI9dZ588Gpi8XYy2jOtqIPQ1AA==
-----END PGP PRIVATE KEY BLOCK-----` });
ecdhXKey = eddsaKey.subkeys[0];
});
it('Ed448 params should be valid', async function() {
await expect(eddsaKey.keyPacket.validate()).to.not.be.rejected;
});
it('detect invalid Ed448 public point', async function() {
const eddsaKeyPacket = await cloneKeyPacket(eddsaKey);
const A = eddsaKeyPacket.publicParams.A;
A[0]++;
await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
const infA = new Uint8Array(A.length);
eddsaKeyPacket.publicParams.A = infA;
await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
});
it('X448 params should be valid', async function() {
await expect(ecdhXKey.keyPacket.validate()).to.not.be.rejected;
});
it('detect invalid x448 public point', async function() {
const ecdhXKeyPacket = await cloneKeyPacket(ecdhXKey);
const A = ecdhXKeyPacket.publicParams.A;
A[0]++;
await expect(ecdhXKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
const infA = new Uint8Array(A.length);
ecdhXKeyPacket.publicParams.A = infA;
await expect(ecdhXKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
});
});
describe('RSA parameter validation', function() {
let rsaKey;
before(async () => {

View File

@ -3223,29 +3223,38 @@ aU71tdtNBQ==
it('Parsing V4 key using curve448 format', async function() {
const privateKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
xX0GZRqLYhwAAAA5U/IaIOge/FoLzCetXKx029bdJHCz2hMFBRMuzq4msjaT
+hLeV6puyC/PeSEfaanqTuo31vvsti2AAIttr4GDGXF4vfPzbzkWV9dT4VVs
IU7QqLv1hzwZ+k7pHroRyXnUiYxRYHuzlg7Vw4CrAtN/8T65OMLAHgYfHAoA
AAA9BQJlGotiIqEGAxidsHRHDsyFTw1Q7OoGEAEnRnxthKMwVBqhIL2o+HUC
GwMCHgkCCwcDFQoIAhYAAycHAgAAAAA2KiC+Y+fhQ/48CkT9WrXTX9SCn3vH
z43Wb++AkmpWL1HQmrJE3S4gGltezZK2E9ovagzxKxVrL14uC6hs6kJ0JIiW
QSeMeexCTy+Gdr6j0wb4FhFNnoIu3yu2ABmZpFX/5/191YeWUryKFDAoUZmK
gQTSOzJEvyO0ACR5L4vV3ADceOAdG8/sqhE89rTSevFXng4JAM0XVXNlckEg
PFVzZXJBQHRlc3QudGVzdD7CwA0GExwKAAAALAUCZRqLYiKhBgMYnbB0Rw7M
hU8NUOzqBhABJ0Z8bYSjMFQaoSC9qPh1AhkBAAAAAFw/IH72M1iyzMWhbgtw
v0SR/XxvOIW/ZrT4Ix9236lvoOE4taL/D46CbZOjm7VAeOSfSdxt1xSKnoAL
RsCNQ8tVPjPXclzqr6R8MbPIgBWxKcMS2eStYpBbG5qAmc+K5jdA2xcl9iW5
bWleZ1LTah4lF6qCiD73IffADXtzw8iAMTX+0wM5N1tJUEGvgqe00ohRKiQA
-----END PGP PRIVATE KEY BLOCK-----` });
xX0GZRqLYhwAAAA52IEq/TpKiPp6RofQaq4uhCruTtiG+qiVFnwsQgeh0ui34kHD
Y1E04mBai0pCoDiFVokwsKt3F5sAAC8lDYfVP/p3atbXJDTJB2W9WmZxIS7pUGhS
bjlWpZB/OVTBsoIfP/2J+Hi4ESwBRfDUDgwK4aJVKsLAIAYfHAoAAAA/BQJlGoti
IqEGobsxt8WKsMuJWANyTXpWMdC1QN/7EyJClfcs+nBgqdUCGwMCHgkCCwcDFQoI
AhYABScHAwcCAAAAAPiGIG2qmhCULQ/+H4rKV0XEM1x0uVY3l878Pa6ijZLouZU/
VRd5PnbGyLPL++q3LDViUUdZ1uusRc01f677Q6wpUU90k8MH/oULwI0+KPtqe1N4
6nr1NTERsAmAaPjUdf4ZUXX/GWiTd/AlsS5JqGnAQxKRJkzCJacOTOElRMjzGUX7
CGaAnhSC86YRZ68ocTPfZysAzRdVc2VyQiA8VXNlckJAdGVzdC50ZXN0PsLADQYT
HAoAAAAsBQJlGotiIqEGobsxt8WKsMuJWANyTXpWMdC1QN/7EyJClfcs+nBgqdUC
GQEAAAAASKwgVzMoPb2Hbr3lbNI1CRWECokYLokL7F8MbYiMnlg+v6QXLdStvT13
ZjxdrWQAx3MbihSOUSXbdAys90yMOAdtognj+x418J/TaYFMtIGBHwoHv8gQVnx9
9ICv8ezx1T5VvGBYNuKZ5Ww0WPEpYMf1VA+Y9JxpohdcRenNBdSug4tLWla2y8NH
aO28Fltpb4AuGQDHewZlGotiGgAAADjdabr1ohAOnbSUUkVhtUM/LVdnYgDLhmaj
YZ1N7TWY0fqEpMk2LLo2165HOmhddRPeTB1TWbuwBwB8lKc3czFUzYcAgvZ08T5S
UUHjfIhjeJeY4yd0OZDfzPw1vbegCc7t94bT+XGoIQbC/Bl7HCyAiMLADQYYHAoA
AAAsBQJlGotiIqEGobsxt8WKsMuJWANyTXpWMdC1QN/7EyJClfcs+nBgqdUCGwwA
AAAAHh0gf2kdqLoXdFX+aNVORr5VCVgcm2gBw8l68lDJ1ftA71bMllFi6Q5sLPUr
6UCpJYYk3o3hYUYzIHmCIZGVYe1pxRgIUNqxydXEfzylJmN5NbiJNkjJizrI7oAR
1mIcEEb/hmRMOUs1V2mcGuoeALBI/r/SyqDE2GRjH6d6g1RS7ZARPPHlZlY4CTqC
4a7L+8odDwA=
=chx0
-----END PGP PRIVATE KEY BLOCK-----
` });
// sanity checks
await expect(privateKey.validate()).to.be.fulfilled;
const signingKey = await privateKey.getSigningKey();
expect(signingKey.keyPacket.algorithm).to.equal(openpgp.enums.publicKey.ed448);
expect(signingKey.getAlgorithmInfo()).to.deep.equal({ algorithm: 'ed448' });
// const encryptionKey = await privateKey.getEncryptionKey();
// expect(encryptionKey.keyPacket.algorithm).to.equal(openpgp.enums.publicKey.x25519);
// expect(encryptionKey.getAlgorithmInfo()).to.deep.equal({ algorithm: 'x25519' });
const encryptionKey = await privateKey.getEncryptionKey();
expect(encryptionKey.keyPacket.algorithm).to.equal(openpgp.enums.publicKey.x448);
expect(encryptionKey.getAlgorithmInfo()).to.deep.equal({ algorithm: 'x448' });
});
it('Testing key ID and fingerprint for V4 keys', async function() {

View File

@ -2131,7 +2131,7 @@ mNwbfFbSNhZYWjFada77EKBn60j8QT/xCQzLR1clci7ieW2knw==
expect(data).to.equal('Hello World!');
});
it('supports encrypting new x25519 format', async function () {
it('supports encrypting/decrypting new x25519 format', async function () {
// v4 key
const privateKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
@ -2160,6 +2160,40 @@ yxsNL0GomZ+hxiE0MOZwRr10DxfVaRabF1fcf9PHSHX2SwEFXUKMIHgbMQs=
expect(data).to.equal(plaintext);
});
it('supports encrypting/decrypting with x448', async function () {
// v4 key
const privateKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
xXsEZRqJ5BwHESfKnw5YJly5WobjigVm0kKY84NxrP6JKeIvIWiFqqSlozGpKZyR
50YbVTHmxpUCuJ7YNwX0UoAAoSO8IXmMM/XMd4ph00ju+fbSHdtQfyNhfFTi3UoM
V5DiFT+uOYDP+zwAwLWCR86csxmCWn6O10DNHcDNF1VzZXJBIDxVc2VyQUB0ZXN0
LnRlc3Q+wroEExwKAD4FAmUaieQJEC8lwIrxSM+5FiEEGR2s5Bj5WVDN0Px6LyXA
ivFIz7kCGwMCHgkCGQECCwcDFQoIAhYAAycHAgAA21/PqAuGDL5+3qrf3YoVOP+5
0BoJ+ZMhzcgax+cQTyndmOZYBfOqV/SJ8mf6CRhbB76JhGIvmRMtyYDQgDMVvcoA
yojVNs6e/Jco16bVJxM85wKDXJuq6AhtPQ8w/0WaCJtEf1uxqeQPEbOM+KtT/xY2
KgDHeQRlGonkGuOtAhogSIU3z/+gFzF8U7JQe7QDRYr9VWfi2WXFFarzg/3DMRur
oIB7mqkaaSatrvVuud1ZmRCWAMM4f57dvSdCKsVqSe+tlS225OmdWmnGLqyErBb6
44E2oENhDUom9OUGUPm8dXUjQbrmw6ec9hNLHWXCpgQYHAoAKgUCZRqJ5AkQLyXA
ivFIz7kWIQQZHazkGPlZUM3Q/HovJcCK8UjPuQIbDAAAZka10c8KlmwftJuboIV5
DalGWrZhbywJpEZRfoikcebSYi5++w1SbpXZGu27sl+BznGyyyqAfxyJjoCZaqCs
ewbKh04DNAg4v4v0W0a8UvD3j/CuciEMXjK9nUErt91zEwxNZy43yrQY2aAayDs8
94FqMAA=
=GBh1
-----END PGP PRIVATE KEY BLOCK-----` });
const plaintext = 'plaintext';
const signed = await openpgp.encrypt({
message: await openpgp.createMessage({ text: plaintext }),
encryptionKeys: privateKey
});
const { data } = await openpgp.decrypt({
message: await openpgp.readMessage({ armoredMessage: signed }),
decryptionKeys: privateKey
});
expect(data).to.equal(plaintext);
});
it('should support encrypting with encrypted key with unknown s2k (unparseableKeyMaterial)', async function() {
const originalDecryptedKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
@ -4370,7 +4404,7 @@ kl0L
message: await openpgp.createMessage({ text: plaintext }),
encryptionKeys: privateKeyCast5,
sessionKey: { data: new Uint8Array(16).fill(1), algorithm: 'cast5' }
})).to.be.rejectedWith(/X25519 keys can only encrypt AES session keys/);
})).to.be.rejectedWith(/X25519 and X448 keys can only encrypt AES session keys/);
await expect(openpgp.decryptSessionKeys({
message: await openpgp.readMessage({ armoredMessage: `-----BEGIN PGP MESSAGE-----
@ -4383,6 +4417,48 @@ A7sB7uYCTVCLIMfPFwVZH+c29gpCzPxSXQ==
})).to.be.rejectedWith(/AES session key expected/);
});
it('should enforce using AES session keys with x448 keys', async function () {
// X448 key (v4) with cast5 as preferred cipher
const privateKeyCast5 = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
xXsEZRrtaRyScvyNjK0o5ccICztnWhA1MSij7WdzPfuNy7ryUzB+kqzpziBR
IIKp5PN0NW3mOYRDnUyo7QHBl4AA30tR5ED8u5v/rNIzKz/mKsD6XeYy+d0Q
5utwuR8BUxx9mcIUGdS65z9H6PUMGnfCwqAGVCTzBrSCHgTNAMK3BBAcCgA7
BYJlGu1pAwsDBwmQkFi4G9HqQDwDFQgKAhYAAhkBApsDAh4BFiEE7kZAI1Dd
SVlLtf4QkFi4G9HqQDwAAPA7E+p0vwVLtUCfT0aBFzapFn8xjoow6jrUNTo3
8EtaN0fqP2vaeQwW/vv26wobD+hbL2RwyFtAEV6AeeDsPVhbx7WA7yKHPzvl
GOYEGw0h57DuhvSxGciuyt0Y5PR2Vrz/2/wHGcEHzsrhTNysUetluxEAx3kE
ZRrtaRrySCLAqKQSATJOXdoRoNKVasJHlKrG3qgMbt1U6uSdctHBitTiHHTf
GU/Jg0ADA3Eg0bCyDupWNACmHJGu7q0o7O7BTAm0AsMbHxoIkNN9JsijwAp5
FLtdXK9cAOkNaXPMkEGQkt1hmoW50lUq0iWcGBpzwqYEGBwKACoFgmUa7WkJ
kJBYuBvR6kA8ApsMFiEE7kZAI1DdSVlLtf4QkFi4G9HqQDwAAD3uf3qdwHY8
65W22GR17PbqF+9uvkPpXLBi32FVPFkxJqYvIN5/LAQ33xdEE0mzO4As4+Oi
x8fsFb2AEXLEwlSnL+Eo0O+iUQd3/94yMbMFRlNxrdaqZ3+7CehbnieI/vby
LIEnN38XBi0HE70uoU5prxUA
-----END PGP PRIVATE KEY BLOCK-----` });
await expect(openpgp.generateSessionKey({
encryptionKeys: privateKeyCast5,
config: { preferredSymmetricAlgorithm: openpgp.enums.symmetric.cast5 }
})).to.be.rejectedWith(/Could not generate a session key compatible with the given `encryptionKeys`/);
await expect(openpgp.encrypt({
message: await openpgp.createMessage({ text: plaintext }),
encryptionKeys: privateKeyCast5,
sessionKey: { data: new Uint8Array(16).fill(1), algorithm: 'cast5' }
})).to.be.rejectedWith(/X25519 and X448 keys can only encrypt AES session keys/);
await expect(openpgp.decryptSessionKeys({
message: await openpgp.readMessage({ armoredMessage: `-----BEGIN PGP MESSAGE-----
wVwD2k7TUuqJwZkaXvEGk7B3pklJ5uRcRdKwwDJ40yKT0m5ic1e/2F+Se3xQ
zDE+N2DZ0B37pu4NUzTGBRo0oLD9EwwZA9+oJpBBOOry3cGmBYWvQHbvBpNE
5X5l8A==
-----END PGP MESSAGE-----` }),
decryptionKeys: privateKeyCast5
})).to.be.rejectedWith(/AES session key expected/);
});
describe('Sign and verify with each curve', function() {
const curves = ['secp256k1' , 'p256', 'p384', 'p521', 'curve25519', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'];
curves.forEach(curve => {