mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-11-23 22:15:52 +00:00
Merge pull request #1877
This commit is contained in:
commit
659e3dbbd0
@ -6,9 +6,8 @@
|
|||||||
import enums from '../enums';
|
import enums from '../enums';
|
||||||
import util from '../util';
|
import util from '../util';
|
||||||
|
|
||||||
const webCrypto = util.getWebCrypto();
|
|
||||||
|
|
||||||
export default async function computeHKDF(hashAlgo, inputKey, salt, info, outLen) {
|
export default async function computeHKDF(hashAlgo, inputKey, salt, info, outLen) {
|
||||||
|
const webCrypto = util.getWebCrypto();
|
||||||
const hash = enums.read(enums.webHash, hashAlgo);
|
const hash = enums.read(enums.webHash, hashAlgo);
|
||||||
if (!hash) throw new Error('Hash algo not supported with HKDF');
|
if (!hash) throw new Error('Hash algo not supported with HKDF');
|
||||||
|
|
||||||
|
|||||||
@ -30,9 +30,6 @@ import * as pkcs5 from '../../pkcs5';
|
|||||||
import { getCipherParams } from '../../cipher';
|
import { getCipherParams } from '../../cipher';
|
||||||
import { generateEphemeralEncryptionMaterial as ecdhXGenerateEphemeralEncryptionMaterial, recomputeSharedSecret as ecdhXRecomputeSharedSecret } from './ecdh_x';
|
import { generateEphemeralEncryptionMaterial as ecdhXGenerateEphemeralEncryptionMaterial, recomputeSharedSecret as ecdhXRecomputeSharedSecret } from './ecdh_x';
|
||||||
|
|
||||||
const webCrypto = util.getWebCrypto();
|
|
||||||
const nodeCrypto = util.getNodeCrypto();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate ECDH parameters
|
* Validate ECDH parameters
|
||||||
* @param {module:type/oid} oid - Elliptic curve object identifier
|
* @param {module:type/oid} oid - Elliptic curve object identifier
|
||||||
@ -238,6 +235,7 @@ async function jsPublicEphemeralKey(curve, Q) {
|
|||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function webPrivateEphemeralKey(curve, V, Q, d) {
|
async function webPrivateEphemeralKey(curve, V, Q, d) {
|
||||||
|
const webCrypto = util.getWebCrypto();
|
||||||
const recipient = privateToJWK(curve.payloadSize, curve.web, Q, d);
|
const recipient = privateToJWK(curve.payloadSize, curve.web, Q, d);
|
||||||
let privateKey = webCrypto.importKey(
|
let privateKey = webCrypto.importKey(
|
||||||
'jwk',
|
'jwk',
|
||||||
@ -289,6 +287,7 @@ async function webPrivateEphemeralKey(curve, V, Q, d) {
|
|||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function webPublicEphemeralKey(curve, Q) {
|
async function webPublicEphemeralKey(curve, Q) {
|
||||||
|
const webCrypto = util.getWebCrypto();
|
||||||
const jwk = rawPublicToJWK(curve.payloadSize, curve.web, Q);
|
const jwk = rawPublicToJWK(curve.payloadSize, curve.web, Q);
|
||||||
let keyPair = webCrypto.generateKey(
|
let keyPair = webCrypto.generateKey(
|
||||||
{
|
{
|
||||||
@ -338,6 +337,7 @@ async function webPublicEphemeralKey(curve, Q) {
|
|||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function nodePrivateEphemeralKey(curve, V, d) {
|
async function nodePrivateEphemeralKey(curve, V, d) {
|
||||||
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
const recipient = nodeCrypto.createECDH(curve.node);
|
const recipient = nodeCrypto.createECDH(curve.node);
|
||||||
recipient.setPrivateKey(d);
|
recipient.setPrivateKey(d);
|
||||||
const sharedKey = new Uint8Array(recipient.computeSecret(V));
|
const sharedKey = new Uint8Array(recipient.computeSecret(V));
|
||||||
@ -354,6 +354,7 @@ async function nodePrivateEphemeralKey(curve, V, d) {
|
|||||||
* @async
|
* @async
|
||||||
*/
|
*/
|
||||||
async function nodePublicEphemeralKey(curve, Q) {
|
async function nodePublicEphemeralKey(curve, Q) {
|
||||||
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
const sender = nodeCrypto.createECDH(curve.node);
|
const sender = nodeCrypto.createECDH(curve.node);
|
||||||
sender.generateKeys();
|
sender.generateKeys();
|
||||||
const sharedKey = new Uint8Array(sender.computeSecret(Q));
|
const sharedKey = new Uint8Array(sender.computeSecret(Q));
|
||||||
|
|||||||
@ -3,9 +3,7 @@
|
|||||||
* @module crypto/public_key/elliptic/ecdh
|
* @module crypto/public_key/elliptic/ecdh
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import x25519 from '@openpgp/tweetnacl';
|
|
||||||
import * as aesKW from '../../aes_kw';
|
import * as aesKW from '../../aes_kw';
|
||||||
import { getRandomBytes } from '../../random';
|
|
||||||
|
|
||||||
import enums from '../../../enums';
|
import enums from '../../../enums';
|
||||||
import util from '../../../util';
|
import util from '../../../util';
|
||||||
@ -55,9 +53,9 @@ export async function generate(algo) {
|
|||||||
if (err.name !== 'NotSupportedError') {
|
if (err.name !== 'NotSupportedError') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
const { default: x25519 } = await import('@openpgp/tweetnacl');
|
||||||
// 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 { secretKey: k, publicKey: A } = x25519.box.keyPair();
|
||||||
const { publicKey: A } = x25519.box.keyPair.fromSecretKey(k);
|
|
||||||
return { A, k };
|
return { A, k };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,14 +80,19 @@ export async function generate(algo) {
|
|||||||
*/
|
*/
|
||||||
export async function validateParams(algo, A, k) {
|
export async function validateParams(algo, A, k) {
|
||||||
switch (algo) {
|
switch (algo) {
|
||||||
case enums.publicKey.x25519: {
|
case enums.publicKey.x25519:
|
||||||
/**
|
// Validation is typically not run for ECDH, since encryption subkeys are only validated
|
||||||
* Derive public point A' from private key
|
// for gnu-dummy keys.
|
||||||
* and expect A == A'
|
// So, for simplicity, we do an encrypt-decrypt round even if WebCrypto support is not available
|
||||||
*/
|
try {
|
||||||
const { publicKey } = x25519.box.keyPair.fromSecretKey(k);
|
const { ephemeralPublicKey, sharedSecret } = await generateEphemeralEncryptionMaterial(algo, A);
|
||||||
return util.equalsUint8Array(A, publicKey);
|
const recomputedSharedSecret = await recomputeSharedSecret(algo, ephemeralPublicKey, A, k);
|
||||||
}
|
|
||||||
|
return util.equalsUint8Array(sharedSecret, recomputedSharedSecret);
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
case enums.publicKey.x448: {
|
case enums.publicKey.x448: {
|
||||||
const x448 = await util.getNobleCurve(enums.publicKey.x448);
|
const x448 = await util.getNobleCurve(enums.publicKey.x448);
|
||||||
/**
|
/**
|
||||||
@ -235,10 +238,10 @@ export async function generateEphemeralEncryptionMaterial(algo, recipientA) {
|
|||||||
if (err.name !== 'NotSupportedError') {
|
if (err.name !== 'NotSupportedError') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo));
|
const { default: x25519 } = await import('@openpgp/tweetnacl');
|
||||||
|
const { secretKey: ephemeralSecretKey, publicKey: ephemeralPublicKey } = x25519.box.keyPair();
|
||||||
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
|
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
|
||||||
assertNonZeroArray(sharedSecret);
|
assertNonZeroArray(sharedSecret);
|
||||||
const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey);
|
|
||||||
return { ephemeralPublicKey, sharedSecret };
|
return { ephemeralPublicKey, sharedSecret };
|
||||||
}
|
}
|
||||||
case enums.publicKey.x448: {
|
case enums.publicKey.x448: {
|
||||||
@ -273,6 +276,7 @@ export async function recomputeSharedSecret(algo, ephemeralPublicKey, A, k) {
|
|||||||
if (err.name !== 'NotSupportedError') {
|
if (err.name !== 'NotSupportedError') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
const { default: x25519 } = await import('@openpgp/tweetnacl');
|
||||||
const sharedSecret = x25519.scalarMult(k, ephemeralPublicKey);
|
const sharedSecret = x25519.scalarMult(k, ephemeralPublicKey);
|
||||||
assertNonZeroArray(sharedSecret);
|
assertNonZeroArray(sharedSecret);
|
||||||
return sharedSecret;
|
return sharedSecret;
|
||||||
|
|||||||
@ -20,7 +20,6 @@
|
|||||||
* @module crypto/public_key/elliptic/eddsa
|
* @module crypto/public_key/elliptic/eddsa
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ed25519 from '@openpgp/tweetnacl';
|
|
||||||
import util from '../../../util';
|
import util from '../../../util';
|
||||||
import enums from '../../../enums';
|
import enums from '../../../enums';
|
||||||
import { getHashByteLength } from '../../hash';
|
import { getHashByteLength } from '../../hash';
|
||||||
@ -59,7 +58,9 @@ export async function generate(algo) {
|
|||||||
if (err.name !== 'NotSupportedError') {
|
if (err.name !== 'NotSupportedError') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
const { default: ed25519 } = await import('@openpgp/tweetnacl');
|
||||||
const seed = getRandomBytes(getPayloadSize(algo));
|
const seed = getRandomBytes(getPayloadSize(algo));
|
||||||
|
// not using `ed25519.sign.keyPair` since it returns the expanded secret, so using `fromSeed` instead is more straightforward
|
||||||
const { publicKey: A } = ed25519.sign.keyPair.fromSeed(seed);
|
const { publicKey: A } = ed25519.sign.keyPair.fromSeed(seed);
|
||||||
return { A, seed };
|
return { A, seed };
|
||||||
}
|
}
|
||||||
@ -111,6 +112,7 @@ export async function sign(algo, hashAlgo, message, publicKey, privateKey, hashe
|
|||||||
if (err.name !== 'NotSupportedError') {
|
if (err.name !== 'NotSupportedError') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
const { default: ed25519 } = await import('@openpgp/tweetnacl');
|
||||||
const secretKey = util.concatUint8Array([privateKey, publicKey]);
|
const secretKey = util.concatUint8Array([privateKey, publicKey]);
|
||||||
const signature = ed25519.sign.detached(hashed, secretKey);
|
const signature = ed25519.sign.detached(hashed, secretKey);
|
||||||
return { RS: signature };
|
return { RS: signature };
|
||||||
@ -157,6 +159,7 @@ export async function verify(algo, hashAlgo, { RS }, m, publicKey, hashed) {
|
|||||||
if (err.name !== 'NotSupportedError') {
|
if (err.name !== 'NotSupportedError') {
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
const { default: ed25519 } = await import('@openpgp/tweetnacl');
|
||||||
return ed25519.sign.detached.verify(hashed, RS, publicKey);
|
return ed25519.sign.detached.verify(hashed, RS, publicKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,15 +182,34 @@ export async function verify(algo, hashAlgo, { RS }, m, publicKey, hashed) {
|
|||||||
*/
|
*/
|
||||||
export async function validateParams(algo, A, seed) {
|
export async function validateParams(algo, A, seed) {
|
||||||
switch (algo) {
|
switch (algo) {
|
||||||
case enums.publicKey.ed25519: {
|
case enums.publicKey.ed25519:
|
||||||
/**
|
// If webcrypto support is available, we sign-verify random data, as the import-export
|
||||||
* Derive public point A' from private key
|
// functions might not implement validity checks.
|
||||||
* and expect A == A'
|
// If we need to fallback to JS, we instead only re-derive the public key,
|
||||||
* TODO: move to sign-verify using WebCrypto (same as ECDSA) when curve is more widely implemented
|
// as this is much faster than sign-verify.
|
||||||
*/
|
try {
|
||||||
const { publicKey } = ed25519.sign.keyPair.fromSeed(seed);
|
const webCrypto = util.getWebCrypto();
|
||||||
return util.equalsUint8Array(A, publicKey);
|
const jwkPrivate = privateKeyToJWK(algo, A, seed);
|
||||||
}
|
const jwkPublic = publicKeyToJWK(algo, A);
|
||||||
|
|
||||||
|
const privateCryptoKey = await webCrypto.importKey('jwk', jwkPrivate, 'Ed25519', false, ['sign']);
|
||||||
|
const publicCryptoKey = await webCrypto.importKey('jwk', jwkPublic, 'Ed25519', false, ['verify']);
|
||||||
|
|
||||||
|
const randomData = getRandomBytes(8);
|
||||||
|
const signature = new Uint8Array(
|
||||||
|
await webCrypto.sign('Ed25519', privateCryptoKey, randomData)
|
||||||
|
);
|
||||||
|
|
||||||
|
const verified = await webCrypto.verify('Ed25519', publicCryptoKey, signature, randomData);
|
||||||
|
return verified;
|
||||||
|
} catch (err) {
|
||||||
|
if (err.name !== 'NotSupportedError') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const { default: ed25519 } = await import('@openpgp/tweetnacl');
|
||||||
|
const { publicKey } = ed25519.sign.keyPair.fromSeed(seed);
|
||||||
|
return util.equalsUint8Array(A, publicKey);
|
||||||
|
}
|
||||||
|
|
||||||
case enums.publicKey.ed448: {
|
case enums.publicKey.ed448: {
|
||||||
const ed448 = await util.getNobleCurve(enums.publicKey.ed448);
|
const ed448 = await util.getNobleCurve(enums.publicKey.ed448);
|
||||||
|
|||||||
@ -21,12 +21,11 @@
|
|||||||
* @module crypto/public_key/elliptic/eddsa_legacy
|
* @module crypto/public_key/elliptic/eddsa_legacy
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import nacl from '@openpgp/tweetnacl';
|
|
||||||
import util from '../../../util';
|
import util from '../../../util';
|
||||||
import enums from '../../../enums';
|
import enums from '../../../enums';
|
||||||
import { getHashByteLength } from '../../hash';
|
import { getHashByteLength } from '../../hash';
|
||||||
import { CurveWithOID, checkPublicPointEnconding } from './oid_curves';
|
import { CurveWithOID, checkPublicPointEnconding } from './oid_curves';
|
||||||
import { sign as eddsaSign, verify as eddsaVerify } from './eddsa';
|
import { sign as eddsaSign, verify as eddsaVerify, validateParams as eddsaValidateParams } from './eddsa';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sign a message using the provided legacy EdDSA key
|
* Sign a message using the provided legacy EdDSA key
|
||||||
@ -97,12 +96,9 @@ export async function validateParams(oid, Q, k) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// First byte is relevant for encoding purposes only
|
||||||
* Derive public point Q' = dG from private key
|
if (Q.length < 1 || Q[0] !== 0x40) {
|
||||||
* and expect Q == Q'
|
return false;
|
||||||
*/
|
}
|
||||||
const { publicKey } = nacl.sign.keyPair.fromSeed(k);
|
return eddsaValidateParams(enums.publicKey.ed25519, Q.subarray(1), k);
|
||||||
const dG = new Uint8Array([0x40, ...publicKey]); // Add public key prefix
|
|
||||||
return util.equalsUint8Array(Q, dG);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,14 +19,13 @@
|
|||||||
* @fileoverview Wrapper of an instance of an Elliptic Curve
|
* @fileoverview Wrapper of an instance of an Elliptic Curve
|
||||||
* @module crypto/public_key/elliptic/curve
|
* @module crypto/public_key/elliptic/curve
|
||||||
*/
|
*/
|
||||||
import nacl from '@openpgp/tweetnacl';
|
|
||||||
import enums from '../../../enums';
|
import enums from '../../../enums';
|
||||||
import util from '../../../util';
|
import util from '../../../util';
|
||||||
import { uint8ArrayToB64, b64ToUint8Array } from '../../../encoding/base64';
|
import { uint8ArrayToB64, b64ToUint8Array } from '../../../encoding/base64';
|
||||||
import OID from '../../../type/oid';
|
import OID from '../../../type/oid';
|
||||||
import { UnsupportedError } from '../../../packet/packet';
|
import { UnsupportedError } from '../../../packet/packet';
|
||||||
import { generate as eddsaGenerate } from './eddsa';
|
import { generate as eddsaGenerate } from './eddsa';
|
||||||
import { generate as ecdhXGenerate } from './ecdh_x';
|
import { generate as ecdhXGenerate, validateParams as ecdhXValidateParams } from './ecdh_x';
|
||||||
|
|
||||||
const webCrypto = util.getWebCrypto();
|
const webCrypto = util.getWebCrypto();
|
||||||
const nodeCrypto = util.getNodeCrypto();
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
@ -252,17 +251,12 @@ async function validateStandardParams(algo, oid, Q, d) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (curveName === enums.curve.curve25519Legacy) {
|
if (curveName === enums.curve.curve25519Legacy) {
|
||||||
d = d.slice().reverse();
|
const dLittleEndian = d.slice().reverse();
|
||||||
// Re-derive public point Q'
|
// First byte is relevant for encoding purposes only
|
||||||
const { publicKey } = nacl.box.keyPair.fromSecretKey(d);
|
if (Q.length < 1 || Q[0] !== 0x40) {
|
||||||
|
|
||||||
Q = new Uint8Array(Q);
|
|
||||||
const dG = new Uint8Array([0x40, ...publicKey]); // Add public key prefix
|
|
||||||
if (!util.equalsUint8Array(dG, Q)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return ecdhXValidateParams(enums.publicKey.x25519, Q.subarray(1), dLittleEndian);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const nobleCurve = await util.getNobleCurve(enums.publicKey.ecdsa, curveName); // excluding curve25519Legacy, ecdh and ecdsa use the same curves
|
const nobleCurve = await util.getNobleCurve(enums.publicKey.ecdsa, curveName); // excluding curve25519Legacy, ecdh and ecdsa use the same curves
|
||||||
|
|||||||
@ -323,7 +323,10 @@ export default () => describe('ECDH key exchange @lightweight', function () {
|
|||||||
const disableNative = () => {
|
const disableNative = () => {
|
||||||
enableNative();
|
enableNative();
|
||||||
// stubbed functions return undefined
|
// stubbed functions return undefined
|
||||||
getWebCryptoStub = sinonSandbox.stub(util, 'getWebCrypto');
|
getWebCryptoStub = sinonSandbox.stub(util, 'getWebCrypto').returns({
|
||||||
|
generateKey: () => { const e = new Error('getWebCrypto is mocked'); e.name = 'NotSupportedError'; throw e; },
|
||||||
|
importKey: () => { const e = new Error('getWebCrypto is mocked'); e.name = 'NotSupportedError'; throw e; }
|
||||||
|
});
|
||||||
getNodeCryptoStub = sinonSandbox.stub(util, 'getNodeCrypto');
|
getNodeCryptoStub = sinonSandbox.stub(util, 'getNodeCrypto');
|
||||||
};
|
};
|
||||||
const enableNative = () => {
|
const enableNative = () => {
|
||||||
@ -331,6 +334,46 @@ export default () => describe('ECDH key exchange @lightweight', function () {
|
|||||||
getNodeCryptoStub && getNodeCryptoStub.restore();
|
getNodeCryptoStub && getNodeCryptoStub.restore();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that the result of `encryptFunction` can be decrypted by `decryptFunction`
|
||||||
|
* with and without native crypto support.
|
||||||
|
* @param encryptFunction - `(data: Uint8Array) => encryptFunctionResult`
|
||||||
|
* @param decryptFunction - `(encryptFunctionResult) => <decryption result>`
|
||||||
|
* @param expectNative - whether native usage is expected for the algorithm
|
||||||
|
*/
|
||||||
|
const testRountripWithAndWithoutNative = async (
|
||||||
|
encryptFunction,
|
||||||
|
decryptFunction, // (encryptFunctionResult) => decryption result
|
||||||
|
expectNative
|
||||||
|
) => {
|
||||||
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
|
const webCrypto = util.getWebCrypto();
|
||||||
|
const data = random.getRandomBytes(16);
|
||||||
|
|
||||||
|
const nativeSpy = webCrypto ? sinonSandbox.spy(webCrypto, 'deriveBits') : sinonSandbox.spy(nodeCrypto, 'createECDH'); // functions used both for encryption and decryption
|
||||||
|
const nativeResult = await encryptFunction(data);
|
||||||
|
const expectedNativeEncryptCallCount = nativeSpy.callCount;
|
||||||
|
disableNative();
|
||||||
|
const nonNativeResult = await encryptFunction(data);
|
||||||
|
expect(nativeSpy.callCount).to.equal(expectedNativeEncryptCallCount); // assert that fallback implementation was called
|
||||||
|
if (expectNative) {
|
||||||
|
expect(nativeSpy.calledOnce).to.be.true;
|
||||||
|
}
|
||||||
|
|
||||||
|
enableNative();
|
||||||
|
expect(await decryptFunction(nativeResult)).to.deep.equal(data);
|
||||||
|
expect(await decryptFunction(nonNativeResult)).to.deep.equal(data);
|
||||||
|
const expectedNativeCallCount = nativeSpy.callCount;
|
||||||
|
disableNative();
|
||||||
|
expect(await decryptFunction(nativeResult)).to.deep.equal(data);
|
||||||
|
expect(await decryptFunction(nonNativeResult)).to.deep.equal(data);
|
||||||
|
expect(nativeSpy.callCount).to.equal(expectedNativeCallCount); // assert that fallback implementation was called
|
||||||
|
if (expectNative) {
|
||||||
|
expect(nativeSpy.callCount).to.equal(3); // one encryption + two decryptions
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
allCurves.forEach(curveName => {
|
allCurves.forEach(curveName => {
|
||||||
it(`${curveName}`, async function () {
|
it(`${curveName}`, async function () {
|
||||||
const nodeCrypto = util.getNodeCrypto();
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
@ -344,21 +387,27 @@ export default () => describe('ECDH key exchange @lightweight', function () {
|
|||||||
const curve = new elliptic_curves.CurveWithOID(curveName);
|
const curve = new elliptic_curves.CurveWithOID(curveName);
|
||||||
const oid = new OID(curve.oid);
|
const oid = new OID(curve.oid);
|
||||||
const kdfParams = new KDFParams({ hash: curve.hash, cipher: curve.cipher });
|
const kdfParams = new KDFParams({ hash: curve.hash, cipher: curve.cipher });
|
||||||
const data = random.getRandomBytes(16);
|
|
||||||
const Q = key_data[curveName].pub;
|
const Q = key_data[curveName].pub;
|
||||||
const d = key_data[curveName].priv;
|
const d = key_data[curveName].priv;
|
||||||
const { publicKey: V, wrappedKey: C } = await ecdh.encrypt(oid, kdfParams, data, Q, fingerprint1);
|
|
||||||
|
|
||||||
const nativeDecryptSpy = webCrypto ? sinonSandbox.spy(webCrypto, 'deriveBits') : sinonSandbox.spy(nodeCrypto, 'createECDH');
|
await testRountripWithAndWithoutNative(
|
||||||
expect(await ecdh.decrypt(oid, kdfParams, V, C, Q, d, fingerprint1)).to.deep.equal(data);
|
data => ecdh.encrypt(oid, kdfParams, data, Q, fingerprint1),
|
||||||
const expectedNativeCallCount = nativeDecryptSpy.callCount;
|
encryptResult => ecdh.decrypt(oid, kdfParams, encryptResult.publicKey, encryptResult.wrappedKey, Q, d, fingerprint1),
|
||||||
disableNative();
|
expectNativeWeb.has(curveName) // all major browsers implement x25519
|
||||||
expect(await ecdh.decrypt(oid, kdfParams, V, C, Q, d, fingerprint1)).to.deep.equal(data);
|
);
|
||||||
expect(nativeDecryptSpy.callCount).to.equal(expectedNativeCallCount); // assert that fallback implementation was called
|
|
||||||
if (expectNativeWeb.has(curveName)) {
|
|
||||||
expect(nativeDecryptSpy.calledOnce).to.be.true;
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Successful exchange x25519 (legacy)', async function () {
|
||||||
|
const curve = new elliptic_curves.CurveWithOID(openpgp.enums.curve.curve25519Legacy);
|
||||||
|
const oid = new OID(curve.oid);
|
||||||
|
const kdfParams = new KDFParams({ hash: curve.hash, cipher: curve.cipher });
|
||||||
|
|
||||||
|
await testRountripWithAndWithoutNative(
|
||||||
|
data => ecdh.encrypt(oid, kdfParams, data, Q1, fingerprint1),
|
||||||
|
encryptResult => ecdh.decrypt(oid, kdfParams, encryptResult.publicKey, encryptResult.wrappedKey, Q1, d1, fingerprint1),
|
||||||
|
false // all major browsers implement x25519, but webkit linux falls back due to bugs
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,17 +4,59 @@ import chaiAsPromised from 'chai-as-promised'; // eslint-disable-line import/new
|
|||||||
chaiUse(chaiAsPromised);
|
chaiUse(chaiAsPromised);
|
||||||
|
|
||||||
import openpgp from '../initOpenpgp.js';
|
import openpgp from '../initOpenpgp.js';
|
||||||
import * as elliptic_curves from '../../src/crypto/public_key/elliptic';
|
import * as elliptic_curves from '../../src/crypto/public_key/elliptic/index.js';
|
||||||
import { computeDigest } from '../../src/crypto/hash';
|
import { computeDigest } from '../../src/crypto/hash/index.js';
|
||||||
import config from '../../src/config';
|
import config from '../../src/config/index.js';
|
||||||
import util from '../../src/util.js';
|
import util from '../../src/util.js';
|
||||||
|
|
||||||
import elliptic_data from './elliptic_data';
|
import elliptic_data from './elliptic_data.js';
|
||||||
import OID from '../../src/type/oid.js';
|
import OID from '../../src/type/oid.js';
|
||||||
|
import { getRandomBytes } from '../../src/crypto/random.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that the result of `signFunction` can be verified by `verifyFunction`
|
||||||
|
* with and without native crypto support.
|
||||||
|
* @param signFunction - `(data: Uint8Array) => signFunctionResult`
|
||||||
|
* @param verifyFunction - `(encryptFunctionResult) => <decryption result>`
|
||||||
|
* @param expectNative - whether native usage is expected for the algorithm
|
||||||
|
*/
|
||||||
|
const testRountripWithAndWithoutNative = async (
|
||||||
|
{ sinonSandbox, enableNative, disableNative },
|
||||||
|
signFunction,
|
||||||
|
verifyFunction, // (signFunctionResult) => verification result
|
||||||
|
expectNative
|
||||||
|
) => {
|
||||||
|
const nodeCrypto = util.getNodeCrypto();
|
||||||
|
const webCrypto = util.getWebCrypto();
|
||||||
|
const data = getRandomBytes(16);
|
||||||
|
const dataDigest = await computeDigest(openpgp.enums.hash.sha512, data);
|
||||||
|
|
||||||
|
const nativeSpySign = webCrypto ? sinonSandbox.spy(webCrypto, 'sign') : sinonSandbox.spy(nodeCrypto, 'createSign');
|
||||||
|
const nativeResult = await signFunction(data, dataDigest);
|
||||||
|
const expectedNativeSignCallCount = nativeSpySign.callCount;
|
||||||
|
disableNative();
|
||||||
|
const nonNativeResult = await signFunction(data, dataDigest);
|
||||||
|
expect(nativeSpySign.callCount).to.equal(expectedNativeSignCallCount); // assert that fallback implementation was called
|
||||||
|
if (expectNative) {
|
||||||
|
expect(nativeSpySign.calledOnce).to.be.true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nativeSpyVerify = webCrypto ? sinonSandbox.spy(webCrypto, 'verify') : sinonSandbox.spy(nodeCrypto, 'createVerify');
|
||||||
|
enableNative();
|
||||||
|
expect(await verifyFunction(nativeResult, data, dataDigest)).to.be.true;
|
||||||
|
expect(await verifyFunction(nonNativeResult, data, dataDigest)).to.be.true;
|
||||||
|
const expectedNativeVerifyCallCount = nativeSpyVerify.callCount;
|
||||||
|
disableNative();
|
||||||
|
expect(await verifyFunction(nativeResult, data, dataDigest)).to.be.true;
|
||||||
|
expect(await verifyFunction(nonNativeResult, data, dataDigest)).be.true;
|
||||||
|
expect(nativeSpyVerify.callCount).to.equal(expectedNativeVerifyCallCount); // assert that fallback implementation was called
|
||||||
|
if (expectNative) {
|
||||||
|
expect(nativeSpyVerify.callCount).to.equal(2);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const key_data = elliptic_data.key_data;
|
const key_data = elliptic_data.key_data;
|
||||||
/* eslint-disable no-invalid-this */
|
export default () => describe('ECC signatures', function () {
|
||||||
export default () => describe('Elliptic Curve Cryptography @lightweight', function () {
|
|
||||||
const signature_data = {
|
const signature_data = {
|
||||||
priv: new Uint8Array([
|
priv: new Uint8Array([
|
||||||
0x14, 0x2B, 0xE2, 0xB7, 0x4D, 0xBD, 0x1B, 0x22,
|
0x14, 0x2B, 0xE2, 0xB7, 0x4D, 0xBD, 0x1B, 0x22,
|
||||||
@ -241,19 +283,103 @@ export default () => describe('Elliptic Curve Cryptography @lightweight', functi
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
const curves = ['secp256k1' , 'nistP256', 'nistP384', 'nistP521', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'];
|
const curves = ['secp256k1' , 'nistP256', 'nistP384', 'nistP521', 'brainpoolP256r1', 'brainpoolP384r1', 'brainpoolP512r1'];
|
||||||
curves.forEach(curveName => it(`${curveName} - Sign and verify message`, async function () {
|
curves.forEach(curveName => it(`${curveName} - Sign and verify message with generated key`, async function () {
|
||||||
|
const sinonState = { sinonSandbox, enableNative, disableNative };
|
||||||
|
|
||||||
const curve = new elliptic_curves.CurveWithOID(curveName);
|
const curve = new elliptic_curves.CurveWithOID(curveName);
|
||||||
const oid = new OID(curve.oid);
|
const oid = new OID(curve.oid);
|
||||||
const { Q: keyPublic, secret: keyPrivate } = await elliptic_curves.generate(curveName);
|
const expectNativeWeb = new Set(['nistP256', 'nistP384']); // older versions of safari do not implement nistP521
|
||||||
const message = new Uint8Array([
|
|
||||||
0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
|
const nativeKey = await elliptic_curves.generate(curveName);
|
||||||
0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF
|
await testRountripWithAndWithoutNative(
|
||||||
]);
|
sinonState,
|
||||||
const messageDigest = await computeDigest(openpgp.enums.hash.sha512, message);
|
(data, dataDigest) => elliptic_curves.ecdsa.sign(oid, openpgp.enums.hash.sha512, data, nativeKey.Q, nativeKey.secret, dataDigest),
|
||||||
await testNativeAndFallback(async () => {
|
(signature, data, dataDigest) => elliptic_curves.ecdsa.verify(oid, openpgp.enums.hash.sha512, signature, data, nativeKey.Q, dataDigest),
|
||||||
const signature = await elliptic_curves.ecdsa.sign(oid, openpgp.enums.hash.sha512, message, keyPublic, keyPrivate, messageDigest);
|
expectNativeWeb.has(curveName)
|
||||||
await expect(elliptic_curves.ecdsa.verify(oid, openpgp.enums.hash.sha512, signature, message, keyPublic, messageDigest)).to.eventually.be.true;
|
);
|
||||||
|
|
||||||
|
sinonSandbox.restore(); // reset spies
|
||||||
|
disableNative();
|
||||||
|
const nonNativeKey = await elliptic_curves.generate(curveName);
|
||||||
|
enableNative();
|
||||||
|
await testRountripWithAndWithoutNative(
|
||||||
|
sinonState,
|
||||||
|
(data, dataDigest) => elliptic_curves.ecdsa.sign(oid, openpgp.enums.hash.sha512, data, nonNativeKey.Q, nonNativeKey.secret, dataDigest),
|
||||||
|
(signature, data, dataDigest) => elliptic_curves.ecdsa.verify(oid, openpgp.enums.hash.sha512, signature, data, nonNativeKey.Q, dataDigest),
|
||||||
|
expectNativeWeb.has(curveName)
|
||||||
|
);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('EdDSA signature', function () {
|
||||||
|
let sinonSandbox;
|
||||||
|
let getWebCryptoStub;
|
||||||
|
let getNodeCryptoStub;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
sinonSandbox = sinon.createSandbox();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
sinonSandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
const disableNative = () => {
|
||||||
|
enableNative();
|
||||||
|
// stubbed functions return undefined
|
||||||
|
getWebCryptoStub = sinonSandbox.stub(util, 'getWebCrypto').returns({
|
||||||
|
generateKey: () => { const e = new Error('getWebCrypto is mocked'); e.name = 'NotSupportedError'; throw e; },
|
||||||
|
importKey: () => { const e = new Error('getWebCrypto is mocked'); e.name = 'NotSupportedError'; throw e; }
|
||||||
});
|
});
|
||||||
|
getNodeCryptoStub = sinonSandbox.stub(util, 'getNodeCrypto');
|
||||||
|
};
|
||||||
|
const enableNative = () => {
|
||||||
|
getWebCryptoStub && getWebCryptoStub.restore();
|
||||||
|
getNodeCryptoStub && getNodeCryptoStub.restore();
|
||||||
|
};
|
||||||
|
|
||||||
|
it('ed25519Legacy - Sign and verify message with generated key', async function () {
|
||||||
|
const sinonState = { sinonSandbox, enableNative, disableNative };
|
||||||
|
const curve = new elliptic_curves.CurveWithOID(openpgp.enums.curve.ed25519Legacy);
|
||||||
|
const oid = new OID(curve.oid);
|
||||||
|
|
||||||
|
const nativeKey = await elliptic_curves.generate(openpgp.enums.curve.ed25519Legacy);
|
||||||
|
await testRountripWithAndWithoutNative(
|
||||||
|
sinonState,
|
||||||
|
(data, dataDigest) => elliptic_curves.eddsaLegacy.sign(oid, openpgp.enums.hash.sha512, data, nativeKey.Q, nativeKey.secret, dataDigest),
|
||||||
|
(signature, data, dataDigest) => elliptic_curves.eddsaLegacy.verify(oid, openpgp.enums.hash.sha512, signature, data, nativeKey.Q, dataDigest)
|
||||||
|
);
|
||||||
|
|
||||||
|
sinonSandbox.restore(); // reset spies
|
||||||
|
disableNative();
|
||||||
|
const nonNativeKey = await elliptic_curves.generate(openpgp.enums.curve.ed25519Legacy);
|
||||||
|
enableNative();
|
||||||
|
await testRountripWithAndWithoutNative(
|
||||||
|
sinonState,
|
||||||
|
(data, dataDigest) => elliptic_curves.eddsaLegacy.sign(oid, openpgp.enums.hash.sha512, data, nonNativeKey.Q, nonNativeKey.secret, dataDigest),
|
||||||
|
(signature, data, dataDigest) => elliptic_curves.eddsaLegacy.verify(oid, openpgp.enums.hash.sha512, signature, data, nonNativeKey.Q, dataDigest)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
['ed25519', 'ed448'].forEach(algoName => it(`${algoName} - Sign and verify message with native generated key`, async function () {
|
||||||
|
const sinonState = { sinonSandbox, enableNative, disableNative };
|
||||||
|
const algo = openpgp.enums.publicKey[algoName];
|
||||||
|
const nativeKey = await elliptic_curves.eddsa.generate(algo);
|
||||||
|
await testRountripWithAndWithoutNative(
|
||||||
|
sinonState,
|
||||||
|
(data, dataDigest) => elliptic_curves.eddsa.sign(algo, openpgp.enums.hash.sha512, data, nativeKey.A, nativeKey.seed, dataDigest),
|
||||||
|
(signature, data, dataDigest) => elliptic_curves.eddsa.verify(algo, openpgp.enums.hash.sha512, signature, data, nativeKey.A, dataDigest)
|
||||||
|
);
|
||||||
|
|
||||||
|
sinonSandbox.restore(); // reset spies
|
||||||
|
disableNative();
|
||||||
|
const nonNativeKey = await elliptic_curves.eddsa.generate(algo);
|
||||||
|
enableNative();
|
||||||
|
await testRountripWithAndWithoutNative(
|
||||||
|
sinonState,
|
||||||
|
(data, dataDigest) => elliptic_curves.eddsa.sign(algo, openpgp.enums.hash.sha512, data, nonNativeKey.A, nonNativeKey.seed, dataDigest),
|
||||||
|
(signature, data, dataDigest) => elliptic_curves.eddsa.verify(algo, openpgp.enums.hash.sha512, signature, data, nonNativeKey.A, dataDigest)
|
||||||
|
);
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -2,7 +2,7 @@ import testBigInteger from './biginteger';
|
|||||||
import testCipher from './cipher';
|
import testCipher from './cipher';
|
||||||
import testHash from './hash';
|
import testHash from './hash';
|
||||||
import testCrypto from './crypto';
|
import testCrypto from './crypto';
|
||||||
import testElliptic from './elliptic';
|
import testElliptic from './ecdsa_eddsa';
|
||||||
import testBrainpoolRFC7027 from './brainpool_rfc7027';
|
import testBrainpoolRFC7027 from './brainpool_rfc7027';
|
||||||
import testECDH from './ecdh';
|
import testECDH from './ecdh';
|
||||||
import testPKCS5 from './pkcs5';
|
import testPKCS5 from './pkcs5';
|
||||||
|
|||||||
@ -90,8 +90,10 @@ async function generatePrivateKeyObject(options) {
|
|||||||
export default () => {
|
export default () => {
|
||||||
describe('EdDSA parameter validation (legacy format)', function() {
|
describe('EdDSA parameter validation (legacy format)', function() {
|
||||||
let eddsaKey;
|
let eddsaKey;
|
||||||
|
let anotherEddsaKey;
|
||||||
before(async () => {
|
before(async () => {
|
||||||
eddsaKey = await generatePrivateKeyObject({ curve: 'ed25519Legacy' });
|
eddsaKey = await generatePrivateKeyObject({ curve: 'ed25519Legacy' });
|
||||||
|
anotherEddsaKey = await generatePrivateKeyObject({ curve: 'ed25519Legacy' });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('EdDSA params should be valid', async function() {
|
it('EdDSA params should be valid', async function() {
|
||||||
@ -100,11 +102,10 @@ export default () => {
|
|||||||
|
|
||||||
it('detect invalid edDSA Q', async function() {
|
it('detect invalid edDSA Q', async function() {
|
||||||
const eddsaKeyPacket = await cloneKeyPacket(eddsaKey);
|
const eddsaKeyPacket = await cloneKeyPacket(eddsaKey);
|
||||||
const Q = eddsaKeyPacket.publicParams.Q;
|
eddsaKeyPacket.publicParams.Q = anotherEddsaKey.keyPacket.publicParams.Q;
|
||||||
Q[0]++;
|
|
||||||
await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
|
|
||||||
const infQ = new Uint8Array(Q.length);
|
const infQ = new Uint8Array(eddsaKeyPacket.publicParams.Q.length);
|
||||||
eddsaKeyPacket.publicParams.Q = infQ;
|
eddsaKeyPacket.publicParams.Q = infQ;
|
||||||
await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
});
|
});
|
||||||
@ -198,13 +199,19 @@ export default () => {
|
|||||||
describe(`ECC ${curve} parameter validation`, () => {
|
describe(`ECC ${curve} parameter validation`, () => {
|
||||||
let ecdsaKey;
|
let ecdsaKey;
|
||||||
let ecdhKey;
|
let ecdhKey;
|
||||||
|
let anotherEcdsaKey;
|
||||||
|
let anotherEcdhKey;
|
||||||
before(async () => {
|
before(async () => {
|
||||||
if (curve !== 'curve25519Legacy') {
|
if (curve !== 'curve25519Legacy') {
|
||||||
ecdsaKey = await generatePrivateKeyObject({ curve });
|
ecdsaKey = await generatePrivateKeyObject({ curve });
|
||||||
ecdhKey = ecdsaKey.subkeys[0];
|
ecdhKey = ecdsaKey.subkeys[0];
|
||||||
|
anotherEcdsaKey = await generatePrivateKeyObject({ curve });
|
||||||
|
anotherEcdhKey = anotherEcdsaKey.subkeys[0];
|
||||||
} else {
|
} else {
|
||||||
const eddsaKey = await generatePrivateKeyObject({ curve: 'ed25519Legacy' });
|
const eddsaKey = await generatePrivateKeyObject({ curve: 'ed25519Legacy' });
|
||||||
ecdhKey = eddsaKey.subkeys[0];
|
ecdhKey = eddsaKey.subkeys[0];
|
||||||
|
const anotherEddsaKey = await generatePrivateKeyObject({ curve: 'ed25519Legacy' });
|
||||||
|
anotherEcdhKey = anotherEddsaKey.subkeys[0];
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -220,10 +227,9 @@ export default () => {
|
|||||||
this.skip();
|
this.skip();
|
||||||
}
|
}
|
||||||
const keyPacket = await cloneKeyPacket(ecdsaKey);
|
const keyPacket = await cloneKeyPacket(ecdsaKey);
|
||||||
const Q = keyPacket.publicParams.Q;
|
keyPacket.publicParams.Q = anotherEcdsaKey.keyPacket.publicParams.Q;
|
||||||
Q[16]++;
|
|
||||||
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
const infQ = new Uint8Array(Q.length);
|
const infQ = new Uint8Array(anotherEcdsaKey.keyPacket.publicParams.Q.length);
|
||||||
infQ[0] = 4;
|
infQ[0] = 4;
|
||||||
keyPacket.publicParams.Q = infQ;
|
keyPacket.publicParams.Q = infQ;
|
||||||
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
@ -235,11 +241,10 @@ export default () => {
|
|||||||
|
|
||||||
it(`ECDH ${curve} - detect invalid Q`, async function() {
|
it(`ECDH ${curve} - detect invalid Q`, async function() {
|
||||||
const keyPacket = await cloneKeyPacket(ecdhKey);
|
const keyPacket = await cloneKeyPacket(ecdhKey);
|
||||||
const Q = keyPacket.publicParams.Q;
|
keyPacket.publicParams.Q = anotherEcdhKey.keyPacket.publicParams.Q;
|
||||||
Q[16]++;
|
|
||||||
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
|
|
||||||
const infQ = new Uint8Array(Q.length);
|
const infQ = new Uint8Array(keyPacket.publicParams.Q.length);
|
||||||
keyPacket.publicParams.Q = infQ;
|
keyPacket.publicParams.Q = infQ;
|
||||||
infQ[0] = 4;
|
infQ[0] = 4;
|
||||||
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
@ -252,9 +257,13 @@ export default () => {
|
|||||||
describe(`Ed${curveID}/X${curveID} parameter validation`, function() {
|
describe(`Ed${curveID}/X${curveID} parameter validation`, function() {
|
||||||
let eddsaKey;
|
let eddsaKey;
|
||||||
let ecdhXKey;
|
let ecdhXKey;
|
||||||
|
let anotherEddsaKey;
|
||||||
|
let anotherEcdhXKey;
|
||||||
before(async () => {
|
before(async () => {
|
||||||
eddsaKey = await generatePrivateKeyObject({ type: `curve${curveID}` });
|
eddsaKey = await generatePrivateKeyObject({ type: `curve${curveID}` });
|
||||||
ecdhXKey = eddsaKey.subkeys[0];
|
ecdhXKey = eddsaKey.subkeys[0];
|
||||||
|
anotherEddsaKey = await generatePrivateKeyObject({ type: `curve${curveID}` });
|
||||||
|
anotherEcdhXKey = anotherEddsaKey.subkeys[0];
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`Ed${curveID} params should be valid`, async function() {
|
it(`Ed${curveID} params should be valid`, async function() {
|
||||||
@ -263,11 +272,10 @@ export default () => {
|
|||||||
|
|
||||||
it(`detect invalid Ed${curveID} public point`, async function() {
|
it(`detect invalid Ed${curveID} public point`, async function() {
|
||||||
const eddsaKeyPacket = await cloneKeyPacket(eddsaKey);
|
const eddsaKeyPacket = await cloneKeyPacket(eddsaKey);
|
||||||
const A = eddsaKeyPacket.publicParams.A;
|
eddsaKeyPacket.publicParams.A = anotherEddsaKey.keyPacket.publicParams.A;
|
||||||
A[0]++;
|
|
||||||
await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
|
|
||||||
const infA = new Uint8Array(A.length);
|
const infA = new Uint8Array(eddsaKeyPacket.publicParams.A.length);
|
||||||
eddsaKeyPacket.publicParams.A = infA;
|
eddsaKeyPacket.publicParams.A = infA;
|
||||||
await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(eddsaKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
});
|
});
|
||||||
@ -278,11 +286,10 @@ export default () => {
|
|||||||
|
|
||||||
it(`detect invalid X${curveID} public point`, async function() {
|
it(`detect invalid X${curveID} public point`, async function() {
|
||||||
const ecdhXKeyPacket = await cloneKeyPacket(ecdhXKey);
|
const ecdhXKeyPacket = await cloneKeyPacket(ecdhXKey);
|
||||||
const A = ecdhXKeyPacket.publicParams.A;
|
ecdhXKeyPacket.publicParams.A = anotherEcdhXKey.keyPacket.publicParams.A;
|
||||||
A[0]++;
|
|
||||||
await expect(ecdhXKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(ecdhXKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
|
|
||||||
const infA = new Uint8Array(A.length);
|
const infA = new Uint8Array(ecdhXKeyPacket.publicParams.A.length);
|
||||||
ecdhXKeyPacket.publicParams.A = infA;
|
ecdhXKeyPacket.publicParams.A = infA;
|
||||||
await expect(ecdhXKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(ecdhXKeyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
});
|
});
|
||||||
@ -291,8 +298,10 @@ export default () => {
|
|||||||
|
|
||||||
describe('RSA parameter validation', function() {
|
describe('RSA parameter validation', function() {
|
||||||
let rsaKey;
|
let rsaKey;
|
||||||
|
let anotherRsaKey;
|
||||||
before(async () => {
|
before(async () => {
|
||||||
rsaKey = await generatePrivateKeyObject({ type: 'rsa', rsaBits: 2048 });
|
rsaKey = await generatePrivateKeyObject({ type: 'rsa', rsaBits: 2048 });
|
||||||
|
anotherRsaKey = await generatePrivateKeyObject({ type: 'rsa', rsaBits: 2048 });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('generated RSA params are valid', async function() {
|
it('generated RSA params are valid', async function() {
|
||||||
@ -301,15 +310,14 @@ export default () => {
|
|||||||
|
|
||||||
it('detect invalid RSA n', async function() {
|
it('detect invalid RSA n', async function() {
|
||||||
const keyPacket = await cloneKeyPacket(rsaKey);
|
const keyPacket = await cloneKeyPacket(rsaKey);
|
||||||
const n = keyPacket.publicParams.n;
|
keyPacket.publicParams.n = anotherRsaKey.keyPacket.publicParams.n;
|
||||||
n[0]++;
|
|
||||||
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('detect invalid RSA e', async function() {
|
it('detect invalid RSA e', async function() {
|
||||||
const keyPacket = await cloneKeyPacket(rsaKey);
|
const keyPacket = await cloneKeyPacket(rsaKey);
|
||||||
const e = keyPacket.publicParams.e;
|
const e = keyPacket.publicParams.e;
|
||||||
e[0]++;
|
e[0]++; // e is hard-coded so we don't take it from `anotherRsaKey`
|
||||||
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
await expect(keyPacket.validate()).to.be.rejectedWith('Key is invalid');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user