mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-06-13 09:46:38 +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);
|
oid, kdfParams, data, Q, fingerprint);
|
||||||
return { V, C: new ECDHSymkey(C) };
|
return { V, C: new ECDHSymkey(C) };
|
||||||
}
|
}
|
||||||
case enums.publicKey.x25519: {
|
case enums.publicKey.x25519:
|
||||||
|
case enums.publicKey.x448: {
|
||||||
if (!util.isAES(symmetricAlgo)) {
|
if (!util.isAES(symmetricAlgo)) {
|
||||||
// see https://gitlab.com/openpgp-wg/rfc4880bis/-/merge_requests/276
|
// 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 { A } = publicParams;
|
||||||
const { ephemeralPublicKey, wrappedKey } = await publicKey.elliptic.ecdhX.encrypt(
|
const { ephemeralPublicKey, wrappedKey } = await publicKey.elliptic.ecdhX.encrypt(
|
||||||
@ -116,7 +117,8 @@ export async function publicKeyDecrypt(algo, publicKeyParams, privateKeyParams,
|
|||||||
return publicKey.elliptic.ecdh.decrypt(
|
return publicKey.elliptic.ecdh.decrypt(
|
||||||
oid, kdfParams, V, C.data, Q, d, fingerprint);
|
oid, kdfParams, V, C.data, Q, d, fingerprint);
|
||||||
}
|
}
|
||||||
case enums.publicKey.x25519: {
|
case enums.publicKey.x25519:
|
||||||
|
case enums.publicKey.x448: {
|
||||||
const { A } = publicKeyParams;
|
const { A } = publicKeyParams;
|
||||||
const { k } = privateKeyParams;
|
const { k } = privateKeyParams;
|
||||||
const { ephemeralPublicKey, C } = sessionKeyParams;
|
const { ephemeralPublicKey, C } = sessionKeyParams;
|
||||||
@ -182,7 +184,8 @@ export function parsePublicKeyParams(algo, bytes) {
|
|||||||
}
|
}
|
||||||
case enums.publicKey.ed25519:
|
case enums.publicKey.ed25519:
|
||||||
case enums.publicKey.ed448:
|
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;
|
const A = bytes.subarray(read, read + getCurvePayloadSize(algo)); read += A.length;
|
||||||
return { read, publicParams: { A } };
|
return { read, publicParams: { A } };
|
||||||
}
|
}
|
||||||
@ -234,8 +237,10 @@ export function parsePrivateKeyParams(algo, bytes, publicParams) {
|
|||||||
const seed = bytes.subarray(read, read + payloadSize); read += seed.length;
|
const seed = bytes.subarray(read, read + payloadSize); read += seed.length;
|
||||||
return { read, privateParams: { seed } };
|
return { read, privateParams: { seed } };
|
||||||
}
|
}
|
||||||
case enums.publicKey.x25519: {
|
case enums.publicKey.x25519:
|
||||||
const k = bytes.subarray(read, read + 32); read += k.length;
|
case enums.publicKey.x448: {
|
||||||
|
const payloadSize = getCurvePayloadSize(algo);
|
||||||
|
const k = bytes.subarray(read, read + payloadSize); read += k.length;
|
||||||
return { read, privateParams: { k } };
|
return { read, privateParams: { k } };
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -275,13 +280,15 @@ export function parseEncSessionKeyParams(algo, bytes) {
|
|||||||
const C = new ECDHSymkey(); C.read(bytes.subarray(read));
|
const C = new ECDHSymkey(); C.read(bytes.subarray(read));
|
||||||
return { V, C };
|
return { V, C };
|
||||||
}
|
}
|
||||||
// Algorithm-Specific Fields for X25519 encrypted session keys:
|
// Algorithm-Specific Fields for X25519 or X448 encrypted session keys:
|
||||||
// - 32 octets representing an ephemeral X25519 public key.
|
// - 32 octets representing an ephemeral X25519 public key (or 57 octets for X448).
|
||||||
// - A one-octet size of the following fields.
|
// - 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 one-octet algorithm identifier, if it was passed (in the case of a v3 PKESK packet).
|
||||||
// - The encrypted session key.
|
// - The encrypted session key.
|
||||||
case enums.publicKey.x25519: {
|
case enums.publicKey.x25519:
|
||||||
const ephemeralPublicKey = bytes.subarray(read, read + 32); read += ephemeralPublicKey.length;
|
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));
|
const C = new ECDHXSymmetricKey(); C.read(bytes.subarray(read));
|
||||||
return { ephemeralPublicKey, C };
|
return { ephemeralPublicKey, C };
|
||||||
}
|
}
|
||||||
@ -298,7 +305,12 @@ export function parseEncSessionKeyParams(algo, bytes) {
|
|||||||
*/
|
*/
|
||||||
export function serializeParams(algo, params) {
|
export function serializeParams(algo, params) {
|
||||||
// Some algorithms do not rely on MPIs to store the binary 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 orderedParams = Object.keys(params).map(name => {
|
||||||
const param = params[name];
|
const param = params[name];
|
||||||
if (!util.isUint8Array(param)) return param.write();
|
if (!util.isUint8Array(param)) return param.write();
|
||||||
@ -351,6 +363,7 @@ export function generateParams(algo, bits, oid) {
|
|||||||
publicParams: { A }
|
publicParams: { A }
|
||||||
}));
|
}));
|
||||||
case enums.publicKey.x25519:
|
case enums.publicKey.x25519:
|
||||||
|
case enums.publicKey.x448:
|
||||||
return publicKey.elliptic.ecdhX.generate(algo).then(({ A, k }) => ({
|
return publicKey.elliptic.ecdhX.generate(algo).then(({ A, k }) => ({
|
||||||
privateParams: { k },
|
privateParams: { k },
|
||||||
publicParams: { A }
|
publicParams: { A }
|
||||||
@ -411,7 +424,8 @@ export async function validateParams(algo, publicParams, privateParams) {
|
|||||||
const { seed } = privateParams;
|
const { seed } = privateParams;
|
||||||
return publicKey.elliptic.eddsa.validateParams(algo, A, seed);
|
return publicKey.elliptic.eddsa.validateParams(algo, A, seed);
|
||||||
}
|
}
|
||||||
case enums.publicKey.x25519: {
|
case enums.publicKey.x25519:
|
||||||
|
case enums.publicKey.x448: {
|
||||||
const { A } = publicParams;
|
const { A } = publicParams;
|
||||||
const { k } = privateParams;
|
const { k } = privateParams;
|
||||||
return publicKey.elliptic.ecdhX.validateParams(algo, A, k);
|
return publicKey.elliptic.ecdhX.validateParams(algo, A, k);
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
* @module crypto/public_key/elliptic/ecdh
|
* @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 * as aesKW from '../../aes_kw';
|
||||||
import { getRandomBytes } from '../../random';
|
import { getRandomBytes } from '../../random';
|
||||||
|
|
||||||
@ -13,7 +14,8 @@ import getCipher from '../../cipher/getCipher';
|
|||||||
import computeHKDF from '../../hkdf';
|
import computeHKDF from '../../hkdf';
|
||||||
|
|
||||||
const HKDF_INFO = {
|
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: {
|
case enums.publicKey.x25519: {
|
||||||
// k stays in little-endian, unlike legacy ECDH over curve25519
|
// k stays in little-endian, unlike legacy ECDH over curve25519
|
||||||
const k = getRandomBytes(32);
|
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 };
|
return { A, k };
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -49,7 +56,15 @@ export async function validateParams(algo, A, k) {
|
|||||||
* Derive public point A' from private key
|
* Derive public point A' from private key
|
||||||
* and expect A == A'
|
* 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);
|
return util.equalsUint8Array(A, publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,8 +89,8 @@ export async function encrypt(algo, data, recipientA) {
|
|||||||
switch (algo) {
|
switch (algo) {
|
||||||
case enums.publicKey.x25519: {
|
case enums.publicKey.x25519: {
|
||||||
const ephemeralSecretKey = getRandomBytes(32);
|
const ephemeralSecretKey = getRandomBytes(32);
|
||||||
const sharedSecret = nacl.scalarMult(ephemeralSecretKey, recipientA);
|
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
|
||||||
const { publicKey: ephemeralPublicKey } = nacl.box.keyPair.fromSecretKey(ephemeralSecretKey);
|
const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey);
|
||||||
const hkdfInput = util.concatUint8Array([
|
const hkdfInput = util.concatUint8Array([
|
||||||
ephemeralPublicKey,
|
ephemeralPublicKey,
|
||||||
recipientA,
|
recipientA,
|
||||||
@ -86,6 +101,20 @@ export async function encrypt(algo, data, recipientA) {
|
|||||||
const wrappedKey = aesKW.wrap(encryptionKey, data);
|
const wrappedKey = aesKW.wrap(encryptionKey, data);
|
||||||
return { ephemeralPublicKey, wrappedKey };
|
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:
|
default:
|
||||||
throw new Error('Unsupported ECDH algorithm');
|
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) {
|
export async function decrypt(algo, ephemeralPublicKey, wrappedKey, A, k) {
|
||||||
switch (algo) {
|
switch (algo) {
|
||||||
case enums.publicKey.x25519: {
|
case enums.publicKey.x25519: {
|
||||||
const sharedSecret = nacl.scalarMult(k, ephemeralPublicKey);
|
const sharedSecret = x25519.scalarMult(k, ephemeralPublicKey);
|
||||||
const hkdfInput = util.concatUint8Array([
|
const hkdfInput = util.concatUint8Array([
|
||||||
ephemeralPublicKey,
|
ephemeralPublicKey,
|
||||||
A,
|
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);
|
const encryptionKey = await computeHKDF(enums.hash.sha256, hkdfInput, new Uint8Array(), HKDF_INFO.x25519, keySize);
|
||||||
return aesKW.unwrap(encryptionKey, wrappedKey);
|
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:
|
default:
|
||||||
throw new Error('Unsupported ECDH algorithm');
|
throw new Error('Unsupported ECDH algorithm');
|
||||||
}
|
}
|
||||||
|
@ -356,6 +356,7 @@ export function isValidSigningKeyPacket(keyPacket, signature) {
|
|||||||
keyAlgo !== enums.publicKey.elgamal &&
|
keyAlgo !== enums.publicKey.elgamal &&
|
||||||
keyAlgo !== enums.publicKey.ecdh &&
|
keyAlgo !== enums.publicKey.ecdh &&
|
||||||
keyAlgo !== enums.publicKey.x25519 &&
|
keyAlgo !== enums.publicKey.x25519 &&
|
||||||
|
keyAlgo !== enums.publicKey.x448 &&
|
||||||
(!signature.keyFlags ||
|
(!signature.keyFlags ||
|
||||||
(signature.keyFlags[0] & enums.keyFlags.signData) !== 0);
|
(signature.keyFlags[0] & enums.keyFlags.signData) !== 0);
|
||||||
}
|
}
|
||||||
|
@ -357,8 +357,9 @@ export class Message {
|
|||||||
await Promise.all(encryptionKeys.map(key => key.getEncryptionKey()
|
await Promise.all(encryptionKeys.map(key => key.getEncryptionKey()
|
||||||
.catch(() => null) // ignore key strength requirements
|
.catch(() => null) // ignore key strength requirements
|
||||||
.then(maybeKey => {
|
.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
|
if (maybeKey && (maybeKey.keyPacket.algorithm === enums.publicKey.x25519 || maybeKey.keyPacket.algorithm === enums.publicKey.x448) &&
|
||||||
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.');
|
!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.publicKeyAlgorithm = bytes[offset++];
|
||||||
this.encrypted = crypto.parseEncSessionKeyParams(this.publicKeyAlgorithm, bytes.subarray(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);
|
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);
|
const { sessionKey, sessionKeyAlgorithm } = decodeSessionKey(this.version, this.publicKeyAlgorithm, decryptedData, randomSessionKey);
|
||||||
|
|
||||||
// v3 Montgomery curves have cleartext cipher algo
|
// 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.sessionKeyAlgorithm = sessionKeyAlgorithm;
|
||||||
}
|
}
|
||||||
this.sessionKey = sessionKey;
|
this.sessionKey = sessionKey;
|
||||||
@ -224,6 +227,7 @@ function encodeSessionKey(version, keyAlgo, cipherAlgo, sessionKeyData) {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
case enums.publicKey.x25519:
|
case enums.publicKey.x25519:
|
||||||
|
case enums.publicKey.x448:
|
||||||
return sessionKeyData;
|
return sessionKeyData;
|
||||||
default:
|
default:
|
||||||
throw new Error('Unsupported public key algorithm');
|
throw new Error('Unsupported public key algorithm');
|
||||||
@ -270,6 +274,7 @@ function decodeSessionKey(version, keyAlgo, decryptedData, randomSessionKey) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
case enums.publicKey.x25519:
|
case enums.publicKey.x25519:
|
||||||
|
case enums.publicKey.x448:
|
||||||
return {
|
return {
|
||||||
sessionKey: decryptedData
|
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);
|
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 => {
|
['p256', 'p384', 'p521'].forEach(curveName => {
|
||||||
it(`NIST ${curveName} - Successful exchange`, async function () {
|
it(`NIST ${curveName} - Successful exchange`, async function () {
|
||||||
const curve = new elliptic_curves.CurveWithOID(curveName);
|
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() {
|
describe('RSA parameter validation', function() {
|
||||||
let rsaKey;
|
let rsaKey;
|
||||||
before(async () => {
|
before(async () => {
|
||||||
|
@ -3223,29 +3223,38 @@ aU71tdtNBQ==
|
|||||||
it('Parsing V4 key using curve448 format', async function() {
|
it('Parsing V4 key using curve448 format', async function() {
|
||||||
const privateKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
const privateKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
xX0GZRqLYhwAAAA5U/IaIOge/FoLzCetXKx029bdJHCz2hMFBRMuzq4msjaT
|
xX0GZRqLYhwAAAA52IEq/TpKiPp6RofQaq4uhCruTtiG+qiVFnwsQgeh0ui34kHD
|
||||||
+hLeV6puyC/PeSEfaanqTuo31vvsti2AAIttr4GDGXF4vfPzbzkWV9dT4VVs
|
Y1E04mBai0pCoDiFVokwsKt3F5sAAC8lDYfVP/p3atbXJDTJB2W9WmZxIS7pUGhS
|
||||||
IU7QqLv1hzwZ+k7pHroRyXnUiYxRYHuzlg7Vw4CrAtN/8T65OMLAHgYfHAoA
|
bjlWpZB/OVTBsoIfP/2J+Hi4ESwBRfDUDgwK4aJVKsLAIAYfHAoAAAA/BQJlGoti
|
||||||
AAA9BQJlGotiIqEGAxidsHRHDsyFTw1Q7OoGEAEnRnxthKMwVBqhIL2o+HUC
|
IqEGobsxt8WKsMuJWANyTXpWMdC1QN/7EyJClfcs+nBgqdUCGwMCHgkCCwcDFQoI
|
||||||
GwMCHgkCCwcDFQoIAhYAAycHAgAAAAA2KiC+Y+fhQ/48CkT9WrXTX9SCn3vH
|
AhYABScHAwcCAAAAAPiGIG2qmhCULQ/+H4rKV0XEM1x0uVY3l878Pa6ijZLouZU/
|
||||||
z43Wb++AkmpWL1HQmrJE3S4gGltezZK2E9ovagzxKxVrL14uC6hs6kJ0JIiW
|
VRd5PnbGyLPL++q3LDViUUdZ1uusRc01f677Q6wpUU90k8MH/oULwI0+KPtqe1N4
|
||||||
QSeMeexCTy+Gdr6j0wb4FhFNnoIu3yu2ABmZpFX/5/191YeWUryKFDAoUZmK
|
6nr1NTERsAmAaPjUdf4ZUXX/GWiTd/AlsS5JqGnAQxKRJkzCJacOTOElRMjzGUX7
|
||||||
gQTSOzJEvyO0ACR5L4vV3ADceOAdG8/sqhE89rTSevFXng4JAM0XVXNlckEg
|
CGaAnhSC86YRZ68ocTPfZysAzRdVc2VyQiA8VXNlckJAdGVzdC50ZXN0PsLADQYT
|
||||||
PFVzZXJBQHRlc3QudGVzdD7CwA0GExwKAAAALAUCZRqLYiKhBgMYnbB0Rw7M
|
HAoAAAAsBQJlGotiIqEGobsxt8WKsMuJWANyTXpWMdC1QN/7EyJClfcs+nBgqdUC
|
||||||
hU8NUOzqBhABJ0Z8bYSjMFQaoSC9qPh1AhkBAAAAAFw/IH72M1iyzMWhbgtw
|
GQEAAAAASKwgVzMoPb2Hbr3lbNI1CRWECokYLokL7F8MbYiMnlg+v6QXLdStvT13
|
||||||
v0SR/XxvOIW/ZrT4Ix9236lvoOE4taL/D46CbZOjm7VAeOSfSdxt1xSKnoAL
|
ZjxdrWQAx3MbihSOUSXbdAys90yMOAdtognj+x418J/TaYFMtIGBHwoHv8gQVnx9
|
||||||
RsCNQ8tVPjPXclzqr6R8MbPIgBWxKcMS2eStYpBbG5qAmc+K5jdA2xcl9iW5
|
9ICv8ezx1T5VvGBYNuKZ5Ww0WPEpYMf1VA+Y9JxpohdcRenNBdSug4tLWla2y8NH
|
||||||
bWleZ1LTah4lF6qCiD73IffADXtzw8iAMTX+0wM5N1tJUEGvgqe00ohRKiQA
|
aO28Fltpb4AuGQDHewZlGotiGgAAADjdabr1ohAOnbSUUkVhtUM/LVdnYgDLhmaj
|
||||||
-----END PGP PRIVATE KEY BLOCK-----` });
|
YZ1N7TWY0fqEpMk2LLo2165HOmhddRPeTB1TWbuwBwB8lKc3czFUzYcAgvZ08T5S
|
||||||
|
UUHjfIhjeJeY4yd0OZDfzPw1vbegCc7t94bT+XGoIQbC/Bl7HCyAiMLADQYYHAoA
|
||||||
|
AAAsBQJlGotiIqEGobsxt8WKsMuJWANyTXpWMdC1QN/7EyJClfcs+nBgqdUCGwwA
|
||||||
|
AAAAHh0gf2kdqLoXdFX+aNVORr5VCVgcm2gBw8l68lDJ1ftA71bMllFi6Q5sLPUr
|
||||||
|
6UCpJYYk3o3hYUYzIHmCIZGVYe1pxRgIUNqxydXEfzylJmN5NbiJNkjJizrI7oAR
|
||||||
|
1mIcEEb/hmRMOUs1V2mcGuoeALBI/r/SyqDE2GRjH6d6g1RS7ZARPPHlZlY4CTqC
|
||||||
|
4a7L+8odDwA=
|
||||||
|
=chx0
|
||||||
|
-----END PGP PRIVATE KEY BLOCK-----
|
||||||
|
` });
|
||||||
// sanity checks
|
// sanity checks
|
||||||
await expect(privateKey.validate()).to.be.fulfilled;
|
await expect(privateKey.validate()).to.be.fulfilled;
|
||||||
const signingKey = await privateKey.getSigningKey();
|
const signingKey = await privateKey.getSigningKey();
|
||||||
expect(signingKey.keyPacket.algorithm).to.equal(openpgp.enums.publicKey.ed448);
|
expect(signingKey.keyPacket.algorithm).to.equal(openpgp.enums.publicKey.ed448);
|
||||||
expect(signingKey.getAlgorithmInfo()).to.deep.equal({ algorithm: 'ed448' });
|
expect(signingKey.getAlgorithmInfo()).to.deep.equal({ algorithm: 'ed448' });
|
||||||
|
|
||||||
// const encryptionKey = await privateKey.getEncryptionKey();
|
const encryptionKey = await privateKey.getEncryptionKey();
|
||||||
// expect(encryptionKey.keyPacket.algorithm).to.equal(openpgp.enums.publicKey.x25519);
|
expect(encryptionKey.keyPacket.algorithm).to.equal(openpgp.enums.publicKey.x448);
|
||||||
// expect(encryptionKey.getAlgorithmInfo()).to.deep.equal({ algorithm: 'x25519' });
|
expect(encryptionKey.getAlgorithmInfo()).to.deep.equal({ algorithm: 'x448' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Testing key ID and fingerprint for V4 keys', async function() {
|
it('Testing key ID and fingerprint for V4 keys', async function() {
|
||||||
|
@ -2131,7 +2131,7 @@ mNwbfFbSNhZYWjFada77EKBn60j8QT/xCQzLR1clci7ieW2knw==
|
|||||||
expect(data).to.equal('Hello World!');
|
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
|
// v4 key
|
||||||
const privateKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
const privateKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
@ -2160,6 +2160,40 @@ yxsNL0GomZ+hxiE0MOZwRr10DxfVaRabF1fcf9PHSHX2SwEFXUKMIHgbMQs=
|
|||||||
expect(data).to.equal(plaintext);
|
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() {
|
it('should support encrypting with encrypted key with unknown s2k (unparseableKeyMaterial)', async function() {
|
||||||
const originalDecryptedKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
const originalDecryptedKey = await openpgp.readKey({ armoredKey: `-----BEGIN PGP PRIVATE KEY BLOCK-----
|
||||||
|
|
||||||
@ -4370,7 +4404,7 @@ kl0L
|
|||||||
message: await openpgp.createMessage({ text: plaintext }),
|
message: await openpgp.createMessage({ text: plaintext }),
|
||||||
encryptionKeys: privateKeyCast5,
|
encryptionKeys: privateKeyCast5,
|
||||||
sessionKey: { data: new Uint8Array(16).fill(1), algorithm: 'cast5' }
|
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({
|
await expect(openpgp.decryptSessionKeys({
|
||||||
message: await openpgp.readMessage({ armoredMessage: `-----BEGIN PGP MESSAGE-----
|
message: await openpgp.readMessage({ armoredMessage: `-----BEGIN PGP MESSAGE-----
|
||||||
@ -4383,6 +4417,48 @@ A7sB7uYCTVCLIMfPFwVZH+c29gpCzPxSXQ==
|
|||||||
})).to.be.rejectedWith(/AES session key expected/);
|
})).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() {
|
describe('Sign and verify with each curve', function() {
|
||||||
const curves = ['secp256k1' , 'p256', 'p384', 'p521', 'curve25519', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'];
|
const curves = ['secp256k1' , 'p256', 'p384', 'p521', 'curve25519', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'];
|
||||||
curves.forEach(curve => {
|
curves.forEach(curve => {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user