mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-11-24 14:35:51 +00:00
Lightweight build: lazy load tweetnacl dependency module (curve25519 JS fallback)
Since all major browsers have shipped support for the curve in WebCrypto, we only load the JS fallback if needed. Also, add native/non-native ECDH test for Curve25519Legacy. (The more modern X25519/X448 algo implementations cannot be tested that way since they include an HKDF step for which we assume native support and do not implement a fallback.)
This commit is contained in:
parent
721b918296
commit
ed5554e114
@ -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 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,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: {
|
||||||
@ -278,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,10 +20,9 @@
|
|||||||
* @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 { computeDigest, getHashByteLength } from '../../hash';
|
import { getHashByteLength } from '../../hash';
|
||||||
import { getRandomBytes } from '../../random';
|
import { getRandomBytes } from '../../random';
|
||||||
import { b64ToUint8Array, uint8ArrayToB64 } from '../../../encoding/base64';
|
import { b64ToUint8Array, uint8ArrayToB64 } from '../../../encoding/base64';
|
||||||
|
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -203,7 +206,7 @@ export async function validateParams(algo, A, seed) {
|
|||||||
if (err.name !== 'NotSupportedError') {
|
if (err.name !== 'NotSupportedError') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
const { default: ed25519 } = await import('@openpgp/tweetnacl');
|
||||||
const { publicKey } = ed25519.sign.keyPair.fromSeed(seed);
|
const { publicKey } = ed25519.sign.keyPair.fromSeed(seed);
|
||||||
return util.equalsUint8Array(A, publicKey);
|
return util.equalsUint8Array(A, publicKey);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,6 @@
|
|||||||
* @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';
|
||||||
|
|||||||
@ -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';
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user