mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-06-11 00:26:42 +00:00
Merge pull request #1782
This commit is contained in:
commit
79014f00f0
23
README.md
23
README.md
@ -57,19 +57,18 @@ library to convert back and forth between them.
|
||||
|
||||
| Curve | Encryption | Signature | NodeCrypto | WebCrypto | Constant-Time |
|
||||
|:---------------:|:----------:|:---------:|:----------:|:---------:|:-----------------:|
|
||||
| curve25519 | ECDH | N/A | No | No | Algorithmically** |
|
||||
| ed25519 | N/A | EdDSA | No | No | Algorithmically** |
|
||||
| nistP256 | ECDH | ECDSA | Yes* | Yes* | If native*** |
|
||||
| nistP384 | ECDH | ECDSA | Yes* | Yes* | If native*** |
|
||||
| nistP521 | ECDH | ECDSA | Yes* | Yes* | If native*** |
|
||||
| brainpoolP256r1 | ECDH | ECDSA | Yes* | No | If native*** |
|
||||
| brainpoolP384r1 | ECDH | ECDSA | Yes* | No | If native*** |
|
||||
| brainpoolP512r1 | ECDH | ECDSA | Yes* | No | If native*** |
|
||||
| secp256k1 | ECDH | ECDSA | Yes* | No | If native*** |
|
||||
| curve25519 | ECDH | N/A | No | Yes* | If native** |
|
||||
| ed25519 | N/A | EdDSA | No | Yes* | If native** |
|
||||
| nistP256 | ECDH | ECDSA | Yes* | Yes* | If native** |
|
||||
| nistP384 | ECDH | ECDSA | Yes* | Yes* | If native** |
|
||||
| nistP521 | ECDH | ECDSA | Yes* | Yes* | If native** |
|
||||
| brainpoolP256r1 | ECDH | ECDSA | Yes* | No | If native** |
|
||||
| brainpoolP384r1 | ECDH | ECDSA | Yes* | No | If native** |
|
||||
| brainpoolP512r1 | ECDH | ECDSA | Yes* | No | If native** |
|
||||
| secp256k1 | ECDH | ECDSA | Yes* | No | If native** |
|
||||
|
||||
\* when available
|
||||
\** the curve25519 and ed25519 implementations are algorithmically constant-time, but may not be constant-time after optimizations of the JavaScript compiler
|
||||
\*** these curves are only constant-time if the underlying native implementation is available and constant-time
|
||||
\* when available
|
||||
\** these curves are only constant-time if the underlying native implementation is available and constant-time
|
||||
|
||||
* If the user's browser supports [native WebCrypto](https://caniuse.com/#feat=cryptography) via the `window.crypto.subtle` API, this will be used. Under Node.js the native [crypto module](https://nodejs.org/api/crypto.html#crypto_crypto) is used.
|
||||
|
||||
|
@ -20,16 +20,15 @@
|
||||
* @module crypto/public_key/elliptic/ecdh
|
||||
*/
|
||||
|
||||
import nacl from '@openpgp/tweetnacl';
|
||||
import { CurveWithOID, jwkToRawPublic, rawPublicToJWK, privateToJWK, validateStandardParams, checkPublicPointEnconding } from './oid_curves';
|
||||
import * as aesKW from '../../aes_kw';
|
||||
import { getRandomBytes } from '../../random';
|
||||
import hash from '../../hash';
|
||||
import enums from '../../../enums';
|
||||
import util from '../../../util';
|
||||
import { b64ToUint8Array } from '../../../encoding/base64';
|
||||
import * as pkcs5 from '../../pkcs5';
|
||||
import { getCipherParams } from '../../cipher';
|
||||
import { generateEphemeralEncryptionMaterial as ecdhXGenerateEphemeralEncryptionMaterial, recomputeSharedSecret as ecdhXRecomputeSharedSecret } from './ecdh_x';
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const nodeCrypto = util.getNodeCrypto();
|
||||
@ -92,10 +91,8 @@ async function kdf(hashAlgo, X, length, param, stripLeading = false, stripTraili
|
||||
async function genPublicEphemeralKey(curve, Q) {
|
||||
switch (curve.type) {
|
||||
case 'curve25519Legacy': {
|
||||
const d = getRandomBytes(32);
|
||||
const { secretKey, sharedKey } = await genPrivateEphemeralKey(curve, Q, null, d);
|
||||
let { publicKey } = nacl.box.keyPair.fromSecretKey(secretKey);
|
||||
publicKey = util.concatUint8Array([new Uint8Array([curve.wireFormatLeadingByte]), publicKey]);
|
||||
const { sharedSecret: sharedKey, ephemeralPublicKey } = await ecdhXGenerateEphemeralEncryptionMaterial(enums.publicKey.x25519, Q.subarray(1));
|
||||
const publicKey = util.concatUint8Array([new Uint8Array([curve.wireFormatLeadingByte]), ephemeralPublicKey]);
|
||||
return { publicKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
|
||||
}
|
||||
case 'web':
|
||||
@ -159,7 +156,7 @@ async function genPrivateEphemeralKey(curve, V, Q, d) {
|
||||
switch (curve.type) {
|
||||
case 'curve25519Legacy': {
|
||||
const secretKey = d.slice().reverse();
|
||||
const sharedKey = nacl.scalarMult(secretKey, V.subarray(1));
|
||||
const sharedKey = await ecdhXRecomputeSharedSecret(enums.publicKey.x25519, V.subarray(1), Q.subarray(1), secretKey);
|
||||
return { secretKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
|
||||
}
|
||||
case 'web':
|
||||
|
@ -11,6 +11,7 @@ import enums from '../../../enums';
|
||||
import util from '../../../util';
|
||||
import computeHKDF from '../../hkdf';
|
||||
import { getCipherParams } from '../../cipher';
|
||||
import { b64ToUint8Array, uint8ArrayToB64 } from '../../../encoding/base64';
|
||||
|
||||
const HKDF_INFO = {
|
||||
x25519: util.encodeUTF8('OpenPGP X25519'),
|
||||
@ -24,12 +25,28 @@ const HKDF_INFO = {
|
||||
*/
|
||||
export async function generate(algo) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.x25519: {
|
||||
// k stays in little-endian, unlike legacy ECDH over curve25519
|
||||
const k = getRandomBytes(32);
|
||||
const { publicKey: A } = x25519.box.keyPair.fromSecretKey(k);
|
||||
return { A, k };
|
||||
}
|
||||
case enums.publicKey.x25519:
|
||||
try {
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const webCryptoKey = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']);
|
||||
|
||||
const privateKey = await webCrypto.exportKey('jwk', webCryptoKey.privateKey);
|
||||
const publicKey = await webCrypto.exportKey('jwk', webCryptoKey.publicKey);
|
||||
|
||||
return {
|
||||
A: new Uint8Array(b64ToUint8Array(publicKey.x)),
|
||||
k: b64ToUint8Array(privateKey.d, true)
|
||||
};
|
||||
} catch (err) {
|
||||
if (err.name !== 'NotSupportedError') {
|
||||
throw err;
|
||||
}
|
||||
// k stays in little-endian, unlike legacy ECDH over curve25519
|
||||
const k = getRandomBytes(32);
|
||||
const { publicKey: A } = x25519.box.keyPair.fromSecretKey(k);
|
||||
return { A, k };
|
||||
}
|
||||
|
||||
case enums.publicKey.x448: {
|
||||
const x448 = await util.getNobleCurve(enums.publicKey.x448);
|
||||
const k = x448.utils.randomPrivateKey();
|
||||
@ -87,16 +104,14 @@ export async function validateParams(algo, A, k) {
|
||||
* @async
|
||||
*/
|
||||
export async function encrypt(algo, data, recipientA) {
|
||||
const { ephemeralPublicKey, sharedSecret } = await generateEphemeralEncryptionMaterial(algo, recipientA);
|
||||
const hkdfInput = util.concatUint8Array([
|
||||
ephemeralPublicKey,
|
||||
recipientA,
|
||||
sharedSecret
|
||||
]);
|
||||
switch (algo) {
|
||||
case enums.publicKey.x25519: {
|
||||
const ephemeralSecretKey = getRandomBytes(32);
|
||||
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
|
||||
const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey);
|
||||
const hkdfInput = util.concatUint8Array([
|
||||
ephemeralPublicKey,
|
||||
recipientA,
|
||||
sharedSecret
|
||||
]);
|
||||
const cipherAlgo = enums.symmetric.aes128;
|
||||
const { keySize } = getCipherParams(cipherAlgo);
|
||||
const encryptionKey = await computeHKDF(enums.hash.sha256, hkdfInput, new Uint8Array(), HKDF_INFO.x25519, keySize);
|
||||
@ -104,15 +119,6 @@ export async function encrypt(algo, data, recipientA) {
|
||||
return { ephemeralPublicKey, wrappedKey };
|
||||
}
|
||||
case enums.publicKey.x448: {
|
||||
const x448 = await util.getNobleCurve(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 cipherAlgo = enums.symmetric.aes256;
|
||||
const { keySize } = getCipherParams(enums.symmetric.aes256);
|
||||
const encryptionKey = await computeHKDF(enums.hash.sha512, hkdfInput, new Uint8Array(), HKDF_INFO.x448, keySize);
|
||||
@ -137,27 +143,20 @@ export async function encrypt(algo, data, recipientA) {
|
||||
* @async
|
||||
*/
|
||||
export async function decrypt(algo, ephemeralPublicKey, wrappedKey, A, k) {
|
||||
const sharedSecret = await recomputeSharedSecret(algo, ephemeralPublicKey, A, k);
|
||||
const hkdfInput = util.concatUint8Array([
|
||||
ephemeralPublicKey,
|
||||
A,
|
||||
sharedSecret
|
||||
]);
|
||||
switch (algo) {
|
||||
case enums.publicKey.x25519: {
|
||||
const sharedSecret = x25519.scalarMult(k, ephemeralPublicKey);
|
||||
const hkdfInput = util.concatUint8Array([
|
||||
ephemeralPublicKey,
|
||||
A,
|
||||
sharedSecret
|
||||
]);
|
||||
const cipherAlgo = enums.symmetric.aes128;
|
||||
const { keySize } = getCipherParams(cipherAlgo);
|
||||
const encryptionKey = await computeHKDF(enums.hash.sha256, hkdfInput, new Uint8Array(), HKDF_INFO.x25519, keySize);
|
||||
return aesKW.unwrap(cipherAlgo, encryptionKey, wrappedKey);
|
||||
}
|
||||
case enums.publicKey.x448: {
|
||||
const x448 = await util.getNobleCurve(enums.publicKey.x448);
|
||||
const sharedSecret = x448.getSharedSecret(k, ephemeralPublicKey);
|
||||
const hkdfInput = util.concatUint8Array([
|
||||
ephemeralPublicKey,
|
||||
A,
|
||||
sharedSecret
|
||||
]);
|
||||
const cipherAlgo = enums.symmetric.aes256;
|
||||
const { keySize } = getCipherParams(enums.symmetric.aes256);
|
||||
const encryptionKey = await computeHKDF(enums.hash.sha512, hkdfInput, new Uint8Array(), HKDF_INFO.x448, keySize);
|
||||
@ -180,3 +179,108 @@ export function getPayloadSize(algo) {
|
||||
throw new Error('Unsupported ECDH algorithm');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate shared secret and ephemeral public key for encryption
|
||||
* @returns {Promise<{ ephemeralPublicKey: Uint8Array, sharedSecret: Uint8Array }>} ephemeral public key (K_A) and shared secret
|
||||
* @async
|
||||
*/
|
||||
export async function generateEphemeralEncryptionMaterial(algo, recipientA) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.x25519:
|
||||
try {
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const jwk = publicKeyToJWK(algo, recipientA);
|
||||
const ephemeralKeyPair = await webCrypto.generateKey('X25519', true, ['deriveKey', 'deriveBits']);
|
||||
const recipientPublicKey = await webCrypto.importKey('jwk', jwk, 'X25519', false, []);
|
||||
const sharedSecretBuffer = await webCrypto.deriveBits(
|
||||
{ name: 'X25519', public: recipientPublicKey },
|
||||
ephemeralKeyPair.privateKey,
|
||||
getPayloadSize(algo) * 8 // in bits
|
||||
);
|
||||
const ephemeralPublicKeyJwt = await webCrypto.exportKey('jwk', ephemeralKeyPair.publicKey);
|
||||
return {
|
||||
sharedSecret: new Uint8Array(sharedSecretBuffer),
|
||||
ephemeralPublicKey: new Uint8Array(b64ToUint8Array(ephemeralPublicKeyJwt.x))
|
||||
};
|
||||
} catch (err) {
|
||||
if (err.name !== 'NotSupportedError') {
|
||||
throw err;
|
||||
}
|
||||
const ephemeralSecretKey = getRandomBytes(getPayloadSize(algo));
|
||||
const sharedSecret = x25519.scalarMult(ephemeralSecretKey, recipientA);
|
||||
const { publicKey: ephemeralPublicKey } = x25519.box.keyPair.fromSecretKey(ephemeralSecretKey);
|
||||
|
||||
return { ephemeralPublicKey, sharedSecret };
|
||||
}
|
||||
case enums.publicKey.x448: {
|
||||
const x448 = await util.getNobleCurve(enums.publicKey.x448);
|
||||
const ephemeralSecretKey = x448.utils.randomPrivateKey();
|
||||
const sharedSecret = x448.getSharedSecret(ephemeralSecretKey, recipientA);
|
||||
const ephemeralPublicKey = x448.getPublicKey(ephemeralSecretKey);
|
||||
return { ephemeralPublicKey, sharedSecret };
|
||||
}
|
||||
default:
|
||||
throw new Error('Unsupported ECDH algorithm');
|
||||
}
|
||||
}
|
||||
|
||||
export async function recomputeSharedSecret(algo, ephemeralPublicKey, A, k) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.x25519:
|
||||
try {
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const privateKeyJWK = privateKeyToJWK(algo, A, k);
|
||||
const ephemeralPublicKeyJWK = publicKeyToJWK(algo, ephemeralPublicKey);
|
||||
const privateKey = await webCrypto.importKey('jwk', privateKeyJWK, 'X25519', false, ['deriveKey', 'deriveBits']);
|
||||
const ephemeralPublicKeyReference = await webCrypto.importKey('jwk', ephemeralPublicKeyJWK, 'X25519', false, []);
|
||||
const sharedSecretBuffer = await webCrypto.deriveBits(
|
||||
{ name: 'X25519', public: ephemeralPublicKeyReference },
|
||||
privateKey,
|
||||
getPayloadSize(algo) * 8 // in bits
|
||||
);
|
||||
return new Uint8Array(sharedSecretBuffer);
|
||||
} catch (err) {
|
||||
if (err.name !== 'NotSupportedError') {
|
||||
throw err;
|
||||
}
|
||||
return x25519.scalarMult(k, ephemeralPublicKey);
|
||||
}
|
||||
case enums.publicKey.x448: {
|
||||
const x448 = await util.getNobleCurve(enums.publicKey.x448);
|
||||
const sharedSecret = x448.getSharedSecret(k, ephemeralPublicKey);
|
||||
return sharedSecret;
|
||||
}
|
||||
default:
|
||||
throw new Error('Unsupported ECDH algorithm');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function publicKeyToJWK(algo, publicKey) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.x25519: {
|
||||
const jwk = {
|
||||
kty: 'OKP',
|
||||
crv: 'X25519',
|
||||
x: uint8ArrayToB64(publicKey, true),
|
||||
ext: true
|
||||
};
|
||||
return jwk;
|
||||
}
|
||||
default:
|
||||
throw new Error('Unsupported ECDH algorithm');
|
||||
}
|
||||
}
|
||||
|
||||
function privateKeyToJWK(algo, publicKey, privateKey) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.x25519: {
|
||||
const jwk = publicKeyToJWK(algo, publicKey);
|
||||
jwk.d = uint8ArrayToB64(privateKey, true);
|
||||
return jwk;
|
||||
}
|
||||
default:
|
||||
throw new Error('Unsupported ECDH algorithm');
|
||||
}
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import util from '../../../util';
|
||||
import enums from '../../../enums';
|
||||
import hash from '../../hash';
|
||||
import { getRandomBytes } from '../../random';
|
||||
import { b64ToUint8Array, uint8ArrayToB64 } from '../../../encoding/base64';
|
||||
|
||||
|
||||
/**
|
||||
@ -34,11 +35,27 @@ import { getRandomBytes } from '../../random';
|
||||
*/
|
||||
export async function generate(algo) {
|
||||
switch (algo) {
|
||||
case enums.publicKey.ed25519: {
|
||||
const seed = getRandomBytes(getPayloadSize(algo));
|
||||
const { publicKey: A } = ed25519.sign.keyPair.fromSeed(seed);
|
||||
return { A, seed };
|
||||
}
|
||||
case enums.publicKey.ed25519:
|
||||
try {
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const webCryptoKey = await webCrypto.generateKey('Ed25519', true, ['sign', 'verify']);
|
||||
|
||||
const privateKey = await webCrypto.exportKey('jwk', webCryptoKey.privateKey);
|
||||
const publicKey = await webCrypto.exportKey('jwk', webCryptoKey.publicKey);
|
||||
|
||||
return {
|
||||
A: new Uint8Array(b64ToUint8Array(publicKey.x)),
|
||||
seed: b64ToUint8Array(privateKey.d, true)
|
||||
};
|
||||
} catch (err) {
|
||||
if (err.name !== 'NotSupportedError') {
|
||||
throw err;
|
||||
}
|
||||
const seed = getRandomBytes(getPayloadSize(algo));
|
||||
const { publicKey: A } = ed25519.sign.keyPair.fromSeed(seed);
|
||||
return { A, seed };
|
||||
}
|
||||
|
||||
case enums.publicKey.ed448: {
|
||||
const ed448 = await util.getNobleCurve(enums.publicKey.ed448);
|
||||
const seed = ed448.utils.randomPrivateKey();
|
||||
@ -68,11 +85,26 @@ export async function sign(algo, hashAlgo, message, publicKey, privateKey, hashe
|
||||
throw new Error('Hash algorithm too weak for EdDSA.');
|
||||
}
|
||||
switch (algo) {
|
||||
case enums.publicKey.ed25519: {
|
||||
const secretKey = util.concatUint8Array([privateKey, publicKey]);
|
||||
const signature = ed25519.sign.detached(hashed, secretKey);
|
||||
return { RS: signature };
|
||||
}
|
||||
case enums.publicKey.ed25519:
|
||||
try {
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const jwk = privateKeyToJWK(algo, publicKey, privateKey);
|
||||
const key = await webCrypto.importKey('jwk', jwk, 'Ed25519', false, ['sign']);
|
||||
|
||||
const signature = new Uint8Array(
|
||||
await webCrypto.sign('Ed25519', key, hashed)
|
||||
);
|
||||
|
||||
return { RS: signature };
|
||||
} catch (err) {
|
||||
if (err.name !== 'NotSupportedError') {
|
||||
throw err;
|
||||
}
|
||||
const secretKey = util.concatUint8Array([privateKey, publicKey]);
|
||||
const signature = ed25519.sign.detached(hashed, secretKey);
|
||||
return { RS: signature };
|
||||
}
|
||||
|
||||
case enums.publicKey.ed448: {
|
||||
const ed448 = await util.getNobleCurve(enums.publicKey.ed448);
|
||||
const signature = ed448.sign(hashed, privateKey);
|
||||
@ -101,7 +133,19 @@ export async function verify(algo, hashAlgo, { RS }, m, publicKey, hashed) {
|
||||
}
|
||||
switch (algo) {
|
||||
case enums.publicKey.ed25519:
|
||||
return ed25519.sign.detached.verify(hashed, RS, publicKey);
|
||||
try {
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const jwk = publicKeyToJWK(algo, publicKey);
|
||||
const key = await webCrypto.importKey('jwk', jwk, 'Ed25519', false, ['verify']);
|
||||
const verified = await webCrypto.verify('Ed25519', key, RS, hashed);
|
||||
return verified;
|
||||
} catch (err) {
|
||||
if (err.name !== 'NotSupportedError') {
|
||||
throw err;
|
||||
}
|
||||
return ed25519.sign.detached.verify(hashed, RS, publicKey);
|
||||
}
|
||||
|
||||
case enums.publicKey.ed448: {
|
||||
const ed448 = await util.getNobleCurve(enums.publicKey.ed448);
|
||||
return ed448.verify(RS, hashed, publicKey);
|
||||
@ -125,6 +169,7 @@ export async function validateParams(algo, A, seed) {
|
||||
/**
|
||||
* Derive public point A' from private key
|
||||
* and expect A == A'
|
||||
* TODO: move to sign-verify using WebCrypto (same as ECDSA) when curve is more widely implemented
|
||||
*/
|
||||
const { publicKey } = ed25519.sign.keyPair.fromSeed(seed);
|
||||
return util.equalsUint8Array(A, publicKey);
|
||||
@ -164,3 +209,31 @@ export function getPreferredHashAlgo(algo) {
|
||||
throw new Error('Unknown EdDSA algo');
|
||||
}
|
||||
}
|
||||
|
||||
const publicKeyToJWK = (algo, publicKey) => {
|
||||
switch (algo) {
|
||||
case enums.publicKey.ed25519: {
|
||||
const jwk = {
|
||||
kty: 'OKP',
|
||||
crv: 'Ed25519',
|
||||
x: uint8ArrayToB64(publicKey, true),
|
||||
ext: true
|
||||
};
|
||||
return jwk;
|
||||
}
|
||||
default:
|
||||
throw new Error('Unsupported EdDSA algorithm');
|
||||
}
|
||||
};
|
||||
|
||||
const privateKeyToJWK = (algo, publicKey, privateKey) => {
|
||||
switch (algo) {
|
||||
case enums.publicKey.ed25519: {
|
||||
const jwk = publicKeyToJWK(algo, publicKey);
|
||||
jwk.d = uint8ArrayToB64(privateKey, true);
|
||||
return jwk;
|
||||
}
|
||||
default:
|
||||
throw new Error('Unsupported EdDSA algorithm');
|
||||
}
|
||||
};
|
||||
|
@ -26,6 +26,7 @@ import util from '../../../util';
|
||||
import enums from '../../../enums';
|
||||
import hash from '../../hash';
|
||||
import { CurveWithOID, checkPublicPointEnconding } from './oid_curves';
|
||||
import { sign as eddsaSign, verify as eddsaVerify } from './eddsa';
|
||||
|
||||
/**
|
||||
* Sign a message using the provided legacy EdDSA key
|
||||
@ -48,8 +49,7 @@ export async function sign(oid, hashAlgo, message, publicKey, privateKey, hashed
|
||||
// see https://tools.ietf.org/id/draft-ietf-openpgp-rfc4880bis-10.html#section-15-7.2
|
||||
throw new Error('Hash algorithm too weak for EdDSA.');
|
||||
}
|
||||
const secretKey = util.concatUint8Array([privateKey, publicKey.subarray(1)]);
|
||||
const signature = nacl.sign.detached(hashed, secretKey);
|
||||
const { RS: signature } = await eddsaSign(enums.publicKey.ed25519, hashAlgo, message, publicKey.subarray(1), privateKey, hashed);
|
||||
// EdDSA signature params are returned in little-endian format
|
||||
return {
|
||||
r: signature.subarray(0, 32),
|
||||
@ -75,8 +75,8 @@ export async function verify(oid, hashAlgo, { r, s }, m, publicKey, hashed) {
|
||||
if (hash.getHashByteLength(hashAlgo) < hash.getHashByteLength(enums.hash.sha256)) {
|
||||
throw new Error('Hash algorithm too weak for EdDSA.');
|
||||
}
|
||||
const signature = util.concatUint8Array([r, s]);
|
||||
return nacl.sign.detached.verify(hashed, signature, publicKey.subarray(1));
|
||||
const RS = util.concatUint8Array([r, s]);
|
||||
return eddsaVerify(enums.publicKey.ed25519, hashAlgo, { RS }, m, publicKey.subarray(1), hashed);
|
||||
}
|
||||
/**
|
||||
* Validate legacy EdDSA parameters
|
||||
|
@ -20,12 +20,13 @@
|
||||
* @module crypto/public_key/elliptic/curve
|
||||
*/
|
||||
import nacl from '@openpgp/tweetnacl';
|
||||
import { getRandomBytes } from '../../random';
|
||||
import enums from '../../../enums';
|
||||
import util from '../../../util';
|
||||
import { uint8ArrayToB64, b64ToUint8Array } from '../../../encoding/base64';
|
||||
import OID from '../../../type/oid';
|
||||
import { UnsupportedError } from '../../../packet/packet';
|
||||
import { generate as eddsaGenerate } from './eddsa';
|
||||
import { generate as ecdhXGenerate } from './ecdh_x';
|
||||
|
||||
const webCrypto = util.getWebCrypto();
|
||||
const nodeCrypto = util.getNodeCrypto();
|
||||
@ -181,18 +182,14 @@ class CurveWithOID {
|
||||
case 'node':
|
||||
return nodeGenKeyPair(this.name);
|
||||
case 'curve25519Legacy': {
|
||||
const privateKey = getRandomBytes(32);
|
||||
privateKey[0] = (privateKey[0] & 127) | 64;
|
||||
privateKey[31] &= 248;
|
||||
const secretKey = privateKey.slice().reverse();
|
||||
const { publicKey: rawPublicKey } = nacl.box.keyPair.fromSecretKey(secretKey);
|
||||
const publicKey = util.concatUint8Array([new Uint8Array([this.wireFormatLeadingByte]), rawPublicKey]);
|
||||
const { k, A } = await ecdhXGenerate(enums.publicKey.x25519);
|
||||
const privateKey = k.slice().reverse();
|
||||
const publicKey = util.concatUint8Array([new Uint8Array([this.wireFormatLeadingByte]), A]);
|
||||
return { publicKey, privateKey };
|
||||
}
|
||||
case 'ed25519Legacy': {
|
||||
const privateKey = getRandomBytes(32);
|
||||
const keyPair = nacl.sign.keyPair.fromSeed(privateKey);
|
||||
const publicKey = util.concatUint8Array([new Uint8Array([this.wireFormatLeadingByte]), keyPair.publicKey]);
|
||||
const { seed: privateKey, A } = await eddsaGenerate(enums.publicKey.ed25519);
|
||||
const publicKey = util.concatUint8Array([new Uint8Array([this.wireFormatLeadingByte]), A]);
|
||||
return { publicKey, privateKey };
|
||||
}
|
||||
default:
|
||||
|
@ -12,7 +12,9 @@ import util from '../../src/util';
|
||||
|
||||
import * as input from './testInputs';
|
||||
|
||||
export default () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cryptography (legacy format)', function () {
|
||||
const isSafariOrHeadlessWebKit = () => typeof window !== 'undefined' && window.navigator.userAgent.match(/WebKit/) && !window.navigator.userAgent.match(/Chrome/);
|
||||
|
||||
export default () => describe('X25519 Cryptography (legacy format)', function () {
|
||||
const data = {
|
||||
light: {
|
||||
id: '1ecdf026c0245830',
|
||||
@ -216,7 +218,9 @@ export default () => (openpgp.config.ci ? describe.skip : describe)('X25519 Cryp
|
||||
expect(await result.signatures[0].verified).to.be.true;
|
||||
});
|
||||
|
||||
describe('Ed25519 Test Vectors from RFC8032', function () {
|
||||
// Safari implements the non-deterministic version of EdDSA (https://cfrg.github.io/draft-irtf-cfrg-det-sigs-with-noise/draft-irtf-cfrg-det-sigs-with-noise.html),
|
||||
// hence these test vectors do not apply.
|
||||
(isSafariOrHeadlessWebKit() ? describe.skip : describe)('Ed25519 Test Vectors from RFC8032', function () {
|
||||
// https://tools.ietf.org/html/rfc8032#section-7.1
|
||||
function testVector(vector) {
|
||||
const curve = new elliptic.CurveWithOID(openpgp.enums.curve.ed25519Legacy);
|
||||
|
Loading…
x
Reference in New Issue
Block a user