Clean up ECDH API

This commit is contained in:
Daniel Huigens 2019-05-01 10:42:35 +02:00
parent ca0322bbea
commit e637e75891
5 changed files with 95 additions and 101 deletions

View File

@ -69,36 +69,34 @@ export default {
*/
publicKeyEncrypt: async function(algo, pub_params, data, fingerprint) {
const types = this.getEncSessionKeyParamTypes(algo);
return (async function() {
switch (algo) {
case enums.publicKey.rsa_encrypt:
case enums.publicKey.rsa_encrypt_sign: {
const m = data.toBN();
const n = pub_params[0].toBN();
const e = pub_params[1].toBN();
const res = await publicKey.rsa.encrypt(m, n, e);
return constructParams(types, [res]);
}
case enums.publicKey.elgamal: {
const m = data.toBN();
const p = pub_params[0].toBN();
const g = pub_params[1].toBN();
const y = pub_params[2].toBN();
const res = await publicKey.elgamal.encrypt(m, p, g, y);
return constructParams(types, [res.c1, res.c2]);
}
case enums.publicKey.ecdh: {
const oid = pub_params[0];
const Q = pub_params[1].toUint8Array();
const kdf_params = pub_params[2];
const { V, C } = await publicKey.elliptic.ecdh.encrypt(
oid, kdf_params.cipher, kdf_params.hash, data, Q, fingerprint);
return constructParams(types, [new BN(V), C]);
}
default:
return [];
switch (algo) {
case enums.publicKey.rsa_encrypt:
case enums.publicKey.rsa_encrypt_sign: {
const m = data.toBN();
const n = pub_params[0].toBN();
const e = pub_params[1].toBN();
const res = await publicKey.rsa.encrypt(m, n, e);
return constructParams(types, [res]);
}
}());
case enums.publicKey.elgamal: {
const m = data.toBN();
const p = pub_params[0].toBN();
const g = pub_params[1].toBN();
const y = pub_params[2].toBN();
const res = await publicKey.elgamal.encrypt(m, p, g, y);
return constructParams(types, [res.c1, res.c2]);
}
case enums.publicKey.ecdh: {
const oid = pub_params[0];
const Q = pub_params[1].toUint8Array();
const kdf_params = pub_params[2];
const { publicKey: V, wrappedKey: C } = await publicKey.elliptic.ecdh.encrypt(
oid, kdf_params.cipher, kdf_params.hash, data, Q, fingerprint);
return constructParams(types, [new BN(V), C]);
}
default:
return [];
}
},
/**
@ -112,43 +110,41 @@ export default {
module:type/ecdh_symkey>}
data_params encrypted session key parameters
* @param {String} fingerprint Recipient fingerprint
* @returns {module:type/mpi} An MPI containing the decrypted data
* @returns {BN} A BN containing the decrypted data
* @async
*/
publicKeyDecrypt: async function(algo, key_params, data_params, fingerprint) {
return new type_mpi(await (async function() {
switch (algo) {
case enums.publicKey.rsa_encrypt_sign:
case enums.publicKey.rsa_encrypt: {
const c = data_params[0].toBN();
const n = key_params[0].toBN(); // n = pq
const e = key_params[1].toBN();
const d = key_params[2].toBN(); // de = 1 mod (p-1)(q-1)
const p = key_params[3].toBN();
const q = key_params[4].toBN();
const u = key_params[5].toBN(); // q^-1 mod p
return publicKey.rsa.decrypt(c, n, e, d, p, q, u);
}
case enums.publicKey.elgamal: {
const c1 = data_params[0].toBN();
const c2 = data_params[1].toBN();
const p = key_params[0].toBN();
const x = key_params[3].toBN();
return publicKey.elgamal.decrypt(c1, c2, p, x);
}
case enums.publicKey.ecdh: {
const oid = key_params[0];
const kdf_params = key_params[2];
const V = data_params[0].toUint8Array();
const C = data_params[1].data;
const d = key_params[3].toUint8Array();
return publicKey.elliptic.ecdh.decrypt(
oid, kdf_params.cipher, kdf_params.hash, V, C, d, fingerprint);
}
default:
throw new Error('Invalid public key encryption algorithm.');
switch (algo) {
case enums.publicKey.rsa_encrypt_sign:
case enums.publicKey.rsa_encrypt: {
const c = data_params[0].toBN();
const n = key_params[0].toBN(); // n = pq
const e = key_params[1].toBN();
const d = key_params[2].toBN(); // de = 1 mod (p-1)(q-1)
const p = key_params[3].toBN();
const q = key_params[4].toBN();
const u = key_params[5].toBN(); // q^-1 mod p
return publicKey.rsa.decrypt(c, n, e, d, p, q, u);
}
}()));
case enums.publicKey.elgamal: {
const c1 = data_params[0].toBN();
const c2 = data_params[1].toBN();
const p = key_params[0].toBN();
const x = key_params[3].toBN();
return publicKey.elgamal.decrypt(c1, c2, p, x);
}
case enums.publicKey.ecdh: {
const oid = key_params[0];
const kdf_params = key_params[2];
const V = data_params[0].toUint8Array();
const C = data_params[1].data;
const d = key_params[3].toUint8Array();
return publicKey.elliptic.ecdh.decrypt(
oid, kdf_params.cipher, kdf_params.hash, V, C, d, fingerprint);
}
default:
throw new Error('Invalid public key encryption algorithm.');
}
},
/** Returns the types comprising the private key of an algorithm

View File

@ -52,13 +52,10 @@ function buildEcdhParam(public_algo, oid, cipher_algo, hash_algo, fingerprint) {
}
// Key Derivation Function (RFC 6637)
async function kdf(hash_algo, S, length, param, curve, stripLeading=false, stripTrailing=false) {
const len = curve.curve.curve.p.byteLength();
// Note: this is not ideal, but the RFC's are unclear
async function kdf(hash_algo, X, length, param, stripLeading=false, stripTrailing=false) {
// Note: X is little endian for Curve25519, big-endian for all others.
// This is not ideal, but the RFC's are unclear
// https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-02#appendix-B
let X = curve.curve.curve.type === 'mont' ?
S.toArrayLike(Uint8Array, 'le', len) :
S.toArrayLike(Uint8Array, 'be', len);
let i;
if (stripLeading) {
// Work around old go crypto bug
@ -88,23 +85,19 @@ async function kdf(hash_algo, S, length, param, curve, stripLeading=false, strip
*/
async function genPublicEphemeralKey(curve, Q) {
if (curve.name === 'curve25519') {
const { secretKey } = nacl.box.keyPair();
const one = curve.curve.curve.one;
const mask = one.ushln(255 - 3).sub(one).ushln(3);
let priv = new BN(secretKey);
priv = priv.or(one.ushln(255 - 1));
priv = priv.and(mask);
priv = priv.toArrayLike(Uint8Array, 'le', 32);
const S = nacl.scalarMult(priv, Q.subarray(1));
const { publicKey } = nacl.box.keyPair.fromSecretKey(priv);
const ret = { V: util.concatUint8Array([new Uint8Array([0x40]), publicKey]), S: new BN(S, 'le') };
return ret;
const { secretKey: d } = nacl.box.keyPair();
const { secretKey, sharedKey } = await genPrivateEphemeralKey(curve, Q, d);
let { publicKey } = nacl.box.keyPair.fromSecretKey(secretKey);
publicKey = util.concatUint8Array([new Uint8Array([0x40]), publicKey]);
return { publicKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
}
const v = await curve.genKeyPair();
Q = curve.keyFromPublic(Q);
const V = new Uint8Array(v.getPublic());
const publicKey = new Uint8Array(v.getPublic());
const S = v.derive(Q);
return { V, S };
const len = curve.curve.curve.p.byteLength();
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
return { publicKey, sharedKey };
}
/**
@ -121,12 +114,12 @@ async function genPublicEphemeralKey(curve, Q) {
*/
async function encrypt(oid, cipher_algo, hash_algo, m, Q, fingerprint) {
const curve = new Curve(oid);
const { V, S } = await genPublicEphemeralKey(curve, Q);
const { publicKey, sharedKey } = await genPublicEphemeralKey(curve, Q);
const param = buildEcdhParam(enums.publicKey.ecdh, oid, cipher_algo, hash_algo, fingerprint);
cipher_algo = enums.read(enums.symmetric, cipher_algo);
const Z = await kdf(hash_algo, S, cipher[cipher_algo].keySize, param, curve);
const C = aes_kw.wrap(Z, m.toString());
return { V, C };
const Z = await kdf(hash_algo, sharedKey, cipher[cipher_algo].keySize, param);
const wrappedKey = aes_kw.wrap(Z, m.toString());
return { publicKey, wrappedKey };
}
/**
@ -142,15 +135,20 @@ async function genPrivateEphemeralKey(curve, V, d) {
if (curve.name === 'curve25519') {
const one = curve.curve.curve.one;
const mask = one.ushln(255 - 3).sub(one).ushln(3);
let priv = new BN(d);
priv = priv.or(one.ushln(255 - 1));
priv = priv.and(mask);
const S = nacl.scalarMult(priv.toArrayLike(Uint8Array, 'le', 32), V.subarray(1));
return new BN(S, 'le');
let secretKey = new BN(d);
secretKey = secretKey.or(one.ushln(255 - 1));
secretKey = secretKey.and(mask);
secretKey = secretKey.toArrayLike(Uint8Array, 'le', 32);
const sharedKey = nacl.scalarMult(secretKey, V.subarray(1));
return { secretKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
}
V = curve.keyFromPublic(V);
d = curve.keyFromPrivate(d);
return d.derive(V);
const secretKey = new Uint8Array(d.getPrivate());
const S = d.derive(V);
const len = curve.curve.curve.p.byteLength();
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
return { secretKey, sharedKey };
}
/**
@ -168,14 +166,14 @@ async function genPrivateEphemeralKey(curve, V, d) {
*/
async function decrypt(oid, cipher_algo, hash_algo, V, C, d, fingerprint) {
const curve = new Curve(oid);
const S = await genPrivateEphemeralKey(curve, V, d);
const { sharedKey } = await genPrivateEphemeralKey(curve, V, d);
const param = buildEcdhParam(enums.publicKey.ecdh, oid, cipher_algo, hash_algo, fingerprint);
cipher_algo = enums.read(enums.symmetric, cipher_algo);
let err;
for (let i = 0; i < 3; i++) {
try {
// Work around old go crypto bug and old OpenPGP.js bug, respectively.
const Z = await kdf(hash_algo, S, cipher[cipher_algo].keySize, param, curve, i === 1, i === 2);
const Z = await kdf(hash_algo, sharedKey, cipher[cipher_algo].keySize, param, i === 1, i === 2);
return new BN(aes_kw.unwrap(Z, C));
} catch (e) {
err = e;

View File

@ -137,8 +137,8 @@ PublicKeyEncryptedSessionKey.prototype.encrypt = async function (key) {
*/
PublicKeyEncryptedSessionKey.prototype.decrypt = async function (key) {
const algo = enums.write(enums.publicKey, this.publicKeyAlgorithm);
const result = await crypto.publicKeyDecrypt(
algo, key.params, this.encrypted, key.getFingerprintBytes());
const result = new type_mpi(await crypto.publicKeyDecrypt(
algo, key.params, this.encrypted, key.getFingerprintBytes()));
let checksum;
let decoded;

View File

@ -364,7 +364,7 @@ describe('API functional testing', function() {
return crypto.publicKeyDecrypt(
1, RSApubMPIs.concat(RSAsecMPIs), RSAEncryptedData
).then(data => {
data = data.write();
data = new openpgp.MPI(data).write();
data = util.Uint8Array_to_str(data.subarray(2, data.length));
const result = crypto.pkcs1.eme.decode(data, RSApubMPIs[0].byteLength());
@ -383,7 +383,7 @@ describe('API functional testing', function() {
return crypto.publicKeyDecrypt(
16, ElgamalpubMPIs.concat(ElgamalsecMPIs), ElgamalEncryptedData
).then(data => {
data = data.write();
data = new openpgp.MPI(data).write();
data = util.Uint8Array_to_str(data.subarray(2, data.length));
const result = crypto.pkcs1.eme.decode(data, ElgamalpubMPIs[0].byteLength());

View File

@ -429,7 +429,7 @@ describe('Elliptic Curve Cryptography', function () {
async function genPublicEphemeralKey(curve, Q, fingerprint) {
const curveObj = new openpgp.crypto.publicKey.elliptic.Curve(curve);
const oid = new openpgp.OID(curveObj.oid);
const { V, S } = await openpgp.crypto.publicKey.elliptic.ecdh.genPublicEphemeralKey(
const { publicKey: V, sharedKey } = await openpgp.crypto.publicKey.elliptic.ecdh.genPublicEphemeralKey(
curveObj, Q
);
let cipher_algo = curveObj.cipher;
@ -439,14 +439,14 @@ describe('Elliptic Curve Cryptography', function () {
);
cipher_algo = openpgp.enums.read(openpgp.enums.symmetric, cipher_algo);
const Z = await openpgp.crypto.publicKey.elliptic.ecdh.kdf(
hash_algo, S, openpgp.crypto.cipher[cipher_algo].keySize, param, curveObj, false
hash_algo, sharedKey, openpgp.crypto.cipher[cipher_algo].keySize, param, curveObj, false
);
return { V, Z };
}
async function genPrivateEphemeralKey(curve, V, d, fingerprint) {
const curveObj = new openpgp.crypto.publicKey.elliptic.Curve(curve);
const oid = new openpgp.OID(curveObj.oid);
const S = await openpgp.crypto.publicKey.elliptic.ecdh.genPrivateEphemeralKey(
const { sharedKey } = await openpgp.crypto.publicKey.elliptic.ecdh.genPrivateEphemeralKey(
curveObj, V, d
);
let cipher_algo = curveObj.cipher;
@ -456,7 +456,7 @@ describe('Elliptic Curve Cryptography', function () {
);
cipher_algo = openpgp.enums.read(openpgp.enums.symmetric, cipher_algo);
const Z = await openpgp.crypto.publicKey.elliptic.ecdh.kdf(
hash_algo, S, openpgp.crypto.cipher[cipher_algo].keySize, param, curveObj, false
hash_algo, sharedKey, openpgp.crypto.cipher[cipher_algo].keySize, param, curveObj, false
);
return Z;
}