mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-06-10 08:06:43 +00:00
crypto-refresh
: add support for X448
This commit is contained in:
parent
1ebf7034f5
commit
56cd448a32
@ -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);
|
||||
|
@ -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');
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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.');
|
||||
}
|
||||
})
|
||||
));
|
||||
|
@ -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
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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 () => {
|
||||
|
@ -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() {
|
||||
|
@ -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 => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user