Skip to content

Commit

Permalink
Merge pull request #1687
Browse files Browse the repository at this point in the history
`crypto-refresh`: minor fixes and updates for X25519/Ed25519 (new format)
  • Loading branch information
larabr committed Oct 10, 2023
2 parents d6145ac + 99ba76c commit ed482a1
Show file tree
Hide file tree
Showing 12 changed files with 102 additions and 78 deletions.
6 changes: 6 additions & 0 deletions openpgp.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -820,7 +820,9 @@ export namespace enums {
dsa = 17,
ecdh = 18,
ecdsa = 19,
/** @deprecated use `eddsaLegacy` instead */
eddsa = 22,
eddsaLegacy = 22,
aedh = 23,
aedsa = 24,
}
Expand All @@ -829,8 +831,12 @@ export namespace enums {
p256 = 'p256',
p384 = 'p384',
p521 = 'p521',
/** @deprecated use `ed25519Legacy` instead */
ed25519 = 'ed25519',
ed25519Legacy = 'ed25519',
/** @deprecated use `x25519Legacy` instead */
curve25519 = 'curve25519',
x25519Legacy = 'curve25519',
secp256k1 = 'secp256k1',
brainpoolP256r1 = 'brainpoolP256r1',
brainpoolP384r1 = 'brainpoolP384r1',
Expand Down
29 changes: 21 additions & 8 deletions src/crypto/crypto.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,7 @@ export function parsePublicKeyParams(algo, bytes) {
const Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2;
return { read: read, publicParams: { oid, Q } };
}
case enums.publicKey.eddsa:
case enums.publicKey.ed25519Legacy: {
case enums.publicKey.eddsaLegacy: {
const oid = new OID(); read += oid.read(bytes);
checkSupportedCurve(oid);
let Q = util.readMPI(bytes.subarray(read)); read += Q.length + 2;
Expand Down Expand Up @@ -224,8 +223,7 @@ export function parsePrivateKeyParams(algo, bytes, publicParams) {
d = util.leftPad(d, curve.payloadSize);
return { read, privateParams: { d } };
}
case enums.publicKey.eddsa:
case enums.publicKey.ed25519Legacy: {
case enums.publicKey.eddsaLegacy: {
const curve = new CurveWithOID(publicParams.oid);
let seed = util.readMPI(bytes.subarray(read)); read += seed.length + 2;
seed = util.leftPad(seed, curve.payloadSize);
Expand Down Expand Up @@ -331,8 +329,7 @@ export function generateParams(algo, bits, oid) {
privateParams: { d: secret },
publicParams: { oid: new OID(oid), Q }
}));
case enums.publicKey.eddsa:
case enums.publicKey.ed25519Legacy:
case enums.publicKey.eddsaLegacy:
return publicKey.elliptic.generate(oid).then(({ oid, Q, secret }) => ({
privateParams: { seed: secret },
publicParams: { oid: new OID(oid), Q }
Expand Down Expand Up @@ -401,8 +398,7 @@ export async function validateParams(algo, publicParams, privateParams) {
const { d } = privateParams;
return algoModule.validateParams(oid, Q, d);
}
case enums.publicKey.eddsa:
case enums.publicKey.ed25519Legacy: {
case enums.publicKey.eddsaLegacy: {
const { Q, oid } = publicParams;
const { seed } = privateParams;
return publicKey.elliptic.eddsaLegacy.validateParams(oid, Q, seed);
Expand Down Expand Up @@ -472,3 +468,20 @@ function checkSupportedCurve(oid) {
throw new UnsupportedError('Unknown curve OID');
}
}

/**
* Get preferred hash algo for a given elliptic algo
* @param {module:enums.publicKey} algo - alrogithm identifier
* @param {module:type/oid} [oid] - curve OID if needed by algo
*/
export function getPreferredCurveHashAlgo(algo, oid) {
switch (algo) {
case enums.publicKey.ecdsa:
case enums.publicKey.eddsaLegacy:
return publicKey.elliptic.getPreferredHashAlgo(oid);
case enums.publicKey.ed25519:
return publicKey.elliptic.eddsa.getPreferredHashAlgo(algo);
default:
throw new Error('Unknown elliptic signing algo');
}
}
2 changes: 0 additions & 2 deletions src/crypto/public_key/elliptic/ecdh_x.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,6 @@ export async function generate(algo) {
case enums.publicKey.x25519: {
// k stays in little-endian, unlike legacy ECDH over curve25519
const k = getRandomBytes(32);
k[0] &= 248;
k[31] = (k[31] & 127) | 64;
const { publicKey: A } = nacl.box.keyPair.fromSecretKey(k);
return { A, k };
}
Expand Down
17 changes: 14 additions & 3 deletions src/crypto/public_key/elliptic/eddsa.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ export async function generate(algo) {
* @async
*/
export async function sign(algo, hashAlgo, message, publicKey, privateKey, hashed) {
if (hash.getHashByteLength(hashAlgo) < hash.getHashByteLength(enums.hash.sha256)) {
// see https://tools.ietf.org/id/draft-ietf-openpgp-rfc4880bis-10.html#section-15-7.2
throw new Error('Hash algorithm too weak: sha256 or stronger is required for EdDSA.');
if (hash.getHashByteLength(hashAlgo) < hash.getHashByteLength(getPreferredHashAlgo(algo))) {
throw new Error('Hash algorithm too weak for EdDSA.');
}
switch (algo) {
case enums.publicKey.ed25519: {
Expand All @@ -90,6 +89,9 @@ export async function sign(algo, hashAlgo, message, publicKey, privateKey, hashe
* @async
*/
export async function verify(algo, hashAlgo, { RS }, m, publicKey, hashed) {
if (hash.getHashByteLength(hashAlgo) < hash.getHashByteLength(getPreferredHashAlgo(algo))) {
throw new Error('Hash algorithm too weak for EdDSA.');
}
switch (algo) {
case enums.publicKey.ed25519: {
return nacl.sign.detached.verify(hashed, RS, publicKey);
Expand Down Expand Up @@ -124,3 +126,12 @@ export async function validateParams(algo, A, seed) {
return false;
}
}

export function getPreferredHashAlgo(algo) {
switch (algo) {
case enums.publicKey.ed25519:
return enums.hash.sha256;
default:
throw new Error('Unknown EdDSA algo');
}
}
5 changes: 4 additions & 1 deletion src/crypto/public_key/elliptic/eddsa_legacy.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ nacl.hash = bytes => new Uint8Array(sha512().update(bytes).digest());
export async function sign(oid, hashAlgo, message, publicKey, privateKey, hashed) {
if (hash.getHashByteLength(hashAlgo) < hash.getHashByteLength(enums.hash.sha256)) {
// see https://tools.ietf.org/id/draft-ietf-openpgp-rfc4880bis-10.html#section-15-7.2
throw new Error('Hash algorithm too weak: sha256 or stronger is required for EdDSA.');
throw new Error('Hash algorithm too weak for EdDSA.');
}
const secretKey = util.concatUint8Array([privateKey, publicKey.subarray(1)]);
const signature = nacl.sign.detached(hashed, secretKey);
Expand All @@ -71,6 +71,9 @@ export async function sign(oid, hashAlgo, message, publicKey, privateKey, hashed
* @async
*/
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));
}
Expand Down
2 changes: 1 addition & 1 deletion src/crypto/public_key/elliptic/oid_curves.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ const curves = {
},
ed25519: {
oid: [0x06, 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01, 0xDA, 0x47, 0x0F, 0x01],
keyType: enums.publicKey.eddsa,
keyType: enums.publicKey.eddsaLegacy,
hash: enums.hash.sha512,
node: false, // nodeCurves.ed25519 TODO
payloadSize: 32
Expand Down
9 changes: 3 additions & 6 deletions src/crypto/signature.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ export function parseSignatureParams(algo, signature) {
// Algorithm-Specific Fields for legacy EdDSA signatures:
// - MPI of an EC point r.
// - EdDSA value s, in MPI, in the little endian representation
case enums.publicKey.eddsa:
case enums.publicKey.ed25519Legacy: {
case enums.publicKey.eddsaLegacy: {
// When parsing little-endian MPI data, we always need to left-pad it, as done with big-endian values:
// https://www.ietf.org/archive/id/draft-ietf-openpgp-rfc4880bis-10.html#section-3.2-9
let r = util.readMPI(signature.subarray(read)); read += r.length + 2;
Expand Down Expand Up @@ -103,8 +102,7 @@ export async function verify(algo, hashAlgo, signature, publicParams, data, hash
const s = util.leftPad(signature.s, curveSize);
return publicKey.elliptic.ecdsa.verify(oid, hashAlgo, { r, s }, data, Q, hashed);
}
case enums.publicKey.eddsa:
case enums.publicKey.ed25519Legacy: {
case enums.publicKey.eddsaLegacy: {
const { oid, Q } = publicParams;
// signature already padded on parsing
return publicKey.elliptic.eddsaLegacy.verify(oid, hashAlgo, signature, data, Q, hashed);
Expand Down Expand Up @@ -158,8 +156,7 @@ export async function sign(algo, hashAlgo, publicKeyParams, privateKeyParams, da
const { d } = privateKeyParams;
return publicKey.elliptic.ecdsa.sign(oid, hashAlgo, data, Q, d, hashed);
}
case enums.publicKey.eddsa:
case enums.publicKey.ed25519Legacy: {
case enums.publicKey.eddsaLegacy: {
const { oid, Q } = publicKeyParams;
const { seed } = privateKeyParams;
return publicKey.elliptic.eddsaLegacy.sign(oid, hashAlgo, data, Q, seed, hashed);
Expand Down
15 changes: 11 additions & 4 deletions src/enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,21 @@ export default {
'2b8104000a': 'secp256k1',
'2B8104000A': 'secp256k1',

/** Ed25519 */
/** Ed25519 - deprecated by crypto-refresh (replaced by standaone Ed25519 algo) */
'ed25519Legacy': 'ed25519',
'ED25519': 'ed25519',
/** @deprecated use `ed25519Legacy` instead */
'ed25519': 'ed25519',
'Ed25519': 'ed25519',
'1.3.6.1.4.1.11591.15.1': 'ed25519',
'2b06010401da470f01': 'ed25519',
'2B06010401DA470F01': 'ed25519',

/** Curve25519 */
/** Curve25519 - deprecated by crypto-refresh (replaced by standaone X25519 algo) */
'x25519Legacy': 'curve25519',
'X25519': 'curve25519',
'cv25519': 'curve25519',
/** @deprecated use `x25519Legacy` instead */
'curve25519': 'curve25519',
'Curve25519': 'curve25519',
'1.3.6.1.4.1.3029.1.5.1': 'curve25519',
Expand Down Expand Up @@ -111,8 +115,11 @@ export default {
ecdsa: 19,
/** EdDSA (Sign only) - deprecated by crypto-refresh (replaced by `ed25519` identifier below)
* [{@link https://tools.ietf.org/html/draft-koch-eddsa-for-openpgp-04|Draft RFC}] */
ed25519Legacy: 22, // NB: this is declared before `eddsa` to translate 22 to 'eddsa' for backwards compatibility
eddsa: 22, // to be deprecated in v6
eddsaLegacy: 22, // NB: this is declared before `eddsa` to translate 22 to 'eddsa' for backwards compatibility
/** @deprecated use `eddsaLegacy` instead */
ed25519Legacy: 22,
/** @deprecated use `eddsaLegacy` instead */
eddsa: 22,
/** Reserved for AEDH */
aedh: 23,
/** Reserved for AEDSA */
Expand Down
34 changes: 17 additions & 17 deletions src/key/factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -197,50 +197,50 @@ async function wrapKeyObject(secretKeyPacket, secretSubkeyPackets, options, conf
const dataToSign = {};
dataToSign.userID = userIDPacket;
dataToSign.key = secretKeyPacket;
const signaturePacket = new SignaturePacket();
signaturePacket.signatureType = enums.signature.certGeneric;
signaturePacket.publicKeyAlgorithm = secretKeyPacket.algorithm;
signaturePacket.hashAlgorithm = await helper.getPreferredHashAlgo(null, secretKeyPacket, undefined, undefined, config);
signaturePacket.keyFlags = [enums.keyFlags.certifyKeys | enums.keyFlags.signData];
signaturePacket.preferredSymmetricAlgorithms = createPreferredAlgos([

const signatureProperties = {};
signatureProperties.signatureType = enums.signature.certGeneric;
signatureProperties.keyFlags = [enums.keyFlags.certifyKeys | enums.keyFlags.signData];
signatureProperties.preferredSymmetricAlgorithms = createPreferredAlgos([
// prefer aes256, aes128, then aes192 (no WebCrypto support: https://www.chromium.org/blink/webcrypto#TOC-AES-support)
enums.symmetric.aes256,
enums.symmetric.aes128,
enums.symmetric.aes192
], config.preferredSymmetricAlgorithm);
if (config.aeadProtect) {
signaturePacket.preferredAEADAlgorithms = createPreferredAlgos([
signatureProperties.preferredAEADAlgorithms = createPreferredAlgos([
enums.aead.eax,
enums.aead.ocb
], config.preferredAEADAlgorithm);
}
signaturePacket.preferredHashAlgorithms = createPreferredAlgos([
signatureProperties.preferredHashAlgorithms = createPreferredAlgos([
// prefer fast asm.js implementations (SHA-256)
enums.hash.sha256,
enums.hash.sha512
], config.preferredHashAlgorithm);
signaturePacket.preferredCompressionAlgorithms = createPreferredAlgos([
signatureProperties.preferredCompressionAlgorithms = createPreferredAlgos([
enums.compression.zlib,
enums.compression.zip,
enums.compression.uncompressed
], config.preferredCompressionAlgorithm);
if (index === 0) {
signaturePacket.isPrimaryUserID = true;
signatureProperties.isPrimaryUserID = true;
}
// integrity protection always enabled
signaturePacket.features = [0];
signaturePacket.features[0] |= enums.features.modificationDetection;
signatureProperties.features = [0];
signatureProperties.features[0] |= enums.features.modificationDetection;
if (config.aeadProtect) {
signaturePacket.features[0] |= enums.features.aead;
signatureProperties.features[0] |= enums.features.aead;
}
if (config.v5Keys) {
signaturePacket.features[0] |= enums.features.v5Keys;
signatureProperties.features[0] |= enums.features.v5Keys;
}
if (options.keyExpirationTime > 0) {
signaturePacket.keyExpirationTime = options.keyExpirationTime;
signaturePacket.keyNeverExpires = false;
signatureProperties.keyExpirationTime = options.keyExpirationTime;
signatureProperties.keyNeverExpires = false;
}
await signaturePacket.sign(secretKeyPacket, dataToSign, options.date);

const signaturePacket = await helper.createSignaturePacket(dataToSign, null, secretKeyPacket, signatureProperties, options.date, undefined, undefined, undefined, config);

return { userIDPacket, signaturePacket };
})).then(list => {
Expand Down
45 changes: 17 additions & 28 deletions src/key/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
*/

import {
PublicKeyPacket,
PublicSubkeyPacket,
SecretKeyPacket,
SecretSubkeyPacket,
SignaturePacket
Expand Down Expand Up @@ -88,23 +86,20 @@ export async function createBindingSignature(subkey, primaryKey, options, config
const dataToSign = {};
dataToSign.key = primaryKey;
dataToSign.bind = subkey;
const subkeySignaturePacket = new SignaturePacket();
subkeySignaturePacket.signatureType = enums.signature.subkeyBinding;
subkeySignaturePacket.publicKeyAlgorithm = primaryKey.algorithm;
subkeySignaturePacket.hashAlgorithm = await getPreferredHashAlgo(null, subkey, undefined, undefined, config);
const signatureProperties = { signatureType: enums.signature.subkeyBinding };
if (options.sign) {
subkeySignaturePacket.keyFlags = [enums.keyFlags.signData];
subkeySignaturePacket.embeddedSignature = await createSignaturePacket(dataToSign, null, subkey, {
signatureProperties.keyFlags = [enums.keyFlags.signData];
signatureProperties.embeddedSignature = await createSignaturePacket(dataToSign, null, subkey, {
signatureType: enums.signature.keyBinding
}, options.date, undefined, undefined, undefined, config);
} else {
subkeySignaturePacket.keyFlags = [enums.keyFlags.encryptCommunication | enums.keyFlags.encryptStorage];
signatureProperties.keyFlags = [enums.keyFlags.encryptCommunication | enums.keyFlags.encryptStorage];
}
if (options.keyExpirationTime > 0) {
subkeySignaturePacket.keyExpirationTime = options.keyExpirationTime;
subkeySignaturePacket.keyNeverExpires = false;
signatureProperties.keyExpirationTime = options.keyExpirationTime;
signatureProperties.keyNeverExpires = false;
}
await subkeySignaturePacket.sign(primaryKey, dataToSign, options.date);
const subkeySignaturePacket = await createSignaturePacket(dataToSign, null, primaryKey, signatureProperties, options.date, undefined, undefined, undefined, config);
return subkeySignaturePacket;
}

Expand All @@ -129,17 +124,11 @@ export async function getPreferredHashAlgo(key, keyPacket, date = new Date(), us
prefAlgo : hashAlgo;
}
}
switch (Object.getPrototypeOf(keyPacket)) {
case SecretKeyPacket.prototype:
case PublicKeyPacket.prototype:
case SecretSubkeyPacket.prototype:
case PublicSubkeyPacket.prototype:
switch (keyPacket.algorithm) {
case enums.publicKey.ecdh:
case enums.publicKey.ecdsa:
case enums.publicKey.eddsa:
prefAlgo = crypto.publicKey.elliptic.getPreferredHashAlgo(keyPacket.publicParams.oid);
}
switch (keyPacket.algorithm) {
case enums.publicKey.ecdsa:
case enums.publicKey.eddsaLegacy:
case enums.publicKey.ed25519:
prefAlgo = crypto.getPreferredCurveHashAlgo(keyPacket.algorithm, keyPacket.publicParams.oid);
}
return crypto.hash.getHashByteLength(hashAlgo) <= crypto.hash.getHashByteLength(prefAlgo) ?
prefAlgo : hashAlgo;
Expand Down Expand Up @@ -344,11 +333,11 @@ export function sanitizeKeyOptions(options, subkeyDefaults = {}) {
} catch (e) {
throw new Error('Unknown curve');
}
if (options.curve === enums.curve.ed25519 || options.curve === enums.curve.curve25519) {
options.curve = options.sign ? enums.curve.ed25519 : enums.curve.curve25519;
if (options.curve === enums.curve.ed25519Legacy || options.curve === enums.curve.x25519Legacy) {
options.curve = options.sign ? enums.curve.ed25519Legacy : enums.curve.x25519Legacy;
}
if (options.sign) {
options.algorithm = options.curve === enums.curve.ed25519 ? enums.publicKey.eddsa : enums.publicKey.ecdsa;
options.algorithm = options.curve === enums.curve.ed25519Legacy ? enums.publicKey.eddsaLegacy : enums.publicKey.ecdsa;
} else {
options.algorithm = enums.publicKey.ecdh;
}
Expand Down Expand Up @@ -377,7 +366,7 @@ export function isValidEncryptionKeyPacket(keyPacket, signature) {
return keyAlgo !== enums.publicKey.dsa &&
keyAlgo !== enums.publicKey.rsaSign &&
keyAlgo !== enums.publicKey.ecdsa &&
keyAlgo !== enums.publicKey.eddsa &&
keyAlgo !== enums.publicKey.eddsaLegacy &&
keyAlgo !== enums.publicKey.ed25519 &&
(!signature.keyFlags ||
(signature.keyFlags[0] & enums.keyFlags.encryptCommunication) !== 0 ||
Expand Down Expand Up @@ -417,7 +406,7 @@ export function checkKeyRequirements(keyPacket, config) {
}
break;
case enums.publicKey.ecdsa:
case enums.publicKey.eddsa:
case enums.publicKey.eddsaLegacy:
case enums.publicKey.ecdh:
if (config.rejectCurves.has(algoInfo.curve)) {
throw new Error(`Support for ${algoInfo.algorithm} keys using curve ${algoInfo.curve} is disabled.`);
Expand Down

0 comments on commit ed482a1

Please sign in to comment.